UNPKG

44.3 kBJavaScriptView Raw
1'use strict'
2// We need TWO queues (work and subtest) and one jobs pool
3//
4// The pool stores buffered subtests being run in parallel.
5//
6// When new subtests are created, they get put in the work queue and also
7// in the subtests queue if they are buffered and jobs>0. When we put a
8// test in the subtest queue, we also process it.
9//
10// Processing the subtest queue means moving tests into the jobs pool until
11// the jobs pool length is at this.jobs
12//
13// Any output functions get put in the work queue if its length > 0 (ie,
14// no cutting the line)
15//
16// Processing the work queue means walking until we run out of things, or
17// encounter an unfinished test. When we encounter ANY kind of test, we
18// block until its output is completed, dumping it all into the parser.
19
20const {format, same, strict, match} = require('tcompare')
21const formatSnapshotDefault = obj => format(obj, { sort: true })
22const Base = require('./base.js')
23const Spawn = require('./spawn.js')
24const Stdin = require('./stdin.js')
25const Deferred = require('trivial-deferred')
26const Pool = require('yapool')
27const TestPoint = require('./point.js')
28const parseTestArgs = require('./parse-test-args.js')
29const loop = require('function-loop')
30const captureStackTrace = require('capture-stack-trace')
31const path = require('path')
32
33const extraFromError = require('./extra-from-error.js')
34const stack = require('./stack.js')
35const synonyms = require('./synonyms.js')
36const assert = require('assert')
37const util = require('util')
38const ownOr = require('own-or')
39const ownOrEnv = require('own-or-env')
40const bindObj = require('bind-obj-methods')
41const cwd = process.cwd()
42
43// TODO: Update stack-utils to use capture-stack-trace, too.
44/* istanbul ignore next */
45if (!Error.captureStackTrace) {
46 Error.captureStackTrace = captureStackTrace
47}
48
49// A sigil object for implicit end() calls that should not
50// trigger an error if the user then calls t.end()
51const IMPLICIT = Symbol('implicit t.end()')
52
53// Sigil to put in the queue to signal the end of all things
54const EOF = Symbol('EOF')
55
56const _currentAssert = Symbol('_currentAssert')
57const _end = Symbol('_end')
58const _snapshot = Symbol('_snapshot')
59const _getSnapshot = Symbol('_getSnapshot')
60const _beforeEnd = Symbol('_beforeEnd')
61const _emits = Symbol('_emits')
62const _nextChildId = Symbol('_nextChildId')
63const _expectUncaught = Symbol('_expectUncaught')
64const Snapshot = require('./snapshot.js')
65
66const hasOwn = (obj, key) =>
67 Object.prototype.hasOwnProperty.call(obj, key)
68
69const isRegExp = re =>
70 Object.prototype.toString.call(re) === '[object RegExp]'
71
72class Waiter {
73 constructor (promise, cb) {
74 this.cb = cb
75 this.ready = false
76 this.value = null
77 this.resolved = false
78 this.rejected = false
79 this.done = false
80 this.finishing = false
81 this.promise = new Promise(res => this.resolve = res)
82 promise.then(value => {
83 this.resolved = true
84 this.value = value
85 this.done = true
86 this.finish()
87 }).catch(er => {
88 this.value = er
89 this.rejected = true
90 this.done = true
91 this.finish()
92 })
93 }
94 finish () {
95 if (this.ready && this.done) {
96 this.finishing = true
97 this.cb(this)
98 this.resolve()
99 }
100 }
101}
102
103class Test extends Base {
104 constructor (options) {
105 options = options || {}
106 super(options)
107 this[_nextChildId] = 1
108 this.pushedEnd = false
109 this.jobs = ownOr(options, 'jobs', 1)
110
111 this.doingStdinOnly = false
112 this.onTeardown = []
113 this.subtests = []
114 this.pool = new Pool()
115 this.queue = ['TAP version 13\n']
116
117 // snapshots are keyed off of the main file that loads the
118 // root test object. Typically, this is the TAP object.
119 // To do this, we climb the ladder and only save in the teardown
120 // of that root (parentless) test object. This allows handling
121 // cases where the same test name can be used multiple times
122 // in a single test file, which would otherwise clobber snapshots.
123 this.writeSnapshot = ownOrEnv(
124 options, 'snapshot', 'TAP_SNAPSHOT', true)
125
126 if (this.parent && this.parent.cleanSnapshot)
127 this.cleanSnapshot = this.parent.cleanSnapshot
128
129 this.formatSnapshot = this.parent && this.parent.formatSnapshot
130
131 this.noparallel = false
132 if (options.cb)
133 this.cb = (...args) => this.hook.runInAsyncScope(options.cb, this, ...args)
134
135 this.occupied = false
136 this[_currentAssert] = null
137 this[_beforeEnd] = []
138 this.count = 0
139 this.n = 0
140 this.ended = false
141 this.explicitEnded = false
142 this.multiEndThrew = false
143 this.assertAt = null
144 this.assertStack = null
145 this.planEnd = -1
146 this.onBeforeEach = []
147 this.onAfterEach = []
148 this.ranAfterEach = false
149
150 this[_expectUncaught] = []
151
152 // bind all methods to this object, so we can pass t.end as a callback
153 // and do `const test = require('tap').test` like people do.
154 const bound = Object.create(null)
155 bindObj(this, this, bound)
156 bindObj(this, Object.getPrototypeOf(this), bound)
157 bindObj(this, Test.prototype, bound)
158 }
159
160 spawn (cmd, args, options, name) {
161 if (typeof args === 'string')
162 args = [ args ]
163
164 args = args || []
165
166 if (typeof options === 'string') {
167 name = options
168 options = {}
169 }
170
171 options = options || {}
172 options.name = ownOr(options, 'name', name)
173 options.command = cmd
174 options.args = args
175
176 return this.sub(Spawn, options, Test.prototype.spawn)
177 }
178
179 sub (Class, extra, caller) {
180 if (this.bailedOut)
181 return
182
183 if (this.doingStdinOnly)
184 throw new Error('cannot run subtests in stdinOnly mode')
185
186 if (this.results || this.ended) {
187 const er = new Error('cannot create subtest after parent test end')
188 this.threw(er)
189 return Promise.resolve(this)
190 }
191
192 extra.childId = this[_nextChildId]++
193
194 if (!extra.skip && this.grep.length) {
195 const m = this.grep[0].test(extra.name)
196 const match = this.grepInvert ? !m : m
197 if (!match) {
198 const p = 'filter' + (this.grepInvert ? ' out' : '') + ': '
199 extra.skip = p + this.grep[0]
200 }
201 }
202
203 if (extra.only && !this.runOnly)
204 this.comment('%j has `only` set but all tests run', extra.name)
205
206 if (this.runOnly && !extra.only)
207 extra.skip = 'filter: only'
208
209 if (extra.todo || extra.skip) {
210 this.pass(extra.name, extra)
211 return Promise.resolve(this)
212 }
213
214 if (!extra.grep) {
215 extra.grep = this.grep.slice(1)
216 extra.grepInvert = this.grepInvert
217 }
218
219 extra.indent = ' '
220 if (this.jobs > 1 && process.env.TAP_BUFFER === undefined)
221 extra.buffered = ownOr(extra, 'buffered', true)
222 else
223 extra.buffered = ownOrEnv(extra, 'buffered', 'TAP_BUFFER', true)
224
225 extra.bail = ownOr(extra, 'bail', this.bail)
226 extra.parent = this
227 extra.stack = stack.captureString(80, caller)
228 extra.context = this.context
229 const t = new Class(extra)
230
231 this.queue.push(t)
232 this.subtests.push(t)
233 this.emit('subtestAdd', t)
234
235 const d = new Deferred()
236 t.deferred = d
237 this.process()
238 return d.promise
239 }
240
241 todo (name, extra, cb) {
242 extra = parseTestArgs(name, extra, cb)
243 extra.todo = extra.todo || true
244 return this.sub(Test, extra, Test.prototype.todo)
245 }
246
247 skip (name, extra, cb) {
248 extra = parseTestArgs(name, extra, cb)
249 extra.skip = extra.skip || true
250 return this.sub(Test, extra, Test.prototype.skip)
251 }
252
253 only (name, extra, cb) {
254 extra = parseTestArgs(name, extra, cb)
255 extra.only = true
256 return this.sub(Test, extra, Test.prototype.only)
257 }
258
259 test (name, extra, cb) {
260 extra = parseTestArgs(name, extra, cb)
261 return this.sub(Test, extra, Test.prototype.test)
262 }
263
264 stdinOnly (extra) {
265 const stream = extra && extra.tapStream || process.stdin
266 if (this.queue.length !== 1 ||
267 this.queue[0] !== 'TAP version 13\n' ||
268 this.processing ||
269 this.results ||
270 this.occupied ||
271 this.pool.length ||
272 this.subtests.length)
273 throw new Error('Cannot use stdinOnly on a test in progress')
274
275 this.doingStdinOnly = true
276 this.queue.length = 0
277 this.parser.on('child', p => {
278 // pretend to be a rooted parser, so it gets counts.
279 p.root = p
280 const t = new Base({
281 name: p.name,
282 parent: this,
283 parser: p,
284 root: p,
285 bail: p.bail,
286 strict: p.strict,
287 omitVersion: p.omitVersion,
288 preserveWhitespace: p.preserveWhitespace,
289 childId: this[_nextChildId]++,
290 })
291 this.emit('subtestAdd', t)
292 this.emit('subtestStart', t)
293 this.emit('subtestProcess', t)
294 p.on('complete', () => this.emit('subtestEnd', t))
295 })
296 stream.pause()
297 stream.pipe(this.parser)
298 stream.resume()
299 }
300
301 stdin (name, extra) {
302 /* istanbul ignore next */
303 extra = parseTestArgs(name, extra, false, '/dev/stdin')
304 return this.sub(Stdin, extra, Test.prototype.stdin)
305 }
306
307 bailout (message) {
308 if (this.parent && (this.results || this.ended))
309 this.parent.bailout(message)
310 else {
311 this.process()
312 message = message ? ' ' + ('' + message).trim() : ''
313 message = message.replace(/[\r\n]/g, ' ')
314 this.parser.write('Bail out!' + message + '\n')
315 }
316 this.end(IMPLICIT)
317 this.process()
318 }
319
320 comment (...args) {
321 const body = util.format(...args)
322 const message = '# ' + body.split(/\r?\n/).join('\n# ') + '\n'
323
324 if (this.results)
325 this.write(message)
326 else
327 this.queue.push(message)
328 this.process()
329 }
330
331 timeout (options) {
332 options = options || {}
333 options.expired = options.expired || this.name
334 if (this.occupied)
335 this.occupied.timeout(options)
336 else
337 Base.prototype.timeout.call(this, options)
338 this.end(IMPLICIT)
339 }
340
341 main (cb) {
342 this.setTimeout(this.options.timeout)
343 this.debug('MAIN pre', this)
344
345 const end = () => {
346 this.debug(' > implicit end for promise')
347 this.end(IMPLICIT)
348 done()
349 }
350
351 const done = (er) => {
352 if (er)
353 this.threw(er)
354
355 if (this.results || this.bailedOut)
356 cb()
357 else
358 this.ondone = cb
359 }
360
361 // This bit of overly clever line-noise wraps the call to user-code
362 // in a try-catch. We can't rely on the domain for this yet, because
363 // the 'end' event can trigger a throw after the domain is unhooked,
364 // but before this is no longer the official "active test"
365 const ret = (() => {
366 try {
367 return this.cb(this)
368 } catch (er) {
369 if (!er || typeof er !== 'object')
370 er = { error: er }
371 er.tapCaught = 'testFunctionThrow'
372 this.threw(er)
373 }
374 })()
375
376 if (ret && ret.then) {
377 this.promise = ret
378 ret.tapAbortPromise = done
379 ret.then(end, er => {
380 if (!er || typeof er !== 'object')
381 er = { error: er }
382 er.tapCaught = 'returnedPromiseRejection'
383 done(er)
384 })
385 } else
386 done()
387
388 this.debug('MAIN post', this)
389 }
390
391 process () {
392 if (this.processing)
393 return this.debug(' < already processing')
394
395 this.debug('\nPROCESSING(%s)', this.name, this.queue.length)
396 this.processing = true
397
398 while (!this.occupied) {
399 const p = this.queue.shift()
400 if (!p)
401 break
402 if (p instanceof Base) {
403 this.processSubtest(p)
404 } else if (p === EOF) {
405 this.debug(' > EOF', this.name)
406 // I AM BECOME EOF, DESTROYER OF STREAMS
407 if (this.writeSnapshot)
408 this[_getSnapshot]().save()
409 this.parser.end()
410 } else if (p instanceof TestPoint) {
411 this.debug(' > TESTPOINT')
412 this.parser.write(p.ok + (++this.n) + p.message)
413 } else if (typeof p === 'string') {
414 this.debug(' > STRING')
415 this.parser.write(p)
416 } else if (p instanceof Waiter) {
417 p.ready = true
418 this.occupied = p
419 p.finish()
420 } else {
421 /* istanbul ignore else */
422 if (Array.isArray(p)) {
423 this.debug(' > METHOD')
424 const m = p.shift()
425 const ret = this[m].apply(this, p)
426 if (ret && typeof ret.then === 'function') {
427 // returned promise
428 ret.then(() => {
429 this.processing = false
430 this.process()
431 }, er => {
432 this.processing = false
433 this.threw(er)
434 })
435 return
436 }
437 } else {
438 throw new Error('weird thing got in the queue')
439 }
440 }
441 }
442
443 while (!this.noparallel && this.pool.length < this.jobs) {
444 const p = this.subtests.shift()
445 if (!p)
446 break
447
448 if (!p.buffered) {
449 this.noparallel = true
450 break
451 }
452
453 this.debug('start subtest', p)
454 this.emit('subtestStart', p)
455 this.pool.add(p)
456 if (this.bailedOut)
457 this.onbufferedend(p)
458 else
459 this.runBeforeEach(p, () =>
460 p.runMain(() => this.onbufferedend(p)))
461 }
462
463 this.debug('done processing', this.queue, this.occupied)
464 this.processing = false
465
466 // just in case any tests ended, and we have sync stuff still
467 // waiting around in the queue to be processed
468 if (!this.occupied && this.queue.length)
469 this.process()
470
471 this.maybeAutoend()
472 }
473
474 processSubtest (p) {
475 this.debug(' > subtest')
476 this.occupied = p
477 if (!p.buffered) {
478 this.emit('subtestStart', p)
479 this.debug(' > subtest indented')
480 p.pipe(this.parser, { end: false })
481 this.runBeforeEach(p, () =>
482 this.writeSubComment(p, () =>
483 p.runMain(() => this.onindentedend(p))))
484 } else if (p.readyToProcess) {
485 this.emit('subtestProcess', p)
486 this.debug(' > subtest buffered, finished')
487 // finished! do the thing!
488 this.occupied = null
489 if (!p.passing() || !p.silent) {
490 this.queue.unshift(['emitSubTeardown', p])
491 this.printResult(p.passing(), p.name, p.options, true)
492 }
493 } else {
494 this.occupied = p
495 this.debug(' > subtest buffered, unfinished', p)
496 // unfinished buffered test.
497 // nothing to do yet, just leave it there.
498 this.queue.unshift(p)
499 }
500 }
501
502 emitSubTeardown (p) {
503 if (!p.onTeardown)
504 return
505
506 const otd = p.onTeardown
507 p.onTeardown = []
508 const threw = er => {
509 if (!er || typeof er !== 'object')
510 er = { error: er }
511 er.tapCaught = 'teardown'
512 delete p.options.time
513 p.threw(er)
514 }
515 for (let i = 0; i < otd.length; i++) {
516 const fn = otd[i]
517 try {
518 const ret = fn.call(p)
519 if (ret && typeof ret.then === 'function') {
520 p.onTeardown = otd.slice(i + 1)
521 this.queue.unshift(['emitSubTeardown', p])
522 return ret.then(() => this.emitSubTeardown(p), er => {
523 if (!er || typeof er !== 'object')
524 er = { error: er }
525 er.tapCaught = 'teardown'
526 throw er
527 })
528 }
529 } catch (er) {
530 threw(er)
531 }
532 }
533
534 p.emit('teardown')
535 }
536
537 writeSubComment (p, cb) {
538 const comment = '# Subtest' +
539 (p.name ? ': ' + p.name : '') +
540 '\n'
541 this.parser.write(comment)
542 cb()
543 }
544
545 onbufferedend (p) {
546 delete p.ondone
547 p.results = p.results || {}
548 p.readyToProcess = true
549 const to = p.options.timeout
550 const dur = (to && p.passing()) ? Date.now() - p.start : null
551 if (dur && dur > to)
552 p.timeout()
553 else
554 p.setTimeout(false)
555 this.debug('%s.onbufferedend', this.name, p.name, p.results.bailout)
556 this.pool.remove(p)
557 p.options.tapChildBuffer = p.output || ''
558 p.options.stack = ''
559 if (p.time)
560 p.options.time = p.time
561 if (this.occupied === p)
562 this.occupied = null
563 p.deferred.resolve(this)
564 this.emit('subtestEnd', p)
565 this.process()
566 }
567
568 onindentedend (p) {
569 this.emit('subtestProcess', p)
570 delete p.ondone
571 this.debug('onindentedend', p)
572 this.noparallel = false
573 const sti = this.subtests.indexOf(p)
574 if (sti !== -1)
575 this.subtests.splice(sti, 1)
576 p.readyToProcess = true
577 p.options.time = p.time
578 const to = p.options.timeout
579 const dur = (to && p.passing()) ? Date.now() - p.start : null
580 if (dur && dur > to)
581 p.timeout()
582 else
583 p.setTimeout(false)
584 this.debug('onindentedend %s(%s)', this.name, p.name)
585 if (!p.bailedOut && !this.bailedOut)
586 assert.equal(this.occupied, p)
587 this.occupied = null
588 this.debug('OIE(%s) b>shift into queue', this.name, this.queue)
589 p.options.stack = ''
590
591 this.queue.unshift(['emitSubTeardown', p])
592 this.printResult(p.passing(), p.name, p.options, true)
593
594 this.debug('OIE(%s) shifted into queue', this.name, this.queue)
595 p.deferred.resolve(this)
596 this.emit('subtestEnd', p)
597 this.process()
598 }
599
600 addAssert (name, length, fn) {
601 if (!name)
602 throw new TypeError('name is required for addAssert')
603
604 if (!(typeof length === 'number' && length >= 0))
605 throw new TypeError('number of args required')
606
607 if (typeof fn !== 'function')
608 throw new TypeError('function required for addAssert')
609
610 if (Test.prototype[name] || this[name])
611 throw new TypeError('attempt to re-define `' + name + '` assert')
612
613 const ASSERT = function (...args) {
614 this.currentAssert = ASSERT
615 if (typeof args[length] === 'object') {
616 args[length + 1] = args[length]
617 args[length] = ''
618 } else {
619 args[length] = args[length] || ''
620 args[length + 1] = args[length + 1] || {}
621 }
622
623 return fn.apply(this, args)
624 }
625 this[name] = ASSERT
626 }
627
628 printResult (ok, message, extra, front) {
629 if (this.doingStdinOnly)
630 throw new Error('cannot print results in stdinOnly mode')
631 const n = this.count + 1
632 this.currentAssert = Test.prototype.printResult
633 const fn = this[_currentAssert]
634 this[_currentAssert] = null
635
636 if (this.planEnd !== -1 && n > this.planEnd) {
637 if (!this.passing())
638 return
639
640 const failMessage = this.explicitEnded
641 ? 'test after end() was called'
642 : 'test count exceeds plan'
643
644 const er = new Error(failMessage)
645 captureStackTrace(er, fn)
646 er.test = this.name
647 er.plan = this.planEnd
648 this.threw(er)
649 return
650 }
651
652 extra = extra || {}
653
654 if (extra.expectFail)
655 ok = !ok
656
657 if (this.assertAt) {
658 extra.at = this.assertAt
659 this.assertAt = null
660 }
661
662 if (this.assertStack) {
663 extra.stack = this.assertStack
664 this.assertStack = null
665 }
666
667 if (hasOwn(extra, 'stack') && !hasOwn(extra, 'at'))
668 extra.at = stack.parseLine(extra.stack.split('\n')[0])
669
670 if (!ok && !extra.skip && !hasOwn(extra, 'at')) {
671 assert.equal(typeof fn, 'function')
672 extra.at = stack.at(fn)
673 if (!extra.todo)
674 extra.stack = stack.captureString(80, fn)
675 }
676
677 const diagnostic =
678 typeof extra.diagnostic === 'boolean' ? extra.diagnostic
679 : process.env.TAP_DIAG === '0' ? false
680 : process.env.TAP_DIAG === '1' ? true
681 : extra.skip ? false
682 : !ok
683
684 if (diagnostic)
685 extra.diagnostic = true
686
687 this.count = n
688 message = message + ''
689 const res = { ok, message, extra }
690
691 const output = new TestPoint(ok, message, extra)
692 // when we jump the queue, skip an extra line
693 if (front)
694 output.message = output.message.trimRight() + '\n\n'
695
696 if (this.occupied && this.occupied instanceof Waiter &&
697 this.occupied.finishing)
698 front = true
699
700 if (front) {
701 this.emit('result', res)
702 this.parser.write(output.ok + (++this.n) + output.message)
703 if (this.bail && !ok && !extra.skip && !extra.todo)
704 this.parser.write('Bail out! ' + message + '\n')
705 } else {
706 this.queue.push(['emit', 'result', res], output)
707 if (this.bail && !ok && !extra.skip && !extra.todo)
708 this.queue.push('Bail out! ' + message + '\n')
709 }
710
711 if (this.planEnd === this.count)
712 this.end(IMPLICIT)
713
714 this.process()
715 }
716
717 pragma (set) {
718 const p = Object.keys(set).reduce((acc, i) =>
719 acc + 'pragma ' + (set[i] ? '+' : '-') + i + '\n', '')
720 this.queue.push(p)
721 this.process()
722 }
723
724 plan (n, comment) {
725 if (this.bailedOut)
726 return
727
728 if (this.planEnd !== -1) {
729 throw new Error('Cannot set plan more than once')
730 }
731
732 if (typeof n !== 'number' || n < 0) {
733 throw new TypeError('plan must be a number')
734 }
735
736 // Cannot get any tests after a trailing plan, or a plan of 0
737 const ending = this.count !== 0 || n === 0
738
739 if (n === 0 && comment && !this.options.skip)
740 this.options.skip = comment
741
742 this.planEnd = n
743 comment = comment ? ' # ' + comment.trim() : ''
744 this.queue.push('1..' + n + comment + '\n')
745
746 if (ending)
747 this.end(IMPLICIT)
748 else
749 this.process()
750 }
751
752 end (implicit) {
753 if (this.doingStdinOnly && implicit !== IMPLICIT)
754 throw new Error('cannot explicitly end while in stdinOnly mode')
755 this.debug('END implicit=%j', implicit === IMPLICIT)
756 if (this.ended && implicit === IMPLICIT)
757 return
758
759 if (this[_beforeEnd].length) {
760 for (let b = 0; b < this[_beforeEnd].length; b++) {
761 const m = this[_beforeEnd][b].shift()
762 this[m].apply(this, this[_beforeEnd][b])
763 }
764 this[_beforeEnd].length = 0
765 }
766
767 // beyond here we have to be actually done with things, or else
768 // the semantic checks on counts and such will be off.
769 if (!queueEmpty(this) || this.occupied) {
770 if (!this.pushedEnd)
771 this.queue.push(['end', implicit])
772 this.pushedEnd = true
773 return this.process()
774 }
775
776 if (!this.ranAfterEach && this.parent) {
777 this.ranAfterEach = true
778 this.parent.runAfterEach(this, () => this[_end](implicit))
779 return
780 } else
781 this[_end](implicit)
782 }
783
784 [_end] (implicit) {
785 this.ended = true
786
787 if (implicit !== IMPLICIT && !this.multiEndThrew) {
788 if (this.explicitEnded) {
789 this.multiEndThrew = true
790 const er = new Error('test end() method called more than once')
791 captureStackTrace(er, this[_currentAssert] ||
792 Test.prototype[_end])
793 er.test = this.name
794 this.threw(er)
795 return
796 }
797 this.explicitEnded = true
798 }
799
800 if (this.planEnd === -1) {
801 this.debug('END(%s) implicit plan', this.name, this.count)
802 this.plan(this.count)
803 }
804
805 this.queue.push(EOF)
806
807 if (this[_expectUncaught].length) {
808 const wanted = this[_expectUncaught]
809 this[_expectUncaught] = []
810 const diag = {
811 wanted: wanted.map(a => a.filter(e => e != null)),
812 test: this.name,
813 at: null,
814 stack: null,
815 }
816 const msg = 'test end without expected uncaught exceptions'
817 this.queue.push(['threw', Object.assign(new Error(msg), diag)])
818 }
819 this.process()
820 }
821
822 threw (er, extra, proxy) {
823 // this can only happen if a beforeEach function raises an error
824 if (this.parent && !this.started) {
825 this.cb = () => {
826 this.threw(er)
827 this.end()
828 }
829 return
830 }
831
832 if (!er || typeof er !== 'object')
833 er = { error: er }
834
835 if (this[_expectUncaught].length && er.tapCaught === 'uncaughtException') {
836 const [wanted, message, extra] = this[_expectUncaught].shift()
837 const actual = isRegExp(wanted) ? er.message : er
838 return wanted
839 ? this.match(actual, wanted, message, extra)
840 : this.pass(message, extra)
841 }
842
843 if (this.name && !proxy)
844 er.test = this.name
845 if (!proxy)
846 extra = extraFromError(er, extra, this.options)
847 Base.prototype.threw.call(this, er, extra, proxy)
848
849 if (!this.results) {
850 this.fail(extra.message || er.message, extra)
851 if (!proxy)
852 this.end(IMPLICIT)
853 }
854 this.process()
855 }
856
857 runBeforeEach (who, cb) {
858 if (this.parent)
859 this.parent.runBeforeEach(who, () => {
860 loop(who, this.onBeforeEach, cb, er => {
861 who.threw(er)
862 cb()
863 })
864 })
865 else
866 loop(who, this.onBeforeEach, cb, er => {
867 who.threw(er)
868 cb()
869 })
870 }
871
872 runAfterEach (who, cb) {
873 loop(who, this.onAfterEach, () => {
874 if (this.parent)
875 this.parent.runAfterEach(who, cb)
876 else
877 cb()
878 }, who.threw)
879 }
880
881 beforeEach (fn) {
882 this.onBeforeEach.push(function (done) {
883 return fn.call(this, done, this)
884 })
885 }
886
887 afterEach (fn) {
888 this.onAfterEach.push(function (done) {
889 return fn.call(this, done, this)
890 })
891 }
892
893 teardown (fn) {
894 this.onTeardown.push(fn)
895 }
896
897 shouldAutoend () {
898 const should = (
899 this.options.autoend &&
900 !this.ended &&
901 !this.occupied &&
902 queueEmpty(this) &&
903 !this.pool.length &&
904 !this.subtests.length &&
905 this.planEnd === -1
906 )
907 return should
908 }
909
910 autoend (value) {
911 // set to false to NOT trigger autoend
912 if (value === false) {
913 this.options.autoend = false
914 clearTimeout(this.autoendTimer)
915 } else {
916 this.options.autoend = true
917 this.maybeAutoend()
918 }
919 }
920
921 maybeAutoend () {
922 if (this.shouldAutoend()) {
923 clearTimeout(this.autoendTimer)
924 this.autoendTimer = setTimeout(() => {
925 if (this.shouldAutoend()) {
926 clearTimeout(this.autoendTimer)
927 this.autoendTimer = setTimeout(() => {
928 if (this.shouldAutoend())
929 this.end(IMPLICIT)
930 })
931 }
932 })
933 }
934 }
935
936 onbail (message) {
937 super.onbail(message)
938 this.end(IMPLICIT)
939 if (!this.parent)
940 this.endAll()
941 }
942
943 endAll (sub) {
944 // in the case of the root TAP test object, we might sometimes
945 // call endAll on a bailing-out test, as the process is ending
946 // In that case, we WILL have a this.occupied and a full queue
947 // These cases are very rare to encounter in other Test objs tho
948 this.processing = true
949 if (this.occupied) {
950 const p = this.occupied
951 if (p.endAll)
952 p.endAll(true)
953 else {
954 p.parser.abort('test unfinished')
955 }
956 } else if (sub) {
957 this.process()
958 if (queueEmpty(this)) {
959 const options = Object.assign({}, this.options)
960 this.options.at = null
961 this.options.stack = ''
962 options.test = this.name
963 this.fail('test unfinished', options)
964 }
965 }
966
967 if (this.promise && this.promise.tapAbortPromise)
968 this.promise.tapAbortPromise()
969
970 if (this.occupied) {
971 this.queue.unshift(this.occupied)
972 this.occupied = null
973 }
974
975 endAllQueue(this.queue)
976 this.processing = false
977 this.process()
978 this.parser.end()
979 }
980
981 get currentAssert () {
982 return this[_currentAssert]
983 }
984
985 set currentAssert (fn) {
986 if (!this[_currentAssert])
987 this[_currentAssert] = fn
988 }
989
990 pass (message, extra) {
991 this.currentAssert = Test.prototype.pass
992
993 if (message && typeof message === 'object')
994 extra = message, message = ''
995
996 if (!extra)
997 extra = {}
998
999 this.printResult(true, message || '(unnamed test)', extra)
1000 return true
1001 }
1002
1003 fail (message, extra) {
1004 this.currentAssert = Test.prototype.fail
1005
1006 if (message && typeof message === 'object')
1007 extra = message, message = ''
1008
1009 if (!extra)
1010 extra = {}
1011
1012 this.printResult(false, message || '(unnamed test)', extra)
1013 return !!(extra.todo || extra.skip)
1014 }
1015
1016 ok (obj, message, extra) {
1017 this.currentAssert = Test.prototype.ok
1018
1019 if (message && typeof message === 'object')
1020 extra = message, message = ''
1021
1022 if (!extra)
1023 extra = {}
1024 message = message || 'expect truthy value'
1025 return obj ? this.pass(message, extra) : this.fail(message, extra)
1026 }
1027
1028 notOk (obj, message, extra) {
1029 this.currentAssert = Test.prototype.notOk
1030
1031 if (message && typeof message === 'object')
1032 extra = message, message = ''
1033
1034 if (!extra)
1035 extra = {}
1036 message = message || 'expect falsey value'
1037 return this.ok(!obj, message, extra)
1038 }
1039
1040 emits (emitter, event, message, extra) {
1041 this.currentAssert = Test.prototype.emits
1042
1043 if (message && typeof message === 'object')
1044 extra = message, message = ''
1045
1046 if (!message)
1047 message = `expect ${event} event to be emitted`
1048
1049 if (!extra)
1050 extra = {}
1051
1052 const handler = () => handler.emitted = true
1053 handler.emitted = false
1054 emitter.once(event, handler)
1055 extra.at = stack.at(Test.prototype.emits)
1056 extra.stack = stack.captureString(80, Test.prototype.emits)
1057 this[_beforeEnd].push([_emits, emitter, event, handler, message, extra])
1058 }
1059
1060 [_emits] (emitter, event, handler, message, extra) {
1061 if (handler.emitted)
1062 return this.pass(message, extra)
1063 else {
1064 emitter.removeListener(event, handler)
1065 return this.fail(message, extra)
1066 }
1067 }
1068
1069 error (er, message, extra) {
1070 this.currentAssert = Test.prototype.error
1071
1072 if (message && typeof message === 'object')
1073 extra = message, message = ''
1074
1075 if (!extra)
1076 extra = {}
1077
1078 if (!er) {
1079 return this.pass(message || 'should not error', extra)
1080 }
1081
1082 if (!(er instanceof Error)) {
1083 extra.found = er
1084 return this.fail(message || 'non-Error error encountered', extra)
1085 }
1086
1087 message = message || er.message
1088 extra.found = er
1089 return this.fail(message, extra)
1090 }
1091
1092 equal (found, wanted, message, extra) {
1093 this.currentAssert = Test.prototype.equal
1094
1095 if (message && typeof message === 'object')
1096 extra = message, message = ''
1097
1098 if (!extra)
1099 extra = {}
1100
1101 message = message || 'should be equal'
1102 if (found === wanted) {
1103 return this.pass(message, extra)
1104 }
1105
1106 extra.found = found
1107 extra.wanted = wanted
1108 extra.compare = '==='
1109
1110 if (typeof found === 'object' &&
1111 typeof wanted === 'object' &&
1112 found &&
1113 wanted &&
1114 same(found, wanted).match) {
1115 extra.note = 'object identities differ'
1116 }
1117
1118 return this.fail(message, extra)
1119 }
1120
1121 not (found, wanted, message, extra) {
1122 this.currentAssert = Test.prototype.not
1123
1124 if (message && typeof message === 'object')
1125 extra = message, message = ''
1126
1127 if (!extra)
1128 extra = {}
1129
1130 message = message || 'should not be equal'
1131 if (found !== wanted) {
1132 return this.pass(message, extra)
1133 }
1134
1135 extra.found = found
1136 extra.doNotWant = wanted
1137 extra.compare = '!=='
1138
1139 return this.fail(message, extra)
1140 }
1141
1142 same (found, wanted, message, extra) {
1143 this.currentAssert = Test.prototype.same
1144
1145 if (message && typeof message === 'object')
1146 extra = message, message = ''
1147
1148 if (!extra)
1149 extra = {}
1150
1151 message = message || 'should be equivalent'
1152 extra.found = found
1153 extra.wanted = wanted
1154 const s = same(found, wanted)
1155 if (!s.match)
1156 extra.diff = s.diff
1157 return this.ok(s.match, message, extra)
1158 }
1159
1160 notSame (found, wanted, message, extra) {
1161 this.currentAssert = Test.prototype.notSame
1162
1163 if (message && typeof message === 'object')
1164 extra = message, message = ''
1165
1166 if (!extra)
1167 extra = {}
1168
1169 message = message || 'should not be equivalent'
1170 extra.found = found
1171 extra.doNotWant = wanted
1172 const s = same(found, wanted)
1173 return this.notOk(s.match, message, extra)
1174 }
1175
1176 strictSame (found, wanted, message, extra) {
1177 this.currentAssert = Test.prototype.strictSame
1178
1179 if (message && typeof message === 'object')
1180 extra = message, message = ''
1181
1182 if (!extra)
1183 extra = {}
1184
1185 message = message || 'should be equivalent strictly'
1186 extra.found = found
1187 extra.wanted = wanted
1188 const s = strict(found, wanted)
1189 if (!s.match)
1190 extra.diff = s.diff
1191 return this.ok(s.match, message, extra)
1192 }
1193
1194 strictNotSame (found, wanted, message, extra) {
1195 this.currentAssert = Test.prototype.strictNotSame
1196
1197 if (message && typeof message === 'object')
1198 extra = message, message = ''
1199
1200 if (!extra)
1201 extra = {}
1202
1203 message = message || 'should not be equivalent strictly'
1204 extra.found = found
1205 extra.doNotWant = wanted
1206 const s = strict(found, wanted)
1207 return this.notOk(s.match, message, extra)
1208 }
1209
1210 get fullname () {
1211 const main = process.argv.slice(1).join(' ').trim()
1212 return (this.parent ? this.parent.fullname
1213 : main.indexOf(cwd) === 0 ? main.substr(cwd.length + 1)
1214 : path.basename(main)).replace(/\\/g, '/') +
1215 ' ' + (this.name || '').trim()
1216 }
1217
1218 matchSnapshot (found, message, extra) {
1219 this.currentAssert = Test.prototype.matchSnapshot
1220
1221 if (message && typeof message === 'object')
1222 extra = message, message = null
1223
1224 if (!extra)
1225 extra = {}
1226
1227 if (!message)
1228 message = 'must match snapshot'
1229
1230 // use notOk because snap doesn't return a truthy value
1231 const m = this.fullname + ' > ' + message
1232 if (typeof found !== 'string') {
1233 found = (this.formatSnapshot || formatSnapshotDefault)(found)
1234 if (typeof found !== 'string')
1235 found = formatSnapshotDefault(found)
1236 }
1237
1238 found = this.cleanSnapshot(found)
1239
1240 return this.writeSnapshot
1241 ? this.notOk(this[_getSnapshot]().snap(found, m),
1242 message, extra)
1243 : this.equal(found, this[_getSnapshot]().read(m),
1244 message, extra)
1245 }
1246
1247 [_getSnapshot] () {
1248 if (this[_snapshot])
1249 return this[_snapshot]
1250
1251 if (this.parent) {
1252 const parentSnapshot = this.parent[_getSnapshot]()
1253 // very rare for the parent to not have one.
1254 /* istanbul ignore else */
1255 if (parentSnapshot)
1256 return this[_snapshot] = parentSnapshot
1257 }
1258
1259 return this[_snapshot] = new Snapshot(this)
1260 }
1261
1262 cleanSnapshot (string) {
1263 return string
1264 }
1265
1266 match (found, wanted, message, extra) {
1267 this.currentAssert = Test.prototype.match
1268
1269 if (message && typeof message === 'object')
1270 extra = message, message = ''
1271
1272 if (!extra)
1273 extra = {}
1274
1275 message = message || 'should match pattern provided'
1276 extra.found = found
1277 extra.pattern = wanted
1278 const s = match(found, wanted)
1279 if (!s.match)
1280 extra.diff = s.diff
1281 return this.ok(s.match, message, extra)
1282 }
1283
1284 notMatch (found, wanted, message, extra) {
1285 this.currentAssert = Test.prototype.notMatch
1286
1287 if (message && typeof message === 'object')
1288 extra = message, message = ''
1289
1290 if (!extra)
1291 extra = {}
1292
1293 message = message || 'should not match pattern provided'
1294 extra.found = found
1295 extra.pattern = wanted
1296 const s = match(found, wanted)
1297 return this.notOk(s.match, message, extra)
1298 }
1299
1300 type (obj, klass, message, extra) {
1301 this.currentAssert = Test.prototype.type
1302
1303 if (message && typeof message === 'object')
1304 extra = message, message = ''
1305
1306 if (!extra)
1307 extra = {}
1308
1309 const name = typeof klass === 'function' ?
1310 klass.name || '(anonymous constructor)'
1311 : klass
1312
1313 message = message || 'type is ' + name
1314
1315 // simplest case, it literally is the same thing
1316 if (obj === klass) {
1317 return this.pass(message, extra)
1318 }
1319
1320 const tof = typeof obj
1321 const type = (!obj && tof === 'object') ? 'null'
1322 // treat as object, but not Object
1323 // t.type(() => {}, Function)
1324 : (tof === 'function' &&
1325 typeof klass === 'function' &&
1326 klass !== Object) ? 'object'
1327 : tof
1328
1329 if (type === 'object' && klass !== 'object') {
1330 if (typeof klass === 'function') {
1331 extra.found = Object.getPrototypeOf(obj).constructor.name
1332 extra.wanted = name
1333 return this.ok(obj instanceof klass, message, extra)
1334 }
1335
1336 // check prototype chain for name
1337 // at this point, we already know klass is not a function
1338 // if the klass specified is an obj in the proto chain, pass
1339 // if the name specified is the name of a ctor in the chain, pass
1340 for (let p = obj; p; p = Object.getPrototypeOf(p)) {
1341 const ctor = p.constructor && p.constructor.name
1342 if (p === klass || ctor === name) {
1343 return this.pass(message, extra)
1344 }
1345 }
1346 }
1347
1348 return this.equal(type, name, message, extra)
1349 }
1350
1351 expectUncaughtException (...args) {
1352 let [_, ...rest] = this.throwsArgs('expect uncaughtException', ...args)
1353 this[_expectUncaught].push(rest)
1354 }
1355
1356 throwsArgs (defaultMessage, ...args) {
1357 let fn, wanted, message, extra
1358 for (let i = 0; i < args.length; i++) {
1359 const arg = args[i]
1360 if (typeof arg === 'function') {
1361 if (arg === Error || arg.prototype instanceof Error) {
1362 wanted = arg
1363 } else if (!fn) {
1364 fn = arg
1365 }
1366 } else if (typeof arg === 'string' && arg) {
1367 message = arg
1368 } else if (typeof arg === 'object') {
1369 if (!wanted) {
1370 wanted = arg
1371 } else {
1372 extra = arg
1373 }
1374 }
1375 }
1376
1377 if (!extra)
1378 extra = {}
1379
1380 if (!message)
1381 message = fn && fn.name || defaultMessage
1382
1383 if (wanted) {
1384 if (wanted instanceof Error) {
1385 const w = {
1386 message: wanted.message
1387 }
1388 if (wanted.name) {
1389 w.name = wanted.name
1390 }
1391
1392 // intentionally copying non-local properties, since this
1393 // is an Error object, and those are funky.
1394 for (let i in wanted) {
1395 w[i] = wanted[i]
1396 }
1397 wanted = w
1398
1399 message += ': ' + (wanted.name || 'Error') + ' ' + wanted.message
1400 extra.wanted = wanted
1401 }
1402 }
1403
1404 return [fn, wanted, message, extra]
1405 }
1406
1407 throws (...args) {
1408 this.currentAssert = Test.prototype.throws
1409
1410 const [fn, wanted, message, extra] =
1411 this.throwsArgs('expected to throw', ...args)
1412
1413 if (typeof fn !== 'function') {
1414 extra.todo = extra.todo || true
1415 return this.pass(message, extra)
1416 }
1417
1418 try {
1419 fn()
1420 return this.fail(message, extra)
1421 } catch (er) {
1422 // 'name' is a getter.
1423 if (er.name) {
1424 Object.defineProperty(er, 'name', {
1425 value: er.name + '',
1426 enumerable: true,
1427 configurable: true,
1428 writable: true
1429 })
1430 }
1431
1432 const actual = isRegExp(wanted) ? er.message : er
1433 return wanted
1434 ? this.match(actual, wanted, message, extra) && er
1435 : this.pass(message, extra) && er
1436 }
1437 }
1438
1439 doesNotThrow (fn, message, extra) {
1440 this.currentAssert = Test.prototype.doesNotThrow
1441
1442 if (message && typeof message === 'object')
1443 extra = message, message = ''
1444
1445 if (!extra)
1446 extra = {}
1447
1448 if (typeof fn === 'string') {
1449 const x = fn
1450 fn = message
1451 message = x
1452 }
1453
1454 if (!message) {
1455 message = fn && fn.name || 'expected to not throw'
1456 }
1457
1458 if (typeof fn !== 'function') {
1459 extra.todo = extra.todo || true
1460 return this.pass(message, extra)
1461 }
1462
1463 try {
1464 fn()
1465 return this.pass(message, extra)
1466 } catch (er) {
1467 const e = extraFromError(er, extra)
1468 e.message = er.message
1469 return this.fail(message, e)
1470 }
1471 }
1472
1473 waitOn (promise, cb) {
1474 const w = new Waiter(promise, w => {
1475 assert.equal(this.occupied, w)
1476 cb(w)
1477 this.occupied = null
1478 this.process()
1479 })
1480 this.queue.push(w)
1481 this.process()
1482 return w.promise
1483 }
1484
1485 // like throws, but rejects a returned promise instead
1486 // also, can pass in a promise instead of a function
1487 rejects (...args) {
1488 this.currentAssert = Test.prototype.rejects
1489
1490 let fn, wanted, extra, promise, message
1491 for (let i = 0; i < args.length; i++) {
1492 const arg = args[i]
1493 if (typeof arg === 'function') {
1494 if (arg === Error || arg.prototype instanceof Error) {
1495 wanted = arg
1496 } else if (!fn) {
1497 fn = arg
1498 }
1499 } else if (typeof arg === 'string' && arg) {
1500 message = arg
1501 } else if (arg && typeof arg.then === 'function' && !promise) {
1502 promise = arg
1503 } else if (typeof arg === 'object') {
1504 if (!wanted) {
1505 wanted = arg
1506 } else {
1507 extra = arg
1508 }
1509 }
1510 }
1511
1512 if (!extra)
1513 extra = {}
1514
1515 if (!message)
1516 message = fn && fn.name || 'expect rejected Promise'
1517
1518 if (wanted) {
1519 if (wanted instanceof Error) {
1520 const w = {
1521 message: wanted.message
1522 }
1523 if (wanted.name)
1524 w.name = wanted.name
1525
1526 // intentionally copying non-local properties, since this
1527 // is an Error object, and those are funky.
1528 for (let i in wanted) {
1529 w[i] = wanted[i]
1530 }
1531 wanted = w
1532
1533 message += ': ' + (wanted.name || 'Error') + ' ' + wanted.message
1534 extra.wanted = wanted
1535 }
1536 }
1537
1538 if (!promise && typeof fn !== 'function') {
1539 extra.todo = extra.todo || true
1540 this.pass(message, extra)
1541 return Promise.resolve(this)
1542 }
1543
1544 if (!promise)
1545 promise = fn()
1546
1547 if (!promise || typeof promise.then !== 'function') {
1548 this.fail(message, extra)
1549 return Promise.resolve(this)
1550 }
1551
1552 extra.at = stack.at(this.currentAssert)
1553 return this.waitOn(promise, w => {
1554 if (!w.rejected) {
1555 extra.found = w.value
1556 return this.fail(message, extra)
1557 }
1558
1559 const er = w.value
1560 // 'name' is a getter.
1561 if (er && er.name) {
1562 Object.defineProperty(er, 'name', {
1563 value: er.name + '',
1564 enumerable: true,
1565 configurable: true,
1566 writable: true
1567 })
1568 }
1569
1570 const actual = isRegExp(wanted) && er ? er.message : er
1571 return wanted ? this.match(actual, wanted, message, extra)
1572 : this.pass(message, extra)
1573 })
1574 }
1575
1576 resolves (promise, message, extra) {
1577 this.currentAssert = Test.prototype.resolves
1578
1579 if (message && typeof message === 'object')
1580 extra = message, message = ''
1581
1582 if (!extra)
1583 extra = {}
1584
1585 if (!message)
1586 message = 'expect resolving Promise'
1587
1588 if (typeof promise === 'function')
1589 promise = promise()
1590
1591 extra.at = stack.at(this.currentAssert)
1592
1593 if (!promise || typeof promise.then !== 'function') {
1594 this.fail(message, extra)
1595 return Promise.resolve(this)
1596 }
1597
1598 return this.waitOn(promise, w => {
1599 extra.found = w.value
1600 this.ok(w.resolved, message, extra)
1601 })
1602 }
1603
1604 resolveMatch (promise, wanted, message, extra) {
1605 this.currentAssert = Test.prototype.resolveMatch
1606
1607 if (message && typeof message === 'object')
1608 extra = message, message = ''
1609
1610 if (!extra)
1611 extra = {}
1612
1613 if (!message)
1614 message = 'expect resolving Promise'
1615
1616 extra.at = stack.at(this.currentAssert)
1617
1618 if (typeof promise === 'function')
1619 promise = promise()
1620
1621 if (!promise || typeof promise.then !== 'function') {
1622 this.fail(message, extra)
1623 return Promise.resolve(this)
1624 }
1625
1626 return this.waitOn(promise, w => {
1627 extra.found = w.value
1628 return w.rejected ? this.fail(message, extra)
1629 : this.match(w.value, wanted, message, extra)
1630 })
1631 }
1632
1633 resolveMatchSnapshot (promise, message, extra) {
1634 this.currentAssert = Test.prototype.resolveMatch
1635
1636 if (message && typeof message === 'object')
1637 extra = message, message = ''
1638
1639 if (!extra)
1640 extra = {}
1641
1642 if (!message)
1643 message = 'expect resolving Promise'
1644
1645 extra.at = stack.at(this.currentAssert)
1646
1647 if (typeof promise === 'function')
1648 promise = promise()
1649
1650 if (!promise || typeof promise.then !== 'function') {
1651 this.fail(message, extra)
1652 return Promise.resolve(this)
1653 }
1654
1655 return this.waitOn(promise, w => {
1656 extra.found = w.value
1657 return w.rejected ? this.fail(message, extra)
1658 : this.matchSnapshot(w.value, message, extra)
1659 })
1660 }
1661}
1662
1663const endAllQueue = queue => {
1664 queue.forEach((p, i) => {
1665 if ((p instanceof Base) && !p.readyToProcess)
1666 queue[i] = new TestPoint(false,
1667 'child test left in queue:' +
1668 ' t.' + p.constructor.name.toLowerCase() + ' ' + p.name,
1669 p.options)
1670 })
1671 queue.push(['end', IMPLICIT])
1672}
1673
1674const queueEmpty = t =>
1675 t.queue.length === 0 ||
1676 t.queue.length === 1 && t.queue[0] === 'TAP version 13\n'
1677
1678Test.prototype.done = Test.prototype.end
1679Test.prototype.tearDown = Test.prototype.teardown
1680
1681Object.keys(synonyms)
1682 .filter(c => Test.prototype[c])
1683 .forEach(c => synonyms[c].forEach(s =>
1684 Object.defineProperty(Test.prototype, s, {
1685 value: Test.prototype[c],
1686 enumerable: false,
1687 configurable: true,
1688 writable: true
1689 })))
1690
1691module.exports = Test