test
Version:
Node.js 18's node:test, as an npm package
175 lines (149 loc) • 5.55 kB
JavaScript
// https://github.com/nodejs/node/blob/1aab13cad9c800f4121c1d35b554b78c1b17bdbd/test/common/index.js
const assert = require('assert')
const path = require('path')
const util = require('util')
const noop = () => {}
const { bin } = require('../../package.json')
process.execPath = path.resolve(__dirname, '..', '..', bin.test)
const mustCallChecks = []
function runCallChecks (exitCode) {
if (exitCode !== 0) return
const failed = mustCallChecks.filter(function (context) {
if ('minimum' in context) {
context.messageSegment = `at least ${context.minimum}`
return context.actual < context.minimum
}
context.messageSegment = `exactly ${context.exact}`
return context.actual !== context.exact
})
failed.forEach(function (context) {
console.log('Mismatched %s function calls. Expected %s, actual %d.',
context.name,
context.messageSegment,
context.actual)
console.log(context.stack.split('\n').slice(2).join('\n'))
})
if (failed.length) process.exit(1)
}
function mustCall (fn, exact) {
return _mustCallInner(fn, exact, 'exact')
}
function getCallSite (top) {
const originalStackFormatter = Error.prepareStackTrace
Error.prepareStackTrace = (err, stack) => // eslint-disable-line n/handle-callback-err
`${stack[0].getFileName()}:${stack[0].getLineNumber()}`
const err = new Error()
Error.captureStackTrace(err, top)
// With the V8 Error API, the stack is not formatted until it is accessed
err.stack // eslint-disable-line no-unused-expressions
Error.prepareStackTrace = originalStackFormatter
return err.stack
}
function mustNotCall (msg) {
const callSite = getCallSite(mustNotCall)
return function mustNotCall (...args) {
const argsInfo = args.length > 0
? `\ncalled with arguments: ${args.map((arg) => util.inspect(arg)).join(', ')}`
: ''
assert.fail(
`${msg || 'function should not have been called'} at ${callSite}` +
argsInfo)
}
}
function _mustCallInner (fn, criteria = 1, field) {
if (process._exiting) { throw new Error('Cannot use common.mustCall*() in process exit handler') }
if (typeof fn === 'number') {
criteria = fn
fn = noop
} else if (fn === undefined) {
fn = noop
}
if (typeof criteria !== 'number') { throw new TypeError(`Invalid ${field} value: ${criteria}`) }
const context = {
[field]: criteria,
actual: 0,
stack: util.inspect(new Error()),
name: fn.name || '<anonymous>'
}
// Add the exit listener only once to avoid listener leak warnings
if (mustCallChecks.length === 0) process.on('exit', runCallChecks)
mustCallChecks.push(context)
const _return = function () { // eslint-disable-line func-style
context.actual++
return fn.apply(this, arguments)
}
// Function instances have own properties that may be relevant.
// Let's replicate those properties to the returned function.
// Refs: https://tc39.es/ecma262/#sec-function-instances
Object.defineProperties(_return, {
name: {
value: fn.name,
writable: false,
enumerable: false,
configurable: true
},
length: {
value: fn.length,
writable: false,
enumerable: false,
configurable: true
}
})
return _return
}
// Useful for testing expected internal/error objects
function expectsError (validator, exact) {
return mustCall((...args) => {
if (args.length !== 1) {
// Do not use `assert.strictEqual()` to prevent `inspect` from
// always being called.
assert.fail(`Expected one argument, got ${util.inspect(args)}`)
}
const error = args.pop()
const descriptor = Object.getOwnPropertyDescriptor(error, 'message')
// The error message should be non-enumerable
assert.strictEqual(descriptor.enumerable, false)
assert.throws(() => { throw error }, validator)
return true
}, exact)
}
if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout !== 'function') {
// `AbortSignal.timeout` is not available on Node.js 14.x, we need to polyfill
// it because some tests are using it. End-users don't need to it.
class AbortError extends Error {
constructor (message = 'The operation was aborted', options = undefined) {
super(message, options)
this.code = 23
}
}
AbortSignal.timeout = function timeout (delay) {
const ac = new AbortController()
setTimeout(() => ac.abort(new AbortError(
'The operation was aborted due to timeout')), delay).unref()
return ac.signal
}
}
if (typeof AbortSignal !== 'undefined' && (process.version.startsWith('v14.') || process.version.startsWith('v16.'))) {
// Implementation of AbortSignal and AbortController differ slightly with the
// v18.x one, creating some difference on the TAP output which makes the tests
// fail. We are overriding the built-ins to make the test pass, however that's
// not necessary to do for the library to work (i.e. end-users don't need it).
const defaultAbortError = new Error('This operation was aborted')
defaultAbortError.code = 20
AbortSignal.abort = function abort (reason = defaultAbortError) {
const controller = new AbortController()
controller.abort(reason)
return controller.signal
}
const nativeAbort = AbortController.prototype.abort
AbortController.prototype.abort = function abort (reason = defaultAbortError) {
this.signal.reason = reason
nativeAbort.call(this, reason)
}
}
module.exports = {
expectsError,
isWindow: process.platform === 'win32',
mustCall,
mustNotCall
}