Skip to content

Commit b555287

Browse files
cjihrigMoLow
andcommitted
test_runner: support running tests in process
This commit introduces a new --experimental-test-isolation flag that, when set to 'none', causes the test runner to execute all tests in the same process. By default, this is the main test runner process, but if watch mode is enabled, it spawns a separate process that runs all of the tests. The default value of the new flag is 'process', which uses the existing behavior of running each test file in its own child process. It is worth noting that when the isolation mode is 'none', globals and all other top level logic (such as top level before() and after() hooks) is shared among all files. Co-authored-by: Moshe Atlow <[email protected]>
1 parent 3d019ce commit b555287

File tree

10 files changed

+193
-83
lines changed

10 files changed

+193
-83
lines changed

doc/api/cli.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,20 @@ generated as part of the test runner output. If no tests are run, a coverage
11101110
report is not generated. See the documentation on
11111111
[collecting code coverage from tests][] for more details.
11121112

1113+
### `--experimental-test-isolation=mode`
1114+
1115+
<!-- YAML
1116+
added: REPLACEME
1117+
-->
1118+
1119+
> Stability: 1.0 - Early development
1120+
1121+
Configures the type of test isolation used in the test runner. When `mode` is
1122+
`'process'`, each test file is run in a separate child process. When `mode` is
1123+
`'none'`, all test files run in the same process as the test runner. The default
1124+
isolation mode is `'process'`. This flag is ignored if the `--test` flag is not
1125+
present.
1126+
11131127
### `--experimental-test-module-mocks`
11141128

11151129
<!-- YAML
@@ -2196,7 +2210,9 @@ added:
21962210
-->
21972211

21982212
The maximum number of test files that the test runner CLI will execute
2199-
concurrently. The default value is `os.availableParallelism() - 1`.
2213+
concurrently. The default concurrency depends on the test isolation mode in use.
2214+
If `--experimental-test-isolation` is set to `'none'`, concurrency defaults to
2215+
one. Otherwise, the default value is `os.availableParallelism() - 1`.
22002216

22012217
### `--test-force-exit`
22022218

doc/api/test.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,9 @@ added:
12391239
- v18.9.0
12401240
- v16.19.0
12411241
changes:
1242+
- version: REPLACEME
1243+
pr-url: https://github.com/nodejs/node/pull/xxxxx
1244+
description: Added the `isolation` option.
12421245
- version:
12431246
- v22.0.0
12441247
- v20.14.0
@@ -1268,8 +1271,13 @@ changes:
12681271
* `inspectPort` {number|Function} Sets inspector port of test child process.
12691272
This can be a number, or a function that takes no arguments and returns a
12701273
number. If a nullish value is provided, each process gets its own port,
1271-
incremented from the primary's `process.debugPort`.
1272-
**Default:** `undefined`.
1274+
incremented from the primary's `process.debugPort`. This option is ignored
1275+
if the `isolation` option is set to `'none'` as no child processes are
1276+
spawned. **Default:** `undefined`.
1277+
* `isolation` {string} Configures the type of test isolation. If set to
1278+
`'process'`, each test file is run in a separate child process. If set to
1279+
`'none'`, all test files run in the current process. The default isolation
1280+
mode is `'process'`.
12731281
* `only`: {boolean} If truthy, the test context will only run tests that
12741282
have the `only` option set
12751283
* `setup` {Function} A function that accepts the `TestsStream` instance

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ Enable the experimental node:sqlite module.
188188
.It Fl -experimental-test-coverage
189189
Enable code coverage in the test runner.
190190
.
191+
.It Fl -experimental-test-isolation Ns = Ns Ar mode
192+
Configures the type of test isolation used in the test runner.
193+
.
191194
.It Fl -experimental-test-module-mocks
192195
Enable module mocking in the test runner.
193196
.

lib/internal/main/test_runner.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ prepareMainThreadExecution(false);
1919
markBootstrapComplete();
2020

