diff --git a/doc/api/test.md b/doc/api/test.md index 8152e90101bd3c..d3faab258d17a6 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -1299,6 +1299,12 @@ changes: * `setup` {Function} A function that accepts the `TestsStream` instance and can be used to setup listeners before any tests are run. **Default:** `undefined`. + * `execArgv` {Array} An array of CLI flags to pass to the `node` executable when + spawning the subprocesses. This option has no effect when `isolation` is `'none`'. + **Default:** `[]` + * `argv` {Array} An array of CLI flags to pass to each test file when spawning the + subprocesses. This option has no effect when `isolation` is `'none'`. + **Default:** `[]`. * `signal` {AbortSignal} Allows aborting an in-progress test execution. * `testNamePatterns` {string|RegExp|Array} A String, RegExp or a RegExp Array, that can be used to only run tests whose name matches the provided pattern. diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 4a19e13d9a029c..09c2aad6f054c9 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -130,7 +130,13 @@ function filterExecArgv(arg, i, arr) { !ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`)); } -function getRunArgs(path, { forceExit, inspectPort, testNamePatterns, testSkipPatterns, only }) { +function getRunArgs(path, { forceExit, + inspectPort, + testNamePatterns, + testSkipPatterns, + only, + argv: suppliedArgs, + execArgv }) { const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv); if (forceExit === true) { ArrayPrototypePush(argv, '--test-force-exit'); @@ -148,12 +154,20 @@ function getRunArgs(path, { forceExit, inspectPort, testNamePatterns, testSkipPa ArrayPrototypePush(argv, '--test-only'); } + for (let i = 0; i < execArgv.length; i++) { + ArrayPrototypePush(argv, execArgv[i]); + } + if (path === kIsolatedProcessName) { ArrayPrototypePush(argv, '--test', ...ArrayPrototypeSlice(process.argv, 1)); } else { ArrayPrototypePush(argv, path); } + for (let i = 0; i < suppliedArgs.length; i++) { + ArrayPrototypePush(argv, suppliedArgs[i]); + } + return argv; } @@ -548,6 +562,8 @@ function run(options = kEmptyObject) { lineCoverage = 0, branchCoverage = 0, functionCoverage = 0, + execArgv = [], + argv = [], } = options; if (files != null) { @@ -643,6 +659,9 @@ function run(options = kEmptyObject) { validateInteger(branchCoverage, 'options.branchCoverage', 0, 100); validateInteger(functionCoverage, 'options.functionCoverage', 0, 100); + validateStringArray(argv, 'options.argv'); + validateStringArray(execArgv, 'options.execArgv'); + const rootTestOptions = { __proto__: null, concurrency, timeout, signal }; const globalOptions = { __proto__: null, @@ -685,6 +704,8 @@ function run(options = kEmptyObject) { forceExit, cwd, isolation, + argv, + execArgv, }; if (isolation === 'process') { diff --git a/test/fixtures/test-runner/print-arguments.js b/test/fixtures/test-runner/print-arguments.js new file mode 100644 index 00000000000000..106f6a9addc0e2 --- /dev/null +++ b/test/fixtures/test-runner/print-arguments.js @@ -0,0 +1,5 @@ +const { test } = require('node:test'); + +test('process.argv is setup', (t) => { + t.assert.deepStrictEqual(process.argv.slice(2), ['--a-custom-argument']); +}); \ No newline at end of file diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index c423465ab849ae..b74ee7d35b3e63 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -33,6 +33,20 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { for await (const _ of stream); }); + const argPrintingFile = join(testFixtures, 'print-arguments.js'); + it('should allow custom arguments via execArgv', async () => { + const result = await run({ files: [argPrintingFile], execArgv: ['-p', '"Printed"'] }).compose(spec).toArray(); + assert.strictEqual(result[0].toString(), 'Printed\n'); + }); + + it('should allow custom arguments via argv', async () => { + const stream = run({ files: [argPrintingFile], argv: ['--a-custom-argument'] }); + stream.on('test:fail', common.mustNotCall()); + stream.on('test:pass', common.mustCall(1)); + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + }); + it('should run same file twice', async () => { const stream = run({ files: [