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 isEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable);
|
10 |
|
11 | module.exports = Test;
|
12 |
|
13 | var nextTick = typeof setImmediate !== 'undefined'
|
14 | ? setImmediate
|
15 | : process.nextTick
|
16 | ;
|
17 | var safeSetTimeout = setTimeout;
|
18 |
|
19 | inherits(Test, EventEmitter);
|
20 |
|
21 | var getTestArgs = function (name_, opts_, cb_) {
|
22 | var name = '(anonymous)';
|
23 | var opts = {};
|
24 | var cb;
|
25 |
|
26 | for (var i = 0; i < arguments.length; i++) {
|
27 | var arg = arguments[i];
|
28 | var t = typeof arg;
|
29 | if (t === 'string') {
|
30 | name = arg;
|
31 | }
|
32 | else if (t === 'object') {
|
33 | opts = arg || opts;
|
34 | }
|
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._timeout = args.opts.timeout;
|
55 | this._objectPrintDepth = args.opts.objectPrintDepth || 5;
|
56 | this._plan = undefined;
|
57 | this._cb = args.cb;
|
58 | this._progeny = [];
|
59 | this._ok = true;
|
60 |
|
61 | for (var prop in this) {
|
62 | this[prop] = (function bind(self, val) {
|
63 | if (typeof val === 'function') {
|
64 | return function bound() {
|
65 | return val.apply(self, arguments);
|
66 | };
|
67 | }
|
68 | else return val;
|
69 | })(this, this[prop]);
|
70 | }
|
71 | }
|
72 |
|
73 | Test.prototype.run = function () {
|
74 | if (this._skip) {
|
75 | this.comment('SKIP ' + this.name);
|
76 | }
|
77 | if (!this._cb || this._skip) {
|
78 | return this._end();
|
79 | }
|
80 | if (this._timeout != null) {
|
81 | this.timeoutAfter(this._timeout);
|
82 | }
|
83 | this.emit('prerun');
|
84 | this._cb(this);
|
85 | this.emit('run');
|
86 | };
|
87 |
|
88 | Test.prototype.test = function (name, opts, cb) {
|
89 | var self = this;
|
90 | var t = new Test(name, opts, cb);
|
91 | this._progeny.push(t);
|
92 | this.pendingCount++;
|
93 | this.emit('test', t);
|
94 | t.on('prerun', function () {
|
95 | self.assertCount++;
|
96 | })
|
97 |
|
98 | if (!self._pendingAsserts()) {
|
99 | nextTick(function () {
|
100 | self._end();
|
101 | });
|
102 | }
|
103 |
|
104 | nextTick(function() {
|
105 | if (!self._plan && self.pendingCount == self._progeny.length) {
|
106 | self._end();
|
107 | }
|
108 | });
|
109 | };
|
110 |
|
111 | Test.prototype.comment = function (msg) {
|
112 | var that = this;
|
113 | trim(msg).split('\n').forEach(function (aMsg) {
|
114 | that.emit('result', trim(aMsg).replace(/^#\s*/, ''));
|
115 | });
|
116 | };
|
117 |
|
118 | Test.prototype.plan = function (n) {
|
119 | this._plan = n;
|
120 | this.emit('plan', n);
|
121 | };
|
122 |
|
123 | Test.prototype.timeoutAfter = function(ms) {
|
124 | if (!ms) throw new Error('timeoutAfter requires a timespan');
|
125 | var self = this;
|
126 | var timeout = safeSetTimeout(function() {
|
127 | self.fail('test timed out after ' + ms + 'ms');
|
128 | self.end();
|
129 | }, ms);
|
130 | this.once('end', function() {
|
131 | clearTimeout(timeout);
|
132 | });
|
133 | }
|
134 |
|
135 | Test.prototype.end = function (err) {
|
136 | var self = this;
|
137 | if (arguments.length >= 1 && !!err) {
|
138 | this.ifError(err);
|
139 | }
|
140 |
|
141 | if (this.calledEnd) {
|
142 | this.fail('.end() called twice');
|
143 | }
|
144 | this.calledEnd = true;
|
145 | this._end();
|
146 | };
|
147 |
|
148 | Test.prototype._end = function (err) {
|
149 | var self = this;
|
150 | if (this._progeny.length) {
|
151 | var t = this._progeny.shift();
|
152 | t.on('end', function () { self._end() });
|
153 | t.run();
|
154 | return;
|
155 | }
|
156 |
|
157 | if (!this.ended) this.emit('end');
|
158 | var pendingAsserts = this._pendingAsserts();
|
159 | if (!this._planError && this._plan !== undefined && pendingAsserts) {
|
160 | this._planError = true;
|
161 | this.fail('plan != count', {
|
162 | expected : this._plan,
|
163 | actual : this.assertCount
|
164 | });
|
165 | }
|
166 | this.ended = true;
|
167 | };
|
168 |
|
169 | Test.prototype._exit = function () {
|
170 | if (this._plan !== undefined &&
|
171 | !this._planError && this.assertCount !== this._plan) {
|
172 | this._planError = true;
|
173 | this.fail('plan != count', {
|
174 | expected : this._plan,
|
175 | actual : this.assertCount,
|
176 | exiting : true
|
177 | });
|
178 | }
|
179 | else if (!this.ended) {
|
180 | this.fail('test exited without ending', {
|
181 | exiting: true
|
182 | });
|
183 | }
|
184 | };
|
185 |
|
186 | Test.prototype._pendingAsserts = function () {
|
187 | if (this._plan === undefined) {
|
188 | return 1;
|
189 | }
|
190 | else {
|
191 | return this._plan - (this._progeny.length + this.assertCount);
|
192 | }
|
193 | };
|
194 |
|
195 | Test.prototype._assert = function assert (ok, opts) {
|
196 | var self = this;
|
197 | var extra = opts.extra || {};
|
198 |
|
199 | var res = {
|
200 | id : self.assertCount ++,
|
201 | ok : Boolean(ok),
|
202 | skip : defined(extra.skip, opts.skip),
|
203 | name : defined(extra.message, opts.message, '(unnamed assert)'),
|
204 | operator : defined(extra.operator, opts.operator),
|
205 | objectPrintDepth : self._objectPrintDepth
|
206 | };
|
207 | if (has(opts, 'actual') || has(extra, 'actual')) {
|
208 | res.actual = defined(extra.actual, opts.actual);
|
209 | }
|
210 | if (has(opts, 'expected') || has(extra, 'expected')) {
|
211 | res.expected = defined(extra.expected, opts.expected);
|
212 | }
|
213 | this._ok = Boolean(this._ok && ok);
|
214 |
|
215 | if (!ok) {
|
216 | res.error = defined(extra.error, opts.error, new Error(res.name));
|
217 | }
|
218 |
|
219 | if (!ok) {
|
220 | var e = new Error('exception');
|
221 | var err = (e.stack || '').split('\n');
|
222 | var dir = path.dirname(__dirname) + path.sep;
|
223 |
|
224 | for (var i = 0; i < err.length; i++) {
|
225 | var m = /^[^\s]*\s*\bat\s+(.+)/.exec(err[i]);
|
226 | if (!m) {
|
227 | continue;
|
228 | }
|
229 |
|
230 | var s = m[1].split(/\s+/);
|
231 | var filem = /((?:\/|[A-Z]:\\)[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[1]);
|
232 | if (!filem) {
|
233 | filem = /((?:\/|[A-Z]:\\)[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[2]);
|
234 |
|
235 | if (!filem) {
|
236 | filem = /((?:\/|[A-Z]:\\)[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[3]);
|
237 |
|
238 | if (!filem) {
|
239 | continue;
|
240 | }
|
241 | }
|
242 | }
|
243 |
|
244 | if (filem[1].slice(0, dir.length) === dir) {
|
245 | continue;
|
246 | }
|
247 |
|
248 | res.functionName = s[0];
|
249 | res.file = filem[1];
|
250 | res.line = Number(filem[2]);
|
251 | if (filem[3]) res.column = filem[3];
|
252 |
|
253 | res.at = m[1];
|
254 | break;
|
255 | }
|
256 | }
|
257 |
|
258 | self.emit('result', res);
|
259 |
|
260 | var pendingAsserts = self._pendingAsserts();
|
261 | if (!pendingAsserts) {
|
262 | if (extra.exiting) {
|
263 | self._end();
|
264 | } else {
|
265 | nextTick(function () {
|
266 | self._end();
|
267 | });
|
268 | }
|
269 | }
|
270 |
|
271 | if (!self._planError && pendingAsserts < 0) {
|
272 | self._planError = true;
|
273 | self.fail('plan != count', {
|
274 | expected : self._plan,
|
275 | actual : self._plan - pendingAsserts
|
276 | });
|
277 | }
|
278 | };
|
279 |
|
280 | Test.prototype.fail = function (msg, extra) {
|
281 | this._assert(false, {
|
282 | message : msg,
|
283 | operator : 'fail',
|
284 | extra : extra
|
285 | });
|
286 | };
|
287 |
|
288 | Test.prototype.pass = function (msg, extra) {
|
289 | this._assert(true, {
|
290 | message : msg,
|
291 | operator : 'pass',
|
292 | extra : extra
|
293 | });
|
294 | };
|
295 |
|
296 | Test.prototype.skip = function (msg, extra) {
|
297 | this._assert(true, {
|
298 | message : msg,
|
299 | operator : 'skip',
|
300 | skip : true,
|
301 | extra : extra
|
302 | });
|
303 | };
|
304 |
|
305 | Test.prototype.ok
|
306 | = Test.prototype['true']
|
307 | = Test.prototype.assert
|
308 | = function (value, msg, extra) {
|
309 | this._assert(value, {
|
310 | message : defined(msg, 'should be truthy'),
|
311 | operator : 'ok',
|
312 | expected : true,
|
313 | actual : value,
|
314 | extra : extra
|
315 | });
|
316 | };
|
317 |
|
318 | Test.prototype.notOk
|
319 | = Test.prototype['false']
|
320 | = Test.prototype.notok
|
321 | = function (value, msg, extra) {
|
322 | this._assert(!value, {
|
323 | message : defined(msg, 'should be falsy'),
|
324 | operator : 'notOk',
|
325 | expected : false,
|
326 | actual : value,
|
327 | extra : extra
|
328 | });
|
329 | };
|
330 |
|
331 | Test.prototype.error
|
332 | = Test.prototype.ifError
|
333 | = Test.prototype.ifErr
|
334 | = Test.prototype.iferror
|
335 | = function (err, msg, extra) {
|
336 | this._assert(!err, {
|
337 | message : defined(msg, String(err)),
|
338 | operator : 'error',
|
339 | actual : err,
|
340 | extra : extra
|
341 | });
|
342 | };
|
343 |
|
344 | Test.prototype.equal
|
345 | = Test.prototype.equals
|
346 | = Test.prototype.isEqual
|
347 | = Test.prototype.is
|
348 | = Test.prototype.strictEqual
|
349 | = Test.prototype.strictEquals
|
350 | = function (a, b, msg, extra) {
|
351 | this._assert(a === b, {
|
352 | message : defined(msg, 'should be equal'),
|
353 | operator : 'equal',
|
354 | actual : a,
|
355 | expected : b,
|
356 | extra : extra
|
357 | });
|
358 | };
|
359 |
|
360 | Test.prototype.notEqual
|
361 | = Test.prototype.notEquals
|
362 | = Test.prototype.notStrictEqual
|
363 | = Test.prototype.notStrictEquals
|
364 | = Test.prototype.isNotEqual
|
365 | = Test.prototype.isNot
|
366 | = Test.prototype.not
|
367 | = Test.prototype.doesNotEqual
|
368 | = Test.prototype.isInequal
|
369 | = function (a, b, msg, extra) {
|
370 | this._assert(a !== b, {
|
371 | message : defined(msg, 'should not be equal'),
|
372 | operator : 'notEqual',
|
373 | actual : a,
|
374 | notExpected : b,
|
375 | extra : extra
|
376 | });
|
377 | };
|
378 |
|
379 | Test.prototype.deepEqual
|
380 | = Test.prototype.deepEquals
|
381 | = Test.prototype.isEquivalent
|
382 | = Test.prototype.same
|
383 | = function (a, b, msg, extra) {
|
384 | this._assert(deepEqual(a, b, { strict: true }), {
|
385 | message : defined(msg, 'should be equivalent'),
|
386 | operator : 'deepEqual',
|
387 | actual : a,
|
388 | expected : b,
|
389 | extra : extra
|
390 | });
|
391 | };
|
392 |
|
393 | Test.prototype.deepLooseEqual
|
394 | = Test.prototype.looseEqual
|
395 | = Test.prototype.looseEquals
|
396 | = function (a, b, msg, extra) {
|
397 | this._assert(deepEqual(a, b), {
|
398 | message : defined(msg, 'should be equivalent'),
|
399 | operator : 'deepLooseEqual',
|
400 | actual : a,
|
401 | expected : b,
|
402 | extra : extra
|
403 | });
|
404 | };
|
405 |
|
406 | Test.prototype.notDeepEqual
|
407 | = Test.prototype.notEquivalent
|
408 | = Test.prototype.notDeeply
|
409 | = Test.prototype.notSame
|
410 | = Test.prototype.isNotDeepEqual
|
411 | = Test.prototype.isNotDeeply
|
412 | = Test.prototype.isNotEquivalent
|
413 | = Test.prototype.isInequivalent
|
414 | = function (a, b, msg, extra) {
|
415 | this._assert(!deepEqual(a, b, { strict: true }), {
|
416 | message : defined(msg, 'should not be equivalent'),
|
417 | operator : 'notDeepEqual',
|
418 | actual : a,
|
419 | notExpected : b,
|
420 | extra : extra
|
421 | });
|
422 | };
|
423 |
|
424 | Test.prototype.notDeepLooseEqual
|
425 | = Test.prototype.notLooseEqual
|
426 | = Test.prototype.notLooseEquals
|
427 | = function (a, b, msg, extra) {
|
428 | this._assert(!deepEqual(a, b), {
|
429 | message : defined(msg, 'should be equivalent'),
|
430 | operator : 'notDeepLooseEqual',
|
431 | actual : a,
|
432 | expected : b,
|
433 | extra : extra
|
434 | });
|
435 | };
|
436 |
|
437 | Test.prototype['throws'] = function (fn, expected, msg, extra) {
|
438 | if (typeof expected === 'string') {
|
439 | msg = expected;
|
440 | expected = undefined;
|
441 | }
|
442 |
|
443 | var caught = undefined;
|
444 |
|
445 | try {
|
446 | fn();
|
447 | } catch (err) {
|
448 | caught = { error : err };
|
449 | if ((err != null) && (!isEnumerable(err, 'message') || !has(err, 'message'))) {
|
450 | var message = err.message;
|
451 | delete err.message;
|
452 | err.message = message;
|
453 | }
|
454 | }
|
455 |
|
456 | var passed = caught;
|
457 |
|
458 | if (expected instanceof RegExp) {
|
459 | passed = expected.test(caught && caught.error);
|
460 | expected = String(expected);
|
461 | }
|
462 |
|
463 | if (typeof expected === 'function' && caught) {
|
464 | passed = caught.error instanceof expected;
|
465 | caught.error = caught.error.constructor;
|
466 | }
|
467 |
|
468 | this._assert(typeof fn === 'function' && passed, {
|
469 | message : defined(msg, 'should throw'),
|
470 | operator : 'throws',
|
471 | actual : caught && caught.error,
|
472 | expected : expected,
|
473 | error: !passed && caught && caught.error,
|
474 | extra : extra
|
475 | });
|
476 | };
|
477 |
|
478 | Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
|
479 | if (typeof expected === 'string') {
|
480 | msg = expected;
|
481 | expected = undefined;
|
482 | }
|
483 | var caught = undefined;
|
484 | try {
|
485 | fn();
|
486 | }
|
487 | catch (err) {
|
488 | caught = { error : err };
|
489 | }
|
490 | this._assert(!caught, {
|
491 | message : defined(msg, 'should not throw'),
|
492 | operator : 'throws',
|
493 | actual : caught && caught.error,
|
494 | expected : expected,
|
495 | error : caught && caught.error,
|
496 | extra : extra
|
497 | });
|
498 | };
|
499 |
|
500 | Test.skip = function (name_, _opts, _cb) {
|
501 | var args = getTestArgs.apply(null, arguments);
|
502 | args.opts.skip = true;
|
503 | return Test(args.name, args.opts, args.cb);
|
504 | };
|
505 |
|
506 |
|
507 |
|