2121
const {
22+
isolation,
2223
perFileTimeout,
2324
runnerConcurrency,
2425
shard,
@@ -38,10 +39,11 @@ if (isUsingInspector()) {
3839
const options = {
3940
concurrency,
4041
inspectPort,
41-
watch: watchMode,
42+
isolation,
4243
setup: setupTestReporters,
43-
timeout: perFileTimeout,
4444
shard,
45+
timeout: perFileTimeout,
46+
watch: watchMode,
4547
};
4648
debug('test runner configuration:', options);
4749
run(options).on('test:fail', (data) => {

lib/internal/test_runner/harness.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ function setup(root) {
208208
};
209209
},
210210
counters: null,
211-
shouldColorizeTestFiles: false,
211+
shouldColorizeTestFiles: shouldColorizeTestFiles(globalOptions.destinations),
212212
teardown: exitHandler,
213213
snapshotManager: null,
214214
};
@@ -218,48 +218,50 @@ function setup(root) {
218218
}
219219

220220
let globalRoot;
221-
let reportersSetup;
222-
function getGlobalRoot() {
221+
let asyncBootstrap;
222+
function lazyBootstrapRoot() {
223223
if (!globalRoot) {
224224
globalRoot = createTestTree({ __proto__: null, entryFile: process.argv?.[1] });
225225
globalRoot.reporter.on('test:fail', (data) => {
226226
if (data.todo === undefined || data.todo === false) {
227227
process.exitCode = kGenericUserError;
228228
}
229229
});
230-
reportersSetup = setupTestReporters(globalRoot.reporter);
231-
globalRoot.harness.shouldColorizeTestFiles ||= shouldColorizeTestFiles(globalRoot);
230+
asyncBootstrap = setupTestReporters(globalRoot.reporter);
232231
}
233232
return globalRoot;
234233
}
235234

235+
function setRootTest(root) {
236+
globalRoot = root;
237+
}
238+
236239
async function startSubtest(subtest) {
237-
if (reportersSetup) {
240+
if (asyncBootstrap) {
238241
// Only incur the overhead of awaiting the Promise once.
239-
await reportersSetup;
240-
reportersSetup = undefined;
241-
}
242-
243-
const root = getGlobalRoot();
244-
if (!root.harness.bootstrapComplete) {
245-
root.harness.bootstrapComplete = true;
246-
queueMicrotask(() => {
247-
root.harness.allowTestsToRun = true;
248-
root.processPendingSubtests();
249-
});
242+
await asyncBootstrap;
243+
asyncBootstrap = undefined;
244+
if (!subtest.root.harness.bootstrapComplete) {
245+
subtest.root.harness.bootstrapComplete = true;
246+
queueMicrotask(() => {
247+
subtest.root.harness.allowTestsToRun = true;
248+
subtest.root.processPendingSubtests();
249+
});
250+
}
250251
}
251252

252253
await subtest.start();
253254
}
254255

255256
function runInParentContext(Factory) {
256257
function run(name, options, fn, overrides) {
257-
const parent = testResources.get(executionAsyncId()) || getGlobalRoot();
258+
const parent = testResources.get(executionAsyncId()) || lazyBootstrapRoot();
258259
const subtest = parent.createSubtest(Factory, name, options, fn, overrides);
259-
if (!(parent instanceof Suite)) {
260-
return startSubtest(subtest);
260+
if (parent instanceof Suite) {
261+
return PromiseResolve();
261262
}
262-
return PromiseResolve();
263+
264+
return startSubtest(subtest);
263265
}
264266

265267
const test = (name, options, fn) => {
@@ -286,7 +288,7 @@ function runInParentContext(Factory) {
286288

287289
function hook(hook) {
288290
return (fn, options) => {
289-
const parent = testResources.get(executionAsyncId()) || getGlobalRoot();
291+
const parent = testResources.get(executionAsyncId()) || lazyBootstrapRoot();
290292
parent.createHook(hook, fn, {
291293
__proto__: null,
292294
...options,
@@ -305,4 +307,5 @@ module.exports = {
305307
after: hook('after'),
306308
beforeEach: hook('beforeEach'),
307309
afterEach: hook('afterEach'),
310+
setRootTest,
308311
};

0 commit comments

Comments
 (0)