mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Most node:bench tests record a sample with `b.start();
process.hrtime.bigint(); b.end(1)`, counting on the extra clock read to
make end() see a later timestamp than start(). On a debian12-x64 CI
host the monotonic clock is coarse enough that all three reads returned
the same value, so end() threw ERR_INVALID_STATE ("insufficient clock
precision for benchmark sample") and test-bench-harness-errors failed;
every test using the idiom can fail the same way there.
Add test/common/bench.js with completeSample(), which spins until
process.hrtime.bigint() moves between start() and end(), and use it at
all of those sites, bench-runner fixtures included.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65780
Fixes: https://github.com/nodejs/node/issues/64226
Refs: https://github.com/nodejs/node/issues/59146
Refs: https://github.com/nodejs/node/issues/54534
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
142 lines
5.2 KiB
JavaScript
142 lines
5.2 KiB
JavaScript
// Flags: --no-warnings
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
const { completeSample } = require('../common/bench');
|
|
const assert = require('assert');
|
|
const { createRunner } = require('node:bench');
|
|
const { setImmediate } = require('timers/promises');
|
|
|
|
async function testSynchronousSuiteFailure() {
|
|
const runner = createRunner({ yieldBetweenSamples: false });
|
|
const completion = runner.suite('outer', () => {
|
|
runner.suite('nested', () => {
|
|
runner.bench('blocked', { samples: 1 }, common.mustNotCall());
|
|
});
|
|
throw new Error('synchronous suite failure');
|
|
});
|
|
const records = await runner.run().toArray();
|
|
await completion;
|
|
const plan = records.find(
|
|
({ type }) => type === 'bench:plan').data;
|
|
const result = records.find(
|
|
({ type }) => type === 'bench:complete').data;
|
|
assert.strictEqual(plan.name, 'blocked');
|
|
assert.strictEqual(plan.selected, true);
|
|
assert.strictEqual(
|
|
records.some(({ type }) => type === 'bench:start'), false);
|
|
assert.strictEqual(result.name, 'blocked');
|
|
assert.strictEqual(result.error.message, 'synchronous suite failure');
|
|
}
|
|
|
|
async function testRunSignal() {
|
|
const runner = createRunner();
|
|
const controller = new AbortController();
|
|
let invocations = 0;
|
|
let abortPromise;
|
|
const completion = runner.bench('aborted between samples', {
|
|
samples: 3,
|
|
}, (b) => {
|
|
invocations++;
|
|
completeSample(b);
|
|
if (b.index === 0) {
|
|
abortPromise = setImmediate().then(() => {
|
|
controller.abort(new Error('run aborted'));
|
|
});
|
|
}
|
|
});
|
|
await runner.run({ signal: controller.signal }).toArray();
|
|
const result = await completion;
|
|
await abortPromise;
|
|
await setImmediate();
|
|
assert.strictEqual(result.error.code, 'ABORT_ERR');
|
|
assert.strictEqual(result.error.cause.message, 'run aborted');
|
|
assert.strictEqual(invocations, 1);
|
|
}
|
|
|
|
async function testRunSignalAfterSample() {
|
|
const runner = createRunner({ yieldBetweenSamples: false });
|
|
const controller = new AbortController();
|
|
const reason = new Error('sample aborted');
|
|
const completion = runner.bench('aborted after sample', {
|
|
samples: 2,
|
|
}, completeSample);
|
|
const stream = runner.run({ signal: controller.signal });
|
|
stream.once('bench:sample', common.mustCall(() => {
|
|
controller.abort(reason);
|
|
}));
|
|
const records = await stream.toArray();
|
|
const result = await completion;
|
|
assert.strictEqual(result.error.code, 'ABORT_ERR');
|
|
assert.strictEqual(result.error.cause, reason);
|
|
assert.strictEqual(result.samples.length, 1);
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testStringNamePattern() {
|
|
const runner = createRunner({ yieldBetweenSamples: false });
|
|
runner.bench('included', { samples: 1 }, completeSample);
|
|
runner.bench('excluded', { samples: 1 }, common.mustNotCall());
|
|
const records = await runner.run({ namePattern: 'included' }).toArray();
|
|
const excluded = records.find(
|
|
({ type, data }) => type === 'bench:complete' &&
|
|
data.name === 'excluded').data;
|
|
const included = records.find(
|
|
({ type, data }) => type === 'bench:complete' &&
|
|
data.name === 'included').data;
|
|
assert.strictEqual(included.error, undefined);
|
|
assert.strictEqual(included.samples.length, 1);
|
|
assert.strictEqual(excluded.skip, 'name pattern');
|
|
}
|
|
|
|
async function testTopLevelRecovery() {
|
|
const runner = createRunner({ yieldBetweenSamples: false });
|
|
const suiteCompletion = runner.suite('nested', () => {
|
|
runner.bench('listener failure', { samples: 1 }, completeSample);
|
|
});
|
|
const stream = runner.run();
|
|
const failure = new Error();
|
|
failure.message = undefined;
|
|
stream.on('bench:start', common.mustCall(() => { throw failure; }));
|
|
const records = await stream.toArray();
|
|
await suiteCompletion;
|
|
const diagnostic = records.find(
|
|
({ type }) => type === 'bench:diagnostic').data;
|
|
const summary = records.find(({ type }) => type === 'bench:summary').data;
|
|
assert.strictEqual(diagnostic.message, 'Error');
|
|
assert.strictEqual(diagnostic.file, undefined);
|
|
assert.strictEqual(diagnostic.line, undefined);
|
|
assert.strictEqual(diagnostic.column, undefined);
|
|
assert.strictEqual(summary.duration_ns, 0n);
|
|
assert.strictEqual(summary.success, false);
|
|
}
|
|
|
|
async function testRepeatedReportingFailure() {
|
|
const runner = createRunner({ yieldBetweenSamples: false });
|
|
const original = new Error('start listener failed');
|
|
const diagnostic = new Error('diagnostic listener failed');
|
|
const summary = new Error('summary listener failed');
|
|
const completion = runner.bench('reporting failures', {
|
|
samples: 1,
|
|
}, completeSample);
|
|
const stream = runner.run();
|
|
stream.on('bench:start', common.mustCall(() => { throw original; }));
|
|
stream.on('bench:diagnostic', common.mustCall(() => {
|
|
throw diagnostic;
|
|
}, 2));
|
|
stream.on('bench:summary', common.mustCall(() => { throw summary; }));
|
|
const ended = new Promise((resolve) => stream.once('end', resolve));
|
|
stream.resume();
|
|
await ended;
|
|
assert.strictEqual((await completion).error, original);
|
|
}
|
|
|
|
(async () => {
|
|
await testSynchronousSuiteFailure();
|
|
await testRunSignal();
|
|
await testRunSignalAfterSample();
|
|
await testStringNamePattern();
|
|
await testTopLevelRecovery();
|
|
await testRepeatedReportingFailure();
|
|
})().then(common.mustCall());
|