forked from enviPath/enviPy
790 lines
19 KiB
JavaScript
790 lines
19 KiB
JavaScript
// We need TWO queues (work and subtest) and one jobs pool
|
|
//
|
|
// The pool stores buffered subtests being run in parallel.
|
|
//
|
|
// When new subtests are created, they get put in the work queue and also
|
|
// in the subtests queue if they are buffered and jobs>0. When we put a
|
|
// test in the subtest queue, we also process it.
|
|
//
|
|
// Processing the subtest queue means moving tests into the jobs pool until
|
|
// the jobs pool length is at this.jobs
|
|
//
|
|
// Any output functions get put in the work queue if its length > 0 (ie,
|
|
// no cutting the line)
|
|
//
|
|
// Processing the work queue means walking until we run out of things, or
|
|
// encounter an unfinished test. When we encounter ANY kind of test, we
|
|
// block until its output is completed, dumping it all into the parser.
|
|
|
|
var Base = require('./base.js')
|
|
var Spawn = require('./spawn.js')
|
|
var Stdin = require('./stdin.js')
|
|
var Deferred = require('trivial-deferred')
|
|
var Pool = require('yapool')
|
|
var TestPoint = require('./point.js')
|
|
var parseTestArgs = require('./parse-test-args.js')
|
|
var loop = require('function-loop')
|
|
|
|
var extraFromError = require('./extra-from-error.js')
|
|
var stack = require('./stack.js')
|
|
var assert = require('assert')
|
|
var util = require('util')
|
|
util.inherits(Test, Base)
|
|
var ownOr = require('own-or')
|
|
var ownOrEnv = require('own-or-env')
|
|
var tapAsserts = require('./asserts.js')
|
|
var Promise = require('bluebird')
|
|
var bindObj = require('bind-obj-methods')
|
|
|
|
// A sigil object for implicit end() calls that should not
|
|
// trigger an error if the user then calls t.end()
|
|
var IMPLICIT = {}
|
|
|
|
// Sigil to put in the queue to signal the end of all things
|
|
var EOF = { EOF: true }
|
|
|
|
function hasOwn (obj, key) {
|
|
return Object.prototype.hasOwnProperty.call(obj, key)
|
|
}
|
|
|
|
module.exports = Test
|
|
|
|
function Test (options) {
|
|
options = options || {}
|
|
if (!(this instanceof Test))
|
|
return new Test(options)
|
|
|
|
Base.call(this, options)
|
|
this.pushedEnd = false
|
|
this.jobs = ownOr(options, 'jobs', 1)
|
|
this.subtests = []
|
|
this.pool = new Pool()
|
|
this.queue = ['TAP version 13\n']
|
|
this.noparallel = false
|
|
this.cb = this.domain.bind(options.cb)
|
|
this.occupied = false
|
|
this.currentAssert = null
|
|
this.count = 0
|
|
this.n = 0
|
|
this.ended = false
|
|
this.explicitEnded = false
|
|
this.multiEndThrew = false
|
|
this.currentAssert = null
|
|
this.assertAt = null
|
|
this.assertStack = null
|
|
this.planEnd = -1
|
|
this.onBeforeEach = []
|
|
this.onAfterEach = []
|
|
this.ranAfterEach = false
|
|
|
|
// bind all methods to this object, so we can pass t.end as a callback
|
|
// and do `var test = require('tap').test` like people do.
|
|
var bound = Object.create(null)
|
|
bindObj(this, this, bound)
|
|
bindObj(this, Object.getPrototypeOf(this), bound)
|
|
bindObj(this, Test.prototype, bound)
|
|
}
|
|
|
|
Test.prototype.current = function () {
|
|
throw new Error('Test.current() as been removed and is no more')
|
|
}
|
|
|
|
Test.prototype.spawn = function spawn (cmd, args, options, name) {
|
|
if (typeof args === 'string') {
|
|
args = [ args ]
|
|
}
|
|
|
|
args = args || []
|
|
|
|
if (typeof options === 'string') {
|
|
name = options
|
|
options = {}
|
|
}
|
|
|
|
options = options || {}
|
|
options.name = ownOr(options, 'name', name)
|
|
options.command = cmd
|
|
options.args = args
|
|
|
|
return this.sub(Spawn, options, spawn)
|
|
}
|
|
|
|
Test.prototype.sub = function (Class, extra, caller) {
|
|
extra = extra || {}
|
|
if (!extra.skip && this.grep.length) {
|
|
var match = this.grep[0].test(extra.name)
|
|
if (this.grepInvert) {
|
|
match = !match
|
|
}
|
|
if (!match) {
|
|
var p = 'filter' + (this.grepInvert ? ' out' : '') + ': '
|
|
extra.skip = p + this.grep[0]
|
|
}
|
|
}
|
|
|
|
if (extra.only && !this.runOnly) {
|
|
this.comment('%j has `only` set but all tests run', extra.name)
|
|
}
|
|
|
|
if (this.runOnly && !extra.only) {
|
|
extra.skip = 'filter: only'
|
|
}
|
|
|
|
if (extra.todo || extra.skip) {
|
|
this.pass(extra.name, extra)
|
|
return Promise.resolve(this)
|
|
}
|
|
|
|
if (!extra.grep) {
|
|
extra.grep = this.grep.slice(1)
|
|
extra.grepInvert = this.grepInvert
|
|
}
|
|
|
|
extra.indent = ' '
|
|
if (this.jobs > 1 && process.env.TAP_BUFFER === undefined)
|
|
extra.buffered = ownOr(extra, 'buffered', true)
|
|
else
|
|
extra.buffered = ownOrEnv(extra, 'buffered', 'TAP_BUFFER', true)
|
|
|
|
extra.bail = ownOr(extra, 'bail', this.bail)
|
|
extra.parent = this
|
|
extra.stack = stack.captureString(80, caller)
|
|
var t = new Class(extra)
|
|
|
|
this.queue.push(t)
|
|
this.subtests.push(t)
|
|
|
|
var d = new Deferred()
|
|
t.deferred = d
|
|
this.process()
|
|
return d.promise
|
|
}
|
|
|
|
Test.prototype.only = function test (name, extra, cb) {
|
|
extra = parseTestArgs(name, extra, cb)
|
|
extra.only = true
|
|
return this.sub(Test, extra, test)
|
|
}
|
|
|
|
Test.prototype.test = function test (name, extra, cb) {
|
|
extra = parseTestArgs(name, extra, cb)
|
|
return this.sub(Test, extra, test)
|
|
}
|
|
|
|
Test.prototype.stdin = function stdin (name, extra) {
|
|
extra = parseTestArgs(name, extra, function () {}, '/dev/stdin')
|
|
return this.sub(Stdin, extra || {}, stdin)
|
|
}
|
|
|
|
Test.prototype.bailout = function (message) {
|
|
if (this.parent && (this.results || this.ended))
|
|
this.parent.bailout(message)
|
|
else {
|
|
this.process()
|
|
message = message ? ' ' + ('' + message).trim() : ''
|
|
message = message.replace(/[\r\n]/g, ' ')
|
|
this.parser.write('Bail out!' + message + '\n')
|
|
}
|
|
this.end(IMPLICIT)
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.comment = function () {
|
|
var message = util.format.apply(util, arguments)
|
|
message = '# ' + message.split(/\r?\n/).join('\n# ') + '\n'
|
|
|
|
if (this.results)
|
|
this.push(message)
|
|
else
|
|
this.queue.push(message)
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.timeout = function (options) {
|
|
options = options || {}
|
|
options.expired = options.expired || this.name
|
|
if (this.occupied)
|
|
this.occupied.timeout(options)
|
|
else
|
|
Base.prototype.timeout.call(this, options)
|
|
this.end(IMPLICIT)
|
|
}
|
|
|
|
Test.prototype.main = function (cb) {
|
|
this.setTimeout(this.options.timeout)
|
|
this.debug('MAIN pre', this)
|
|
|
|
var self = this
|
|
try {
|
|
var ret = this.cb(this)
|
|
} catch (er) {
|
|
this.threw(er)
|
|
}
|
|
|
|
if (ret && ret.then) {
|
|
this.promise = ret
|
|
ret.tapAbortPromise = done
|
|
ret.then(end, done)
|
|
} else
|
|
done()
|
|
|
|
function end () {
|
|
self.debug(' > implicit end for promise')
|
|
self.end(IMPLICIT)
|
|
done()
|
|
}
|
|
|
|
function done (er) {
|
|
if (er)
|
|
self.threw(er)
|
|
|
|
if (self.results || self.bailedOut)
|
|
cb()
|
|
else
|
|
self.ondone = cb
|
|
}
|
|
|
|
this.debug('MAIN post', this)
|
|
}
|
|
|
|
Test.prototype.process = function () {
|
|
if (this.processing)
|
|
return this.debug(' < already processing')
|
|
|
|
this.debug('\nPROCESSING(%s)', this.name, this.queue.length)
|
|
this.processing = true
|
|
|
|
var p
|
|
|
|
while (!this.occupied && (p = this.queue.shift())) {
|
|
this.debug('PROCESS(%s)', this.name, p)
|
|
if (p instanceof Base) {
|
|
this.processSubtest(p)
|
|
} else if (p === EOF) {
|
|
this.debug(' > EOF', this.name)
|
|
// I AM BECOME EOF, DESTROYER OF STREAMS
|
|
this.parser.end()
|
|
} else if (p instanceof TestPoint) {
|
|
this.debug(' > TESTPOINT')
|
|
this.parser.write(p.ok + (++this.n) + p.message)
|
|
} else if (typeof p === 'string') {
|
|
this.debug(' > STRING')
|
|
this.parser.write(p)
|
|
} else if (Array.isArray(p)) {
|
|
this.debug(' > METHOD')
|
|
var m = p.shift()
|
|
this[m].apply(this, p)
|
|
} else {
|
|
throw new Error('weird thing got in the queue')
|
|
}
|
|
}
|
|
|
|
while (!this.noparallel &&
|
|
this.pool.length < this.jobs &&
|
|
(p = this.subtests.shift())) {
|
|
if (!p.buffered) {
|
|
this.noparallel = true
|
|
break
|
|
}
|
|
this.debug('start subtest', p)
|
|
this.pool.add(p)
|
|
if (this.bailedOut)
|
|
this.onbufferedend(p)
|
|
else
|
|
this.runBeforeEach(p,
|
|
p.main.bind(p,
|
|
this.onbufferedend.bind(this, p)))
|
|
}
|
|
|
|
this.debug('done processing', this.queue, this.occupied)
|
|
this.processing = false
|
|
|
|
// just in case any tests ended, and we have sync stuff still
|
|
// waiting around in the queue to be processed
|
|
if (!this.occupied && this.queue.length)
|
|
this.process()
|
|
|
|
this.maybeAutoend()
|
|
}
|
|
|
|
Test.prototype.processSubtest = function (p) {
|
|
this.debug(' > subtest')
|
|
this.occupied = p
|
|
if (!p.buffered) {
|
|
if (this.bailedOut)
|
|
return this.onindentedend(p)
|
|
this.debug(' > subtest indented')
|
|
p.pipe(this.parser, { end: false })
|
|
this.runBeforeEach(p,
|
|
this.writeSubComment.bind(this, p,
|
|
p.main.bind(p,
|
|
this.onindentedend.bind(this, p))))
|
|
} else if (p.readyToProcess) {
|
|
this.debug(' > subtest buffered, finished')
|
|
// finished! do the thing!
|
|
this.occupied = null
|
|
if (!p.passing() || !p.silent) {
|
|
this.queue.unshift(['emitSubTeardown', p])
|
|
this.printResult(p.passing(), p.name, p.options, true)
|
|
}
|
|
} else {
|
|
this.occupied = p
|
|
this.debug(' > subtest buffered, unfinished', p)
|
|
// unfinished buffered test.
|
|
// nothing to do yet, just leave it there.
|
|
this.queue.unshift(p)
|
|
}
|
|
}
|
|
|
|
Test.prototype.emitSubTeardown = function (p) {
|
|
try {
|
|
p.emit('teardown')
|
|
} catch (er) {
|
|
delete p.options.time
|
|
p.threw(er)
|
|
}
|
|
}
|
|
|
|
Test.prototype.writeSubComment = function (p, cb) {
|
|
var comment = '# Subtest'
|
|
if (p.name)
|
|
comment += ': ' + p.name
|
|
comment += '\n'
|
|
this.parser.write(comment)
|
|
cb()
|
|
}
|
|
|
|
Test.prototype.onbufferedend = function (p, er) {
|
|
delete p.ondone
|
|
p.results = p.results || {}
|
|
p.readyToProcess = true
|
|
var to = p.options.timeout
|
|
if (to && p.passing())
|
|
var dur = Date.now() - p.start
|
|
if (dur && dur > to)
|
|
p.timeout()
|
|
else
|
|
p.setTimeout(false)
|
|
this.debug('%s.onbufferedend', this.name, p.name, p.results.bailout)
|
|
this.pool.remove(p)
|
|
p.options.tapChildBuffer = p.output || ''
|
|
p.options.stack = ''
|
|
if (p.time)
|
|
p.options.time = p.time
|
|
if (this.occupied === p)
|
|
this.occupied = null
|
|
if (er)
|
|
this.threw(er)
|
|
p.deferred.resolve(this)
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.onindentedend = function (p, er) {
|
|
delete p.ondone
|
|
this.debug('onindentedend', p)
|
|
this.noparallel = false
|
|
var sti = this.subtests.indexOf(p)
|
|
if (sti !== -1)
|
|
this.subtests.splice(sti, 1)
|
|
p.readyToProcess = true
|
|
p.results = p.results || {}
|
|
if (p.time)
|
|
p.options.time = p.time
|
|
var to = p.options.timeout
|
|
if (to && p.passing())
|
|
var dur = Date.now() - p.start
|
|
if (dur && dur > to)
|
|
p.timeout()
|
|
else
|
|
p.setTimeout(false)
|
|
this.debug('onindentedend %s(%s)', this.name, p.name, er || 'ok')
|
|
assert(this.occupied === p)
|
|
this.occupied = null
|
|
this.debug('OIE(%s) b>shift into queue', this.name, this.queue)
|
|
p.options.stack = ''
|
|
|
|
this.queue.unshift(['emitSubTeardown', p])
|
|
this.printResult(p.passing(), p.name, p.options, true)
|
|
|
|
this.debug('OIE(%s) shifted into queue', this.name, this.queue)
|
|
if (er)
|
|
this.threw(er)
|
|
p.deferred.resolve(this)
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.addAssert = function (name, length, fn) {
|
|
if (!name)
|
|
throw new TypeError('name is required for addAssert')
|
|
|
|
if (!(typeof length === 'number' && length >= 0))
|
|
throw new TypeError('number of args required')
|
|
|
|
if (typeof fn !== 'function')
|
|
throw new TypeError('function required for addAssert')
|
|
|
|
if (Test.prototype[name] || this[name])
|
|
throw new TypeError('attempt to re-define `' + name + '` assert')
|
|
|
|
this[name] = function ASSERT () {
|
|
if (!this.currentAssert) {
|
|
this.currentAssert = ASSERT
|
|
}
|
|
var args = new Array(length + 2)
|
|
for (var i = 0; i < length; i++) {
|
|
args[i] = arguments[i]
|
|
}
|
|
if (typeof arguments[length] === 'object') {
|
|
args[length] = ''
|
|
args[length + 1] = arguments[length]
|
|
} else {
|
|
args[length] = arguments[length] || ''
|
|
args[length + 1] = arguments[length + 1] || {}
|
|
}
|
|
|
|
return fn.apply(this, args)
|
|
}
|
|
}
|
|
|
|
Test.prototype.fail = function fail (message, extra) {
|
|
if (!this.currentAssert) {
|
|
this.currentAssert = fail
|
|
}
|
|
|
|
if (message && typeof message === 'object') {
|
|
extra = message
|
|
message = ''
|
|
} else {
|
|
if (!message) {
|
|
message = ''
|
|
}
|
|
if (!extra) {
|
|
extra = {}
|
|
}
|
|
}
|
|
|
|
this.printResult(false, message, extra)
|
|
|
|
var ret = true
|
|
if (!extra.todo && !extra.skip)
|
|
ret = false
|
|
|
|
return ret
|
|
}
|
|
|
|
Test.prototype.pass = function pass (message, extra) {
|
|
if (!this.currentAssert) {
|
|
this.currentAssert = pass
|
|
}
|
|
this.printResult(true, message || '(unnamed test)', extra)
|
|
return true
|
|
}
|
|
|
|
Test.prototype.printResult = function pR (ok, message, extra, front) {
|
|
var n = this.count + 1
|
|
if (this.planEnd !== -1 && n > this.planEnd) {
|
|
if (!this.passing())
|
|
return
|
|
|
|
var failMessage = this.explicitEnded
|
|
? 'test after end() was called'
|
|
: 'test count exceeds plan'
|
|
|
|
var er = new Error(failMessage)
|
|
Error.captureStackTrace(er, this.currentAssert || pR)
|
|
er.test = this.name
|
|
er.plan = this.planEnd
|
|
this.threw(er)
|
|
return
|
|
}
|
|
|
|
extra = extra || {}
|
|
|
|
if (this.assertAt) {
|
|
extra.at = this.assertAt
|
|
this.assertAt = null
|
|
}
|
|
|
|
if (this.assertStack) {
|
|
extra.stack = this.assertStack
|
|
this.assertStack = null
|
|
}
|
|
|
|
if (hasOwn(extra, 'stack') && !hasOwn(extra, 'at'))
|
|
extra.at = stack.parseLine(extra.stack.split('\n')[0])
|
|
|
|
var fn = this.currentAssert || pR
|
|
this.currentAssert = null
|
|
if (!ok && !extra.skip && !hasOwn(extra, 'at')) {
|
|
assert.equal(typeof fn, 'function')
|
|
extra.at = stack.at(fn)
|
|
if (!extra.todo)
|
|
extra.stack = stack.captureString(80, fn)
|
|
}
|
|
|
|
var diagnostic
|
|
if (!ok)
|
|
diagnostic = true
|
|
|
|
if (extra.skip)
|
|
diagnostic = false
|
|
|
|
if (process.env.TAP_DIAG === '0')
|
|
diagnostic = false
|
|
|
|
if (typeof extra.diagnostic === 'boolean')
|
|
diagnostic = extra.diagnostic
|
|
|
|
if (diagnostic)
|
|
extra.diagnostic = true
|
|
|
|
this.count = n
|
|
var res = { ok: ok, message: message, extra: extra }
|
|
var output = new TestPoint(ok, message, extra)
|
|
// when we jump the queue, skip an extra line
|
|
if (front)
|
|
output.message = output.message.trimRight() + '\n\n'
|
|
|
|
if (front) {
|
|
this.emit('result', res)
|
|
this.parser.write(output.ok + (++this.n) + output.message)
|
|
} else
|
|
this.queue.push(['emit', 'result', res], output)
|
|
|
|
if (this.planEnd === this.count)
|
|
this.end(IMPLICIT)
|
|
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.pragma = function (set) {
|
|
var p = ''
|
|
Object.keys(set).forEach(function (i) {
|
|
p += 'pragma ' + (set[i] ? '+' : '-') + i + '\n'
|
|
})
|
|
this.queue.push(p)
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.plan = function (n, comment) {
|
|
if (this.bailedOut)
|
|
return
|
|
|
|
if (this.planEnd !== -1) {
|
|
throw new Error('Cannot set plan more than once')
|
|
}
|
|
|
|
if (typeof n !== 'number' || n < 0) {
|
|
throw new TypeError('plan must be a number')
|
|
}
|
|
|
|
// Cannot get any tests after a trailing plan, or a plan of 0
|
|
var ending = false
|
|
if (this.count !== 0 || n === 0) {
|
|
ending = true
|
|
}
|
|
|
|
if (n === 0)
|
|
this.skip = comment || true
|
|
|
|
this.planEnd = n
|
|
comment = comment ? ' # ' + comment.trim() : ''
|
|
this.queue.push('1..' + n + comment + '\n')
|
|
|
|
if (ending)
|
|
this.end(IMPLICIT)
|
|
else
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.done = Test.prototype.end = function (implicit) {
|
|
this.debug('END implicit=%j', implicit === IMPLICIT)
|
|
if (this.ended && implicit === IMPLICIT)
|
|
return
|
|
|
|
// beyond here we have to be actually done with things, or else
|
|
// the semantic checks on counts and such will be off.
|
|
if (!queueEmpty(this) || this.occupied) {
|
|
if (!this.pushedEnd)
|
|
this.queue.push(['end', implicit])
|
|
this.pushedEnd = true
|
|
return this.process()
|
|
}
|
|
|
|
if (!this.ranAfterEach && this.parent) {
|
|
this.ranAfterEach = true
|
|
this.parent.runAfterEach(this, end.bind(this, implicit))
|
|
} else
|
|
end.call(this, implicit)
|
|
}
|
|
|
|
function end (implicit) {
|
|
this.ended = true
|
|
|
|
if (implicit !== IMPLICIT && !this.multiEndThrew) {
|
|
if (this.explicitEnded) {
|
|
this.multiEndThrew = true
|
|
var er = new Error('test end() method called more than once')
|
|
Error.captureStackTrace(er, this.currentAssert || end)
|
|
er.test = this.name
|
|
this.threw(er)
|
|
return
|
|
}
|
|
this.explicitEnded = true
|
|
}
|
|
|
|
if (this.planEnd === -1) {
|
|
this.debug('END(%s) implicit plan', this.name, this.count)
|
|
this.plan(this.count)
|
|
}
|
|
|
|
this.queue.push(EOF)
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.threw = function (er, extra, proxy) {
|
|
this.debug('THREW', er.message, extra, proxy)
|
|
|
|
// event emitters 'error' events need to re-throw so that they
|
|
// can jump out of the flow like a normal throw. They'll just
|
|
// end up back here once that happens, though, unless there's a
|
|
// try/catch somewhere in the call stack.
|
|
if (er.domainEmitter) {
|
|
delete er.domainEmitter
|
|
throw er
|
|
}
|
|
|
|
if (this.name && !proxy)
|
|
er.test = this.name
|
|
if (!proxy)
|
|
extra = extraFromError(er, extra, this.options)
|
|
Base.prototype.threw.call(this, er, extra, proxy)
|
|
|
|
if (!this.results) {
|
|
this.fail(extra.message || er.message, extra)
|
|
if (!proxy)
|
|
this.end(IMPLICIT)
|
|
}
|
|
this.process()
|
|
}
|
|
|
|
Test.prototype.runBeforeEach = function (who, cb) {
|
|
var self = this
|
|
if (this.parent)
|
|
this.parent.runBeforeEach(who, function () {
|
|
loop(who, self.onBeforeEach, cb, who.threw)
|
|
})
|
|
else
|
|
loop(who, self.onBeforeEach, cb, who.threw)
|
|
}
|
|
|
|
Test.prototype.runAfterEach = function (who, cb) {
|
|
var self = this
|
|
loop(who, self.onAfterEach, function () {
|
|
if (self.parent)
|
|
self.parent.runAfterEach(who, cb)
|
|
else
|
|
cb()
|
|
}, who.threw)
|
|
}
|
|
|
|
Test.prototype.beforeEach = function (fn) {
|
|
this.onBeforeEach.push(fn)
|
|
}
|
|
|
|
Test.prototype.afterEach = function (fn) {
|
|
this.onAfterEach.push(fn)
|
|
}
|
|
|
|
Test.prototype.teardown = Test.prototype.tearDown = function (fn) {
|
|
this.on('teardown', fn)
|
|
}
|
|
|
|
Test.prototype.shouldAutoend = function () {
|
|
var should = (
|
|
this.options.autoend &&
|
|
!this.ended &&
|
|
!this.occupied &&
|
|
queueEmpty(this) &&
|
|
!this.pool.length &&
|
|
!this.subtests.length &&
|
|
this.planEnd === -1
|
|
)
|
|
return should
|
|
}
|
|
|
|
Test.prototype.autoend = function () {
|
|
this.options.autoend = true
|
|
this.maybeAutoend()
|
|
}
|
|
|
|
Test.prototype.maybeAutoend = function () {
|
|
if (this.autoendTimer)
|
|
clearTimeout(this.autoendTimer)
|
|
|
|
if (this.shouldAutoend()) {
|
|
var self = this
|
|
self.autoendTimer = setTimeout(function () {
|
|
if (self.shouldAutoend()) {
|
|
self.autoendTimer = setTimeout(function () {
|
|
if (self.shouldAutoend()) {
|
|
self.end(IMPLICIT)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function endAllQueue (queue) {
|
|
queue.forEach(function (p, i) {
|
|
if ((p instanceof Base) && !p.readyToProcess)
|
|
queue[i] = new TestPoint(false,
|
|
'child test left in queue ' + p.constructor.name + ': ' +
|
|
p.name, p.options)
|
|
})
|
|
queue.push(['end', IMPLICIT])
|
|
}
|
|
|
|
function queueEmpty (t) {
|
|
return t.queue.length === 0 ||
|
|
t.queue.length === 1 && t.queue[0] === 'TAP version 13\n'
|
|
}
|
|
|
|
Test.prototype.endAll = function (sub) {
|
|
this.processing = true
|
|
if (this.occupied) {
|
|
var p = this.occupied
|
|
if (p.endAll)
|
|
p.endAll(true)
|
|
else {
|
|
p.parser.abort('test unfinished')
|
|
}
|
|
} else if (sub) {
|
|
this.process()
|
|
if (queueEmpty(this)) {
|
|
var options = Object.keys(this.options).reduce(function (o, k) {
|
|
o[k] = this.options[k]
|
|
return o
|
|
}.bind(this), {})
|
|
this.options.at = null
|
|
this.options.stack = ''
|
|
options.test = this.name
|
|
this.fail('test unfinished', options)
|
|
}
|
|
}
|
|
if (this.promise && this.promise.tapAbortPromise)
|
|
this.promise.tapAbortPromise()
|
|
if (this.occupied) {
|
|
this.queue.unshift(this.occupied)
|
|
this.occupied = null
|
|
}
|
|
endAllQueue(this.queue)
|
|
this.processing = false
|
|
this.process()
|
|
this.parser.end()
|
|
}
|
|
|
|
// Add all the asserts
|
|
tapAsserts.decorate(Test.prototype)
|