UNPKG

8.56 kBJavaScriptView Raw
1/*
2 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18/*
19 * lpad and rpad are taken from expresso
20 * <https://github.com/visionmedia/expresso> which is MIT licensed.
21 * Copyright(c) TJ Holowaychuk <tj@vision-media.ca>
22 */
23
24var util = require('util');
25var fs = require('fs');
26var path = require('path');
27var EventEmitter = require('events').EventEmitter;
28
29var sprintf = require('sprintf').sprintf;
30var async = require('async');
31
32var constants = require('./constants');
33var errors = require('./errors');
34
35var printMsg = function(msg, verbosity, minVerbosity) {
36 if (verbosity >= minVerbosity) {
37 util.puts(msg);
38 }
39};
40
41var isNullOrUndefined = function(value) {
42 if (value === null || ((typeof value === 'string') && value === 'null') ||
43 value === undefined || ((typeof value === 'string') &&
44 value === 'undefined')) {
45 return true;
46 }
47
48 return false;
49};
50
51/**
52 * Call a function and ignore any error thrown.
53 *
54 * @param {Function} func Function to call.
55 * @param {Object} context Context in which the function is called.
56 * @param {Array} Function argument
57 * @param {Function} callback Optional callback which is called at the end.
58 */
59var callIgnoringError = function(func, context, args, callback) {
60 try {
61 func.apply(context, args);
62 }
63 catch (err) {}
64
65 if (callback) {
66 callback();
67 }
68};
69
70/**
71 * Return Unix timestamp.
72 *
73 * @return {Number} Unix timestamp.
74 */
75var getUnixTimestamp = function() {
76 return (new Date().getTime() / 1000);
77};
78
79var addCharacters = function(string, width, character) {
80 var width_ = width || 80;
81 var character_ = character || ' ';
82 var stringLen = string.length;
83 var left = (width_ - stringLen);
84
85 if (left <= 0) {
86 return string;
87 }
88
89 while (left--) {
90 string += character_;
91 }
92
93 return string;
94};
95
96/**
97 * Pad the given string to the maximum width provided.
98 *
99 * @param {String} str
100 * @param {Number} width
101 * @return {String}
102 */
103function lpad(str, width) {
104 str = String(str);
105 var n = width - str.length;
106 if (n < 1) return str;
107 while (n--) str = ' ' + str;
108 return str;
109}
110
111/**
112 * Pad the given string to the maximum width provided.
113 *
114 * @param {String} str
115 * @param {Number} width
116 * @return {String}
117 */
118function rpad(str, width) {
119 str = String(str);
120 var n = width - str.length;
121 if (n < 1) return str;
122 while (n--) str = str + ' ';
123 return str;
124}
125
126function LineProcessor(initialData) {
127 this._buffer = initialData || '';
128}
129
130util.inherits(LineProcessor, EventEmitter);
131
132LineProcessor.prototype.appendData = function(data) {
133 this._buffer += data;
134 this._processData();
135};
136
137LineProcessor.prototype._processData = function() {
138 var newLineMarkerIndex, line;
139
140 newLineMarkerIndex = this._buffer.indexOf('\n');
141 while (newLineMarkerIndex !== -1) {
142 line = this._buffer.substring(0, newLineMarkerIndex);
143 this._buffer = this._buffer.substring(newLineMarkerIndex + 1);
144 newLineMarkerIndex = this._buffer.indexOf('\n');
145
146 this.emit('line', line);
147 }
148};
149
150function parseResultLine(line) {
151 // Returns a triple (end, fileName, resultObj)
152 var startMarkerIndex, endMarkerIndex, testFileEndMarkerIndex;
153 var coverageEndMarker, split, fileName, resultObj;
154
155 testFileEndMarkerIndex = line.indexOf(constants.TEST_FILE_END_MARKER);
156 coverageEndMarker = line.indexOf(constants.COVERAGE_END_MARKER);
157 startMarkerIndex = line.indexOf(constants.TEST_START_MARKER);
158 endMarkerIndex = line.indexOf(constants.TEST_END_MARKER);
159
160 if (testFileEndMarkerIndex !== -1) {
161 // end marker
162 fileName = line.substring(0, testFileEndMarkerIndex);
163 return [ true, fileName, null ];
164 }
165 else if (coverageEndMarker !== -1) {
166 // coverage result
167 split = line.split(constants.DELIMITER);
168 resultObj = {
169 'coverage': split[1].replace(constants.COVERAGE_END_MARKER, '')
170 };
171
172 return [ false, split[0], resultObj ];
173 }
174 else {
175 // test result
176 line = line.replace(constants.TEST_START_MARKER, '')
177 .replace(constants.TEST_END_MARKER, '');
178 split = line.split(constants.DELIMITER);
179 resultObj = JSON.parse(split[1]);
180
181 return [ null, split[0], resultObj ];
182 }
183}
184
185function isTestFile(filePath) {
186 var exportedValues, key, value;
187
188 try {
189 exportedValues = require(filePath);
190 }
191 catch (err) {
192 return false;
193 }
194
195 for (key in exportedValues) {
196 if (exportedValues.hasOwnProperty(key)) {
197 value = exportedValues[key];
198 if (key.indexOf('test') === 0 && (typeof value === 'function')) {
199 return true;
200 }
201 }
202 }
203
204 return false;
205}
206
207/**
208 * Get files in a directory which match the provided name pattern.
209 * Note: This function recurses into sub-directories.
210 *
211 * @param {String} directory Directory to search.
212 * @param {String} matchPattern File name match pattern.
213 * @param {Object} options Optional options object.
214 * @param {Function} callback Callback called with (err, matchingFilePaths).
215 */
216function getMatchingFiles(directory, matchPattern, options, callback) {
217 options = options || {};
218 var matchedFiles = [],
219 recurse = options.recurse || false;
220
221 fs.readdir(directory, function(err, files) {
222 if (err) {
223 callback(null, matchedFiles);
224 return;
225 }
226
227 async.forEach(files, function(file, callback) {
228 var filePath = path.join(directory, file);
229 fs.stat(filePath, function(err, stats) {
230 if (err) {
231 callback();
232 }
233 else if (stats.isDirectory() && recurse) {
234 getMatchingFiles(filePath, matchPattern, options, callback);
235 }
236 else if (matchPattern.test(file)) {
237 matchedFiles.push(filePath);
238 callback();
239 }
240 else {
241 callback();
242 }
243 });
244 },
245
246 function(err) {
247 callback(err, matchedFiles);
248 });
249 });
250};
251
252function findTestFiles(basePath, callback) {
253 getMatchingFiles(basePath, /\.js/, {'recurse': true}, callback);
254}
255
256function parseArguments(rules, args) {
257 var key, value, rule;
258 var result = {};
259
260 for (key in rules) {
261 if (rules.hasOwnProperty(key)) {
262 rule = rules[key];
263 value = args[rule['pos']];
264
265 if (isNullOrUndefined(value)) {
266 value = null;
267 }
268 else if (rule['type'] === 'number') {
269 value = parseInt(value, 10);
270 }
271
272 result[key] = value;
273 }
274 }
275
276 return result;
277}
278
279/**
280 * Wrap a function so that the original function will only be called once,
281 * regardless of how many times the wrapper is called.
282 * @param {Function} fn The to wrap.
283 * @return {Function} A function which will call fn the first time it is called.
284 */
285function fireOnce(fn) {
286 var fired = false;
287 return function wrapped() {
288 if (!fired) {
289 fired = true;
290 fn.apply(null, arguments);
291 }
292 };
293};
294
295/**
296 * Very simple object merging.
297 * Merges two objects together, returning a new object containing a
298 * superset of all attributes. Attributes in b are prefered if both
299 * objects have identical keys.
300 *
301 * @param {Object} a Object to merge.
302 * @param {Object} b Object to merge, wins on conflict.
303 * @return {Object} The merged object.
304 */
305function merge(a, b) {
306 var c = {};
307 var attrname;
308 for (attrname in a) {
309 if (a.hasOwnProperty(attrname)) {
310 c[attrname] = a[attrname];
311 }
312 }
313 for (attrname in b) {
314 if (b.hasOwnProperty(attrname)) {
315 c[attrname] = b[attrname];
316 }
317 }
318 return c;
319}
320
321exports.printMsg = printMsg;
322exports.isNullOrUndefined = isNullOrUndefined;
323
324exports.getUnixTimestamp = getUnixTimestamp;
325exports.addCharacters = addCharacters;
326
327exports.rpad = rpad;
328exports.lpad = lpad;
329
330exports.LineProcessor = LineProcessor;
331exports.parseResultLine = parseResultLine;
332exports.getMatchingFiles = getMatchingFiles;
333exports.findTestFiles = findTestFiles;
334exports.parseArguments = parseArguments;
335exports.fireOnce = fireOnce;
336exports.merge = merge;