Skip to content

Commit f8cad07

Browse files
jasnelladuh95
authored andcommitted
test: expand test coverage of node:bench
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: #65631 Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent f19dd1b commit f8cad07

7 files changed

Lines changed: 161 additions & 17 deletions

File tree

test/fixtures/bench-runner/malformed-record.cjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,24 @@ const record = kind === 'summary' ? {
4141
runId: process.env.NODE_BENCH_RUN_ID,
4242
success: true,
4343
},
44+
} : kind === 'name-path' ? {
45+
...plan,
46+
data: {
47+
...plan.data,
48+
namePath: [1],
49+
},
4450
} : kind === 'plan' ? {
4551
...plan,
4652
data: {
4753
...plan.data,
4854
samples: 0,
4955
},
56+
} : kind === 'timeout' ? {
57+
...plan,
58+
data: {
59+
...plan.data,
60+
timeout: -1,
61+
},
5062
} : kind === 'identity' ? {
5163
type: 'bench:complete',
5264
data: {

test/fixtures/bench-runner/run-file.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ bench('run file', {
77
params: {
88
context: process.env.NODE_BENCH_CONTEXT,
99
exposed: typeof globalThis.gc === 'function',
10+
omitted: process.env.NODE_BENCH_OMITTED === undefined,
1011
value: process.env.NODE_BENCH_RUN_FILE ?? 'unset',
1112
},
1213
}, (b) => {

test/parallel/test-bench-cli.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,9 @@ for (const { kind, message } of [
488488
{ kind: 'sequence', message: /valid record sequence/ },
489489
{ kind: 'record', message: /not a valid benchmark record/ },
490490
{ kind: 'identity', message: /not a valid benchmark record/ },
491+
{ kind: 'name-path', message: /not a valid benchmark record/ },
491492
{ kind: 'plan', message: /not a valid benchmark plan/ },
493+
{ kind: 'timeout', message: /not a valid benchmark plan/ },
492494
{ kind: 'diagnostic', message: /not a valid benchmark diagnostic/ },
493495
{ kind: 'diagnostic-order', message: /valid lifecycle sequence/ },
494496
{ kind: 'summary', message: /not a valid benchmark summary/ },
@@ -525,6 +527,22 @@ for (const { kind, message } of [
525527
assert.strictEqual(records.at(-1).data.success, false);
526528
}
527529

530+
{
531+
const result = spawnBench([
532+
'--bench-isolation=none',
533+
'--bench-reporter=json',
534+
fixtures.path('bench-runner/a.cjs'),
535+
fixtures.path('bench-runner/exit-code.cjs'),
536+
]);
537+
assert.strictEqual(result.status, 1);
538+
const records = parseRecords(result);
539+
const diagnostic = records.find(({ type, data }) =>
540+
type === 'bench:diagnostic' && /set exit code/.test(data.message)).data;
541+
assert.strictEqual(diagnostic.entryFile, null);
542+
assert.strictEqual(diagnostic.fileRunId, null);
543+
assert.strictEqual(diagnostic.file, null);
544+
}
545+
528546
for (const { mode, message } of [
529547
{ mode: 'code', message: /failed with exit code 2/ },
530548
{ mode: 'late', message: /failed with exit code 2/ },

test/parallel/test-bench-errors.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const common = require('../common');
55
const assert = require('assert');
6-
const { bench, run } = require('node:bench');
6+
const { bench, run, suite } = require('node:bench');
77
const { setTimeout } = require('timers/promises');
88

99
const options = { samples: 1 };
@@ -58,8 +58,11 @@ bench('continues', options, complete);
5858
bench('timeout', { samples: 1, timeout: 10 }, async () => {
5959
await new Promise(() => {});
6060
});
61-
bench('after unsettled timeout', options, common.mustNotCall());
62-
bench.skip('skipped after unsettled timeout', options, common.mustNotCall());
61+
const suiteCompletion = suite('after unsettled timeout suite', () => {
62+
bench('after unsettled timeout', options, common.mustNotCall());
63+
bench.skip('skipped after unsettled timeout', options, common.mustNotCall());
64+
});
65+
suiteCompletion.then(common.mustCall());
6366

6467
const completions = [];
6568
const sampleNames = [];

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

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,20 @@ async function testRunSignal() {
6262
async function testRunSignalAfterSample() {
6363
const runner = createRunner({ yieldBetweenSamples: false });
6464
const controller = new AbortController();
65+
const reason = new Error('sample aborted');
6566
const completion = runner.bench('aborted after sample', {
66-
samples: 1,
67-
}, (b) => {
68-
complete(b);
69-
controller.abort(new Error('sample aborted'));
70-
});
71-
await runner.run({ signal: controller.signal }).toArray();
67+
samples: 2,
68+
}, complete);
69+
const stream = runner.run({ signal: controller.signal });
70+
stream.once('bench:sample', common.mustCall(() => {
71+
controller.abort(reason);
72+
}));
73+
const records = await stream.toArray();
7274
const result = await completion;
73-
await setImmediate();
7475
assert.strictEqual(result.error.code, 'ABORT_ERR');
75-
assert.strictEqual(result.error.cause.message, 'sample aborted');
76+
assert.strictEqual(result.error.cause, reason);
77+
assert.strictEqual(result.samples.length, 1);
78+
assert.strictEqual(records.at(-1).data.success, false);
7679
}
7780

7881
async function testStringNamePattern() {
@@ -93,12 +96,15 @@ async function testStringNamePattern() {
9396

9497
async function testTopLevelRecovery() {
9598
const runner = createRunner({ yieldBetweenSamples: false });
96-
runner.bench('listener failure', { samples: 1 }, complete);
99+
const suiteCompletion = runner.suite('nested', () => {
100+
runner.bench('listener failure', { samples: 1 }, complete);
101+
});
97102
const stream = runner.run();
98103
const failure = new Error();
99104
failure.message = undefined;
100105
stream.on('bench:start', common.mustCall(() => { throw failure; }));
101106
const records = await stream.toArray();
107+
await suiteCompletion;
102108
const diagnostic = records.find(
103109
({ type }) => type === 'bench:diagnostic').data;
104110
const summary = records.find(({ type }) => type === 'bench:summary').data;
@@ -110,10 +116,31 @@ async function testTopLevelRecovery() {
110116
assert.strictEqual(summary.success, false);
111117
}
112118

119+
async function testRepeatedReportingFailure() {
120+
const runner = createRunner({ yieldBetweenSamples: false });
121+
const original = new Error('start listener failed');
122+
const diagnostic = new Error('diagnostic listener failed');
123+
const summary = new Error('summary listener failed');
124+
const completion = runner.bench('reporting failures', {
125+
samples: 1,
126+
}, complete);
127+
const stream = runner.run();
128+
stream.on('bench:start', common.mustCall(() => { throw original; }));
129+
stream.on('bench:diagnostic', common.mustCall(() => {
130+
throw diagnostic;
131+
}, 2));
132+
stream.on('bench:summary', common.mustCall(() => { throw summary; }));
133+
const ended = new Promise((resolve) => stream.once('end', resolve));
134+
stream.resume();
135+
await ended;
136+
assert.strictEqual((await completion).error, original);
137+
}
138+
113139
(async () => {
114140
await testSynchronousSuiteFailure();
115141
await testRunSignal();
116142
await testRunSignalAfterSample();
117143
await testStringNamePattern();
118144
await testTopLevelRecovery();
145+
await testRepeatedReportingFailure();
119146
})().then(common.mustCall());

test/parallel/test-bench-run-file.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ assert.throws(() => runFile(fixture, { execArgv: [fixture] }), {
2929
assert.throws(() => runFile(fixture, { execArgv: ['-e', '0'] }), {
3030
code: 'ERR_INVALID_ARG_VALUE',
3131
});
32+
assert.throws(() => runFile(fixture, { execArgv: ['-e0'] }), {
33+
code: 'ERR_INVALID_ARG_VALUE',
34+
});
3235
assert.throws(() => runFile(fixture, { execArgv: ['--require'] }), {
3336
code: 'ERR_INVALID_ARG_VALUE',
3437
});
38+
assert.throws(() => runFile(fixture, { execArgv: ['--require', ''] }), {
39+
code: 'ERR_INVALID_ARG_VALUE',
40+
});
3541
assert.throws(() => runFile(fixture, { execArgv: ['--require='] }), {
3642
code: 'ERR_INVALID_ARG_VALUE',
3743
});
@@ -73,11 +79,12 @@ assert.throws(() => runFile(fixture, { signal: { aborted: false } }), {
7379
});
7480

7581
async function testRunFile() {
76-
const execArgv = ['--expose-gc', '-r', 'fs'];
82+
const execArgv = ['--expose-gc', '--no-warnings', '-r', 'fs'];
7783
const env = {
7884
__proto__: null,
7985
...process.env,
8086
NODE_BENCH_CONTEXT: 'not-a-child',
87+
NODE_BENCH_OMITTED: undefined,
8188
NODE_BENCH_RUN_FILE: 'original',
8289
NODE_CHANNEL_FD: '999',
8390
NODE_CHANNEL_SERIALIZATION_MODE: 'json',
@@ -93,12 +100,14 @@ async function testRunFile() {
93100
assert.strictEqual(records.at(-1).type, 'bench:summary');
94101
assert.strictEqual(plan.params.context, 'child');
95102
assert.strictEqual(plan.params.exposed, true);
103+
assert.strictEqual(plan.params.omitted, true);
96104
assert.strictEqual(plan.params.value, 'original');
97105
assert.strictEqual(result.error, undefined);
98106
assert.strictEqual(result.samples.length, 1);
99107
assert.strictEqual(typeof result.samples[0].duration_ns, 'bigint');
100108
assert.notStrictEqual(result.samples[0].detail.pid, process.pid);
101109
assert(result.samples[0].detail.execArgv.includes('--expose-gc'));
110+
assert(result.samples[0].detail.execArgv.includes('--no-warnings'));
102111
assert(result.samples[0].detail.execArgv.includes('-r'));
103112
assert.strictEqual(summary.success, true);
104113
assert.strictEqual(summary.file, fixture);
@@ -258,13 +267,16 @@ async function testParentExitCode() {
258267
}
259268

260269
async function testDefaultExecArgvSnapshot() {
261-
const stream = runFile(fixture);
262-
process.execArgv.push('--require=/does/not/exist.cjs');
270+
const original = Array.from(process.execArgv);
263271
try {
272+
process.execArgv.push('--bench', '--eval', 'throw new Error()');
273+
const stream = runFile(fixture);
274+
process.execArgv.push('--require=/does/not/exist.cjs');
264275
const records = await stream.toArray();
265276
assert.strictEqual(records.at(-1).data.success, true);
266277
} finally {
267-
process.execArgv.pop();
278+
process.execArgv.length = 0;
279+
process.execArgv.push(...original);
268280
}
269281
}
270282

@@ -280,6 +292,22 @@ async function testExecPathSnapshot() {
280292
}
281293
}
282294

295+
async function testSpawnFailure() {
296+
const execPath = process.execPath;
297+
let stream;
298+
try {
299+
process.execPath = fixtures.path('does-not-exist-node');
300+
stream = runFile(fixture);
301+
} finally {
302+
process.execPath = execPath;
303+
}
304+
const records = await stream.toArray();
305+
const diagnostic = records.find(
306+
({ type }) => type === 'bench:diagnostic').data;
307+
assert.strictEqual(diagnostic.error.code, 'ENOENT');
308+
assert.strictEqual(records.at(-1).data.success, false);
309+
}
310+
283311
function testEvalParent() {
284312
const script = `
285313
require('node:bench').runFile(${JSON.stringify(fixture)})
@@ -394,6 +422,7 @@ async function testDestroy() {
394422
await testParentExitCode();
395423
await testDefaultExecArgvSnapshot();
396424
await testExecPathSnapshot();
425+
await testSpawnFailure();
397426
await testPreAborted();
398427
await testDestroy();
399428
testEvalParent();

test/parallel/test-bench-stream.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,34 @@ async function testPlanBackpressure() {
8181
);
8282
}
8383

84+
async function testDestroyWhileBlocked() {
85+
const runner = createRunner({ yieldBetweenSamples: false });
86+
const completions = [];
87+
for (let i = 0; i < 32; i++) {
88+
completions.push(runner.bench(`destroyed ${i}`, {
89+
samples: 1,
90+
}, recordSample));
91+
}
92+
const stream = runner.run();
93+
const unblocked = stream.waitForDrain();
94+
const iterator = stream[Symbol.asyncIterator]();
95+
await iterator.next();
96+
await unblocked;
97+
for (let i = 0; i < 100 &&
98+
stream.readableLength < stream.readableHighWaterMark; i++) {
99+
await setImmediate();
100+
}
101+
assert.strictEqual(stream.readableLength, stream.readableHighWaterMark);
102+
103+
const draining = stream.waitForDrain();
104+
const closed = new Promise((resolve) => stream.once('close', resolve));
105+
stream.destroy();
106+
await assert.rejects(draining, { code: 'ERR_INVALID_STATE' });
107+
await assert.rejects(stream.waitForDrain(), { code: 'ERR_INVALID_STATE' });
108+
await closed;
109+
await Promise.all(completions);
110+
}
111+
84112
async function testNamedEventsWithoutReading() {
85113
const runner = createRunner();
86114
const sampleCount = 64;
@@ -192,7 +220,13 @@ async function testRecordOwnership() {
192220
expectedError.context = {
193221
note: 'preserved',
194222
callback() {},
223+
get accessor() { return 'value'; },
224+
};
225+
expectedError.customContext = {
226+
__proto__: {},
227+
note: 'preserved',
195228
};
229+
expectedError.arrayPayload = [new WeakMap()];
196230
const innerError = new Error('inner failure');
197231
innerError.code = 'ERR_INNER';
198232
const aggregate = new AggregateError([innerError], 'aggregate failure');
@@ -217,6 +251,16 @@ async function testRecordOwnership() {
217251
const caused = runner.bench('owned cause', { samples: 1 }, () => {
218252
throw causedError;
219253
});
254+
const throwingNameError = new Error('throwing name');
255+
Object.defineProperty(throwingNameError, 'name', {
256+
configurable: true,
257+
get() { throw new Error('name getter'); },
258+
});
259+
const throwingName = runner.bench('throwing name', {
260+
samples: 1,
261+
}, () => {
262+
throw throwingNameError;
263+
});
220264
const thrownValue = new WeakMap();
221265
const uncloneable = runner.bench('uncloneable error', {
222266
samples: 1,
@@ -265,6 +309,7 @@ async function testRecordOwnership() {
265309
const measuredResult = await measured;
266310
const failedResult = await failed;
267311
await caused;
312+
const throwingNameResult = await throwingName;
268313
const uncloneableResult = await uncloneable;
269314
const trappedResult = await trapped;
270315
const afterTrapResult = await afterTrap;
@@ -278,6 +323,8 @@ async function testRecordOwnership() {
278323
({ name }) => name === 'owned error');
279324
const streamCaused = streamResults.find(
280325
({ name }) => name === 'owned cause');
326+
const streamThrowingName = streamResults.find(
327+
({ name }) => name === 'throwing name');
281328
const streamSummary = records.find(
282329
({ type }) => type === 'bench:summary').data;
283330

@@ -299,12 +346,18 @@ async function testRecordOwnership() {
299346
assert.strictEqual(streamFailed.error.cause, streamFailed.error);
300347
assert.strictEqual(streamFailed.error.context.note, 'preserved');
301348
assert.strictEqual(streamFailed.error.context.callback, undefined);
349+
assert.strictEqual(streamFailed.error.context.accessor, undefined);
350+
assert.strictEqual(streamFailed.error.customContext.note, 'preserved');
351+
assert.deepStrictEqual(streamFailed.error.arrayPayload, [undefined]);
302352
assert.strictEqual(failedResult.error, expectedError);
303353
assert.strictEqual(failedResult.error.code, 'ERR_EXPECTED');
304354
assert.strictEqual(failedResult.error.cause, failedResult.error);
305355
assert.strictEqual(uncloneableResult.error, thrownValue);
306356
assert.strictEqual(trappedResult.error, proxyError);
307357
assert.strictEqual(afterTrapResult.error, undefined);
358+
assert.strictEqual(throwingNameResult.error, throwingNameError);
359+
assert.strictEqual(streamThrowingName.error.name, 'Error');
360+
assert.strictEqual(streamThrowingName.error.message, 'throwing name');
308361
assert(streamCaused.error.cause instanceof AggregateError);
309362
assert.strictEqual(streamCaused.error.cause.name, 'AggregateError');
310363
assert.strictEqual(streamCaused.error.cause.code, 'ERR_AGGREGATE');
@@ -313,12 +366,13 @@ async function testRecordOwnership() {
313366
streamCaused.error.references.get('self'), streamCaused.error);
314367
assert.strictEqual(streamCaused.error.members.has(streamCaused.error), true);
315368
assert.notStrictEqual(eventSummary, streamSummary);
316-
assert.strictEqual(streamSummary.counts.total, 6);
369+
assert.strictEqual(streamSummary.counts.total, 7);
317370
}
318371

319372
(async () => {
320373
await testReadableBackpressure();
321374
await testPlanBackpressure();
375+
await testDestroyWhileBlocked();
322376
await testNamedEventsWithoutReading();
323377
await testCancellationCompletesBenchmarks();
324378
await testDeliveryDoesNotConsumeTimeout();

0 commit comments

Comments
 (0)