Skip to content

Commit ef4bdbf

Browse files
authored
test_runner: finish build phase before running tests
This commit updates the test runner to wait for suites to finish building before starting any tests. This is necessary when test filtering is enabled, as suites may transition from filtered to not filtered depending on what is inside of them. Fixes: #54084 Fixes: #54154 PR-URL: #54423 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]>
1 parent 561bc87 commit ef4bdbf

9 files changed

+302
-14
lines changed

lib/internal/test_runner/harness.js

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
'use strict';
22
const {
33
ArrayPrototypeForEach,
4+
ArrayPrototypePush,
45
FunctionPrototypeBind,
56
PromiseResolve,
67
SafeMap,
8+
SafePromiseAllReturnVoid,
79
} = primordials;
810
const { getCallerLocation } = internalBinding('util');
911
const {
@@ -24,6 +26,7 @@ const {
2426
shouldColorizeTestFiles,
2527
} = require('internal/test_runner/utils');
2628
const { queueMicrotask } = require('internal/process/task_queues');
29+
const { createDeferredPromise } = require('internal/util');
2730
const { bigint: hrtime } = process.hrtime;
2831
const resolvedPromise = PromiseResolve();
2932
const testResources = new SafeMap();
@@ -32,9 +35,12 @@ let globalRoot;
3235
testResources.set(reporterScope.asyncId(), reporterScope);
3336

3437
function createTestTree(rootTestOptions, globalOptions) {
38+
const buildPhaseDeferred = createDeferredPromise();
3539
const harness = {
3640
__proto__: null,
37-
allowTestsToRun: false,
41+
buildPromise: buildPhaseDeferred.promise,
42+
buildSuites: [],
43+
isWaitingForBuildPhase: false,
3844
bootstrapPromise: resolvedPromise,
3945
watching: false,
4046
config: globalOptions,
@@ -56,6 +62,13 @@ function createTestTree(rootTestOptions, globalOptions) {
5662
shouldColorizeTestFiles: shouldColorizeTestFiles(globalOptions.destinations),
5763
teardown: null,
5864
snapshotManager: null,
65+
async waitForBuildPhase() {
66+
if (harness.buildSuites.length > 0) {
67+
await SafePromiseAllReturnVoid(harness.buildSuites);
68+
}
69+
70+
buildPhaseDeferred.resolve();
71+
},
5972
};
6073

6174
harness.resetCounters();
@@ -243,14 +256,25 @@ function lazyBootstrapRoot() {
243256
}
244257

245258
async function startSubtestAfterBootstrap(subtest) {
246-
if (subtest.root.harness.bootstrapPromise) {
247-
// Only incur the overhead of awaiting the Promise once.
248-
await subtest.root.harness.bootstrapPromise;
249-
subtest.root.harness.bootstrapPromise = null;
250-
queueMicrotask(() => {
251-
subtest.root.harness.allowTestsToRun = true;
252-
subtest.root.processPendingSubtests();
253-
});
259+
if (subtest.root.harness.buildPromise) {
260+
if (subtest.root.harness.bootstrapPromise) {
261+
await subtest.root.harness.bootstrapPromise;
262+
subtest.root.harness.bootstrapPromise = null;
263+
}
264+
265+
if (subtest.buildSuite) {
266+
ArrayPrototypePush(subtest.root.harness.buildSuites, subtest.buildSuite);
267+
}
268+
269+
if (!subtest.root.harness.isWaitingForBuildPhase) {
270+
subtest.root.harness.isWaitingForBuildPhase = true;
271+
queueMicrotask(() => {
272+
subtest.root.harness.waitForBuildPhase();
273+
});
274+
}
275+
276+
await subtest.root.harness.buildPromise;
277+
subtest.root.harness.buildPromise = null;
254278
}
255279

256280
await subtest.start();

lib/internal/test_runner/runner.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ function run(options = kEmptyObject) {
610610
}
611611
const runFiles = () => {
612612
root.harness.bootstrapPromise = null;
613-
root.harness.allowTestsToRun = true;
613+
root.harness.buildPromise = null;
614614
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
615615
const subtest = runTestFile(path, filesWatcher, opts);
616616
filesWatcher?.runningSubtests.set(path, subtest);

lib/internal/test_runner/test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ class Test extends AsyncResource {
766766
// it. Otherwise, return a Promise to the caller and mark the test as
767767
// pending for later execution.
768768
this.reporter.enqueue(this.nesting, this.loc, this.name);
769-
if (!this.root.harness.allowTestsToRun || !this.parent.hasConcurrency()) {
769+
if (this.root.harness.buildPromise || !this.parent.hasConcurrency()) {
770770
const deferred = createDeferredPromise();
771771

772772
deferred.test = this;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Flags: --test-name-pattern=enabled
2+
'use strict';
3+
const common = require('../../../common');
4+
const { suite, test } = require('node:test');
5+
6+
suite('async suite', async () => {
7+
await 1;
8+
test('enabled 1', common.mustCall());
9+
await 1;
10+
test('not run', common.mustNotCall());
11+
await 1;
12+
});
13+
14+
suite('sync suite', () => {
15+
test('enabled 2', common.mustCall());
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
TAP version 13
2+
# Subtest: async suite
3+
# Subtest: enabled 1
4+
ok 1 - enabled 1
5+
---
6+
duration_ms: *
7+
...
8+
1..1
9+
ok 1 - async suite
10+
---
11+
duration_ms: *
12+
type: 'suite'
13+
...
14+
# Subtest: sync suite
15+
# Subtest: enabled 2
16+
ok 1 - enabled 2
17+
---
18+
duration_ms: *
19+
...
20+
1..1
21+
ok 2 - sync suite
22+
---
23+
duration_ms: *
24+
type: 'suite'
25+
...
26+
1..2
27+
# tests 2
28+
# suites 2
29+
# pass 2
30+
# fail 0
31+
# cancelled 0
32+
# skipped 0
33+
# todo 0
34+
# duration_ms *
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Flags: --test-only
2+
import { describe, test, after } from 'node:test';
3+
4+
after(() => { console.log('with global after()'); });
5+
await Promise.resolve();
6+
7+
console.log('Execution order was:');
8+
const ll = (t) => { console.log(` * ${t.fullName}`) };
9+
10+
describe('A', () => {
11+
test.only('A', ll);
12+
test('B', ll);
13+
describe.only('C', () => {
14+
test.only('A', ll);
15+
test('B', ll);
16+
});
17+
describe('D', () => {
18+
test.only('A', ll);
19+
test('B', ll);
20+
});
21+
});
22+
describe.only('B', () => {
23+
test('A', ll);
24+
test('B', ll);
25+
describe('C', () => {
26+
test('A', ll);
27+
});
28+
});
29+
describe('C', () => {
30+
test.only('A', ll);
31+
test('B', ll);
32+
describe.only('C', () => {
33+
test('A', ll);
34+
test('B', ll);
35+
});
36+
describe('D', () => {
37+
test('A', ll);
38+
test.only('B', ll);
39+
});
40+
});
41+
describe('D', () => {
42+
test('A', ll);
43+
test.only('B', ll);
44+
});
45+
describe.only('E', () => {
46+
test('A', ll);
47+
test('B', ll);
48+
});
49+
test.only('F', ll);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
Execution order was:
2+
* A > A
3+
* A > C > A
4+
* A > D > A
5+
* B > A
6+
* B > B
7+
* B > C > A
8+
* C > A
9+
* C > C > A
10+
* C > C > B
11+
* C > D > B
12+
* D > B
13+
* E > A
14+
* E > B
15+
* F
16+
with global after()
17+
TAP version 13
18+
# Subtest: A
19+
# Subtest: A
20+
ok 1 - A
21+
---
22+
duration_ms: *
23+
...
24+
# Subtest: C
25+
# Subtest: A
26+
ok 1 - A
27+
---
28+
duration_ms: *
29+
...
30+
1..1
31+
ok 2 - C
32+
---
33+
duration_ms: *
34+
type: 'suite'
35+
...
36+
# Subtest: D
37+
# Subtest: A
38+
ok 1 - A
39+
---
40+
duration_ms: *
41+
...
42+
1..1
43+
ok 3 - D
44+
---
45+
duration_ms: *
46+
type: 'suite'
47+
...
48+
1..3
49+
ok 1 - A
50+
---
51+
duration_ms: *
52+
type: 'suite'
53+
...
54+
# Subtest: B
55+
# Subtest: A
56+
ok 1 - A
57+
---
58+
duration_ms: *
59+
...
60+
# Subtest: B
61+
ok 2 - B
62+
---
63+
duration_ms: *
64+
...
65+
# Subtest: C
66+
# Subtest: A
67+
ok 1 - A
68+
---
69+
duration_ms: *
70+
...
71+
1..1
72+
ok 3 - C
73+
---
74+
duration_ms: *
75+
type: 'suite'
76+
...
77+
1..3
78+
ok 2 - B
79+
---
80+
duration_ms: *
81+
type: 'suite'
82+
...
83+
# Subtest: C
84+
# Subtest: A
85+
ok 1 - A
86+
---
87+
duration_ms: *
88+
...
89+
# Subtest: C
90+
# Subtest: A
91+
ok 1 - A
92+
---
93+
duration_ms: *
94+
...
95+
# Subtest: B
96+
ok 2 - B
97+
---
98+
duration_ms: *
99+
...
100+
1..2
101+
ok 2 - C
102+
---
103+
duration_ms: *
104+
type: 'suite'
105+
...
106+
# Subtest: D
107+
# Subtest: B
108+
ok 1 - B
109+
---
110+
duration_ms: *
111+
...
112+
1..1
113+
ok 3 - D
114+
---
115+
duration_ms: *
116+
type: 'suite'
117+
...
118+
1..3
119+
ok 3 - C
120+
---
121+
duration_ms: *
122+
type: 'suite'
123+
...
124+
# Subtest: D
125+
# Subtest: B
126+
ok 1 - B
127+
---
128+
duration_ms: *
129+
...
130+
1..1
131+
ok 4 - D
132+
---
133+
duration_ms: *
134+
type: 'suite'
135+
...
136+
# Subtest: E
137+
# Subtest: A
138+
ok 1 - A
139+
---
140+
duration_ms: *
141+
...
142+
# Subtest: B
143+
ok 2 - B
144+
---
145+
duration_ms: *
146+
...
147+
1..2
148+
ok 5 - E
149+
---
150+
duration_ms: *
151+
type: 'suite'
152+
...
153+
# Subtest: F
154+
ok 6 - F
155+
---
156+
duration_ms: *
157+
...
158+
1..6
159+
# tests 14
160+
# suites 10
161+
# pass 14
162+
# fail 0
163+
# cancelled 0
164+
# skipped 0
165+
# todo 0
166+
# duration_ms *

test/fixtures/test-runner/output/source_mapped_locations.snapshot

-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ not ok 1 - fails
2121
*
2222
*
2323
*
24-
*
25-
*
26-
*
2724
...
2825
1..1
2926
# tests 1

test/parallel/test-runner-output.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ const tests = [
101101
{ name: 'test-runner/output/eval_dot.js', transform: specTransform },
102102
{ name: 'test-runner/output/eval_spec.js', transform: specTransform },
103103
{ name: 'test-runner/output/eval_tap.js' },
104+
{ name: 'test-runner/output/filtered-suite-delayed-build.js' },
105+
{ name: 'test-runner/output/filtered-suite-order.mjs' },
104106
{ name: 'test-runner/output/filtered-suite-throws.js' },
105107
{ name: 'test-runner/output/hooks.js' },
106108
{ name: 'test-runner/output/hooks_spec_reporter.js', transform: specTransform },

0 commit comments

Comments
 (0)