test_runner: avoid reading process state directly in run()#62377
test_runner: avoid reading process state directly in run()#62377murataslan1 wants to merge 2 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! |
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