UNPKG

perf-insight

Version:

Performance benchmarking tool for NodeJS.

118 lines 4.94 kB
import { fork } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import chalk from 'chalk'; import { Argument, program as defaultCommand } from 'commander'; import { findFiles } from './findFiles.mjs'; const urlRunnerCli = new URL('./runBenchmarkCli.mjs', import.meta.url).toString(); const pathToRunnerCliModule = fileURLToPath(urlRunnerCli); export async function app(program = defaultCommand) { const argument = new Argument('[filter...]', 'Perf file filter.'); argument.variadic = true; program .name('perf-insight') .addArgument(argument) .description('Benchmark performance suites found in `**/*.perf.{js,cjs,mjs}`.') .option('-a, --all', 'Run all perf files.', false) .option('-f, --file <glob...>', 'Globs to search for perf files.', appendValue) .option('-x, --exclude <glob...>', 'Globs to exclude from search.', appendValue) .option('-t, --timeout <timeout>', 'Override the timeout for each test suite.', (v) => Number(v)) .option('-s, --suite <suite...>', 'Run only matching suites.', appendValue) .option('-T, --test <test...>', 'Run only matching test found in suites', appendValue) .option('--fail-fast', 'Stop on first failure.', false) .option('--repeat <count>', 'Repeat the tests.', (v) => Number(v), 1) .option('--register <loader>', 'Register a module loader. (e.g. ts-node/esm)', appendValue) .action(async (suiteNamesToRun, options, command) => { if (!suiteNamesToRun.length && !(options.all || options.file?.length)) { console.error(chalk.red('No tests to run.')); console.error(chalk.yellow(`Use ${chalk.green('--all')} to run all tests.\n`)); command.help(); } // console.log('%o', options); const fileGlobs = options.file?.length ? options.file : ['**/*.perf.{js,mjs,cjs}']; const excludes = options.exclude?.length ? options.exclude : []; const found = await findFiles([...fileGlobs, '!**/node_modules/**'], { excludes }); if (!found.length) { console.error(chalk.red('No perf files found.')); return; } const files = found.filter((file) => !suiteNamesToRun.length || suiteNamesToRun.some((name) => file.toLowerCase().includes(name.toLowerCase()))); await spawnRunners(files, options); return process.exitCode ? console.log(chalk.red('failed.')) : console.log(chalk.green('done.')); }); program.showHelpAfterError(); return program; } const defaultAbortTimeout = 1000 * 60 * 5; // 5 minutes async function spawnRunners(files, options) { const cliOptions = []; if (options.repeat) { cliOptions.push('--repeat', options.repeat.toString()); } if (options.timeout) { cliOptions.push('--timeout', options.timeout.toString()); } if (options.suite?.length) { cliOptions.push(...options.suite.flatMap((s) => ['--suite', s])); } if (options.test?.length) { cliOptions.push(...options.test.flatMap((t) => ['--test', t])); } if (options.register?.length) { cliOptions.push(...options.register.flatMap((r) => ['--register', r])); } for (const file of files) { try { const code = await spawnRunner([file, ...cliOptions]); if (code) { // console.error('Runner failed with "%s" code: %d', file, code); process.exitCode ??= code; if (options.failFast) { break; } } } catch (e) { console.error('Failed to spawn runner.', e); process.exitCode ??= 1; } } } function spawnRunner(args) { const ac = new AbortController(); const timeout = setTimeout(() => ac.abort(), defaultAbortTimeout); const process = fork(pathToRunnerCliModule, args, { stdio: 'inherit', signal: ac.signal }); return new Promise((resolve, reject) => { let completed = false; let error = undefined; let exitCode = undefined; function complete() { if (completed) return; clearTimeout(timeout); completed = true; if (process.connected) process.disconnect(); return error ? reject(error) : resolve(exitCode); } process.on('error', (err) => { error = err; console.error('Runner error: %o', err); complete(); }); process.on('exit', (code, _signal) => { exitCode = code ?? undefined; complete(); }); }); } export async function run(argv, program) { const prog = await app(program); await prog.parseAsync(argv); } function appendValue(v, prev) { if (!prev) return [v]; return [...prev, v]; } //# sourceMappingURL=app.mjs.map