Skip to content

Commit

Permalink
test_runner: introduce NODE_TEST_WORKER_ID for improved concurrent te…
Browse files Browse the repository at this point in the history
…st 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.
  • Loading branch information
cu8code committed Dec 5, 2024
1 parent 3fb2ea8 commit aa6e400
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 3 deletions.
9 changes: 9 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,15 @@ each other in ways that are not possible when isolation is enabled. For example,
if a test relies on global state, it is possible for that state to be modified
by a test originating from another file.

## Environment Variables

### `NODE_TEST_WORKER_ID`

when running the test in parallel using the `--experimental-test-isolation=process`
flag, this environment variable will be set to a number between 1 to N (where N is the
number of files). When running the test in isolation using the
`--experimental-test-isolation=none` flag, this environment variable will be set to `1`.

## Collecting code coverage

> Stability: 1 - Experimental
Expand Down
9 changes: 6 additions & 3 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,14 +358,14 @@ class FileTest extends Test {
}
}

function runTestFile(path, filesWatcher, opts) {
function runTestFile(path, filesWatcher, opts, workerId = 1) {
const watchMode = filesWatcher != null;
const testPath = path === kIsolatedProcessName ? '' : path;
const testOpts = { __proto__: null, signal: opts.signal };
const subtest = opts.root.createSubtest(FileTest, testPath, testOpts, async (t) => {
const args = getRunArgs(path, opts);
const stdio = ['pipe', 'pipe', 'pipe'];
const env = { __proto__: null, ...process.env, NODE_TEST_CONTEXT: 'child-v8' };
const env = { __proto__: null, ...process.env, NODE_TEST_CONTEXT: 'child-v8', NODE_TEST_WORKER_ID: workerId };
if (watchMode) {
stdio.push('ipc');
env.WATCH_REPORT_DEPENDENCIES = '1';
Expand Down Expand Up @@ -724,8 +724,10 @@ function run(options = kEmptyObject) {
runFiles = () => {
root.harness.bootstrapPromise = null;
root.harness.buildPromise = null;
let workerId = 1;
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
const subtest = runTestFile(path, filesWatcher, opts);
const subtest = runTestFile(path, filesWatcher, opts, workerId);
workerId++;
filesWatcher?.runningSubtests.set(path, subtest);
return subtest;
});
Expand Down Expand Up @@ -766,6 +768,7 @@ function run(options = kEmptyObject) {

root.entryFile = resolve(testFile);
debug('loading test file:', fileURL.href);
process.env.NODE_TEST_WORKER_ID = 1;
try {
await cascadedLoader.import(fileURL, parent, { __proto__: null });
} catch (err) {
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/NODE_TEST_WORKER_ID/1.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file tests the `NODE_TEST_WORKER_ID` feature.
// The test logic is implemented in `test/parallel/test-runner-cli.js`.
const test = require('node:test');

test('test1', t => {
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
});
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/NODE_TEST_WORKER_ID/2.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file tests the `NODE_TEST_WORKER_ID` feature.
// The test logic is implemented in `test/parallel/test-runner-cli.js`.
const test = require('node:test');

test('test2', t => {
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
});
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/NODE_TEST_WORKER_ID/3.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file tests the `NODE_TEST_WORKER_ID` feature.
// The test logic is implemented in `test/parallel/test-runner-cli.js`.
const test = require('node:test');

test('test3', t => {
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
});
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/NODE_TEST_WORKER_ID/4.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file tests the `NODE_TEST_WORKER_ID` feature.
// The test logic is implemented in `test/parallel/test-runner-cli.js`.
const test = require('node:test');

test('test4', t => {
console.log('NODE_TEST_WORKER_ID', process.env.NODE_TEST_WORKER_ID)
});
28 changes: 28 additions & 0 deletions test/parallel/test-runner-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,31 @@ for (const isolation of ['none', 'process']) {
assert.strictEqual(child.status, 0);
assert.strictEqual(child.signal, null);
}

{
const args = ['--test', '--experimental-test-isolation=process'];
const child = spawnSync(process.execPath, args, { cwd: join(testFixtures, 'NODE_TEST_WORKER_ID') });

assert.strictEqual(child.stderr.toString(), '');
const stdout = child.stdout.toString();

assert.match(stdout, /NODE_TEST_WORKER_ID 1/);
assert.match(stdout, /NODE_TEST_WORKER_ID 2/);
assert.match(stdout, /NODE_TEST_WORKER_ID 3/);
assert.match(stdout, /NODE_TEST_WORKER_ID 4/);

assert.strictEqual(child.status, 0);
}

{
const args = ['--test', '--experimental-test-isolation=none'];
const child = spawnSync(process.execPath, args, { cwd: join(testFixtures, 'NODE_TEST_WORKER_ID') });

assert.strictEqual(child.stderr.toString(), '');
const stdout = child.stdout.toString();
const regex = /NODE_TEST_WORKER_ID 1/g;
const result = stdout.match(regex);

assert.strictEqual(result.length, 4);
assert.strictEqual(child.status, 0);
}

0 comments on commit aa6e400

Please sign in to comment.