diff --git a/lib/internal/test_runner/reporter/spec.js b/lib/internal/test_runner/reporter/spec.js index 16cbdf1d5aa901..7afd30aa563a0f 100644 --- a/lib/internal/test_runner/reporter/spec.js +++ b/lib/internal/test_runner/reporter/spec.js @@ -12,7 +12,6 @@ const { StringPrototypeRepeat, } = primordials; const assert = require('assert'); -const Transform = require('internal/streams/transform'); const { inspectWithNoCustomRetry } = require('internal/errors'); const { green, blue, red, white, gray, shouldColorize } = require('internal/util/colors'); const { kSubtestsFailed } = require('internal/test_runner/test'); @@ -35,26 +34,24 @@ const symbols = { 'arrow:right': '\u25B6 ', 'hyphen:minus': '\uFE63 ', }; -class SpecReporter extends Transform { - #stack = []; - #reported = []; - #indentMemo = new SafeMap(); - #failedTests = []; - constructor() { - super({ writableObjectMode: true }); - } +async function* specReporter(source) { + const stack = []; + const reported = []; + const indentMemo = new SafeMap(); + const failedTests = []; - #indent(nesting) { - let value = this.#indentMemo.get(nesting); + function indent(nesting) { + let value = indentMemo.get(nesting); if (value === undefined) { value = StringPrototypeRepeat(' ', nesting); - this.#indentMemo.set(nesting, value); + indentMemo.set(nesting, value); } return value; } - #formatError(error, indent) { + + function formatError(error, indent) { if (!error) return ''; const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error; const message = ArrayPrototypeJoin( @@ -64,7 +61,8 @@ class SpecReporter extends Transform { ), `\n${indent} `); return `\n${indent} ${message}\n`; } - #formatTestReport(type, data, prefix = '', indent = '', hasChildren = false, skippedSubtest = false) { + + function formatTestReport(type, data, prefix = '', indent = '', hasChildren = false, skippedSubtest = false) { let color = colors[type] ?? white; let symbol = symbols[type] ?? ' '; const duration_ms = data.details?.duration_ms ? ` ${gray}(${data.details.duration_ms}ms)${white}` : ''; @@ -73,76 +71,76 @@ class SpecReporter extends Transform { // If this test has had children - it was already reported, so slightly modify the output return `${prefix}${indent}${color}${symbols['arrow:right']}${white}${title}\n`; } - const error = this.#formatError(data.details?.error, indent); + const error = formatError(data.details?.error, indent); if (skippedSubtest) { color = gray; symbol = symbols['hyphen:minus']; } return `${prefix}${indent}${color}${symbol}${title}${white}${error}`; } - #handleTestReportEvent(type, data) { - const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event + + function handleTestReportEvent(type, data) { + const subtest = ArrayPrototypeShift(stack); // This is the matching `test:start` event if (subtest) { assert(subtest.type === 'test:start'); assert(subtest.data.nesting === data.nesting); assert(subtest.data.name === data.name); } let prefix = ''; - while (this.#stack.length) { + while (stack.length) { // Report all the parent `test:start` events - const parent = ArrayPrototypePop(this.#stack); + const parent = ArrayPrototypePop(stack); assert(parent.type === 'test:start'); const msg = parent.data; - ArrayPrototypeUnshift(this.#reported, msg); - prefix += `${this.#indent(msg.nesting)}${symbols['arrow:right']}${msg.name}\n`; + ArrayPrototypeUnshift(reported, msg); + prefix += `${indent(msg.nesting)}${symbols['arrow:right']}${msg.name}\n`; } let hasChildren = false; - if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) { - ArrayPrototypeShift(this.#reported); + if (reported[0] && reported[0].nesting === data.nesting && reported[0].name === data.name) { + ArrayPrototypeShift(reported); hasChildren = true; } const skippedSubtest = subtest && data.skip && data.skip !== undefined; - const indent = this.#indent(data.nesting); - return `${this.#formatTestReport(type, data, prefix, indent, hasChildren, skippedSubtest)}\n`; + const _indent = indent(data.nesting); + return `${formatTestReport(type, data, prefix, _indent, hasChildren, skippedSubtest)}\n`; } - #handleEvent({ type, data }) { + + for await (const { type, data } of source) { switch (type) { case 'test:fail': if (data.details?.error?.failureType !== kSubtestsFailed) { - ArrayPrototypePush(this.#failedTests, data); + ArrayPrototypePush(failedTests, data); } - return this.#handleTestReportEvent(type, data); + yield handleTestReportEvent(type, data); + break; case 'test:pass': - return this.#handleTestReportEvent(type, data); + yield handleTestReportEvent(type, data); + break; case 'test:start': - ArrayPrototypeUnshift(this.#stack, { __proto__: null, data, type }); + ArrayPrototypeUnshift(stack, { __proto__: null, data, type }); break; case 'test:stderr': case 'test:stdout': - return data.message; + yield data.message; + break; case 'test:diagnostic': - return `${colors[type]}${this.#indent(data.nesting)}${symbols[type]}${data.message}${white}\n`; + yield `${colors[type]}${indent(data.nesting)}${symbols[type]}${data.message}${white}\n`; + break; case 'test:coverage': - return getCoverageReport(this.#indent(data.nesting), data.summary, symbols['test:coverage'], blue, true); + yield getCoverageReport(indent(data.nesting), data.summary, symbols['test:coverage'], blue, true); + break; } } - _transform({ type, data }, encoding, callback) { - callback(null, this.#handleEvent({ type, data })); - } - _flush(callback) { - if (this.#failedTests.length === 0) { - callback(null, ''); - return; - } + if (failedTests.length !== 0) { const results = [`\n${colors['test:fail']}${symbols['test:fail']}failing tests:${white}\n`]; - for (let i = 0; i < this.#failedTests.length; i++) { - ArrayPrototypePush(results, this.#formatTestReport( + for (let i = 0; i < failedTests.length; i++) { + ArrayPrototypePush(results, formatTestReport( 'test:fail', - this.#failedTests[i], + failedTests[i], )); } - callback(null, ArrayPrototypeJoin(results, '\n')); + yield results.join('\n'); } } -module.exports = SpecReporter; +module.exports = specReporter; diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index b5c41b8ae925de..45df61cb6dcc86 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -76,12 +76,17 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { }); it('should be piped with spec', async () => { - const specReporter = new spec(); - const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(specReporter).toArray(); + const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(spec).toArray(); const stringResults = result.map((bfr) => bfr.toString()); assert.match(stringResults[0], /this should pass/); assert.match(stringResults[1], /tests 1/); - assert.match(stringResults[1], /pass 1/); + assert.match(stringResults[2], /suites 0/); + assert.match(stringResults[3], /pass 1/); + assert.match(stringResults[4], /fail 0/); + assert.match(stringResults[5], /cancelled 0/); + assert.match(stringResults[6], /skipped 0/); + assert.match(stringResults[7], /todo 0/); + assert.match(stringResults[8], /duration_ms \d+\.?\d*/); }); it('should be piped with tap', async () => {