diff --git a/docs/06-configuration.md b/docs/06-configuration.md index 31531fe29..c3941f41a 100644 --- a/docs/06-configuration.md +++ b/docs/06-configuration.md @@ -337,3 +337,19 @@ These may also export a function which is then invoked, and can receive argument The `nodeArguments` configuration may be used to specify additional arguments for launching worker processes. These are combined with `--node-arguments` passed on the CLI and any arguments passed to the `node` binary when starting AVA. [CLI]: ./05-command-line.md + +## Node arguments filter for worker threads + +In a config file only, `filterNodeArgumentsForWorkerThreads` may provide a function used for filtering `nodeArguments` sent to worker threads. This enables excluding arguments that throw if sent to a thread. The filter is ignored by worker processes. + +`ava.config.js`: +```js +const processOnly = new Set([ + '--allow-natives-syntax', + '--expose-gc' +]); + +export default { + filterNodeArgumentsForWorkerThreads: argument => !processOnly.has(argument) +} +``` diff --git a/lib/cli.js b/lib/cli.js index 4de8cd57d..dd0c9feb2 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -388,9 +388,15 @@ export default async function loadCli() { // eslint-disable-line complexity exit(error.message); } + const workerThreads = combined.workerThreads !== false; + let nodeArguments; try { nodeArguments = normalizeNodeArguments(conf.nodeArguments, argv['node-arguments']); + if (workerThreads && 'filterNodeArgumentsForWorkerThreads' in conf) { + const {filterNodeArgumentsForWorkerThreads: filter} = conf; + nodeArguments = nodeArguments.filter(argument => filter(argument)); + } } catch (error) { exit(error.message); } diff --git a/lib/load-config.js b/lib/load-config.js index b6526a3c2..be7b36d96 100644 --- a/lib/load-config.js +++ b/lib/load-config.js @@ -157,6 +157,13 @@ export async function loadConfig({configFile, resolveFrom = process.cwd(), defau ...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir, configFile, }; + if ( + 'filterNodeArgumentsForWorkerThreads' in config + && typeof config.filterNodeArgumentsForWorkerThreads !== 'function' + ) { + throw new Error(`filterNodeArgumentsForWorkerThreads from ${fileForErrorMessage} must be a function`); + } + const {nonSemVerExperiments: experiments} = config; if (!isPlainObject(experiments)) { throw new Error(`nonSemVerExperiments from ${fileForErrorMessage} must be an object`); diff --git a/test-tap/api.js b/test-tap/api.js index e65eb5982..6820efd2d 100644 --- a/test-tap/api.js +++ b/test-tap/api.js @@ -1,5 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; +import process from 'node:process'; import {fileURLToPath} from 'node:url'; import ciInfo from 'ci-info'; @@ -20,6 +21,7 @@ async function apiCreator(options = {}) { options.globs = normalizeGlobs({ files: options.files, ignoredByWatcher: options.watchMode?.ignoreChanges, extensions: options.extensions, providers: [], }); + options.nodeArguments = process.execArgv; const instance = new Api(options); return instance; diff --git a/test-tap/fixture/load-config/non-function/ava.config.js b/test-tap/fixture/load-config/non-function/ava.config.js new file mode 100644 index 000000000..66d67d233 --- /dev/null +++ b/test-tap/fixture/load-config/non-function/ava.config.js @@ -0,0 +1,3 @@ +export default { + filterNodeArgumentsForWorkerThreads: [], +}; diff --git a/test-tap/fixture/load-config/non-function/package.json b/test-tap/fixture/load-config/non-function/package.json new file mode 100644 index 000000000..bedb411a9 --- /dev/null +++ b/test-tap/fixture/load-config/non-function/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/config/loader.js b/test/config/loader.js index 74c43ffbc..af25c248d 100644 --- a/test/config/loader.js +++ b/test/config/loader.js @@ -117,6 +117,8 @@ test.serial('throws an error if a .js config file has no default export', notOk( test.serial('throws an error if a config file contains `ava` property', notOk('contains-ava-property')); +test.serial('throws an error if a config file contains a non-function `filterNodeArgumentsForWorkerThreads` property', notOk('non-function')); + test.serial('throws an error if a config file contains a non-object `nonSemVerExperiments` property', notOk('non-object-experiments')); test.serial('throws an error if a config file enables an unsupported experiment', notOk('unsupported-experiments')); diff --git a/test/config/snapshots/loader.js.md b/test/config/snapshots/loader.js.md index 499926429..7a3c373d3 100644 --- a/test/config/snapshots/loader.js.md +++ b/test/config/snapshots/loader.js.md @@ -40,6 +40,12 @@ Generated by [AVA](https://avajs.dev). 'Encountered ’ava’ property in ava.config.js; avoid wrapping the configuration' +## throws an error if a config file contains a non-function `filterNodeArgumentsForWorkerThreads` property + +> error message + + 'filterNodeArgumentsForWorkerThreads from ava.config.js must be a function' + ## throws an error if a config file contains a non-object `nonSemVerExperiments` property > error message @@ -57,3 +63,9 @@ Generated by [AVA](https://avajs.dev). > error message 'Conflicting configuration in ava.config.js and ava.config.cjs' + +## throws an error if a config file contains a non-function `threadArgumentsFilter` property + +> error message + + 'threadArgumentsFilter from ava.config.js must be a function' diff --git a/test/config/snapshots/loader.js.snap b/test/config/snapshots/loader.js.snap index 83344ffc9..f42c1bf10 100644 Binary files a/test/config/snapshots/loader.js.snap and b/test/config/snapshots/loader.js.snap differ diff --git a/test/node-arguments/fixtures/thread-arguments-filter/package.json b/test/node-arguments/fixtures/thread-arguments-filter/package.json new file mode 100644 index 000000000..bedb411a9 --- /dev/null +++ b/test/node-arguments/fixtures/thread-arguments-filter/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/node-arguments/fixtures/thread-arguments-filter/process.js b/test/node-arguments/fixtures/thread-arguments-filter/process.js new file mode 100644 index 000000000..b831c723a --- /dev/null +++ b/test/node-arguments/fixtures/thread-arguments-filter/process.js @@ -0,0 +1,7 @@ +import test from 'ava'; + +test('exec arguments unfiltered', t => { + t.plan(2); + t.truthy(process.execArgv.includes('--throw-deprecation')); + t.truthy(process.execArgv.includes('--allow-natives-syntax')); +}); diff --git a/test/node-arguments/fixtures/thread-arguments-filter/thread-arguments-filter.config.mjs b/test/node-arguments/fixtures/thread-arguments-filter/thread-arguments-filter.config.mjs new file mode 100644 index 000000000..f8a3cddf9 --- /dev/null +++ b/test/node-arguments/fixtures/thread-arguments-filter/thread-arguments-filter.config.mjs @@ -0,0 +1,9 @@ +const processOnly = new Set(['--allow-natives-syntax']); + +export default { + nodeArguments: [ + '--throw-deprecation', + '--allow-natives-syntax', + ], + filterNodeArgumentsForWorkerThreads: argument => !processOnly.has(argument), +}; diff --git a/test/node-arguments/fixtures/thread-arguments-filter/thread.js b/test/node-arguments/fixtures/thread-arguments-filter/thread.js new file mode 100644 index 000000000..9d65540bb --- /dev/null +++ b/test/node-arguments/fixtures/thread-arguments-filter/thread.js @@ -0,0 +1,7 @@ +import test from 'ava'; + +test('exec arguments filtered', t => { + t.plan(2); + t.truthy(process.execArgv.includes('--throw-deprecation')); + t.falsy(process.execArgv.includes('--allow-natives-syntax')); +}); diff --git a/test/node-arguments/snapshots/test.js.md b/test/node-arguments/snapshots/test.js.md index 12555650a..9fdb63532 100644 --- a/test/node-arguments/snapshots/test.js.md +++ b/test/node-arguments/snapshots/test.js.md @@ -15,6 +15,28 @@ Generated by [AVA](https://avajs.dev). }, ] +## `filterNodeArgumentsForWorkerThreads` configuration filters arguments for worker thread + +> tests pass + + [ + { + file: 'thread.js', + title: 'exec arguments filtered', + }, + ] + +## `filterNodeArgumentsForWorkerThreads` configuration ignored for worker process + +> tests pass + + [ + { + file: 'process.js', + title: 'exec arguments unfiltered', + }, + ] + ## detects incomplete --node-arguments > fails with message @@ -31,3 +53,25 @@ Generated by [AVA](https://avajs.dev). title: 'works', }, ] + +## `threadArgumentsFilter` configuration filters arguments for worker thread + +> tests pass + + [ + { + file: 'thread.js', + title: 'exec arguments filtered', + }, + ] + +## `threadArgumentsFilter` configuration ignored for worker process + +> tests pass + + [ + { + file: 'process.js', + title: 'exec arguments unfiltered', + }, + ] diff --git a/test/node-arguments/snapshots/test.js.snap b/test/node-arguments/snapshots/test.js.snap index c8e204a51..bbccccf41 100644 Binary files a/test/node-arguments/snapshots/test.js.snap and b/test/node-arguments/snapshots/test.js.snap differ diff --git a/test/node-arguments/test.js b/test/node-arguments/test.js index 7b1c3a5fe..92134a0f7 100644 --- a/test/node-arguments/test.js +++ b/test/node-arguments/test.js @@ -13,6 +13,26 @@ test('passed node arguments to workers', async t => { t.snapshot(result.stats.passed, 'tests pass'); }); +test('`filterNodeArgumentsForWorkerThreads` configuration filters arguments for worker thread', async t => { + const options = { + cwd: cwd('thread-arguments-filter'), + }; + + const result = await fixture(['--config=thread-arguments-filter.config.mjs', 'thread.js'], options); + + t.snapshot(result.stats.passed, 'tests pass'); +}); + +test('`filterNodeArgumentsForWorkerThreads` configuration ignored for worker process', async t => { + const options = { + cwd: cwd('thread-arguments-filter'), + }; + + const result = await fixture(['--config=thread-arguments-filter.config.mjs', '--no-worker-threads', 'process.js'], options); + + t.snapshot(result.stats.passed, 'tests pass'); +}); + test('detects incomplete --node-arguments', async t => { const options = { cwd: cwd('node-arguments'),