test_runner: expose worker ID for concurrent test execution · nodejs/node@0d97ec4
1+'use strict';
2+require('../common');
3+const fixtures = require('../common/fixtures');
4+const assert = require('node:assert');
5+const { spawnSync } = require('node:child_process');
6+const { test } = require('node:test');
7+8+test('NODE_TEST_WORKER_ID is set for concurrent test files', async () => {
9+const args = [
10+'--test',
11+fixtures.path('test-runner', 'worker-id', 'test-1.mjs'),
12+fixtures.path('test-runner', 'worker-id', 'test-2.mjs'),
13+fixtures.path('test-runner', 'worker-id', 'test-3.mjs'),
14+];
15+const result = spawnSync(process.execPath, args, {
16+cwd: fixtures.path(),
17+env: { ...process.env }
18+});
19+20+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
21+});
22+23+test('NODE_TEST_WORKER_ID is set with explicit concurrency', async () => {
24+const args = [
25+'--test',
26+'--test-concurrency=2',
27+fixtures.path('test-runner', 'worker-id', 'test-1.mjs'),
28+fixtures.path('test-runner', 'worker-id', 'test-2.mjs'),
29+];
30+const result = spawnSync(process.execPath, args, {
31+cwd: fixtures.path(),
32+env: { ...process.env }
33+});
34+35+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
36+});
37+38+test('NODE_TEST_WORKER_ID is 1 with concurrency=1', async () => {
39+const args = ['--test', '--test-concurrency=1', fixtures.path('test-runner', 'worker-id', 'test-1.mjs')];
40+const result = spawnSync(process.execPath, args, {
41+cwd: fixtures.path(),
42+env: { ...process.env }
43+});
44+45+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
46+});
47+48+test('NODE_TEST_WORKER_ID with explicit isolation=process', async () => {
49+const args = [
50+'--test',
51+'--test-isolation=process',
52+fixtures.path('test-runner', 'worker-id', 'test-1.mjs'),
53+fixtures.path('test-runner', 'worker-id', 'test-2.mjs'),
54+];
55+const result = spawnSync(process.execPath, args, {
56+cwd: fixtures.path(),
57+env: { ...process.env }
58+});
59+60+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
61+});
62+63+test('NODE_TEST_WORKER_ID is 1 with isolation=none', async () => {
64+const args = [
65+'--test',
66+'--test-isolation=none',
67+fixtures.path('test-runner', 'worker-id', 'test-1.mjs'),
68+fixtures.path('test-runner', 'worker-id', 'test-2.mjs'),
69+];
70+const result = spawnSync(process.execPath, args, {
71+cwd: fixtures.path(),
72+env: { ...process.env }
73+});
74+75+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
76+});
77+78+test('context.workerId matches NODE_TEST_WORKER_ID', async () => {
79+const args = ['--test', fixtures.path('test-runner', 'worker-id', 'test-1.mjs')];
80+const result = spawnSync(process.execPath, args, {
81+cwd: fixtures.path(),
82+env: { ...process.env }
83+});
84+85+// The fixture tests already verify that context.workerId matches the env var
86+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
87+});
88+89+test('worker IDs are reused when more tests than concurrency', async () => {
90+const tmpdir = require('../common/tmpdir');
91+const { writeFileSync } = require('node:fs');
92+tmpdir.refresh();
93+94+// Create 9 separate test files dynamically
95+const testFiles = [];
96+const usageFile = tmpdir.resolve('worker-usage.txt');
97+for (let i = 1; i <= 9; i++) {
98+const testFile = tmpdir.resolve(`reuse-test-${i}.mjs`);
99+writeFileSync(
100+testFile,
101+`import { test } from 'node:test';
102+import { appendFileSync } from 'node:fs';
103+104+test('track worker ${i}', () => {
105+ const workerId = process.env.NODE_TEST_WORKER_ID;
106+ const usageFile = process.env.WORKER_USAGE_FILE;
107+ appendFileSync(usageFile, workerId + '\\n');
108+});
109+`,
110+);
111+testFiles.push(testFile);
112+}
113+114+const args = ['--test', '--test-concurrency=3', ...testFiles];
115+const result = spawnSync(process.execPath, args, {
116+env: { ...process.env, WORKER_USAGE_FILE: usageFile }
117+});
118+119+assert.strictEqual(result.status, 0, `Test failed: ${result.stderr.toString()}`);
120+121+// Read and analyze worker IDs used
122+const { readFileSync } = require('node:fs');
123+const workerIds = readFileSync(usageFile, 'utf8').trim().split('\n');
124+125+// Count occurrences of each worker ID
126+const workerCounts = {};
127+workerIds.forEach((id) => {
128+workerCounts[id] = (workerCounts[id] || 0) + 1;
129+});
130+131+const uniqueWorkers = Object.keys(workerCounts);
132+assert.strictEqual(
133+uniqueWorkers.length,
134+3,
135+`Should have exactly 3 unique worker IDs, got ${uniqueWorkers.length}: ${uniqueWorkers.join(', ')}`
136+);
137+138+Object.entries(workerCounts).forEach(([id, count]) => {
139+assert.strictEqual(count, 3, `Worker ID ${id} should be used 3 times, got ${count}`);
140+});
141+});