From 86185b4ef7677cba26ef20a0b57e29fc28db0a24 Mon Sep 17 00:00:00 2001 From: bojavou <134245276+bojavou@users.noreply.github.com> Date: Tue, 20 Aug 2024 07:55:43 -0600 Subject: [PATCH] Add `filterNodeArgumentsForWorkerThreads` option This filter function can be used to remove node arguments that may be applied to the main process, but cannot be used to start worker threads. It can only be configured in a JavaScript configuration file. It does not apply when using child processes. Co-authored-by: Tommy Co-authored-by: Mark Wubben --- docs/06-configuration.md | 16 +++++++ lib/cli.js | 6 +++ lib/load-config.js | 7 +++ test-tap/api.js | 2 + .../load-config/non-function/ava.config.js | 3 ++ .../load-config/non-function/package.json | 3 ++ test/config/loader.js | 2 + test/config/snapshots/loader.js.md | 12 +++++ test/config/snapshots/loader.js.snap | Bin 594 -> 660 bytes .../thread-arguments-filter/package.json | 3 ++ .../thread-arguments-filter/process.js | 7 +++ .../thread-arguments-filter.config.mjs | 9 ++++ .../thread-arguments-filter/thread.js | 7 +++ test/node-arguments/snapshots/test.js.md | 44 ++++++++++++++++++ test/node-arguments/snapshots/test.js.snap | Bin 440 -> 583 bytes test/node-arguments/test.js | 20 ++++++++ 16 files changed, 141 insertions(+) create mode 100644 test-tap/fixture/load-config/non-function/ava.config.js create mode 100644 test-tap/fixture/load-config/non-function/package.json create mode 100644 test/node-arguments/fixtures/thread-arguments-filter/package.json create mode 100644 test/node-arguments/fixtures/thread-arguments-filter/process.js create mode 100644 test/node-arguments/fixtures/thread-arguments-filter/thread-arguments-filter.config.mjs create mode 100644 test/node-arguments/fixtures/thread-arguments-filter/thread.js 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 83344ffc9c7ecd858f0f0c67af9dad1e56a16a00..f42c1bf106bb3d426148c89bf67ebcde225b7a99 100644 GIT binary patch literal 660 zcmV;F0&D$2RzVmBDV)KoEvoA;g(Cm^oOyyC>7(^Yz>d?#&EllZ(b3NT{j>k<{K;Y~$(j3oL*95x7q5J-a_#^bNL3U} z2C?`Y65-6u9H0_ZN{-kY$5^>sy!&;Nrs(ne!LywgJI!zR`OeGT{gH5HP2vsDy7RT7 zH^N{TaE{U9V^lPHDO8x8;f$if36sT`Y`I`4$vK?SlJRZFXtJ*A#8?8U+R8b$@HM-- zszg1dKo_3a8R2!{;~wy2B(+U}#e)@0yXue`OZ#9vxV-&8$2mb%xYrORg#%HD%EH~t z46%O-^cU}hF!7?7kP5k$Ldn<4fKXX93=$WV1loSsb1|B9WlHk7H1@kb#Fr16Ue@kE z8bcQLhAiBnFH|?8f9avW5AX#GHBDc3E2}y28`>UbZZuZzxXlg=q?BQ8m&)tRM?L28 zxGmTOrpbBF!Ct80ZG7;7$~JiqXQly9ggqK|FvU4ep;D#|y0crf zAPvoi?H`K>00000000A(mCtU|KoG`LA;g(Cm^+X&G;kqG5OAuBDsE(KkK=9EJJ#;H zO>S`H5jfIE<4HQ}KT+H)PMU*bdo?q^@7tO6-#pQr-nifQIfIoe*t=i%S%x0Je||Q3F$ur3=aZLbv$@1*m3n2?U3_iP zD>k4E;4Mzum(%M|FM|rx8(fn-vLTHL>9S=g$vIi*8Q*q{g4QNjDowyulexeKzBaC| zDz=_7pp7TvWbqLA+W>eu=UQiA*n?)wy6lik`WYw>5*pSEyd@CI*Dy^5T>z)0{O`() zv44#Ci@QRYdbV50m|R0)$=6n_f=Z)U#>q9M5!){YD(1^BO-a7sAiwh=**-G8tlfWV zj7c~jldwZ?s~$!FGC+SH;fp2;ruS~;wMqRE+vCik#@-$`y<-?M%CXjcLUd(Z@l6otdPp-)N?IHS)0s6(lq+A=jCHI$9UXfjFgiDoj^tdsR zp9Uh|#lj)CT*fvhp}=>12*&3)h#`ZCrpRH`Lmb_m82EHQS44zrrw<`k@u^ML9Q^(1 g+?;hMeFeS_WPEtyS77grRE!}%0O7-DwzUQT0O=MZ00000 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 c8e204a5149c2e10bab9fc13bc365185e521c98d..bbccccf41e85c97ad9c54ef26ccfe632b0cbd8b0 100644 GIT binary patch literal 583 zcmV-N0=WG_RzVuY%w;co4L^*^o;(~S(M)~e{8*W1R;Vo=E0f46faDaP# z>>P$5&k&ncx7!8W2SmU-h|ij!b_M`50QlMj?!6gJNJVTdmc3pN@Gf8runPy+x`%DR zP9JZi#!|ECm#UM6>cNCp{_a#oHoM1kN@}S{NaufRU0>m z+G+H1OCO5}00000000B6Qn60MFc9^%1P}7$F^)Sr9W~Wvmll61R?B`5f9x zEQrrwq6`1Rckm%Nty0l;U;s;&@8rAFd+*+Ro@$$o>2-l#d5tqZ5kx4tu@bp(MO~rs z#J%NH>&ECvkr5Lrt*4iTl){V0uP!@a&4EMcT!r^K+*8=^Lv-t$m; { 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'),