Skip to content

Commit b5254f9

Browse files
committed
test_runner: add 'test:summary' event
This commit adds a new 'test:summary' event to the test runner's reporting interface. This new event serves two purposes: - The test runner internals no longer change the process exit code. This may be important to run() users. - The reporting interface now has a single event that can identify passing or failing test runs. Refs: #53867 Refs: #54812
1 parent 9404d3a commit b5254f9

File tree

6 files changed

+28
-13
lines changed

6 files changed

+28
-13
lines changed

lib/internal/main/test_runner.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ if (isUsingInspector() && options.isolation === 'process') {
3131
options.globPatterns = ArrayPrototypeSlice(process.argv, 1);
3232

3333
debug('test runner configuration:', options);
34-
run(options).on('test:fail', (data) => {
35-
if (data.todo === undefined || data.todo === false) {
34+
run(options).on('test:summary', (data) => {
35+
if (!data.success) {
3636
process.exitCode = kGenericUserError;
3737
}
3838
});

lib/internal/test_runner/harness.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function createTestTree(rootTestOptions, globalOptions) {
5858
suites: 0,
5959
};
6060
},
61+
success: true,
6162
counters: null,
6263
shouldColorizeTestFiles: shouldColorizeTestFiles(globalOptions.destinations),
6364
teardown: null,
@@ -124,7 +125,7 @@ function createProcessEventHandler(eventName, rootTest) {
124125
}
125126

126127
rootTest.diagnostic(msg);
127-
process.exitCode = kGenericUserError;
128+
rootTest.harness.success = false;
128129
return;
129130
}
130131

@@ -146,7 +147,7 @@ function configureCoverage(rootTest, globalOptions) {
146147
const msg = `Warning: Code coverage could not be enabled. ${err}`;
147148

148149
rootTest.diagnostic(msg);
149-
process.exitCode = kGenericUserError;
150+
rootTest.harness.success = false;
150151
}
151152
}
152153

@@ -165,7 +166,7 @@ function collectCoverage(rootTest, coverage) {
165166
const msg = `Warning: Could not ${op} code coverage. ${err}`;
166167

167168
rootTest.diagnostic(msg);
168-
process.exitCode = kGenericUserError;
169+
rootTest.harness.success = false;
169170
}
170171

171172
return summary;
@@ -239,14 +240,16 @@ function lazyBootstrapRoot() {
239240
if (!globalRoot) {
240241
// This is where the test runner is bootstrapped when node:test is used
241242
// without the --test flag or the run() API.
243+
const entryFile = process.argv?.[1];
242244
const rootTestOptions = {
243245
__proto__: null,
244-
entryFile: process.argv?.[1],
246+
entryFile,
247+
loc: entryFile ? [1, 1, entryFile] : undefined,
245248
};
246249
const globalOptions = parseCommandLine();
247250
createTestTree(rootTestOptions, globalOptions);
248-
globalRoot.reporter.on('test:fail', (data) => {
249-
if (data.todo === undefined || data.todo === false) {
251+
globalRoot.reporter.on('test:summary', (data) => {
252+
if (!data.success) {
250253
process.exitCode = kGenericUserError;
251254
}
252255
});

lib/internal/test_runner/test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const {
2929
SymbolDispose,
3030
} = primordials;
3131
const { getCallerLocation } = internalBinding('util');
32-
const { exitCodes: { kGenericUserError } } = internalBinding('errors');
3332
const { addAbortListener } = require('internal/events/abort_listener');
3433
const { queueMicrotask } = require('internal/process/task_queues');
3534
const { AsyncResource } = require('async_hooks');
@@ -1026,12 +1025,14 @@ class Test extends AsyncResource {
10261025
for (let i = 0; i < coverages.length; i++) {
10271026
const { threshold, actual, name } = coverages[i];
10281027
if (actual < threshold) {
1029-
process.exitCode = kGenericUserError;
1028+
harness.success = false;
10301029
reporter.diagnostic(nesting, loc, `Error: ${NumberPrototypeToFixed(actual, 2)}% ${name} coverage does not meet threshold of ${threshold}%.`);
10311030
}
10321031
}
10331032
}
10341033

1034+
reporter.summary(nesting, loc, harness.success, harness.counters);
1035+
10351036
if (harness.watching) {
10361037
this.reported = false;
10371038
harness.resetCounters();

lib/internal/test_runner/tests_stream.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ class TestsStream extends Readable {
132132
});
133133
}
134134

135+
summary(nesting, loc, success, counts) {
136+
this[kEmitMessage]('test:summary', {
137+
__proto__: null,
138+
success,
139+
counts,
140+
...loc,
141+
});
142+
}
143+
135144
end() {
136145
this.#tryPush(null);
137146
}

lib/internal/test_runner/utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,10 @@ function countCompletedTest(test, harness = test.root.harness) {
355355
harness.counters.todo++;
356356
} else if (test.cancelled) {
357357
harness.counters.cancelled++;
358+
harness.success = false;
358359
} else if (!test.passed) {
359360
harness.counters.failed++;
361+
harness.success = false;
360362
} else {
361363
harness.counters.passed++;
362364
}

test/parallel/test-runner-reporters.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('node:test reporters', { concurrency: true }, () => {
113113
testFile]);
114114
assert.strictEqual(child.stderr.toString(), '');
115115
const stdout = child.stdout.toString();
116-
assert.match(stdout, /{"test:enqueue":5,"test:dequeue":5,"test:complete":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/);
116+
assert.match(stdout, /{"test:enqueue":5,"test:dequeue":5,"test:complete":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:summary":2,"test:diagnostic":\d+}$/);
117117
assert.strictEqual(stdout.slice(0, filename.length + 2), `${filename} {`);
118118
});
119119
});
@@ -125,7 +125,7 @@ describe('node:test reporters', { concurrency: true }, () => {
125125
assert.strictEqual(child.stderr.toString(), '');
126126
assert.match(
127127
child.stdout.toString(),
128-
/^package: reporter-cjs{"test:enqueue":5,"test:dequeue":5,"test:complete":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
128+
/^package: reporter-cjs{"test:enqueue":5,"test:dequeue":5,"test:complete":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:summary":2,"test:diagnostic":\d+}$/,
129129
);
130130
});
131131

@@ -136,7 +136,7 @@ describe('node:test reporters', { concurrency: true }, () => {
136136
assert.strictEqual(child.stderr.toString(), '');
137137
assert.match(
138138
child.stdout.toString(),
139-
/^package: reporter-esm{"test:enqueue":5,"test:dequeue":5,"test:complete":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
139+
/^package: reporter-esm{"test:enqueue":5,"test:dequeue":5,"test:complete":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:summary":2,"test:diagnostic":\d+}$/,
140140
);
141141
});
142142

0 commit comments

Comments
 (0)