Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9cbf897

Browse filesBrowse files
HinataKah0danielleadams
authored andcommitted
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...). Updated SpecReporter: 1. When there is a 'test:fail' event, the test will be stored. 2. After no more input, all the failed tests will be flushed. 3. Extract the logic for formatting a test report into a re-usable function. Fixes: #47110 PR-URL: #47164 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 9ac01ec commit 9cbf897
Copy full SHA for 9cbf897

File tree

Expand file treeCollapse file tree

4 files changed

+315
-40
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+315
-40
lines changed
Open diff view settings
Collapse file

‎lib/internal/test_runner/reporter/spec.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/reporter/spec.js
+57-35Lines changed: 57 additions & 35 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 });
@@ -60,54 +62,74 @@ class SpecReporter extends Transform {
6062
), `\n${indent} `);
6163
return `\n${indent} ${message}\n`;
6264
}
63-
#handleEvent({ type, data }) {
65+
#formatTestReport(type, data, prefix = '', indent = '', hasChildren = false, skippedSubtest = false) {
6466
let color = colors[type] ?? white;
6567
let symbol = symbols[type] ?? ' ';
66-
68+
const duration_ms = data.details?.duration_ms ? ` ${gray}(${data.details.duration_ms}ms)${white}` : '';
69+
const title = `${data.name}${duration_ms}${skippedSubtest ? ' # SKIP' : ''}`;
70+
if (hasChildren) {
71+
// If this test has had children - it was already reported, so slightly modify the output
72+
return `${prefix}${indent}${color}${symbols['arrow:right']}${white}${title}\n`;
73+
}
74+
const error = this.#formatError(data.details?.error, indent);
75+
if (skippedSubtest) {
76+
color = gray;
77+
symbol = symbols['hyphen:minus'];
78+
}
79+
return `${prefix}${indent}${color}${symbol}${title}${white}${error}`;
80+
}
81+
#handleTestReportEvent(type, data) {
82+
const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event
83+
if (subtest) {
84+
assert(subtest.type === 'test:start');
85+
assert(subtest.data.nesting === data.nesting);
86+
assert(subtest.data.name === data.name);
87+
}
88+
let prefix = '';
89+
while (this.#stack.length) {
90+
// Report all the parent `test:start` events
91+
const parent = ArrayPrototypePop(this.#stack);
92+
assert(parent.type === 'test:start');
93+
const msg = parent.data;
94+
ArrayPrototypeUnshift(this.#reported, msg);
95+
prefix += `${this.#indent(msg.nesting)}${symbols['arrow:right']}${msg.name}\n`;
96+
}
97+
let hasChildren = false;
98+
if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) {
99+
ArrayPrototypeShift(this.#reported);
100+
hasChildren = true;
101+
}
102+
const skippedSubtest = subtest && data.skip && data.skip !== undefined;
103+
const indent = this.#indent(data.nesting);
104+
return `${this.#formatTestReport(type, data, prefix, indent, hasChildren, skippedSubtest)}\n`;
105+
}
106+
#handleEvent({ type, data }) {
67107
switch (type) {
68108
case 'test:fail':
69-
case 'test:pass': {
70-
const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event
71-
if (subtest) {
72-
assert(subtest.type === 'test:start');
73-
assert(subtest.data.nesting === data.nesting);
74-
assert(subtest.data.name === data.name);
75-
}
76-
let prefix = '';
77-
while (this.#stack.length) {
78-
// Report all the parent `test:start` events
79-
const parent = ArrayPrototypePop(this.#stack);
80-
assert(parent.type === 'test:start');
81-
const msg = parent.data;
82-
ArrayPrototypeUnshift(this.#reported, msg);
83-
prefix += `${this.#indent(msg.nesting)}${symbols['arrow:right']}${msg.name}\n`;
84-
}
85-
const skippedSubtest = subtest && data.skip && data.skip !== undefined;
86-
const indent = this.#indent(data.nesting);
87-
const duration_ms = data.details?.duration_ms ? ` ${gray}(${data.details.duration_ms}ms)${white}` : '';
88-
const title = `${data.name}${duration_ms}${skippedSubtest ? ' # SKIP' : ''}`;
89-
if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) {
90-
// If this test has had children - it was already reported, so slightly modify the output
91-
ArrayPrototypeShift(this.#reported);
92-
return `${prefix}${indent}${color}${symbols['arrow:right']}${white}${title}\n\n`;
93-
}
94-
const error = this.#formatError(data.details?.error, indent);
95-
if (skippedSubtest) {
96-
color = gray;
97-
symbol = symbols['hyphen:minus'];
98-
}
99-
return `${prefix}${indent}${color}${symbol}${title}${error}${white}\n`;
100-
}
109+
ArrayPrototypePush(this.#failedTests, data);
110+
return this.#handleTestReportEvent(type, data);
111+
case 'test:pass':
112+
return this.#handleTestReportEvent(type, data);
101113
case 'test:start':
102114
ArrayPrototypeUnshift(this.#stack, { __proto__: null, data, type });
103115
break;
104116
case 'test:diagnostic':
105-
return `${color}${this.#indent(data.nesting)}${symbol}${data.message}${white}\n`;
117+
return `${colors[type]}${this.#indent(data.nesting)}${symbols[type]}${data.message}${white}\n`;
106118
}
107119
}
108120
_transform({ type, data }, encoding, callback) {
109121
callback(null, this.#handleEvent({ type, data }));
110122
}
123+
_flush(callback) {
124+
const results = [`\n${colors['test:fail']}${symbols['test:fail']}failing tests:${white}\n`];
125+
for (let i = 0; i < this.#failedTests.length; i++) {
126+
ArrayPrototypePush(results, this.#formatTestReport(
127+
'test:fail',
128+
this.#failedTests[i],
129+
));
130+
}
131+
callback(null, ArrayPrototypeJoin(results, '\n'));
132+
}
111133
}
112134

113135
module.exports = SpecReporter;
Collapse file

‎test/message/test_runner_output_spec_reporter.out‎

Copy file name to clipboardExpand all lines: test/message/test_runner_output_spec_reporter.out
+209Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,212 @@
283283
skipped 10
284284
todo 5
285285
duration_ms *
286+
287+
failing tests:
288+
289+
sync fail todo (*ms)
290+
Error: thrown from sync fail todo
291+
*
292+
*
293+
*
294+
*
295+
*
296+
*
297+
*
298+
299+
sync fail todo with message (*ms)
300+
Error: thrown from sync fail todo with message
301+
*
302+
*
303+
*
304+
*
305+
*
306+
*
307+
*
308+
309+
sync throw fail (*ms)
310+
Error: thrown from sync throw fail
311+
*
312+
*
313+
*
314+
*
315+
*
316+
*
317+
*
318+
319+
async throw fail (*ms)
320+
Error: thrown from async throw fail
321+
*
322+
*
323+
*
324+
*
325+
*
326+
*
327+
*
328+
329+
async skip fail (*ms)
330+
Error: thrown from async throw fail
331+
*
332+
*
333+
*
334+
*
335+
*
336+
*
337+
*
338+
339+
async assertion fail (*ms)
340+
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
341+
342+
true !== false
343+
344+
*
345+
*
346+
*
347+
*
348+
*
349+
*
350+
* {
351+
generatedMessage: true,
352+
code: 'ERR_ASSERTION',
353+
actual: true,
354+
expected: false,
355+
operator: 'strictEqual'
356+
}
357+
358+
reject fail (*ms)
359+
Error: rejected from reject fail
360+
*
361+
*
362+
*
363+
*
364+
*
365+
*
366+
*
367+
368+
+sync throw fail (*ms)
369+
Error: thrown from subtest sync throw fail
370+
*
371+
*
372+
*
373+
*
374+
*
375+
*
376+
*
377+
*
378+
*
379+
*
380+
381+
subtest sync throw fail (*ms)
382+
'1 subtest failed'
383+
384+
sync throw non-error fail (*ms)
385+
Symbol(thrown symbol from sync throw non-error fail)
386+
387+
+long running (*ms)
388+
'test did not finish before its parent and was cancelled'
389+
390+
top level (*ms)
391+
'1 subtest failed'
392+
393+
sync skip option is false fail (*ms)
394+
Error: this should be executed
395+
*
396+
*
397+
*
398+
*
399+
*
400+
*
401+
*
402+
403+
callback fail (*ms)
404+
Error: callback failure
405+
*
406+
*
407+
408+
callback also returns a Promise (*ms)
409+
'passed a callback but also returned a Promise'
410+
411+
callback throw (*ms)
412+
Error: thrown from callback throw
413+
*
414+
*
415+
*
416+
*
417+
*
418+
*
419+
*
420+
421+
callback called twice (*ms)
422+
'callback invoked multiple times'
423+
424+
callback called twice in future tick (*ms)
425+
Error [ERR_TEST_FAILURE]: callback invoked multiple times
426+
*
427+
failureType: 'multipleCallbackInvocations',
428+
cause: 'callback invoked multiple times',
429+
code: 'ERR_TEST_FAILURE'
430+
}
431+
432+
callback async throw (*ms)
433+
Error: thrown from callback async throw
434+
*
435+
*
436+
437+
custom inspect symbol fail (*ms)
438+
customized
439+
440+
custom inspect symbol that throws fail (*ms)
441+
{ foo: 1, [Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]] }
442+
443+
sync throw fails at first (*ms)
444+
Error: thrown from subtest sync throw fails at first
445+
*
446+
*
447+
*
448+
*
449+
*
450+
*
451+
*
452+
*
453+
*
454+
*
455+
456+
sync throw fails at second (*ms)
457+
Error: thrown from subtest sync throw fails at second
458+
*
459+
*
460+
*
461+
*
462+
*
463+
*
464+
*
465+
*
466+
*
467+
*
468+
469+
subtest sync throw fails (*ms)
470+
'2 subtests failed'
471+
472+
timed out async test (*ms)
473+
'test timed out after 5ms'
474+
475+
timed out callback test (*ms)
476+
'test timed out after 5ms'
477+
478+
rejected thenable (*ms)
479+
'custom error'
480+
481+
unfinished test with uncaughtException (*ms)
482+
Error: foo
483+
*
484+
*
485+
*
486+
487+
unfinished test with unhandledRejection (*ms)
488+
Error: bar
489+
*
490+
*
491+
*
492+
493+
invalid subtest fail (*ms)
494+
'test could not be started because its parent finished'
Collapse file

‎test/pseudo-tty/test_runner_default_reporter.js‎

Copy file name to clipboardExpand all lines: test/pseudo-tty/test_runner_default_reporter.js
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ const test = require('node:test');
99
test('should pass', () => {});
1010
test('should fail', () => { throw new Error('fail'); });
1111
test('should skip', { skip: true }, () => {});
12+
test('parent', () => {
13+
test('should fail', () => { throw new Error('fail'); });
14+
test('should pass but parent fail', () => {});
15+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.