1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | const {format, same, strict, match} = require('tcompare')
|
21 | const formatSnapshotDefault = obj => format(obj, { sort: true })
|
22 | const Base = require('./base.js')
|
23 | const Spawn = require('./spawn.js')
|
24 | const Stdin = require('./stdin.js')
|
25 | const Deferred = require('trivial-deferred')
|
26 | const Pool = require('yapool')
|
27 | const TestPoint = require('./point.js')
|
28 | const parseTestArgs = require('./parse-test-args.js')
|
29 | const loop = require('function-loop')
|
30 | const captureStackTrace = require('capture-stack-trace')
|
31 | const path = require('path')
|
32 |
|
33 | const extraFromError = require('./extra-from-error.js')
|
34 | const stack = require('./stack.js')
|
35 | const synonyms = require('./synonyms.js')
|
36 | const assert = require('assert')
|
37 | const util = require('util')
|
38 | const ownOr = require('own-or')
|
39 | const ownOrEnv = require('own-or-env')
|
40 | const bindObj = require('bind-obj-methods')
|
41 | const cwd = process.cwd()
|
42 |
|
43 |
|
44 |
|
45 | if (!Error.captureStackTrace) {
|
46 | Error.captureStackTrace = captureStackTrace
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 | const IMPLICIT = Symbol('implicit t.end()')
|
52 |
|
53 |
|
54 | const EOF = Symbol('EOF')
|
55 |
|
56 | const _currentAssert = Symbol('_currentAssert')
|
57 | const _end = Symbol('_end')
|
58 | const _snapshot = Symbol('_snapshot')
|
59 | const _getSnapshot = Symbol('_getSnapshot')
|
60 | const _beforeEnd = Symbol('_beforeEnd')
|
61 | const _emits = Symbol('_emits')
|
62 | const _nextChildId = Symbol('_nextChildId')
|
63 | const _expectUncaught = Symbol('_expectUncaught')
|
64 | const Snapshot = require('./snapshot.js')
|
65 |
|
66 | const hasOwn = (obj, key) =>
|
67 | Object.prototype.hasOwnProperty.call(obj, key)
|
68 |
|
69 | const isRegExp = re =>
|
70 | Object.prototype.toString.call(re) === '[object RegExp]'
|
71 |
|
72 | class 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 |
|
103 | class 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 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
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 |
|
153 |
|
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 |
|
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 |
|
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 |
|
362 |
|
363 |
|
364 |
|
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 |
|
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 |
|
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 |
|
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 |
|
467 |
|
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 |
|
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 |
|
497 |
|
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 |
|
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 |
|
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 |
|
768 |
|
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 |
|
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 |
|
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 |
|
945 |
|
946 |
|
947 |
|
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 |
|
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 |
|
1254 |
|
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 |
|
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 |
|
1323 |
|
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 |
|
1337 |
|
1338 |
|
1339 |
|
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 |
|
1393 |
|
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 |
|
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 |
|
1486 |
|
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 |
|
1527 |
|
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 |
|
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 |
|
1663 | const 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 |
|
1674 | const queueEmpty = t =>
|
1675 | t.queue.length === 0 ||
|
1676 | t.queue.length === 1 && t.queue[0] === 'TAP version 13\n'
|
1677 |
|
1678 | Test.prototype.done = Test.prototype.end
|
1679 | Test.prototype.tearDown = Test.prototype.teardown
|
1680 |
|
1681 | Object.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 |
|
1691 | module.exports = Test
|