Skip to content

Commit 6401472

Browse files
committed
lib: set exit code on failed node:bench run()
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
1 parent 95009e8 commit 6401472

7 files changed

Lines changed: 96 additions & 5 deletions

File tree

‎doc/api/bench.md‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ node --experimental-bench --bench benchmark.mjs
6262
Benchmarks are executed serially in declaration order. Declared benchmarks are
6363
scheduled automatically. Call `run()` during the same turn as the declarations
6464
to consume the event stream or configure filtering.
65-
If an automatically scheduled run fails and `run()` was not called, the process
66-
exit code is set to `1`.
65+
If a benchmark run started automatically or through the module-level `run()`
66+
fails, the process exit code is set to `1` unless `process.exitCode` has
67+
already been set. Runners created with [`createRunner()`][] leave the exit code
68+
to their caller.
6769

6870
## Measurement model
6971

@@ -304,7 +306,8 @@ runner and obtain its {BenchmarksStream}.
304306

305307
Each runner can be started once. Its `run()` function accepts the same options
306308
as the module-level [`run()`][]. `run({ yieldBetweenSamples })` overrides the
307-
value passed to `createRunner()`.
309+
value passed to `createRunner()`. Unlike the module-level runner, a failed
310+
benchmark in an explicit runner does not change the process exit code.
308311

309312
## `bench([name][, options], fn)`
310313

@@ -498,6 +501,9 @@ execution begins. Calling `run()` is optional when the returned stream is not
498501
needed. An explicit runner created by `createRunner()` does not run
499502
automatically, so its `run()` function may be called later.
500503

504+
If any benchmark fails, the process exit code is set to `1` once the run
505+
finishes, unless `process.exitCode` has already been set.
506+
501507
```mjs
502508
import { bench, run } from 'node:bench';
503509

@@ -843,6 +849,7 @@ A completed benchmark result contains:
843849
* `skewness` {number} The skewness of the scaled rate histogram.
844850

845851
[`context.record()`]: #contextrecordsample
852+
[`createRunner()`]: #createrunneroptions
846853
[`run()`]: #runoptions
847854
[benchmark result]: #benchmark-result
848855
[command-line options documentation]: cli.md#--bench

‎lib/internal/bench_runner/harness.js‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ class Harness {
104104
#abortReason = null;
105105
#autoRun;
106106
#buildPromises = [];
107+
#cliRun = false;
107108
#duplicateErrors = new SafeMap();
108109
#explicitRun = false;
109110
#fileScopeStorage = new AsyncLocalStorage();
@@ -318,6 +319,7 @@ class Harness {
318319
try {
319320
this.configure(options);
320321
this.#explicitRun = true;
322+
this.#cliRun = force;
321323
} finally {
322324
this.#starting = false;
323325
}
@@ -1035,8 +1037,10 @@ class Harness {
10351037
this.root.completion.resolve();
10361038
this.root.emitDestroy();
10371039
this.#storage.disable();
1038-
if (!this.#explicitRun && !this.success) {
1039-
process.exitCode = kGenericUserError;
1040+
// Runners created with createRunner() leave the exit code to their
1041+
// caller, and the CLI runner derives it from the aggregated summary.
1042+
if (this.#autoRun && !this.#cliRun && !this.success) {
1043+
process.exitCode ??= kGenericUserError;
10401044
}
10411045
}
10421046
}

‎test/parallel/test-bench-clock-precision.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ const completion = bench('zero duration', { samples: 1 }, (b) => {
1919
const result = await completion;
2020
assert.strictEqual(result.error.code, 'ERR_INVALID_STATE');
2121
assert.match(result.error.message, /insufficient clock precision/);
22+
// The failed run sets the exit code; reset it so the test itself passes.
23+
assert.strictEqual(process.exitCode, 1);
24+
process.exitCode = 0;
2225
})().then(common.mustCall());

‎test/parallel/test-bench-errors.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ stream.on('end', common.mustCall(() => {
8181
total: 17,
8282
});
8383
assert.strictEqual(summary.success, false);
84+
// The failed run sets the exit code; reset it so the test itself passes.
85+
assert.strictEqual(process.exitCode, 1);
86+
process.exitCode = 0;
8487

8588
const byName = new Map();
8689
for (const result of completions) {

‎test/parallel/test-bench-hook-errors.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ stream.on('end', common.mustCall(() => {
5757
total: 4,
5858
});
5959
assert.strictEqual(summary.success, false);
60+
// The failed run sets the exit code; reset it so the test itself passes.
61+
assert.strictEqual(process.exitCode, 1);
62+
process.exitCode = 0;
6063

6164
const byName = new Map(completions.map((result) => [result.name, result]));
6265
assert.strictEqual(byName.get('blocked by before').error.message,

‎test/parallel/test-bench-reporters.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ bench('json failed', { samples: 1 }, () => {
5555
skipped: 0,
5656
total: 2,
5757
});
58+
// The failed run sets the exit code; reset it so the test itself passes.
59+
assert.strictEqual(process.exitCode, 1);
60+
process.exitCode = 0;
5861

5962
const synthetic = [
6063
{
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Flags: --experimental-bench --no-warnings
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
const { spawnSync } = require('child_process');
7+
8+
function runScript(script) {
9+
return spawnSync(process.execPath, [
10+
'--no-warnings',
11+
'--experimental-bench',
12+
'-e',
13+
script,
14+
]);
15+
}
16+
17+
// A failing benchmark consumed through the module-level run() sets the exit
18+
// code.
19+
{
20+
const child = runScript(`
21+
const { bench, run } = require('node:bench');
22+
bench('failure', () => { throw new Error('boom'); });
23+
(async () => { for await (const record of run()); })();
24+
`);
25+
assert.strictEqual(child.status, 1, child.stderr.toString());
26+
}
27+
28+
// A successful run through the module-level run() keeps the default exit code.
29+
{
30+
const child = runScript(`
31+
const { bench, run } = require('node:bench');
32+
bench('success', { samples: 1 }, (b) => {
33+
b.record({ duration_ns: 1n, operations: 1 });
34+
});
35+
(async () => { for await (const record of run()); })();
36+
`);
37+
assert.strictEqual(child.status, 0, child.stderr.toString());
38+
}
39+
40+
// An exit code set by the user is preserved when the explicit run fails.
41+
{
42+
const child = runScript(`
43+
const { bench, run } = require('node:bench');
44+
process.exitCode = 3;
45+
bench('failure', () => { throw new Error('boom'); });
46+
(async () => { for await (const record of run()); })();
47+
`);
48+
assert.strictEqual(child.status, 3, child.stderr.toString());
49+
}
50+
51+
// An exit code set by the user is preserved when an automatic run fails.
52+
{
53+
const child = runScript(`
54+
process.exitCode = 3;
55+
require('node:bench').bench('failure', () => { throw new Error(); });
56+
`);
57+
assert.strictEqual(child.status, 3, child.stderr.toString());
58+
}
59+
60+
// Runners created with createRunner() leave the exit code to the caller.
61+
{
62+
const child = runScript(`
63+
const runner = require('node:bench').createRunner();
64+
runner.bench('failure', () => { throw new Error('boom'); });
65+
(async () => { for await (const record of runner.run()); })();
66+
`);
67+
assert.strictEqual(child.status, 0, child.stderr.toString());
68+
}

0 commit comments

Comments
 (0)