◐ Shell
clean mode source ↗

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+

});