Files
node/test/parallel/test-bench-cli.js
Marco Ippolito 3c577146aa src: mark config file as stable
Signed-off-by: Marco Ippolito <marcoippolito54@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66431
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com>
2026-10-05 16:47:38 +00:00

439 lines
13 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('assert');
const { spawnSync } = require('child_process');
const fs = require('fs');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const basicPattern = fixtures.path('bench-runner/[ab].*');
const spawnTimeout = common.platformTimeout(30_000);
tmpdir.refresh();
function spawnNode(args, options = undefined) {
const result = spawnSync(process.execPath, args, {
__proto__: null,
encoding: 'utf8',
timeout: spawnTimeout,
...options,
});
assert.ifError(result.error);
assert.strictEqual(result.signal, null);
return result;
}
function spawnBench(args, options = undefined) {
return spawnNode([
'--no-warnings', '--experimental-bench', '--bench', ...args,
], options);
}
function parseRecords(result) {
assert.strictEqual(result.stderr, '');
return parseOutput(result.stdout);
}
function parseOutput(output) {
return output.trim().split('\n').map((line) => JSON.parse(line));
}
for (const option of [
'--bench',
'--bench-isolation=process',
'--bench-name-pattern=benchmark',
'--bench-reporter=spec',
'--bench-reporter-destination=stdout',
'--bench-samples=1',
'--bench-warmup=0',
]) {
const result = spawnNode([option]);
assert.notStrictEqual(result.status, 0);
assert.match(result.stderr, /--experimental-bench is required/);
}
{
const result = spawnBench([]);
assert.strictEqual(result.status, 1);
assert.strictEqual(result.stdout, '');
assert.match(result.stderr, /--bench requires at least one file or glob/);
}
{
const result = spawnBench(['does-not-exist-*.js']);
assert.strictEqual(result.status, 1);
assert.strictEqual(result.stdout, '');
assert.match(result.stderr, /^Could not find/);
}
if (common.canCreateSymLink()) {
const dangling = tmpdir.resolve('dangling.cjs');
fs.symlinkSync(tmpdir.resolve('missing.cjs'), dangling);
const result = spawnBench([dangling]);
assert.strictEqual(result.status, 1);
assert.strictEqual(result.stdout, '');
assert.match(result.stderr, /^Could not find/);
}
for (const { patterns, message } of [
{
patterns: [
fixtures.path('bench-runner/a.cjs'),
fixtures.path('bench-runner/b.mjs'),
],
message: /benchmark child process requires exactly one file/,
},
{
patterns: [fixtures.path('bench-runner/missing.cjs')],
message: /^Could not find/,
},
]) {
const result = spawnNode([
'--no-warnings',
'--experimental-bench',
'--require', fixtures.path('bench-runner/fake-ipc.cjs'),
'--bench',
...patterns,
], {
__proto__: null,
env: { __proto__: null, ...process.env, NODE_BENCH_CONTEXT: 'child' },
});
assert.strictEqual(result.status, 1);
assert.strictEqual(result.stdout, '');
assert.match(result.stderr, message);
}
{
const result = spawnBench(['--bench-reporter=json', basicPattern]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const plans = records.filter(
({ type }) => type === 'bench:plan').map(({ data }) => data);
const completions = records.filter(
({ type }) => type === 'bench:complete');
assert.deepStrictEqual(plans.map(({ name }) => name), ['alpha', 'beta']);
assert.deepStrictEqual(completions.map(({ data }) => data.name), [
'alpha',
'beta',
]);
assert.notStrictEqual(
completions[0].data.params.pid,
completions[1].data.params.pid,
);
assert.deepStrictEqual(completions.map(({ data }) => data.params.file), [
'a',
'b',
]);
assert.match(completions[0].data.samples[0].duration_ns, /^\d+$/);
assert(Number.isFinite(completions[0].data.summary.mean));
const summaries = records.filter(
({ type }) => type === 'bench:summary');
assert.strictEqual(summaries.length, 1);
assert.strictEqual(summaries[0].data.file, null);
assert.deepStrictEqual(summaries[0].data.counts, {
completed: 2,
failed: 0,
skipped: 0,
total: 2,
});
}
for (const isolation of ['process', 'none']) {
const result = spawnBench([
`--bench-isolation=${isolation}`,
'--bench-reporter=json',
fixtures.path('bench-runner/identity-entry-*.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const plans = records.filter(
({ type }) => type === 'bench:plan').map(({ data }) => data);
const completions = records.filter(
({ type }) => type === 'bench:complete').map(({ data }) => data);
const summary = records.at(-1).data;
assert.strictEqual(plans.length, 2);
for (const plan of plans) {
const planIndex = records.findIndex(({ type, data }) =>
type === 'bench:plan' && data.fileRunId === plan.fileRunId);
const startIndex = records.findIndex(({ type, data }) =>
type === 'bench:start' && data.fileRunId === plan.fileRunId);
assert(planIndex < startIndex);
}
assert.strictEqual(completions.length, 2);
assert.strictEqual(completions[0].benchId, completions[1].benchId);
assert.strictEqual(completions[0].runId, completions[1].runId);
assert.strictEqual(completions[0].runId, summary.runId);
assert.notStrictEqual(
completions[0].fileRunId, completions[1].fileRunId);
assert.deepStrictEqual(completions.map(({ entryFile }) => entryFile), [
fixtures.path('bench-runner/identity-entry-a.cjs'),
fixtures.path('bench-runner/identity-entry-b.cjs'),
]);
assert.deepStrictEqual(completions.map(({ namePath }) => namePath), [
['shared suite', 'shared identity'],
['shared suite', 'shared identity'],
]);
assert.strictEqual(summary.fileRunId, null);
assert.strictEqual(summary.entryFile, null);
}
{
const result = spawnBench([
'--bench-reporter=json',
fixtures.path('bench-runner/identity-suite.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const completions = records.filter(
({ type }) => type === 'bench:complete').map(({ data }) => data);
const parentId = JSON.stringify([
fixtures.path('bench-runner/identity-suite.cjs'),
['cross-module suite'],
]);
assert.deepStrictEqual(completions.map(({ name }) => name), [
'child a',
'child b',
]);
assert(completions.every((completion) =>
completion.parentId === parentId));
assert.deepStrictEqual(completions.map(({ namePath }) => namePath), [
['cross-module suite', 'child a'],
['cross-module suite', 'child b'],
]);
assert(completions.every(({ entryFile }) =>
entryFile === fixtures.path('bench-runner/identity-suite.cjs')));
}
for (const isolation of ['process', 'none']) {
const result = spawnBench([
'--require', fixtures.path('bench-runner/identity-preload.cjs'),
`--bench-isolation=${isolation}`,
'--bench-reporter=json',
fixtures.path('bench-runner/identity-entry-*.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const preloads = records.filter(
({ type, data }) => type === 'bench:complete' &&
data.name === 'preload identity').map(({ data }) => data);
assert.strictEqual(preloads.length, isolation === 'process' ? 2 : 1);
assert.strictEqual(
new Set(preloads.map(({ fileRunId }) => fileRunId)).size,
preloads.length,
);
assert(preloads.every(({ entryFile }) => entryFile === null));
assert(preloads.every(
({ runId }) => runId === records.at(-1).data.runId));
}
{
const result = spawnBench([
'--bench-isolation=none',
'--bench-reporter=json',
fixtures.path('bench-runner/a.cjs'),
fixtures.path('bench-runner/identity-hook.cjs'),
]);
assert.strictEqual(result.status, 1);
const records = parseRecords(result);
const diagnostic = records.find(
({ type, data }) => type === 'bench:diagnostic' &&
data.message === 'scoped hook failed').data;
const completion = records.find(
({ type, data }) => type === 'bench:complete' &&
data.name === 'scoped hook benchmark').data;
assert.strictEqual(diagnostic.entryFile,
fixtures.path('bench-runner/identity-hook.cjs'));
assert.strictEqual(diagnostic.fileRunId, completion.fileRunId);
}
{
const result = spawnBench([
'--bench-reporter=json',
'--bench-isolation=none',
basicPattern,
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const completions = records.filter(
({ type }) => type === 'bench:complete');
assert.strictEqual(completions.length, 2);
assert.strictEqual(
completions[0].data.params.pid,
completions[1].data.params.pid,
);
assert.strictEqual(records.at(-1).data.file, null);
}
{
const result = spawnBench([
'--bench-reporter=json',
'--bench-name-pattern=selected',
'--bench-samples=2',
'--bench-warmup=3',
fixtures.path('bench-runner/options.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const plans = records.filter(
({ type }) => type === 'bench:plan').map(({ data }) => data);
const samples = records.filter(({ type }) => type === 'bench:sample');
assert.deepStrictEqual(samples.map(({ data }) => data.operations), [4, 5]);
assert.deepStrictEqual(plans.map((plan) => ({
name: plan.name,
samples: plan.samples,
selected: plan.selected,
skip: plan.skip,
timeout: plan.timeout,
warmup: plan.warmup,
yieldBetweenSamples: plan.yieldBetweenSamples,
})), [
{
name: 'selected',
samples: 2,
selected: true,
skip: undefined,
timeout: null,
warmup: 3,
yieldBetweenSamples: true,
},
{
name: 'filtered out',
samples: 2,
selected: false,
skip: 'name pattern',
timeout: null,
warmup: 3,
yieldBetweenSamples: true,
},
]);
const completions = records.filter(
({ type }) => type === 'bench:complete');
const selected = completions.find(({ data }) => data.name === 'selected').data;
assert.deepStrictEqual(selected.params, {
boolean: true,
number: 42,
string: 'value',
});
assert.strictEqual(selected.samples.length, 2);
assert.strictEqual(
selected.summary.mean,
(selected.samples[0].rate + selected.samples[1].rate) / 2,
);
assert(Number.isFinite(selected.summary.medianConfidenceInterval.lower));
const filtered = completions.find(
({ data }) => data.name === 'filtered out').data;
assert.strictEqual(filtered.skip, 'name pattern');
}
{
const result = spawnBench([
'--require',
fixtures.path('bench-runner/preload.cjs'),
'--bench-reporter=json',
fixtures.path('bench-runner/preloaded.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const completion = records.find(
({ type }) => type === 'bench:complete');
assert.deepStrictEqual(completion.data.params, { preloaded: true });
}
for (const isolation of ['process', 'none']) {
const result = spawnBench([
`--bench-isolation=${isolation}`,
'--bench-reporter=json',
'--import', fixtures.fileURL('bench-runner/import.mjs'),
fixtures.path('bench-runner/imported.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const completion = records.find(
({ type }) => type === 'bench:complete');
assert.deepStrictEqual(completion.data.params, { imported: 'loaded' });
}
{
const lock = tmpdir.resolve('serial.lock');
const result = spawnBench([
'--bench-reporter=json',
fixtures.path('bench-runner/serial-[ab].cjs'),
], {
env: { __proto__: null, ...process.env, NODE_BENCH_LOCK: lock },
});
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
assert.deepStrictEqual(records.filter(
({ type }) => type === 'bench:complete').map(({ data }) => data.name), [
'serial a',
'serial b',
]);
assert.strictEqual(fs.existsSync(lock), false);
}
{
const result = spawnBench([
'--bench-reporter=json',
fixtures.path('bench-runner/ipc.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
assert.strictEqual(records.find(
({ type }) => type === 'bench:complete').data.name, 'user IPC');
assert.strictEqual(records.at(-1).data.file,
fixtures.path('bench-runner/ipc.cjs'));
}
{
const result = spawnBench([
'--bench-reporter=json',
'--random-seed=17',
'--stack-trace-limit=17',
fixtures.path('bench-runner/v8-option.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
assert.strictEqual(records.find(
({ type }) => type === 'bench:complete').data.name, 'V8 option');
}
if (common.hasInspector) {
const result = spawnBench([
'--inspect=0',
'--bench-reporter=json',
fixtures.path('bench-runner/inspector.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
const diagnostics = records.filter(
({ type }) => type === 'bench:diagnostic')
.map(({ data }) => data.message).join('');
assert.match(diagnostics, /Debugger listening on ws:\/\//);
const completion = records.find(
({ type }) => type === 'bench:complete').data;
assert.strictEqual(completion.params.inspectPort, '--inspect-port=0');
}
{
// --experimental-bench is passed on the command line rather than via the
// config file's "nodeOptions" section because builds configured with
// --without-node-options ignore that section entirely.
const result = spawnNode([
'--no-warnings',
'--experimental-bench',
`--config-file=${fixtures.path('bench-runner/node.config.json')}`,
fixtures.path('bench-runner/a.cjs'),
]);
assert.strictEqual(result.status, 0);
const records = parseRecords(result);
assert.strictEqual(records.find(
({ type }) => type === 'bench:complete').data.samples.length, 1);
}