⚝
One Hat Cyber Team
⚝
Your IP:
172.22.0.1
Server IP:
151.80.20.34
Server:
Linux 794f04d97d5e 5.15.0-143-generic #153-Ubuntu SMP Fri Jun 13 19:10:45 UTC 2025 x86_64
Server Software:
Apache/2.4.62 (Debian)
PHP Version:
8.2.28
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
nodejs
/
libtap
/
lib
/
View File Name :
test.js
'use strict' // 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. // grab a reference right away, in case the user clubs global.process const process = require('./process.js') const path = require('path') const assert = require('assert') const util = require('util') const {format, same, strict, match, has, hasStrict} = require('tcompare') const Deferred = require('trivial-deferred') const loop = require('function-loop') const ownOr = require('own-or') const ownOrEnv = require('own-or-env') const bindObj = require('bind-obj-methods') const esc = require('./esc.js') const Base = require('./base.js') const Spawn = require('./spawn.js') const Stdin = require('./stdin.js') const TestPoint = require('./point.js') const parseTestArgs = require('./parse-test-args.js') const Fixture = require('./fixture.js') const Mock = require('./mock.js') const cleanYamlObject = require('./clean-yaml-object.js') const extraFromError = require('./extra-from-error.js') const stack = require('./stack.js') const settings = require('../settings.js') const Snapshot = require('./snapshot.js') const Waiter = require('./waiter.js') const findMainScript = require('./find-main-script.js') const formatSnapshotDefault = obj => format(obj, { sort: true }) const cwd = process.cwd() // A sigil object for implicit end() calls that should not // trigger an error if the user then calls t.end() const IMPLICIT = Symbol('implicit t.end()') // Sigil to put in the queue to signal the end of all things const EOF = Symbol('EOF') const _currentAssert = Symbol('_currentAssert') const _end = Symbol('_end') const _snapshot = Symbol('_snapshot') const _getSnapshot = Symbol('_getSnapshot') const _beforeEnd = Symbol('_beforeEnd') const _emits = Symbol('_emits') const _nextChildId = Symbol('_nextChildId') const _expectUncaught = Symbol('_expectUncaught') const _createdFixture = Symbol('_createdFixture') const _beforeCalled = Symbol('_beforeCalled') const _printedResult = Symbol('_printedResult') const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) const isRegExp = re => Object.prototype.toString.call(re) === '[object RegExp]' const normalizeMessageExtra = (defaultMessage, message, extra) => { if (message && typeof message === 'object') { return [defaultMessage, message] } return [ message || defaultMessage, extra || {} ] } class Test extends Base { constructor (options) { options = options || {} super(options) const cmp = ownOr(options, 'compareOptions', undefined) this.compareOptions = cmp && typeof cmp === 'object' ? Object.create(cmp) : {} this[_nextChildId] = 1 this.pushedEnd = false this.jobs = ownOr(options, 'jobs', 1) this.doingStdinOnly = false this.onTeardown = [] this[_createdFixture] = false this.subtests = [] this.pool = new Set() this.queue = ['TAP version 13\n'] // snapshots are keyed off of the main file that loads the // root test object. Typically, this is the TAP object. // To do this, we climb the ladder and only save in the teardown // of that root (parentless) test object. This allows handling // cases where the same test name can be used multiple times // in a single test file, which would otherwise clobber snapshots. this.writeSnapshot = hasOwn(options, 'snapshot') ? options.snapshot : this.parent ? this.parent.writeSnapshot : process.env.TAP_SNAPSHOT === '1' if (this.parent && this.parent.cleanSnapshot) this.cleanSnapshot = this.parent.cleanSnapshot this.formatSnapshot = this.parent && this.parent.formatSnapshot this.noparallel = false if (options.cb) this.cb = (...args) => this.hook.runInAsyncScope(options.cb, this, ...args) this.occupied = false this[_currentAssert] = null this[_beforeEnd] = [] this.count = 0 this.n = 0 this.ended = false this.explicitEnded = false this.multiEndThrew = false this.assertAt = null this.assertStack = null this.planEnd = -1 this.onBeforeEach = [] this.onAfterEach = [] this.ranAfterEach = false this[_expectUncaught] = [] // bind all methods to this object, so we can pass t.end as a callback // and do `const test = require('tap').test` like people do. const bound = Object.create(null) bindObj(this, this, bound) bindObj(this, Object.getPrototypeOf(this), bound) bindObj(this, Test.prototype, bound) } 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, Test.prototype.spawn) } sub (Class, extra, caller) { if (this.bailedOut) return if (this.doingStdinOnly) throw new Error('cannot run subtests in stdinOnly mode') if (this.results || this.ended) { const er = new Error('cannot create subtest after parent test end') Error.captureStackTrace(er, caller) this.threw(er) return Promise.resolve(this) } extra.childId = this[_nextChildId]++ if (!extra.skip && this.grep.length) { const m = this.grep[0].test(extra.name) const match = this.grepInvert ? !m : m if (!match) { const 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.saveFixture = ownOr(extra, 'saveFixture', this.saveFixture) extra.parent = this extra.stack = stack.captureString(80, caller) extra.context = this.context extra.compareOptions = this.compareOptions const t = new Class(extra) this.queue.push(t) this.subtests.push(t) this.emit('subtestAdd', t) const d = new Deferred() t.deferred = d this.process() return d.promise } todo (name, extra, cb) { extra = parseTestArgs(name, extra, cb) extra.todo = extra.todo || true return this.sub(Test, extra, Test.prototype.todo) } skip (name, extra, cb) { extra = parseTestArgs(name, extra, cb) extra.skip = extra.skip || true return this.sub(Test, extra, Test.prototype.skip) } only (name, extra, cb) { extra = parseTestArgs(name, extra, cb) extra.only = true return this.sub(Test, extra, Test.prototype.only) } test (name, extra, cb) { extra = parseTestArgs(name, extra, cb) return this.sub(Test, extra, Test.prototype.test) } stdinOnly (extra) { const stream = extra && extra.tapStream || process.stdin if (!stream) throw new Error('cannot read stdin without stdin stream') if (this.queue.length !== 1 || this.queue[0] !== 'TAP version 13\n' || this.processing || this.results || this.occupied || this.pool.size || this.subtests.length) throw new Error('Cannot use stdinOnly on a test in progress') this.doingStdinOnly = true this.queue.length = 0 this.parser.on('child', p => { // pretend to be a rooted parser, so it gets counts. p.root = p const t = new Base({ name: p.name, parent: this, parser: p, root: p, bail: p.bail, strict: p.strict, omitVersion: p.omitVersion, preserveWhitespace: p.preserveWhitespace, childId: this[_nextChildId]++, }) this.emit('subtestAdd', t) this.emit('subtestStart', t) this.emit('subtestProcess', t) p.on('complete', () => { t.time = p.time this.emit('subtestEnd', t) }) }) stream.pause() stream.pipe(this.parser) stream.resume() } stdin (name, extra) { extra = parseTestArgs(name, extra, false, '/dev/stdin') return this.sub(Stdin, extra, Test.prototype.stdin) } bailout (message) { if (this.parent && (this.results || this.ended)) this.parent.bailout(message) else { this.process() message = message ? ' ' + ('' + esc(message)).trim() : '' message = message.replace(/[\r\n]/g, ' ') this.parser.write('Bail out!' + message + '\n') } this.end(IMPLICIT) this.process() } comment (...args) { const body = util.format(...args) const message = '# ' + body.split(/\r?\n/).join('\n# ') + '\n' if (this.results) this.write(message) else this.queue.push(message) this.process() } timeout (options) { options = options || {} options.expired = options.expired || this.name if (this.occupied && this.occupied.timeout) this.occupied.timeout(options) else Base.prototype.timeout.call(this, options) this.end(IMPLICIT) } main (cb) { this.setTimeout(this.options.timeout) this.debug('MAIN pre', this) const end = () => { this.debug(' > implicit end for promise') this.end(IMPLICIT) done() } const done = (er) => { if (er) this.threw(er) if (this.results || this.bailedOut) cb() else this.ondone = cb } // This bit of overly clever line-noise wraps the call to user-code // in a try-catch. We can't rely on the domain for this yet, because // the 'end' event can trigger a throw after the domain is unhooked, // but before this is no longer the official "active test" const ret = (() => { try { return this.cb(this) } catch (er) { if (!er || typeof er !== 'object') er = { error: er } er.tapCaught = 'testFunctionThrow' this.threw(er) } })() if (ret && ret.then) { this.promise = ret ret.tapAbortPromise = done ret.then(end, er => { if (!er || typeof er !== 'object') er = { error: er } er.tapCaught = 'returnedPromiseRejection' done(er) }) } else done() this.debug('MAIN post', this) } process () { if (this.processing) return this.debug(' < already processing') this.debug('\nPROCESSING(%s)', this.name, this.queue.length) this.processing = true while (!this.occupied) { const p = this.queue.shift() if (!p) break if (p instanceof Base) { this.processSubtest(p) } else if (p === EOF) { this.debug(' > EOF', this.name) // I AM BECOME EOF, DESTROYER OF STREAMS if (this.writeSnapshot) this[_getSnapshot]().save() this.parser.end() } else if (p instanceof TestPoint) { this.debug(' > TESTPOINT') if (p.extra.tapChildBuffer || p.extra.tapChildBuffer === '') { this.writeSubComment(p, () => {}) this.parser.write(p.extra.tapChildBuffer) } this.emit('res', p.res) this.parser.write(p.ok + (++this.n) + p.message) } else if (typeof p === 'string') { this.debug(' > STRING') this.parser.write(p) } else if (p instanceof Waiter) { p.ready = true this.occupied = p p.finish() } else { /* istanbul ignore else */ if (Array.isArray(p)) { this.debug(' > METHOD') const m = p.shift() const ret = this[m].apply(this, p) if (ret && typeof ret.then === 'function') { // returned promise ret.then(() => { this.processing = false this.process() }, er => { this.processing = false this.threw(er) }) return } } else { throw new Error('weird thing got in the queue') } } } while (!this.noparallel && this.pool.size < this.jobs) { const p = this.subtests.shift() if (!p) break if (!p.buffered) { this.noparallel = true break } this.debug('start subtest', p) this.emit('subtestStart', p) this.pool.add(p) if (this.bailedOut) this.onbufferedend(p) else this.runBeforeEach(p, () => p.runMain(() => this.onbufferedend(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() } processSubtest (p) { this.debug(' > subtest') this.occupied = p if (!p.buffered) { this.emit('subtestStart', p) this.debug(' > subtest indented') p.pipe(this.parser, { end: false }) this.runBeforeEach(p, () => this.writeSubComment(p, () => p.runMain(() => this.onindentedend(p)))) } else if (p.readyToProcess) { this.emit('subtestProcess', p) 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) } } emitSubTeardown (p) { // if it's not a thing that CAN have teardowns, nothing to do here if (!p.onTeardown) return const otd = p.onTeardown p.onTeardown = [] const threw = er => { if (!er || typeof er !== 'object') er = { error: er } er.tapCaught = 'teardown' delete p.options.time p.threw(er) } for (let i = 0; i < otd.length; i++) { const fn = otd[i] try { const ret = fn.call(p) if (ret && typeof ret.then === 'function') { p.onTeardown = otd.slice(i + 1) this.queue.unshift(['emitSubTeardown', p]) return ret.then(() => this.emitSubTeardown(p), er => { if (!er || typeof er !== 'object') er = { error: er } er.tapCaught = 'teardown' throw er }) } } catch (er) { threw(er) } } // ok we're done, just delete the fixture if it created one. // do this AFTER all user-generated teardowns, and asynchronously so // that we can do the fancy backoff dance for Win32's weirdo fs. if (p[_createdFixture]) { const {rmdirRecursive} = settings return new Promise((res, rej) => { rmdirRecursive(p[_createdFixture], er => er ? /* istanbul ignore next - rimraf never fails lol */ rej(er) : res()) }).then(() => p.emit('teardown')) } else p.emit('teardown') } writeSubComment (p, cb) { const comment = '# Subtest' + (p.name ? ': ' + esc(p.name) : '') + '\n' this.parser.write(comment) cb() } onbufferedend (p) { delete p.ondone p.results = p.results || {} p.readyToProcess = true const to = p.options.timeout const dur = (to && p.passing()) ? Date.now() - p.start : null if (dur && dur > to) p.timeout() else p.setTimeout(false) this.debug('%s.onbufferedend', this.name, p.name, p.results.bailout) this.pool.delete(p) p.options.tapChildBuffer = p.output || '' p.options.stack = '' if (p.time) p.options.time = p.time if (this.occupied === p) this.occupied = null p.deferred.resolve(p.results) this.emit('subtestEnd', p) this.process() } onindentedend (p) { this.emit('subtestProcess', p) delete p.ondone this.debug('onindentedend', p) this.noparallel = false const sti = this.subtests.indexOf(p) if (sti !== -1) this.subtests.splice(sti, 1) p.readyToProcess = true p.options.time = p.time const to = p.options.timeout const dur = (to && p.passing()) ? Date.now() - p.start : null if (dur && dur > to) p.timeout() else p.setTimeout(false) this.debug('onindentedend %s(%s)', this.name, p.name) 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) p.deferred.resolve(p.results) this.emit('subtestEnd', p) this.process() } addAssert (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') const ASSERT = function (...args) { this.currentAssert = ASSERT args.splice(length, 0, ...normalizeMessageExtra('', ...args.splice(length, 2))) return fn.apply(this, args) } this[name] = ASSERT } static addAssert (name, length, fn) { this.prototype.addAssert(name, length, fn) } printResult (ok, message, extra, front) { this[_printedResult] = true if (this.doingStdinOnly) throw new Error('cannot print results in stdinOnly mode') const n = this.count + 1 this.currentAssert = Test.prototype.printResult const fn = this[_currentAssert] this[_currentAssert] = null if (this.planEnd !== -1 && n > this.planEnd) { if (!this.passing()) return const failMessage = this.explicitEnded ? 'test after end() was called' : 'test count exceeds plan' const er = new Error(failMessage) Error.captureStackTrace(er, fn) er.test = this.name er.plan = this.planEnd this.threw(er) return } extra = extra || {} if (extra.expectFail) ok = !ok 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]) 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) } const diagnostic = typeof extra.diagnostic === 'boolean' ? extra.diagnostic : process.env.TAP_DIAG === '0' ? false : process.env.TAP_DIAG === '1' ? true : extra.skip ? false : !ok if (diagnostic) extra.diagnostic = true this.count = n message = message + '' const res = { ok, message, extra } const tp = new TestPoint(ok, message, extra) // when we jump the queue, skip an extra line if (front) tp.message = tp.message.trimRight() + '\n\n' if (this.occupied && this.occupied instanceof Waiter && this.occupied.finishing) front = true if (front) { if (extra.tapChildBuffer || extra.tapChildBuffer === '') { this.writeSubComment(tp, () => {}) this.parser.write(extra.tapChildBuffer) } this.emit('result', res) this.parser.write(tp.ok + (++this.n) + tp.message) if (this.bail && !ok && !extra.skip && !extra.todo) this.parser.write('Bail out! ' + message + '\n') } else { this.queue.push(tp) if (this.bail && !ok && !extra.skip && !extra.todo) this.queue.push('Bail out! ' + message + '\n') } if (this.planEnd === this.count) this.end(IMPLICIT) this.process() } pragma (set) { const p = Object.keys(set).reduce((acc, i) => acc + 'pragma ' + (set[i] ? '+' : '-') + i + '\n', '') this.queue.push(p) this.process() } plan (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 const ending = this.count !== 0 || n === 0 if (n === 0 && comment && !this.options.skip) this.options.skip = comment this.planEnd = n comment = comment ? ' # ' + esc(comment.trim()) : '' this.queue.push('1..' + n + comment + '\n') if (ending) this.end(IMPLICIT) else this.process() } end (implicit) { if (this.doingStdinOnly && implicit !== IMPLICIT) throw new Error('cannot explicitly end while in stdinOnly mode') this.debug('END implicit=%j', implicit === IMPLICIT) if (this.ended && implicit === IMPLICIT) return if (this[_beforeEnd].length) { for (let b = 0; b < this[_beforeEnd].length; b++) { const m = this[_beforeEnd][b].shift() this[m].apply(this, this[_beforeEnd][b]) } this[_beforeEnd].length = 0 } // 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, () => this[_end](implicit)) return } else this[_end](implicit) } [_end] (implicit) { this.ended = true if (implicit !== IMPLICIT && !this.multiEndThrew) { if (this.explicitEnded) { this.multiEndThrew = true const er = new Error('test end() method called more than once') Error.captureStackTrace(er, this[_currentAssert] || Test.prototype[_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) if (this[_expectUncaught].length) { const wanted = this[_expectUncaught] this[_expectUncaught] = [] const diag = { wanted: wanted.map(a => a.filter(e => e != null)), test: this.name, at: null, stack: null, } const msg = 'test end without expected uncaught exceptions' this.queue.push(['threw', Object.assign(new Error(msg), diag)]) } this.process() } threw (er, extra, proxy) { // this can only happen if a beforeEach function raises an error if (this.parent && !this.started) { this.cb = () => { this.threw(er) this.end() } return } if (!er || typeof er !== 'object') er = { error: er } if (this[_expectUncaught].length && ( er.tapCaught === 'uncaughtException' || er.tapCaught === 'unhandledRejection')) { const [wanted, message, extra] = this[_expectUncaught].shift() const actual = isRegExp(wanted) ? er.message : er return wanted ? this.match(actual, wanted, message, extra) : this.pass(message, extra) } 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) } // threw while waiting for a promise to resolve. // probably it's not ever gonna. if (this.occupied && this.occupied instanceof Waiter) this.occupied.abort(Object.assign( new Error('error thrown while awaiting Promise'), { thrown: er } )) this.process() } runBeforeEach (who, cb) { if (this.parent) this.parent.runBeforeEach(who, () => { loop(who, this.onBeforeEach, cb, er => { who.threw(er) cb() }) }) else loop(who, this.onBeforeEach, cb, er => { who.threw(er) cb() }) } runAfterEach (who, cb) { loop(who, this.onAfterEach, () => { if (this.parent) this.parent.runAfterEach(who, cb) else cb() }, who.threw) } beforeEach (fn) { // use function so that 'this' can be overridden this.onBeforeEach.push(function () { return fn.call(this, this) }) } afterEach (fn) { // use function so that 'this' can be overridden this.onAfterEach.push(function () { return fn.call(this, this) }) } teardown (fn) { this.onTeardown.push(fn) } shouldAutoend () { const should = ( this.options.autoend && !this.ended && !this.occupied && queueEmpty(this) && !this.pool.size && !this.subtests.length && this.planEnd === -1 ) return should } autoend (value) { // set to false to NOT trigger autoend if (value === false) { this.options.autoend = false clearTimeout(this.autoendTimer) } else { this.options.autoend = true this.maybeAutoend() } } maybeAutoend () { if (this.shouldAutoend()) { clearTimeout(this.autoendTimer) this.autoendTimer = setTimeout(() => { if (this.shouldAutoend()) { clearTimeout(this.autoendTimer) this.autoendTimer = setTimeout(() => { if (this.shouldAutoend()) this.end(IMPLICIT) }) } }) } } onbail (message) { super.onbail(message) this.end(IMPLICIT) if (!this.parent) this.endAll() } endAll (sub) { // in the case of the root TAP test object, we might sometimes // call endAll on a bailing-out test, as the process is ending // In that case, we WILL have a this.occupied and a full queue // These cases are very rare to encounter in other Test objs tho this.processing = true if (this.occupied) { const p = this.occupied if (p instanceof Waiter) p.abort(new Error('test unfinished')) else if (p.endAll) p.endAll(true) else p.parser.abort('test unfinished') } else if (sub) { this.process() if (queueEmpty(this)) { const options = Object.assign({}, this.options) 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() } get currentAssert () { return this[_currentAssert] } set currentAssert (fn) { if (!this[_currentAssert]) this[_currentAssert] = fn } pass (message, extra) { this.currentAssert = Test.prototype.pass this.printResult(true, ...normalizeMessageExtra('(unnamed test)', message, extra)) return true } fail (message, extra) { [message, extra] = normalizeMessageExtra('(unnamed test)', message, extra) this.currentAssert = Test.prototype.fail this.printResult(false, message, extra) return !!(extra.todo || extra.skip) } ok (obj, message, extra) { [message, extra] = normalizeMessageExtra('expect truthy value', message, extra) this.currentAssert = Test.prototype.ok return obj ? this.pass(message, extra) : this.fail(message, extra) } notOk (obj, message, extra) { [message, extra] = normalizeMessageExtra('expect falsey value', message, extra) this.currentAssert = Test.prototype.notOk return this.ok(!obj, message, extra) } emits (emitter, event, message, extra) { [message, extra] = normalizeMessageExtra(`expect ${event} event to be emitted`, message, extra) this.currentAssert = Test.prototype.emits let resolve let reject const p = new Promise((res, rej) => { resolve = res reject = rej }) const handler = () => { handler.emitted = true resolve() } handler.emitted = false emitter.once(event, handler) extra.at = stack.at(Test.prototype.emits) extra.stack = stack.captureString(80, Test.prototype.emits) this[_beforeEnd].push([_emits, emitter, event, handler, message, extra]) return p } [_emits] (emitter, event, handler, message, extra, reject) { if (handler.emitted) return this.pass(message, extra) else { emitter.removeListener(event, handler) return this.fail(message, extra) } } error (er, message, extra) { [message, extra] = normalizeMessageExtra('', message, extra) this.currentAssert = Test.prototype.error if (!er) { return this.pass(message || 'should not error', extra) } if (!(er instanceof Error)) { extra.found = er return this.fail(message || 'non-Error error encountered', extra) } message = message || er.message extra.origin = cleanYamlObject(extraFromError(er)) extra.found = er return this.fail(message, extra) } equal (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should be equal', message, extra) this.currentAssert = Test.prototype.equal if (found === wanted) { return this.pass(message, extra) } const objects = found && wanted && typeof found === 'object' && typeof wanted === 'object' if (objects) { const s = strict(found, wanted, this.compareOptions) if (!s.match) extra.diff = s.diff else { extra.found = found extra.wanted = wanted extra.note = 'object identities differ' } } else { extra.found = found extra.wanted = wanted } extra.compare = '===' return this.fail(message, extra) } not (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should not be equal', message, extra) this.currentAssert = Test.prototype.not if (found !== wanted) { return this.pass(message, extra) } extra.found = found extra.doNotWant = wanted extra.compare = '!==' return this.fail(message, extra) } same (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should be equivalent', message, extra) this.currentAssert = Test.prototype.same const s = same(found, wanted, this.compareOptions) if (!s.match) extra.diff = s.diff else { extra.found = found extra.wanted = wanted } return this.ok(s.match, message, extra) } notSame (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should not be equivalent', message, extra) this.currentAssert = Test.prototype.notSame extra.found = found extra.doNotWant = wanted const s = same(found, wanted, this.compareOptions) return this.notOk(s.match, message, extra) } strictSame (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should be equivalent strictly', message, extra) this.currentAssert = Test.prototype.strictSame const s = strict(found, wanted, this.compareOptions) if (!s.match) extra.diff = s.diff else { extra.found = found extra.wanted = wanted } return this.ok(s.match, message, extra) } strictNotSame (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should not be equivalent strictly', message, extra) this.currentAssert = Test.prototype.strictNotSame extra.found = found extra.doNotWant = wanted const s = strict(found, wanted, this.compareOptions) return this.notOk(s.match, message, extra) } get fullname () { const main = process.argv.slice(1).join(' ').trim() return (this.parent ? this.parent.fullname : main.indexOf(cwd) === 0 ? main.substr(cwd.length + 1) : path.basename(main)).replace(/\\/g, '/') + ' ' + (this.name || '').trim() } get snapshotFile () { return this[_getSnapshot]().file } set snapshotFile (file) { // if we're using the parent's snapshot, we're setting a new file // so we need to un-hook from the parent's snapshot object const usingParent = this.parent && file !== this.parent.snapshotFile if (usingParent) { this[_snapshot] = new Snapshot() } this[_getSnapshot]().file = file } get testdirName () { const re = /[^a-zA-Z0-9\._\-]+/ig if (!this.parent) { const main = findMainScript('TAP') // put in a prefix in the dirname so do not inadvertently run it // on a subsequent tap invocation, if it was saved. const dir = path.dirname(main) const base = 'tap-testdir-' + (path.basename(main).replace(/\.[^.]+$/, '') + ' ' + process.argv.slice(2).join(' ')).trim() return dir + path.sep + base.replace(re, '-') } return this.parent.testdirName + '-' + (this.name || 'unnamed test').replace(re, '-') } testdir (fixture) { const {rmdirRecursiveSync} = settings const dir = this.testdirName rmdirRecursiveSync(dir) if (!this.saveFixture) this[_createdFixture] = dir Fixture.make(dir, fixture || {}) return dir } fixture (type, content) { return new Fixture(type, content) } mock (module, mocks) { const {file} = stack.at(Test.prototype.mock) const resolved = path.resolve(file) return Mock.get(resolved, module, mocks) } matchSnapshot (found, message, extra) { [message, extra] = normalizeMessageExtra('must match snapshot', message, extra) this.currentAssert = Test.prototype.matchSnapshot // use notOk because snap doesn't return a truthy value const m = this.fullname + ' > ' + message if (typeof found !== 'string') { found = (this.formatSnapshot || formatSnapshotDefault)(found) if (typeof found !== 'string') found = formatSnapshotDefault(found) } found = this.cleanSnapshot(found) return this.writeSnapshot ? this.notOk(this[_getSnapshot]().snap(found, m), message, extra) : this.equal(found, this[_getSnapshot]().read(m), message, extra) } [_getSnapshot] () { if (this[_snapshot]) return this[_snapshot] if (this.parent) { const parentSnapshot = this.parent[_getSnapshot]() // very rare for the parent to not have one. /* istanbul ignore else */ if (parentSnapshot) return this[_snapshot] = parentSnapshot } return this[_snapshot] = new Snapshot() } cleanSnapshot (string) { return string } has (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should contain all provided fields', message, extra) this.currentAssert = Test.prototype.has const s = has(found, wanted, this.compareOptions) if (!s.match) extra.diff = s.diff else { extra.found = found extra.pattern = wanted } return this.ok(s.match, message, extra) } notHas (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should not contain all provided fields', message, extra) this.currentAssert = Test.prototype.notHas extra.found = found extra.pattern = wanted const s = has(found, wanted, this.compareOptions) return this.notOk(s.match, message, extra) } hasStrict (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should contain all provided fields strictly', message, extra) this.currentAssert = Test.prototype.hasStrict const s = hasStrict(found, wanted, this.compareOptions) if (!s.match) extra.diff = s.diff else { extra.found = found extra.pattern = wanted } return this.ok(s.match, message, extra) } notHasStrict (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should not contain all provided fields strictly', message, extra) this.currentAssert = Test.prototype.notHasStrict extra.found = found extra.pattern = wanted const s = hasStrict(found, wanted, this.compareOptions) return this.notOk(s.match, message, extra) } hasProp (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should have the named property', message, extra) this.currentAssert = Test.prototype.hasProp extra.found = found extra.pattern = wanted if (typeof wanted !== 'string') { return this.fail('property name must be a string', extra) } try { const s = (wanted in found) && found[wanted] !== undefined return this.ok(s, message, extra) } catch (e) { return this.fail(e.message, extra) } } hasOwnProp (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should have the named own property', message, extra) this.currentAssert = Test.prototype.hasOwnProp extra.found = found extra.pattern = wanted if (typeof wanted !== 'string') { return this.fail('property name must be a string', extra) } try { const s = hasOwn(found, wanted) && found[wanted] !== undefined return this.ok(s, message, extra) } catch (e) { return this.fail(e.message, extra) } } hasProps (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should have the named properties', message, extra) this.currentAssert = Test.prototype.hasProps extra.found = found extra.pattern = wanted // explicitly do not allow String objects, because regular strings not allowed // even though they are technically iterable "objects" if (!wanted || typeof wanted !== 'object' || !wanted[Symbol.iterator] || wanted instanceof String) { return this.fail('property list must be iterable object', extra) } for (const prop of wanted) { if (typeof prop !== 'string') { return this.fail('property name must be a string', extra) } try { const s = (prop in found) && found[prop] !== undefined if (!s) { return this.fail(message, extra) } } catch (e) { return this.fail(e.message, extra) } } return this.pass(message, extra) } hasOwnProps (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should have the named own properties', message, extra) this.currentAssert = Test.prototype.hasOwnProps extra.found = found extra.pattern = wanted // explicitly do not allow String objects, because regular strings not allowed // even though they are technically iterable "objects" if (!wanted || typeof wanted !== 'object' || !wanted[Symbol.iterator] || wanted instanceof String) { return this.fail('property list must be iterable object', extra) } for (const prop of wanted) { if (typeof prop !== 'string') { return this.fail('property name must be a string', extra) } try { const s = hasOwn(found, prop) && found[prop] !== undefined if (!s) { return this.fail(message, extra) } } catch (e) { return this.fail(e.message, extra) } } return this.pass(message, extra) } match (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should match pattern provided', message, extra) this.currentAssert = Test.prototype.match const s = match(found, wanted, this.compareOptions) if (!s.match) extra.diff = s.diff else extra.found = found extra.pattern = wanted return this.ok(s.match, message, extra) } notMatch (found, wanted, message, extra) { [message, extra] = normalizeMessageExtra('should not match pattern provided', message, extra) this.currentAssert = Test.prototype.notMatch extra.found = found extra.pattern = wanted const s = match(found, wanted, this.compareOptions) return this.notOk(s.match, message, extra) } type (obj, klass, message, extra) { this.currentAssert = Test.prototype.type const name = typeof klass === 'function' ? klass.name || '(anonymous constructor)' : klass; [message, extra] = normalizeMessageExtra(`type is ${name}`, message, extra) // simplest case, it literally is the same thing if (obj === klass) { return this.pass(message, extra) } const tof = typeof obj const type = (!obj && tof === 'object') ? 'null' // treat as object, but not Object // t.type(() => {}, Function) : (tof === 'function' && typeof klass === 'function' && klass !== Object) ? 'object' : tof if (type === 'object' && klass !== 'object') { if (typeof klass === 'function') { extra.found = Object.getPrototypeOf(obj).constructor.name extra.wanted = name return this.ok(obj instanceof klass, message, extra) } // check prototype chain for name // at this point, we already know klass is not a function // if the klass specified is an obj in the proto chain, pass // if the name specified is the name of a ctor in the chain, pass for (let p = obj; p; p = Object.getPrototypeOf(p)) { const ctor = p.constructor && p.constructor.name if (p === klass || ctor === name) { return this.pass(message, extra) } } } return this.equal(type, name, message, extra) } expectUncaughtException (...args) { let [, ...rest] = this.throwsArgs('expect uncaughtException', ...args) this[_expectUncaught].push(rest) } throwsArgs (defaultMessage, ...args) { let fn, wanted, message, extra for (let i = 0; i < args.length; i++) { const arg = args[i] if (typeof arg === 'function') { if (arg === Error || arg.prototype instanceof Error) { wanted = arg } else if (!fn) { fn = arg } } else if (typeof arg === 'string' && arg) { message = arg } else if (typeof arg === 'object') { if (!wanted) { wanted = arg } else { extra = arg } } } [message, extra] = normalizeMessageExtra(defaultMessage, message, extra) if (wanted) { if (wanted instanceof Error) { const w = { message: wanted.message } if (wanted.name) { w.name = wanted.name } // intentionally copying non-local properties, since this // is an Error object, and those are funky. for (let i in wanted) { w[i] = wanted[i] } wanted = w message += ': ' + (wanted.name || 'Error') + ' ' + wanted.message extra.wanted = wanted } } return [fn, wanted, message, extra] } throws (...args) { this.currentAssert = Test.prototype.throws const [fn, wanted, message, extra] = this.throwsArgs('expected to throw', ...args) if (typeof fn !== 'function') { extra.todo = extra.todo || true return this.pass(message, extra) } try { fn() return this.fail(message, extra) } catch (er) { // 'name' is a getter. if (er.name) { Object.defineProperty(er, 'name', { value: er.name + '', enumerable: true, configurable: true, writable: true }) } const actual = isRegExp(wanted) ? er.message : er return wanted ? this.match(actual, wanted, message, extra) && er : this.pass(message, extra) && er } } doesNotThrow (fn, message, extra) { [message, extra] = normalizeMessageExtra('', message, extra) this.currentAssert = Test.prototype.doesNotThrow if (typeof fn === 'string') { const x = fn fn = message message = x } if (!message) { message = fn && fn.name || 'expected to not throw' } if (typeof fn !== 'function') { extra.todo = extra.todo || true return this.pass(message, extra) } try { fn() return this.pass(message, extra) } catch (er) { const e = extraFromError(er, extra) e.message = er.message return this.fail(message, e) } } before (fn) { this.currentAssert = Test.prototype.before if (this.occupied || this[_printedResult]) throw new Error('t.before() called after starting tests') if (this[_beforeCalled]) throw new Error('called t.before() more than once') this[_beforeCalled] = true // if it throws, we let it kill the test const ret = fn.call(this) if (ret && typeof ret.then === 'function') this.waitOn(ret, w => { if (w.rejected) { // sort of a mini bailout, just for this one test // drop everything from the queue, quit right away this.queue.length = 0 this.threw(w.value) this.planEnd = -1 this.count = 1 this.end() } }) } waitOn (promise, cb, expectReject) { const w = new Waiter(promise, w => { assert.equal(this.occupied, w) cb(w) this.occupied = null this.process() }, expectReject) this.queue.push(w) this.process() return w.promise } // like throws, but rejects a returned promise instead // also, can pass in a promise instead of a function rejects (...args) { this.currentAssert = Test.prototype.rejects let fn, wanted, extra, promise, message for (let i = 0; i < args.length; i++) { const arg = args[i] if (typeof arg === 'function') { if (arg === Error || arg.prototype instanceof Error) { wanted = arg } else if (!fn) { fn = arg } } else if (typeof arg === 'string' && arg) { message = arg } else if (arg && typeof arg.then === 'function' && !promise) { promise = arg } else if (typeof arg === 'object') { if (!wanted) { wanted = arg } else { extra = arg } } } if (!extra) extra = {} if (!message) message = fn && fn.name || 'expect rejected Promise' if (wanted) { if (wanted instanceof Error) { const w = { message: wanted.message } if (wanted.name) w.name = wanted.name // intentionally copying non-local properties, since this // is an Error object, and those are funky. for (let i in wanted) { w[i] = wanted[i] } wanted = w message += ': ' + (wanted.name || 'Error') + ' ' + wanted.message extra.wanted = wanted } } if (!promise && typeof fn !== 'function') { extra.todo = extra.todo || true this.pass(message, extra) return Promise.resolve(this) } if (!promise) promise = fn() if (!promise || typeof promise.then !== 'function') { this.fail(message, extra) return Promise.resolve(this) } extra.at = stack.at(this.currentAssert) return this.waitOn(promise, w => { if (!w.rejected) { extra.found = w.value return this.fail(message, extra) } const er = w.value // 'name' is a getter. if (er && er.name) { Object.defineProperty(er, 'name', { value: er.name + '', enumerable: true, configurable: true, writable: true }) } const actual = isRegExp(wanted) && er ? er.message : er return wanted ? this.match(actual, wanted, message, extra) : this.pass(message, extra) }, true) } resolves (promise, message, extra) { [message, extra] = normalizeMessageExtra('expect resolving Promise', message, extra) this.currentAssert = Test.prototype.resolves if (typeof promise === 'function') promise = promise() extra.at = stack.at(this.currentAssert) if (!promise || typeof promise.then !== 'function') { this.fail(message, extra) return Promise.resolve(this) } return this.waitOn(promise, w => { extra.found = w.value this.ok(w.resolved, message, extra) }) } resolveMatch (promise, wanted, message, extra) { [message, extra] = normalizeMessageExtra('expect resolving Promise', message, extra) this.currentAssert = Test.prototype.resolveMatch extra.at = stack.at(this.currentAssert) if (typeof promise === 'function') promise = promise() if (!promise || typeof promise.then !== 'function') { this.fail(message, extra) return Promise.resolve(this) } return this.waitOn(promise, w => { extra.found = w.value return w.rejected ? this.fail(message, extra) : this.match(w.value, wanted, message, extra) }) } resolveMatchSnapshot (promise, message, extra) { [message, extra] = normalizeMessageExtra('expect resolving Promise', message, extra) this.currentAssert = Test.prototype.resolveMatch extra.at = stack.at(this.currentAssert) if (typeof promise === 'function') promise = promise() if (!promise || typeof promise.then !== 'function') { this.fail(message, extra) return Promise.resolve(this) } return this.waitOn(promise, w => { extra.found = w.value return w.rejected ? this.fail(message, extra) : this.matchSnapshot(w.value, message, extra) }) } } const endAllQueue = queue => { queue.forEach((p, i) => { if ((p instanceof Base) && !p.readyToProcess) queue[i] = new TestPoint(false, 'child test left in queue:' + ' t.' + p.constructor.name.toLowerCase() + ' ' + p.name, p.options) }) queue.push(['end', IMPLICIT]) } const queueEmpty = t => t.queue.length === 0 || t.queue.length === 1 && t.queue[0] === 'TAP version 13\n' module.exports = Test