UNPKG

15.1 kBJavaScriptView Raw
1var deepEqual = require('deep-equal');
2var defined = require('defined');
3var path = require('path');
4var inherits = require('inherits');
5var EventEmitter = require('events').EventEmitter;
6var has = require('has');
7var trim = require('string.prototype.trim');
8var bind = require('function-bind');
9var forEach = require('for-each');
10var isEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable);
11var toLowerCase = bind.call(Function.call, String.prototype.toLowerCase);
12
13module.exports = Test;
14
15var nextTick = typeof setImmediate !== 'undefined'
16 ? setImmediate
17 : process.nextTick;
18var safeSetTimeout = setTimeout;
19var safeClearTimeout = clearTimeout;
20
21inherits(Test, EventEmitter);
22
23var getTestArgs = function (name_, opts_, cb_) {
24 var name = '(anonymous)';
25 var opts = {};
26 var cb;
27
28 for (var i = 0; i < arguments.length; i++) {
29 var arg = arguments[i];
30 var t = typeof arg;
31 if (t === 'string') {
32 name = arg;
33 } else if (t === 'object') {
34 opts = arg || opts;
35 } else if (t === 'function') {
36 cb = arg;
37 }
38 }
39 return { name: name, opts: opts, cb: cb };
40};
41
42function Test(name_, opts_, cb_) {
43 if (! (this instanceof Test)) {
44 return new Test(name_, opts_, cb_);
45 }
46
47 var args = getTestArgs(name_, opts_, cb_);
48
49 this.readable = true;
50 this.name = args.name || '(anonymous)';
51 this.assertCount = 0;
52 this.pendingCount = 0;
53 this._skip = args.opts.skip || false;
54 this._todo = args.opts.todo || false;
55 this._timeout = args.opts.timeout;
56 this._plan = undefined;
57 this._cb = args.cb;
58 this._progeny = [];
59 this._ok = true;
60 var depthEnvVar = process.env.NODE_TAPE_OBJECT_PRINT_DEPTH;
61 if (args.opts.objectPrintDepth) {
62 this._objectPrintDepth = args.opts.objectPrintDepth;
63 } else if (depthEnvVar) {
64 if (toLowerCase(depthEnvVar) === 'infinity') {
65 this._objectPrintDepth = Infinity;
66 } else {
67 this._objectPrintDepth = depthEnvVar;
68 }
69 } else {
70 this._objectPrintDepth = 5;
71 }
72
73 for (var prop in this) {
74 this[prop] = (function bind(self, val) {
75 if (typeof val === 'function') {
76 return function bound() {
77 return val.apply(self, arguments);
78 };
79 }
80 return val;
81 })(this, this[prop]);
82 }
83}
84
85Test.prototype.run = function () {
86 if (this._skip) {
87 this.comment('SKIP ' + this.name);
88 }
89 if (!this._cb || this._skip) {
90 return this._end();
91 }
92 if (this._timeout != null) {
93 this.timeoutAfter(this._timeout);
94 }
95 this.emit('prerun');
96 this._cb(this);
97 this.emit('run');
98};
99
100Test.prototype.test = function (name, opts, cb) {
101 var self = this;
102 var t = new Test(name, opts, cb);
103 this._progeny.push(t);
104 this.pendingCount++;
105 this.emit('test', t);
106 t.on('prerun', function () {
107 self.assertCount++;
108 })
109
110 if (!self._pendingAsserts()) {
111 nextTick(function () {
112 self._end();
113 });
114 }
115
116 nextTick(function () {
117 if (!self._plan && self.pendingCount == self._progeny.length) {
118 self._end();
119 }
120 });
121};
122
123Test.prototype.comment = function (msg) {
124 var that = this;
125 forEach(trim(msg).split('\n'), function (aMsg) {
126 that.emit('result', trim(aMsg).replace(/^#\s*/, ''));
127 });
128};
129
130Test.prototype.plan = function (n) {
131 this._plan = n;
132 this.emit('plan', n);
133};
134
135Test.prototype.timeoutAfter = function (ms) {
136 if (!ms) throw new Error('timeoutAfter requires a timespan');
137 var self = this;
138 var timeout = safeSetTimeout(function () {
139 self.fail('test timed out after ' + ms + 'ms');
140 self.end();
141 }, ms);
142 this.once('end', function () {
143 safeClearTimeout(timeout);
144 });
145}
146
147Test.prototype.end = function (err) {
148 var self = this;
149 if (arguments.length >= 1 && !!err) {
150 this.ifError(err);
151 }
152
153 if (this.calledEnd) {
154 this.fail('.end() called twice');
155 }
156 this.calledEnd = true;
157 this._end();
158};
159
160Test.prototype._end = function (err) {
161 var self = this;
162 if (this._progeny.length) {
163 var t = this._progeny.shift();
164 t.on('end', function () { self._end() });
165 t.run();
166 return;
167 }
168
169 if (!this.ended) this.emit('end');
170 var pendingAsserts = this._pendingAsserts();
171 if (!this._planError && this._plan !== undefined && pendingAsserts) {
172 this._planError = true;
173 this.fail('plan != count', {
174 expected : this._plan,
175 actual : this.assertCount
176 });
177 }
178 this.ended = true;
179};
180
181Test.prototype._exit = function () {
182 if (this._plan !== undefined &&
183 !this._planError && this.assertCount !== this._plan) {
184 this._planError = true;
185 this.fail('plan != count', {
186 expected : this._plan,
187 actual : this.assertCount,
188 exiting : true
189 });
190 } else if (!this.ended) {
191 this.fail('test exited without ending', {
192 exiting: true
193 });
194 }
195};
196
197Test.prototype._pendingAsserts = function () {
198 if (this._plan === undefined) {
199 return 1;
200 }
201 return this._plan - (this._progeny.length + this.assertCount);
202};
203
204Test.prototype._assert = function assert(ok, opts) {
205 var self = this;
206 var extra = opts.extra || {};
207
208 var res = {
209 id: self.assertCount++,
210 ok: Boolean(ok),
211 skip: defined(extra.skip, opts.skip),
212 todo: defined(extra.todo, opts.todo, self._todo),
213 name: defined(extra.message, opts.message, '(unnamed assert)'),
214 operator: defined(extra.operator, opts.operator),
215 objectPrintDepth: self._objectPrintDepth
216 };
217 if (has(opts, 'actual') || has(extra, 'actual')) {
218 res.actual = defined(extra.actual, opts.actual);
219 }
220 if (has(opts, 'expected') || has(extra, 'expected')) {
221 res.expected = defined(extra.expected, opts.expected);
222 }
223 this._ok = Boolean(this._ok && ok);
224
225 if (!ok && !res.todo) {
226 res.error = defined(extra.error, opts.error, new Error(res.name));
227 }
228
229 if (!ok) {
230 var e = new Error('exception');
231 var err = (e.stack || '').split('\n');
232 var dir = __dirname + path.sep;
233
234 for (var i = 0; i < err.length; i++) {
235 /*
236 Stack trace lines may resemble one of the following. We need
237 to should correctly extract a function name (if any) and
238 path / line no. for each line.
239
240 at myFunction (/path/to/file.js:123:45)
241 at myFunction (/path/to/file.other-ext:123:45)
242 at myFunction (/path to/file.js:123:45)
243 at myFunction (C:\path\to\file.js:123:45)
244 at myFunction (/path/to/file.js:123)
245 at Test.<anonymous> (/path/to/file.js:123:45)
246 at Test.bound [as run] (/path/to/file.js:123:45)
247 at /path/to/file.js:123:45
248
249 Regex has three parts. First is non-capturing group for 'at '
250 (plus anything preceding it).
251
252 /^(?:[^\s]*\s*\bat\s+)/
253
254 Second captures function call description (optional). This is
255 not necessarily a valid JS function name, but just what the
256 stack trace is using to represent a function call. It may look
257 like `<anonymous>` or 'Test.bound [as run]'.
258
259 For our purposes, we assume that, if there is a function
260 name, it's everything leading up to the first open
261 parentheses (trimmed) before our pathname.
262
263 /(?:(.*)\s+\()?/
264
265 Last part captures file path plus line no (and optional
266 column no).
267
268 /((?:\/|[a-zA-Z]:\\)[^:\)]+:(\d+)(?::(\d+))?)/
269 */
270 var re = /^(?:[^\s]*\s*\bat\s+)(?:(.*)\s+\()?((?:\/|[a-zA-Z]:\\)[^:\)]+:(\d+)(?::(\d+))?)/
271 var m = re.exec(err[i]);
272
273 if (!m) {
274 continue;
275 }
276
277 var callDescription = m[1] || '<anonymous>';
278 var filePath = m[2];
279
280 if (filePath.slice(0, dir.length) === dir) {
281 continue;
282 }
283
284 // Function call description may not (just) be a function name.
285 // Try to extract function name by looking at first "word" only.
286 res.functionName = callDescription.split(/\s+/)[0]
287 res.file = filePath;
288 res.line = Number(m[3]);
289 if (m[4]) res.column = Number(m[4]);
290
291 res.at = callDescription + ' (' + filePath + ')';
292 break;
293 }
294 }
295
296 self.emit('result', res);
297
298 var pendingAsserts = self._pendingAsserts();
299 if (!pendingAsserts) {
300 if (extra.exiting) {
301 self._end();
302 } else {
303 nextTick(function () {
304 self._end();
305 });
306 }
307 }
308
309 if (!self._planError && pendingAsserts < 0) {
310 self._planError = true;
311 self.fail('plan != count', {
312 expected : self._plan,
313 actual : self._plan - pendingAsserts
314 });
315 }
316};
317
318Test.prototype.fail = function (msg, extra) {
319 this._assert(false, {
320 message : msg,
321 operator : 'fail',
322 extra : extra
323 });
324};
325
326Test.prototype.pass = function (msg, extra) {
327 this._assert(true, {
328 message : msg,
329 operator : 'pass',
330 extra : extra
331 });
332};
333
334Test.prototype.skip = function (msg, extra) {
335 this._assert(true, {
336 message : msg,
337 operator : 'skip',
338 skip : true,
339 extra : extra
340 });
341};
342
343function assert(value, msg, extra) {
344 this._assert(value, {
345 message : defined(msg, 'should be truthy'),
346 operator : 'ok',
347 expected : true,
348 actual : value,
349 extra : extra
350 });
351}
352Test.prototype.ok
353= Test.prototype['true']
354= Test.prototype.assert
355= assert;
356
357function notOK(value, msg, extra) {
358 this._assert(!value, {
359 message : defined(msg, 'should be falsy'),
360 operator : 'notOk',
361 expected : false,
362 actual : value,
363 extra : extra
364 });
365}
366Test.prototype.notOk
367= Test.prototype['false']
368= Test.prototype.notok
369= notOK;
370
371function error(err, msg, extra) {
372 this._assert(!err, {
373 message : defined(msg, String(err)),
374 operator : 'error',
375 actual : err,
376 extra : extra
377 });
378}
379Test.prototype.error
380= Test.prototype.ifError
381= Test.prototype.ifErr
382= Test.prototype.iferror
383= error;
384
385function equal(a, b, msg, extra) {
386 this._assert(a === b, {
387 message : defined(msg, 'should be equal'),
388 operator : 'equal',
389 actual : a,
390 expected : b,
391 extra : extra
392 });
393}
394Test.prototype.equal
395= Test.prototype.equals
396= Test.prototype.isEqual
397= Test.prototype.is
398= Test.prototype.strictEqual
399= Test.prototype.strictEquals
400= equal;
401
402function notEqual(a, b, msg, extra) {
403 this._assert(a !== b, {
404 message : defined(msg, 'should not be equal'),
405 operator : 'notEqual',
406 actual : a,
407 expected : b,
408 extra : extra
409 });
410}
411Test.prototype.notEqual
412= Test.prototype.notEquals
413= Test.prototype.notStrictEqual
414= Test.prototype.notStrictEquals
415= Test.prototype.isNotEqual
416= Test.prototype.isNot
417= Test.prototype.not
418= Test.prototype.doesNotEqual
419= Test.prototype.isInequal
420= notEqual;
421
422function tapeDeepEqual(a, b, msg, extra) {
423 this._assert(deepEqual(a, b, { strict: true }), {
424 message : defined(msg, 'should be equivalent'),
425 operator : 'deepEqual',
426 actual : a,
427 expected : b,
428 extra : extra
429 });
430}
431Test.prototype.deepEqual
432= Test.prototype.deepEquals
433= Test.prototype.isEquivalent
434= Test.prototype.same
435= tapeDeepEqual;
436
437function deepLooseEqual(a, b, msg, extra) {
438 this._assert(deepEqual(a, b), {
439 message : defined(msg, 'should be equivalent'),
440 operator : 'deepLooseEqual',
441 actual : a,
442 expected : b,
443 extra : extra
444 });
445}
446Test.prototype.deepLooseEqual
447= Test.prototype.looseEqual
448= Test.prototype.looseEquals
449= deepLooseEqual;
450
451function notDeepEqual(a, b, msg, extra) {
452 this._assert(!deepEqual(a, b, { strict: true }), {
453 message : defined(msg, 'should not be equivalent'),
454 operator : 'notDeepEqual',
455 actual : a,
456 expected : b,
457 extra : extra
458 });
459}
460Test.prototype.notDeepEqual
461= Test.prototype.notDeepEquals
462= Test.prototype.notEquivalent
463= Test.prototype.notDeeply
464= Test.prototype.notSame
465= Test.prototype.isNotDeepEqual
466= Test.prototype.isNotDeeply
467= Test.prototype.isNotEquivalent
468= Test.prototype.isInequivalent
469= notDeepEqual;
470
471function notDeepLooseEqual(a, b, msg, extra) {
472 this._assert(!deepEqual(a, b), {
473 message : defined(msg, 'should be equivalent'),
474 operator : 'notDeepLooseEqual',
475 actual : a,
476 expected : b,
477 extra : extra
478 });
479}
480Test.prototype.notDeepLooseEqual
481= Test.prototype.notLooseEqual
482= Test.prototype.notLooseEquals
483= notDeepLooseEqual;
484
485Test.prototype['throws'] = function (fn, expected, msg, extra) {
486 if (typeof expected === 'string') {
487 msg = expected;
488 expected = undefined;
489 }
490
491 var caught = undefined;
492
493 try {
494 fn();
495 } catch (err) {
496 caught = { error : err };
497 if ((err != null) && (!isEnumerable(err, 'message') || !has(err, 'message'))) {
498 var message = err.message;
499 delete err.message;
500 err.message = message;
501 }
502 }
503
504 var passed = caught;
505
506 if (expected instanceof RegExp) {
507 passed = expected.test(caught && caught.error);
508 expected = String(expected);
509 }
510
511 if (typeof expected === 'function' && caught) {
512 passed = caught.error instanceof expected;
513 caught.error = caught.error.constructor;
514 }
515
516 this._assert(typeof fn === 'function' && passed, {
517 message : defined(msg, 'should throw'),
518 operator : 'throws',
519 actual : caught && caught.error,
520 expected : expected,
521 error: !passed && caught && caught.error,
522 extra : extra
523 });
524};
525
526Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
527 if (typeof expected === 'string') {
528 msg = expected;
529 expected = undefined;
530 }
531 var caught = undefined;
532 try {
533 fn();
534 }
535 catch (err) {
536 caught = { error : err };
537 }
538 this._assert(!caught, {
539 message : defined(msg, 'should not throw'),
540 operator : 'throws',
541 actual : caught && caught.error,
542 expected : expected,
543 error : caught && caught.error,
544 extra : extra
545 });
546};
547
548Test.skip = function (name_, _opts, _cb) {
549 var args = getTestArgs.apply(null, arguments);
550 args.opts.skip = true;
551 return Test(args.name, args.opts, args.cb);
552};
553
554// vim: set softtabstop=4 shiftwidth=4: