UNPKG

test

Version:

Node.js 18's node:test, as an npm package

234 lines (194 loc) 6.63 kB
// https://github.com/nodejs/node/blob/2f38c74e263ed2e7f3b087efb9adee2442dd25c4/lib/internal/test_runner/tap_stream.js 'use strict' const { ArrayPrototypeForEach, ArrayPrototypeJoin, ArrayPrototypePush, ObjectEntries, RegExpPrototypeSymbolReplace, SafeMap, StringPrototypeReplaceAll, StringPrototypeSplit, StringPrototypeRepeat } = require('#internal/per_context/primordials') const { inspectWithNoCustomRetry } = require('#internal/errors') const { isError, kEmptyObject } = require('#internal/util') const kDefaultIndent = ' ' // 4 spaces const kFrameStartRegExp = /^ {4}at / const kLineBreakRegExp = /\n|\r\n/ const kDefaultTAPVersion = 13 const inspectOptions = { colors: false, breakLength: Infinity } let testModule // Lazy loaded due to circular dependency. function lazyLoadTest () { testModule = testModule ?? require('#internal/test_runner/test') return testModule } async function * tapReporter (source) { yield `TAP version ${kDefaultTAPVersion}\n` for await (const { type, data } of source) { switch (type) { case 'test:fail': yield reportTest(data.nesting, data.testNumber, 'not ok', data.name, data.skip, data.todo) yield reportDetails(data.nesting, data.details) break case 'test:pass': yield reportTest(data.nesting, data.testNumber, 'ok', data.name, data.skip, data.todo) yield reportDetails(data.nesting, data.details) break case 'test:plan': yield `${indent(data.nesting)}1..${data.count}\n` break case 'test:start': yield `${indent(data.nesting)}# Subtest: ${tapEscape(data.name)}\n` break case 'test:diagnostic': yield `${indent(data.nesting)}# ${tapEscape(data.message)}\n` break } } } function reportTest (nesting, testNumber, status, name, skip, todo) { let line = `${indent(nesting)}${status} ${testNumber}` if (name) { line += ` ${tapEscape(`- ${name}`)}` } if (skip !== undefined) { line += ` # SKIP${typeof skip === 'string' && skip.length ? ` ${tapEscape(skip)}` : ''}` } else if (todo !== undefined) { line += ` # TODO${typeof todo === 'string' && todo.length ? ` ${tapEscape(todo)}` : ''}` } line += '\n' return line } function reportDetails (nesting, data = kEmptyObject) { const { error, duration_ms } = data // eslint-disable-line camelcase const _indent = indent(nesting) let details = `${_indent} ---\n` details += jsToYaml(_indent, 'duration_ms', duration_ms) details += jsToYaml(_indent, null, error) details += `${_indent} ...\n` return details } const memo = new SafeMap() function indent (nesting) { let value = memo.get(nesting) if (value === undefined) { value = StringPrototypeRepeat(kDefaultIndent, nesting) memo.set(nesting, value) } return value } // In certain places, # and \ need to be escaped as \# and \\. function tapEscape (input) { let result = StringPrototypeReplaceAll(input, '\b', '\\b') result = StringPrototypeReplaceAll(result, '\f', '\\f') result = StringPrototypeReplaceAll(result, '\t', '\\t') result = StringPrototypeReplaceAll(result, '\n', '\\n') result = StringPrototypeReplaceAll(result, '\r', '\\r') result = StringPrototypeReplaceAll(result, '\v', '\\v') result = StringPrototypeReplaceAll(result, '\\', '\\\\') result = StringPrototypeReplaceAll(result, '#', '\\#') return result } function jsToYaml (indent, name, value) { if (value === null || value === undefined) { return '' } if (typeof value !== 'object') { const prefix = `${indent} ${name}: ` if (typeof value !== 'string') { return `${prefix}${inspectWithNoCustomRetry(value, inspectOptions)}\n` } const lines = StringPrototypeSplit(value, kLineBreakRegExp) if (lines.length === 1) { return `${prefix}${inspectWithNoCustomRetry(value, inspectOptions)}\n` } let str = `${prefix}|-\n` for (let i = 0; i < lines.length; i++) { str += `${indent} ${lines[i]}\n` } return str } const entries = ObjectEntries(value) const isErrorObj = isError(value) let result = '' for (let i = 0; i < entries.length; i++) { const { 0: key, 1: value } = entries[i] if (isErrorObj && (key === 'cause' || key === 'code')) { continue } result += jsToYaml(indent, key, value) } if (isErrorObj) { const { kTestCodeFailure, kUnwrapErrors } = lazyLoadTest() const { cause, code, failureType, message, expected, actual, operator, stack } = value let errMsg = message ?? '<unknown error>' let errStack = stack let errCode = code let errExpected = expected let errActual = actual let errOperator = operator let errIsAssertion = isAssertionLike(value) // If the ERR_TEST_FAILURE came from an error provided by user code, // then try to unwrap the original error message and stack. if (code === 'ERR_TEST_FAILURE' && kUnwrapErrors.has(failureType)) { errStack = cause?.stack ?? errStack errCode = cause?.code ?? errCode if (isAssertionLike(cause)) { errExpected = cause.expected errActual = cause.actual errOperator = cause.operator ?? errOperator errIsAssertion = true } if (failureType === kTestCodeFailure) { errMsg = cause?.message ?? errMsg } } result += jsToYaml(indent, 'error', errMsg) if (errCode) { result += jsToYaml(indent, 'code', errCode) } if (errIsAssertion) { result += jsToYaml(indent, 'expected', errExpected) result += jsToYaml(indent, 'actual', errActual) if (errOperator) { result += jsToYaml(indent, 'operator', errOperator) } } if (typeof errStack === 'string') { const frames = [] ArrayPrototypeForEach( StringPrototypeSplit(errStack, kLineBreakRegExp), (frame) => { const processed = RegExpPrototypeSymbolReplace( kFrameStartRegExp, frame, '' ) if (processed.length > 0 && processed.length !== frame.length) { ArrayPrototypePush(frames, processed) } } ) if (frames.length > 0) { const frameDelimiter = `\n${indent} ` result += `${indent} stack: |-${frameDelimiter}` result += `${ArrayPrototypeJoin(frames, frameDelimiter)}\n` } } } return result } function isAssertionLike (value) { return value && typeof value === 'object' && 'expected' in value && 'actual' in value } module.exports = tapReporter