test_runner: avoid reading process state directly in run()#62377
test_runner: avoid reading process state directly in run()#62377murataslan1 wants to merge 3 commits intonodejs:mainfrom
Conversation
Refs: nodejs#53867 The run() function is exposed as a public API via node:test module. Previously, it accessed process.argv, process.execArgv, process.cwd(), and process.env directly, which meant programmatic API users could not fully control the test runner behavior. This change: - Captures process.argv, process.cwd(), process.env, and process.execArgv in the CLI entry point (main/test_runner.js) and passes them explicitly to run() as options - Adds a processExecArgv option for V8 flag propagation - Replaces process.env fallback in runTestFile() with opts.env - Replaces process.argv usage in getRunArgs() with globPatterns - Replaces process.env.NODE_TEST_CONTEXT check with env option - Maintains backwards compatibility: when options are not provided, run() falls back to process state as before
|
Review requested:
|
There was a problem hiding this comment.
Pull request overview
This PR updates the Node.js test runner so the public node:test run() API can be controlled programmatically without implicitly reading from process globals (argv/cwd/env/execArgv), while keeping backwards-compatible fallbacks.
Changes:
- Capture
process.argv,process.cwd(),process.env, andprocess.execArgvin the CLI entry point and pass them intorun()as options. - Add/propagate
processExecArgvfor forwarding V8 flags to child processes. - Replace remaining direct reads of
process.argv/process.envin runner internals with option-based values.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| lib/internal/test_runner/runner.js | Adds option plumbing/defaults for process state, uses passed-in argv/env/execArgv equivalents when spawning child test processes. |
| lib/internal/main/test_runner.js | Captures process state at the CLI entry point and passes it down via run() options. |
Comments suppressed due to low confidence (1)
lib/internal/test_runner/runner.js:787
run()now treats any definedenvas user-provided, and rejects it whenisolation === 'none'. Because the CLI entry point always passesoptions.env = process.env,node --test --test-isolation=nonewill now throw. Consider adjusting the "user provided" detection (e.g., distinguish internal captured env from an explicit API override) or relaxing the isolation='none' restriction for the default/inherited env case.
if (userProvidedEnv) {
validateObject(env);
if (isolation === 'none') {
throw new ERR_INVALID_ARG_VALUE('options.env', env, 'is not supported with isolation=\'none\'');
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
lib/internal/test_runner/runner.js
Outdated
| processExecArgv, | ||
| globPatterns, | ||
| rerunFailuresFilePath, |
There was a problem hiding this comment.
globPatterns is forwarded into runArgs via ArrayPrototypePushApply() (so it must be an array of strings). Currently run() only ensures it's an array; consider validating its element types (e.g., string array) to avoid child-spawn argument errors caused by non-string entries.
lib/internal/main/test_runner.js
Outdated
| // Capture process state explicitly so run() does not need to access it. | ||
| options.globPatterns = ArrayPrototypeSlice(process.argv, 1); | ||
| options.cwd = process.cwd(); | ||
| options.env = process.env; |
There was a problem hiding this comment.
options.env is being set unconditionally, which makes run() think the caller explicitly provided an env option. With --test-isolation=none, run() currently rejects options.env, so this change will break node --test --test-isolation=none. Consider only setting/passing options.env in the CLI when isolation='process' (or update run() to treat this captured env as non-user-provided).
| options.env = process.env; | |
| if (options.isolation === 'process') { | |
| options.env = process.env; | |
| } |
| argv = [], | ||
| cwd = process.cwd(), | ||
| cwd, | ||
| processExecArgv, | ||
| rerunFailuresFilePath, |
There was a problem hiding this comment.
processExecArgv is consumed by getRunArgs() as an array (it is filtered and iterated), but there is no validation for the options.processExecArgv type. If a public API caller passes a non-array value, this will fail at runtime in less obvious ways. Consider validating it (e.g., validateStringArray(processExecArgv, 'options.processExecArgv')) when it is explicitly provided.
lib/internal/main/test_runner.js
Outdated
| options.globPatterns = ArrayPrototypeSlice(process.argv, 1); | ||
| options.cwd = process.cwd(); | ||
| options.env = process.env; | ||
| options.processExecArgv = process.execArgv; |
There was a problem hiding this comment.
How's this differ from execArgv?
There was a problem hiding this comment.
Yeah you're right, dropped it. The idea was to avoid the direct process.execArgv read, but it doesn't really make sense as a user-facing option — it's just the parent's V8 flags that get forwarded to child processes. Reverted to reading it directly.
|
Blocking as this needs documentation and tests |
- Remove processExecArgv option: process.execArgv is runtime state that should not be a public API option. Keep reading it directly in getRunArgs() as before. - Fix env with isolation=none: only set options.env from CLI entry point when isolation is not 'none', preventing ERR_INVALID_ARG_VALUE when using --test-isolation=none. - Add documentation for the env option in doc/api/test.md. - Add tests for env option acceptance and env+isolation=none rejection.
|
Pushed fixes based on the feedback:
Let me know if anything else needs changing! |
|
tightened globPatterns validation — now uses validateStringArray for consistency with argv/execArgv. the env+isolation=none edge case was already handled in the earlier commits (cli only sets env when isolation isn't none, and runner.js tracks userProvidedEnv separately). think this should be good to go now, lmk if anything else needs attention |
Refs: #53867
Summary
The
run()function is exposed as a public API vianode:test, but it accessedprocess.argv,process.execArgv,process.cwd(), andprocess.envdirectly. This meant programmatic API users could not fully control the test runner behavior.PR #54705 added
cwdas an option but other process state references remained (as noted by @cjihrig).Changes
process.argv,process.cwd(),process.env, andprocess.execArgvin the CLI entry point (lib/internal/main/test_runner.js) and pass them explicitly torun()as optionsprocessExecArgvoption for V8 flag propagation to child processesprocess.envfallback inrunTestFile()withopts.envprocess.argvusage ingetRunArgs()withglobPatternsprocess.env.NODE_TEST_CONTEXTcheck withenvoptionrun()falls back to process state as beforeTest plan
run()calls now respect explicitly providedenv,cwd,processExecArgvoptions instead of reading from process globals