mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: https://github.com/nodejs/node/pull/65920 Reviewed-By: Jordan Harband <ljharb@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Xuguang Mei <meixuguang@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
434 lines
15 KiB
JavaScript
434 lines
15 KiB
JavaScript
// Flags: --experimental-bench --no-warnings
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
const fixtures = require('../common/fixtures');
|
|
const assert = require('assert');
|
|
const { spawnSync } = require('child_process');
|
|
const { once } = require('events');
|
|
const path = require('path');
|
|
const { runFile } = require('node:bench');
|
|
|
|
const fixture = fixtures.path('bench-runner/run-file.cjs');
|
|
const relativeFixture = path.relative(process.cwd(), fixture);
|
|
|
|
assert.throws(() => runFile(null), { code: 'ERR_INVALID_ARG_TYPE' });
|
|
assert.throws(() => runFile(fixture, null), { code: 'ERR_INVALID_ARG_TYPE' });
|
|
assert.throws(() => runFile(fixture, { execArgv: null }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: [1] }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['--bench'] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: [fixture] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['-e', '0'] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['-e0'] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['--require'] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['--require', ''] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['--require='] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: [`-r=${fixture}`] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['--prof-process'] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { execArgv: ['--prof_process'] }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, {
|
|
execArgv: ['--bench_name_pattern=run'],
|
|
}), { code: 'ERR_INVALID_ARG_VALUE' });
|
|
assert.throws(() => runFile(`${fixture}\0`), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(Buffer.from(`${fixture}\0`)), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { env: null }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { env: { INVALID: 1 } }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { env: { INVALID: 'x\0' } }), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { env: { NODE_CHANNEL_FD: 1 } }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { signal: {} }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => runFile(fixture, { signal: { aborted: false } }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
|
|
async function testRunFile() {
|
|
const execArgv = ['--expose-gc', '--no-warnings', '-r', 'fs'];
|
|
const env = {
|
|
__proto__: null,
|
|
...process.env,
|
|
NODE_BENCH_CONTEXT: 'not-a-child',
|
|
NODE_BENCH_OMITTED: undefined,
|
|
NODE_BENCH_RUN_FILE: 'original',
|
|
NODE_CHANNEL_FD: '999',
|
|
NODE_CHANNEL_SERIALIZATION_MODE: 'json',
|
|
};
|
|
const stream = runFile(relativeFixture, { env, execArgv });
|
|
execArgv.length = 0;
|
|
env.NODE_BENCH_RUN_FILE = 'mutated';
|
|
const records = await stream.toArray();
|
|
const plan = records.find(({ type }) => type === 'bench:plan').data;
|
|
const result = records.find(({ type }) => type === 'bench:complete').data;
|
|
const summary = records.at(-1).data;
|
|
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(plan.params.context, 'child');
|
|
assert.strictEqual(plan.params.exposed, true);
|
|
assert.strictEqual(plan.params.omitted, true);
|
|
assert.strictEqual(plan.params.value, 'original');
|
|
assert.strictEqual(result.error, undefined);
|
|
assert.strictEqual(result.samples.length, 1);
|
|
assert.strictEqual(typeof result.samples[0].duration_ns, 'bigint');
|
|
assert.notStrictEqual(result.samples[0].detail.pid, process.pid);
|
|
assert(result.samples[0].detail.execArgv.includes('--expose-gc'));
|
|
assert(result.samples[0].detail.execArgv.includes('--no-warnings'));
|
|
assert(result.samples[0].detail.execArgv.includes('-r'));
|
|
assert.strictEqual(summary.success, true);
|
|
assert.strictEqual(summary.file, fixture);
|
|
assert.strictEqual(summary.entryFile, fixture);
|
|
assert.strictEqual(summary.fileRunId, result.fileRunId);
|
|
assert.strictEqual(summary.runId, result.runId);
|
|
assert.strictEqual(records.filter(
|
|
({ type }) => type === 'bench:summary').length, 1);
|
|
}
|
|
|
|
async function testConcurrentCalls() {
|
|
const [cjsRecords, esmRecords] = await Promise.all([
|
|
runFile(fixtures.fileURL('bench-runner/a.cjs')).toArray(),
|
|
runFile(Buffer.from(path.relative(
|
|
process.cwd(),
|
|
fixtures.path('bench-runner/b.mjs'),
|
|
))).toArray(),
|
|
]);
|
|
const cjsResult = cjsRecords.find(
|
|
({ type }) => type === 'bench:complete').data;
|
|
const esmResult = esmRecords.find(
|
|
({ type }) => type === 'bench:complete').data;
|
|
assert.strictEqual(cjsResult.name, 'alpha');
|
|
assert.strictEqual(esmResult.name, 'beta');
|
|
assert.notStrictEqual(cjsResult.params.pid, esmResult.params.pid);
|
|
assert.notStrictEqual(cjsResult.runId, esmResult.runId);
|
|
assert.strictEqual(
|
|
cjsRecords.at(-1).data.file,
|
|
fixtures.path('bench-runner/a.cjs'),
|
|
);
|
|
assert.strictEqual(
|
|
esmRecords.at(-1).data.file,
|
|
fixtures.path('bench-runner/b.mjs'),
|
|
);
|
|
}
|
|
|
|
async function testLoadFailure() {
|
|
const missing = path.resolve(fixtures.fixturesDir, 'does-not-exist.cjs');
|
|
const records = await runFile(missing).toArray();
|
|
const diagnostics = records.filter(
|
|
({ type }) => type === 'bench:diagnostic');
|
|
assert(diagnostics.some(
|
|
({ data }) => data.level === 'error' && /failed with exit code/.test(
|
|
data.message)));
|
|
assert.strictEqual(records.some(
|
|
({ type }) => type === 'bench:complete'), false);
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testEvaluationFailure() {
|
|
const records = await runFile(fixtures.path(
|
|
'bench-runner/load-error-after-declaration.cjs')).toArray();
|
|
assert(records.some(({ type, data }) =>
|
|
type === 'bench:diagnostic' &&
|
|
data.message === 'load failed after declaration'));
|
|
const result = records.find(
|
|
({ type }) => type === 'bench:complete').data;
|
|
assert.strictEqual(result.name, 'declared before load error');
|
|
assert.strictEqual(result.error, undefined);
|
|
assert.strictEqual(records.at(-1).data.counts.completed, 1);
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testStructuredError() {
|
|
const records = await runFile(
|
|
fixtures.path('bench-runner/error.cjs')).toArray();
|
|
const result = records.find(
|
|
({ type }) => type === 'bench:complete').data;
|
|
assert.strictEqual(result.error.code, 'ERR_BENCHMARK_FIXTURE');
|
|
assert.deepStrictEqual(result.error.cause, { value: 42n });
|
|
assert.match(result.error.stack, /error\.cjs/);
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testAbort() {
|
|
const controller = new AbortController();
|
|
const reason = new Error('cancel run file');
|
|
const stream = runFile(
|
|
fixtures.path('bench-runner/run-file-blocked.cjs'),
|
|
{ signal: controller.signal });
|
|
stream.once('bench:start', common.mustCall(() => controller.abort(reason)));
|
|
const records = await stream.toArray();
|
|
const diagnostic = records.find(({ type, data }) =>
|
|
type === 'bench:diagnostic' && data.error?.code === 'ABORT_ERR').data;
|
|
assert.strictEqual(diagnostic.error.cause.message, reason.message);
|
|
assert.strictEqual(records.some(
|
|
({ type }) => type === 'bench:complete'), false);
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
assert.deepStrictEqual(records.at(-1).data.counts, {
|
|
__proto__: null,
|
|
completed: 0,
|
|
failed: 0,
|
|
skipped: 0,
|
|
total: 1,
|
|
});
|
|
}
|
|
|
|
async function testPartialAbort() {
|
|
const controller = new AbortController();
|
|
const stream = runFile(
|
|
fixtures.path('bench-runner/run-file-partial.cjs'),
|
|
{ signal: controller.signal });
|
|
stream.on('bench:start', ({ name }) => {
|
|
if (name === 'aborted run file') controller.abort();
|
|
});
|
|
const records = await stream.toArray();
|
|
const summary = records.at(-1).data;
|
|
assert.strictEqual(records.filter(
|
|
({ type }) => type === 'bench:complete').length, 1);
|
|
assert.deepStrictEqual(summary.counts, {
|
|
__proto__: null,
|
|
completed: 1,
|
|
failed: 0,
|
|
skipped: 0,
|
|
total: 2,
|
|
});
|
|
assert.strictEqual(summary.success, false);
|
|
}
|
|
|
|
async function testPostSummaryAbort() {
|
|
const controller = new AbortController();
|
|
const stream = runFile(
|
|
fixtures.path('bench-runner/run-file-lingering.cjs'),
|
|
{ signal: controller.signal });
|
|
stream.on('bench:diagnostic', common.mustCall(({ message, stream }) => {
|
|
if (stream === 'stdout' && /child disconnected/.test(message)) {
|
|
controller.abort(new Error('stop lingering child'));
|
|
}
|
|
}, 2));
|
|
const records = await stream.toArray();
|
|
assert(records.some(({ type, data }) =>
|
|
type === 'bench:diagnostic' && data.error?.code === 'ABORT_ERR'));
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(records.at(-1).data.counts.completed, 1);
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testExitCode() {
|
|
const records = await runFile(
|
|
fixtures.path('bench-runner/exit-code.cjs')).toArray();
|
|
assert(records.some(({ type, data }) =>
|
|
type === 'bench:diagnostic' &&
|
|
/set exit code/.test(data.message)));
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testParentExitCode() {
|
|
process.exitCode = 42;
|
|
try {
|
|
const records = await runFile(fixture).toArray();
|
|
assert.strictEqual(records.at(-1).data.success, true);
|
|
} finally {
|
|
process.exitCode = undefined;
|
|
}
|
|
}
|
|
|
|
async function testDefaultExecArgvSnapshot() {
|
|
const original = Array.from(process.execArgv);
|
|
try {
|
|
process.execArgv.push('--bench', '--eval', 'throw new Error()');
|
|
const stream = runFile(fixture);
|
|
process.execArgv.push('--require=/does/not/exist.cjs');
|
|
const records = await stream.toArray();
|
|
assert.strictEqual(records.at(-1).data.success, true);
|
|
} finally {
|
|
process.execArgv.length = 0;
|
|
process.execArgv.push(...original);
|
|
}
|
|
}
|
|
|
|
async function testExecPathSnapshot() {
|
|
const stream = runFile(fixture);
|
|
const execPath = process.execPath;
|
|
process.execPath = '/does/not/exist';
|
|
try {
|
|
const records = await stream.toArray();
|
|
assert.strictEqual(records.at(-1).data.success, true);
|
|
} finally {
|
|
process.execPath = execPath;
|
|
}
|
|
}
|
|
|
|
async function testSpawnFailure() {
|
|
const execPath = process.execPath;
|
|
let stream;
|
|
try {
|
|
process.execPath = fixtures.path('does-not-exist-node');
|
|
stream = runFile(fixture);
|
|
} finally {
|
|
process.execPath = execPath;
|
|
}
|
|
const records = await stream.toArray();
|
|
const diagnostic = records.find(
|
|
({ type }) => type === 'bench:diagnostic').data;
|
|
assert.strictEqual(diagnostic.error.code, 'ENOENT');
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
function testEvalParent() {
|
|
const script = `
|
|
require('node:bench').runFile(${JSON.stringify(fixture)})
|
|
.on('bench:summary', (summary) => console.log(summary.success));
|
|
`;
|
|
const result = spawnSync(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-bench',
|
|
'-e',
|
|
script,
|
|
], { encoding: 'utf8' });
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
assert.strictEqual(result.stdout, 'true\n');
|
|
}
|
|
|
|
function testPermissions() {
|
|
const fsReadScript = `
|
|
const assert = require('assert');
|
|
const { runFile } = require('node:bench');
|
|
const { pathToFileURL } = require('url');
|
|
const target = ${JSON.stringify(fixture)};
|
|
assert.strictEqual(process.permission.has('child'), true);
|
|
assert.strictEqual(process.permission.has('fs.read', target), false);
|
|
Promise.all([
|
|
target,
|
|
Buffer.from(target),
|
|
pathToFileURL(target),
|
|
].map(async (input) => {
|
|
const records = await runFile(input).toArray();
|
|
const diagnostic = records.find(({ type, data }) =>
|
|
type === 'bench:diagnostic' &&
|
|
data.error?.code === 'ERR_ACCESS_DENIED');
|
|
assert.strictEqual(diagnostic.data.error.permission, 'FileSystemRead');
|
|
assert.strictEqual(diagnostic.data.error.resource, target);
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
})).catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|
|
`;
|
|
const result = spawnSync(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-bench',
|
|
'--permission',
|
|
'--allow-child-process',
|
|
'-e',
|
|
fsReadScript,
|
|
], { encoding: 'utf8' });
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
|
|
const childProcessScript = `
|
|
const assert = require('assert');
|
|
const { runFile } = require('node:bench');
|
|
const target = ${JSON.stringify(fixture)};
|
|
assert.strictEqual(process.permission.has('fs.read', target), true);
|
|
assert.strictEqual(process.permission.has('child'), false);
|
|
runFile(target).toArray().then((records) => {
|
|
const diagnostic = records.find(({ type, data }) =>
|
|
type === 'bench:diagnostic' &&
|
|
data.error?.code === 'ERR_ACCESS_DENIED');
|
|
assert.strictEqual(diagnostic.data.error.permission, 'ChildProcess');
|
|
assert.strictEqual(diagnostic.data.error.resource, process.execPath);
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|
|
`;
|
|
const childProcessResult = spawnSync(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-bench',
|
|
'--permission',
|
|
`--allow-fs-read=${fixture}`,
|
|
'-e',
|
|
childProcessScript,
|
|
], { encoding: 'utf8' });
|
|
assert.strictEqual(
|
|
childProcessResult.status,
|
|
0,
|
|
childProcessResult.stderr,
|
|
);
|
|
}
|
|
|
|
async function testPreAborted() {
|
|
const records = await runFile(fixture, {
|
|
signal: AbortSignal.abort(new Error('already cancelled')),
|
|
}).toArray();
|
|
assert.strictEqual(records.some(({ type }) => type === 'bench:start'), false);
|
|
assert.strictEqual(records.find(({ type }) =>
|
|
type === 'bench:diagnostic').data.error.code, 'ABORT_ERR');
|
|
assert.strictEqual(records.at(-1).type, 'bench:summary');
|
|
assert.strictEqual(records.at(-1).data.success, false);
|
|
}
|
|
|
|
async function testDestroy() {
|
|
const stream = runFile(
|
|
fixtures.path('bench-runner/run-file-blocked.cjs'));
|
|
stream.once('bench:start', common.mustCall(() => stream.destroy()));
|
|
await once(stream, 'close');
|
|
assert.strictEqual(stream.destroyed, true);
|
|
}
|
|
|
|
(async () => {
|
|
await testRunFile();
|
|
await testConcurrentCalls();
|
|
await testLoadFailure();
|
|
await testEvaluationFailure();
|
|
await testStructuredError();
|
|
await testAbort();
|
|
await testPartialAbort();
|
|
await testPostSummaryAbort();
|
|
await testExitCode();
|
|
await testParentExitCode();
|
|
await testDefaultExecArgvSnapshot();
|
|
await testExecPathSnapshot();
|
|
await testSpawnFailure();
|
|
await testPreAborted();
|
|
await testDestroy();
|
|
testEvalParent();
|
|
testPermissions();
|
|
})().then(common.mustCall());
|