Skip to content

Commit 98fa308

Browse files
committed
test_runner: report failing tests after summary
Re-output failing tests after summary has been printed. This behavior follows other popular test runners (e.g. jest, mocha, etc...). Updates SpecReporter: 1. When there is a 'test:fail' event, the failed test will be stored. 2. Introduce a new 'test:eot' event in which all the failed tests will be dumped. Fixes: #47110
1 parent 30d92e8 commit 98fa308

File tree

7 files changed

+88
-6
lines changed

7 files changed

+88
-6
lines changed

lib/internal/test_runner/reporter/spec.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
ArrayPrototypeJoin,
55
ArrayPrototypePop,
6+
ArrayPrototypePush,
67
ArrayPrototypeShift,
78
ArrayPrototypeUnshift,
89
hardenRegExp,
@@ -36,6 +37,7 @@ class SpecReporter extends Transform {
3637
#stack = [];
3738
#reported = [];
3839
#indentMemo = new SafeMap();
40+
#failedTests = [];
3941

4042
constructor() {
4143
super({ writableObjectMode: true });
@@ -57,15 +59,16 @@ class SpecReporter extends Transform {
5759
RegExpPrototypeSymbolSplit(
5860
hardenRegExp(/\r?\n/),
5961
inspectWithNoCustomRetry(err, inspectOptions),
60-
), `\n${indent} `);
61-
return `\n${indent} ${message}\n`;
62+
), indent !== undefined ? `\n${indent} ` : '');
63+
return `${indent !== undefined ? `\n${indent} ` : ''}${message}\n`;
6264
}
6365
#handleEvent({ type, data }) {
6466
let color = colors[type] ?? white;
6567
let symbol = symbols[type] ?? ' ';
6668

6769
switch (type) {
6870
case 'test:fail':
71+
ArrayPrototypePush(this.#failedTests, data);
6972
case 'test:pass': {
7073
const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event
7174
if (subtest) {
@@ -103,6 +106,17 @@ class SpecReporter extends Transform {
103106
break;
104107
case 'test:diagnostic':
105108
return `${color}${this.#indent(data.nesting)}${symbol}${data.message}${white}\n`;
109+
case 'test:eot':
110+
color = colors['test:fail'];
111+
symbol = symbols['test:fail'];
112+
let results = [];
113+
for (let i = 0; i < this.#failedTests.length; i++) {
114+
const failed = this.#failedTests[i];
115+
const duration_ms = failed.details?.duration_ms ? ` ${gray}(${failed.details.duration_ms}ms)${white}` : '';
116+
const result = `${color}${symbol}${failed.file} ${failed.name}${duration_ms}\n${this.#formatError(failed.details?.error)}`;
117+
ArrayPrototypePush(results, result);
118+
}
119+
return ArrayPrototypeJoin(results, '');
106120
}
107121
}
108122
_transform({ type, data }, encoding, callback) {

lib/internal/test_runner/test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,8 @@ class Test extends AsyncResource {
655655
this.reporter.coverage(this.nesting, kFilename, this.harness.coverage);
656656
}
657657

658+
this.reporter.endOfTests();
659+
658660
this.reporter.push(null);
659661
}
660662
}

lib/internal/test_runner/tests_stream.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class TestsStream extends Readable {
5151
this.#emit('test:start', { __proto__: null, nesting, file, name });
5252
}
5353

54+
endOfTests() {
55+
this.#emit('test:eot', null);
56+
}
57+
5458
diagnostic(nesting, file, message) {
5559
this.#emit('test:diagnostic', { __proto__: null, nesting, file, message });
5660
}

test/fixtures/test-runner/custom_reporters/custom.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const path = require('path');
44
module.exports = async function * customReporter(source) {
55
const counters = {};
66
for await (const event of source) {
7-
if (event.data.file) {
7+
if (event.data?.file) {
88
assert.strictEqual(event.data.file, path.resolve(__dirname, '../reporters.js'));
99
}
1010
counters[event.type] = (counters[event.type] ?? 0) + 1;

test/message/test_runner_output_spec_reporter.out

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,63 @@
282282
skipped 10
283283
todo 5
284284
duration_ms *
285+
* sync fail todo (*ms)
286+
*
287+
* sync fail todo with message (*ms)
288+
*
289+
* sync throw fail (*ms)
290+
*
291+
* async throw fail (*ms)
292+
*
293+
* async skip fail (*ms)
294+
*
295+
* async assertion fail (*ms)
296+
*
297+
* reject fail (*ms)
298+
*
299+
* +sync throw fail (*ms)
300+
*
301+
* subtest sync throw fail (*ms)
302+
'1 subtest failed'
303+
* sync throw non-error fail (*ms)
304+
Symbol(thrown symbol from sync throw non-error fail)
305+
* +long running (*ms)
306+
'test did not finish before its parent and was cancelled'
307+
* top level (*ms)
308+
'1 subtest failed'
309+
* sync skip option is false fail (*ms)
310+
*
311+
* callback fail (*ms)
312+
*
313+
* callback also returns a Promise (*ms)
314+
'passed a callback but also returned a Promise'
315+
* callback throw (*ms)
316+
*
317+
* callback called twice (*ms)
318+
'callback invoked multiple times'
319+
* callback called twice in future tick (*ms)
320+
*
321+
* callback async throw (*ms)
322+
*
323+
* custom inspect symbol fail (*ms)
324+
customized
325+
* custom inspect symbol that throws fail (*ms)
326+
{ foo: 1, [Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]] }
327+
* sync throw fails at first (*ms)
328+
*
329+
* sync throw fails at second (*ms)
330+
*
331+
* subtest sync throw fails (*ms)
332+
'2 subtests failed'
333+
* timed out async test (*ms)
334+
'test timed out after 5ms'
335+
* timed out callback test (*ms)
336+
'test timed out after 5ms'
337+
* rejected thenable (*ms)
338+
'custom error'
339+
* unfinished test with uncaughtException (*ms)
340+
*
341+
* unfinished test with unhandledRejection (*ms)
342+
*
343+
* invalid subtest fail (*ms)
344+
'test could not be started because its parent finished'

test/parallel/test-runner-reporters.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ describe('node:test reporters', { concurrency: true }, () => {
9090
testFile]);
9191
assert.strictEqual(child.stderr.toString(), '');
9292
const stdout = child.stdout.toString();
93-
assert.match(stdout, /{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/);
93+
assert.match(stdout, /{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+,"test:eot":1}$/);
9494
assert.strictEqual(stdout.slice(0, filename.length + 2), `${filename} {`);
9595
});
9696
});
@@ -102,7 +102,7 @@ describe('node:test reporters', { concurrency: true }, () => {
102102
assert.strictEqual(child.stderr.toString(), '');
103103
assert.match(
104104
child.stdout.toString(),
105-
/^package: reporter-cjs{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
105+
/^package: reporter-cjs{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+,"test:eot":1}$/,
106106
);
107107
});
108108

@@ -113,7 +113,7 @@ describe('node:test reporters', { concurrency: true }, () => {
113113
assert.strictEqual(child.stderr.toString(), '');
114114
assert.match(
115115
child.stdout.toString(),
116-
/^package: reporter-esm{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
116+
/^package: reporter-esm{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+,"test:eot":1}$/,
117117
);
118118
});
119119

test/pseudo-tty/test_runner_default_reporter.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@
1717
[34m* skipped 1[39m
1818
[34m* todo 0[39m
1919
[34m* duration_ms *[39m
20+
[31m* should fail [90m(*ms)[39m
21+
*

0 commit comments

Comments
 (0)