1 | var deepEqual = require('deep-equal');
|
2 | var defined = require('defined');
|
3 | var path = require('path');
|
4 | var inherits = require('inherits');
|
5 | var EventEmitter = require('events').EventEmitter;
|
6 | var has = require('has');
|
7 | var trim = require('string.prototype.trim');
|
8 | var bind = require('function-bind');
|
9 | var forEach = require('for-each');
|
10 | var isEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable);
|
11 | var toLowerCase = bind.call(Function.call, String.prototype.toLowerCase);
|
12 |
|
13 | module.exports = Test;
|
14 |
|
15 | var nextTick = typeof setImmediate !== 'undefined'
|
16 | ? setImmediate
|
17 | : process.nextTick;
|
18 | var safeSetTimeout = setTimeout;
|
19 | var safeClearTimeout = clearTimeout;
|
20 |
|
21 | inherits(Test, EventEmitter);
|
22 |
|
23 | var 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 |
|
42 | function 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 |
|
85 | Test.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 |
|
100 | Test.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 |
|
123 | Test.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 |
|
130 | Test.prototype.plan = function (n) {
|
131 | this._plan = n;
|
132 | this.emit('plan', n);
|
133 | };
|
134 |
|
135 | Test.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 |
|
147 | Test.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 |
|
160 | Test.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 |
|
181 | Test.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 |
|
197 | Test.prototype._pendingAsserts = function () {
|
198 | if (this._plan === undefined) {
|
199 | return 1;
|
200 | }
|
201 | return this._plan - (this._progeny.length + this.assertCount);
|
202 | };
|
203 |
|
204 | Test.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 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
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 |
|
285 |
|
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 |
|
318 | Test.prototype.fail = function (msg, extra) {
|
319 | this._assert(false, {
|
320 | message : msg,
|
321 | operator : 'fail',
|
322 | extra : extra
|
323 | });
|
324 | };
|
325 |
|
326 | Test.prototype.pass = function (msg, extra) {
|
327 | this._assert(true, {
|
328 | message : msg,
|
329 | operator : 'pass',
|
330 | extra : extra
|
331 | });
|
332 | };
|
333 |
|
334 | Test.prototype.skip = function (msg, extra) {
|
335 | this._assert(true, {
|
336 | message : msg,
|
337 | operator : 'skip',
|
338 | skip : true,
|
339 | extra : extra
|
340 | });
|
341 | };
|
342 |
|
343 | function 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 | }
|
352 | Test.prototype.ok
|
353 | = Test.prototype['true']
|
354 | = Test.prototype.assert
|
355 | = assert;
|
356 |
|
357 | function 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 | }
|
366 | Test.prototype.notOk
|
367 | = Test.prototype['false']
|
368 | = Test.prototype.notok
|
369 | = notOK;
|
370 |
|
371 | function 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 | }
|
379 | Test.prototype.error
|
380 | = Test.prototype.ifError
|
381 | = Test.prototype.ifErr
|
382 | = Test.prototype.iferror
|
383 | = error;
|
384 |
|
385 | function 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 | }
|
394 | Test.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 |
|
402 | function 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 | }
|
411 | Test.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 |
|
422 | function 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 | }
|
431 | Test.prototype.deepEqual
|
432 | = Test.prototype.deepEquals
|
433 | = Test.prototype.isEquivalent
|
434 | = Test.prototype.same
|
435 | = tapeDeepEqual;
|
436 |
|
437 | function 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 | }
|
446 | Test.prototype.deepLooseEqual
|
447 | = Test.prototype.looseEqual
|
448 | = Test.prototype.looseEquals
|
449 | = deepLooseEqual;
|
450 |
|
451 | function 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 | }
|
460 | Test.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 |
|
471 | function 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 | }
|
480 | Test.prototype.notDeepLooseEqual
|
481 | = Test.prototype.notLooseEqual
|
482 | = Test.prototype.notLooseEquals
|
483 | = notDeepLooseEqual;
|
484 |
|
485 | Test.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 |
|
526 | Test.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 |
|
548 | Test.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 |
|