Skip to content

Commit 392f8f7

Browse files
committed
test_runner: introduce NODE_TEST_WORKER_ID for improved concurrent test execution
Added a new environment variable, `NODE_TEST_WORKER_ID`, which ranges from 1 to N when `--experimental-test-isolation=process` is enabled and defaults to 1 when `--experimental-test-isolation=none` is used.
1 parent 3fb2ea8 commit 392f8f7

File tree

6 files changed

+127
-12
lines changed

6 files changed

+127
-12
lines changed

doc/api/cli.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,15 +2157,6 @@ are:
21572157
* Running `pre` or `post` scripts in addition to the specified script.
21582158
* Defining package manager-specific environment variables.
21592159

2160-
#### Environment variables
2161-
2162-
The following environment variables are set when running a script with `--run`:
2163-
2164-
* `NODE_RUN_SCRIPT_NAME`: The name of the script being run. For example, if
2165-
`--run` is used to run `test`, the value of this variable will be `test`.
2166-
* `NODE_RUN_PACKAGE_JSON_PATH`: The path to the `package.json` that is being
2167-
processed.
2168-
21692160
### `--secure-heap=n`
21702161

21712162
<!-- YAML
@@ -3306,6 +3297,12 @@ If `value` equals `'child'`, test reporter options will be overridden and test
33063297
output will be sent to stdout in the TAP format. If any other value is provided,
33073298
Node.js makes no guarantees about the reporter format used or its stability.
33083299

3300+
### `NODE_TEST_WORKER_ID`
3301+
3302+
This environment variable is set by the test runner. When using process-level test
3303+
isolation, each worker process is assigned a unique ID, starting at `1`. This is
3304+
set to 1 for all tests when test isolation is disabled.
3305+
33093306
### `NODE_TLS_REJECT_UNAUTHORIZED=value`
33103307

33113308
If `value` equals `'0'`, certificate validation is disabled for TLS connections.

lib/internal/test_runner/runner.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,14 @@ class FileTest extends Test {
358358
}
359359
}
360360

361-
function runTestFile(path, filesWatcher, opts) {
361+
function runTestFile(path, filesWatcher, opts, workerId = 1) {
362362
const watchMode = filesWatcher != null;
363363
const testPath = path === kIsolatedProcessName ? '' : path;
364364
const testOpts = { __proto__: null, signal: opts.signal };
365365
const subtest = opts.root.createSubtest(FileTest, testPath, testOpts, async (t) => {
366366
const args = getRunArgs(path, opts);
367367
const stdio = ['pipe', 'pipe', 'pipe'];
368-
const env = { __proto__: null, ...process.env, NODE_TEST_CONTEXT: 'child-v8' };
368+
const env = { __proto__: null, ...process.env, NODE_TEST_CONTEXT: 'child-v8', NODE_TEST_WORKER_ID: workerId };
369369
if (watchMode) {
370370
stdio.push('ipc');
371371
env.WATCH_REPORT_DEPENDENCIES = '1';
@@ -724,8 +724,10 @@ function run(options = kEmptyObject) {
724724
runFiles = () => {
725725
root.harness.bootstrapPromise = null;
726726
root.harness.buildPromise = null;
727+
let workerId = 1;
727728
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
728-
const subtest = runTestFile(path, filesWatcher, opts);
729+
const subtest = runTestFile(path, filesWatcher, opts, workerId);
730+
workerId++;
729731
filesWatcher?.runningSubtests.set(path, subtest);
730732
return subtest;
731733
});
@@ -766,6 +768,7 @@ function run(options = kEmptyObject) {
766768

767769
root.entryFile = resolve(testFile);
768770
debug('loading test file:', fileURL.href);
771+
process.env.NODE_TEST_WORKER_ID = 1;
769772
try {
770773
await cascadedLoader.import(fileURL, parent, { __proto__: null });
771774
} catch (err) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const test = require('node:test');
2+
3+
test('test1', t => {
4+
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
5+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const test = require('node:test');
2+
3+
test('test2', t => {
4+
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
5+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const test = require('node:test');
2+
3+
test('test3', t => {
4+
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
5+
});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const test = require('node:test');
6+
const { spawnSync, spawn } = require('child_process');
7+
const { join } = require('path');
8+
const fixtures = require('../common/fixtures');
9+
const testFixtures = fixtures.path('test-runner');
10+
11+
test('testing with isolation enabled', () => {
12+
const args = ['--test', '--experimental-test-isolation=process'];
13+
const child = spawnSync(process.execPath, args, { cwd: join(testFixtures, 'worker-id') });
14+
15+
assert.strictEqual(child.stderr.toString(), '');
16+
const stdout = child.stdout.toString();
17+
18+
assert.match(stdout, /NODE_TEST_WORKER_ID 1/);
19+
assert.match(stdout, /NODE_TEST_WORKER_ID 2/);
20+
assert.match(stdout, /NODE_TEST_WORKER_ID 3/);
21+
22+
assert.strictEqual(child.status, 0);
23+
});
24+
25+
test('testing with isolation disabled', () => {
26+
const args = ['--test', '--experimental-test-isolation=none'];
27+
const child = spawnSync(process.execPath, args, { cwd: join(testFixtures, 'worker-id') });
28+
29+
assert.strictEqual(child.stderr.toString(), '');
30+
const stdout = child.stdout.toString();
31+
const regex = /NODE_TEST_WORKER_ID 1/g;
32+
const result = stdout.match(regex);
33+
34+
assert.strictEqual(result.length, 3);
35+
assert.strictEqual(child.status, 0);
36+
});
37+
38+
39+
test('testing with isolation enabled in watch mode', async () => {
40+
const args = ['--watch', '--test', '--experimental-test-isolation=process'];
41+
42+
const child = spawn(process.execPath, args, { cwd: join(testFixtures, 'worker-id') });
43+
44+
let outputData = '';
45+
let errorData = '';
46+
47+
child.stdout.on('data', (data) => {
48+
outputData += data.toString();
49+
});
50+
51+
child.stderr.on('data', (data) => {
52+
errorData += data.toString();
53+
});
54+
55+
setTimeout(() => {
56+
child.kill();
57+
58+
assert.strictEqual(errorData, '');
59+
assert.match(outputData, /NODE_TEST_WORKER_ID 1/);
60+
assert.match(outputData, /NODE_TEST_WORKER_ID 2/);
61+
assert.match(outputData, /NODE_TEST_WORKER_ID 3/);
62+
63+
if (child.exitCode !== null) {
64+
assert.strictEqual(child.exitCode, null); // Since we killed it manually
65+
}
66+
}, 1000);
67+
68+
});
69+
70+
test('testing with isolation disabled in watch mode', async () => {
71+
const args = ['--watch', '--test', '--experimental-test-isolation=none'];
72+
73+
const child = spawn(process.execPath, args, { cwd: join(testFixtures, 'worker-id') });
74+
75+
let outputData = '';
76+
let errorData = '';
77+
78+
child.stdout.on('data', (data) => {
79+
outputData += data.toString();
80+
});
81+
82+
child.stderr.on('data', (data) => {
83+
errorData += data.toString();
84+
});
85+
86+
setTimeout(() => {
87+
child.kill();
88+
89+
assert.strictEqual(errorData, '');
90+
const regex = /NODE_TEST_WORKER_ID 1/g;
91+
const result = outputData.match(regex);
92+
93+
assert.strictEqual(result.length, 3);
94+
95+
if (child.exitCode !== null) {
96+
assert.strictEqual(child.exitCode, null);
97+
}
98+
}, 1000);
99+
100+
});

0 commit comments

Comments
 (0)