Skip to content

Commit d61c000

Browse files
committed
test_runner: change root test to be a suite
1 parent 74c7664 commit d61c000

20 files changed

Lines changed: 338 additions & 43 deletions

lib/internal/test_runner/harness.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ const {
2121
parseCommandLine,
2222
setupTestReporters,
2323
} = require('internal/test_runner/utils');
24+
const { setImmediate } = require('timers');
2425
const { bigint: hrtime } = process.hrtime;
2526

2627
const testResources = new SafeMap();
2728

2829
function createTestTree(options = kEmptyObject) {
29-
return setup(new Test({ __proto__: null, ...options, name: '<root>' }));
30+
return setup(new Suite({ __proto__: null, ...options, name: '<root>' }));
3031
}
3132

3233
function createProcessEventHandler(eventName, rootTest) {
@@ -140,8 +141,8 @@ function setup(root) {
140141
const rejectionHandler =
141142
createProcessEventHandler('unhandledRejection', root);
142143
const coverage = configureCoverage(root, globalOptions);
143-
const exitHandler = async () => {
144-
await root.run(new ERR_TEST_FAILURE(
144+
const exitHandler = () => {
145+
root.postRun(new ERR_TEST_FAILURE(
145146
'Promise resolution is still pending but the event loop has already resolved',
146147
kCancelledByParent));
147148

@@ -150,8 +151,8 @@ function setup(root) {
150151
process.removeListener('uncaughtException', exceptionHandler);
151152
};
152153

153-
const terminationHandler = async () => {
154-
await exitHandler();
154+
const terminationHandler = () => {
155+
exitHandler();
155156
process.exit();
156157
};
157158

@@ -196,6 +197,7 @@ function getGlobalRoot() {
196197
}
197198
});
198199
reportersSetup = setupTestReporters(globalRoot);
200+
setImmediate(() => globalRoot.run());
199201
}
200202
return globalRoot;
201203
}
@@ -208,6 +210,10 @@ async function startSubtest(subtest, parent) {
208210
} else {
209211
await subtest.start();
210212
}
213+
if (parent.parent === null && parent.endTime !== null) {
214+
parent.endTime = null;
215+
parent.processPendingSubtests();
216+
}
211217
}
212218

213219
function runInParentContext(Factory) {

lib/internal/test_runner/runner.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const {
1616
PromisePrototypeThen,
1717
SafePromiseAll,
1818
SafePromiseAllReturnVoid,
19-
SafePromiseAllSettledReturnVoid,
2019
PromiseResolve,
2120
SafeMap,
2221
SafeSet,
@@ -378,7 +377,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
378377
throw err;
379378
}
380379
});
381-
return subtest.start();
380+
return subtest.enqueue();
382381
}
383382

384383
function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
@@ -404,6 +403,7 @@ function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) {
404403
}
405404
await runningSubtests.get(file);
406405
runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher, testNamePatterns));
406+
root.processPendingSubtests();
407407
}, undefined, (error) => {
408408
triggerUncaughtException(error, true /* fromPromise */);
409409
}));
@@ -476,22 +476,24 @@ function run(options) {
476476
testFiles = ArrayPrototypeFilter(testFiles, (_, index) => index % shard.total === shard.index - 1);
477477
}
478478

479-
let postRun = () => root.postRun();
480479
let filesWatcher;
481480
if (watch) {
482481
filesWatcher = watchFiles(testFiles, root, inspectPort, signal, testNamePatterns);
483-
postRun = undefined;
484482
}
485483
const runFiles = () => {
486484
root.harness.bootstrapComplete = true;
487-
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
488-
const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns);
489-
filesWatcher?.runningSubtests.set(path, subtest);
490-
return subtest;
491-
});
485+
for (let i = 0; i < testFiles.length; i++) {
486+
const path = testFiles[i];
487+
const enqueued = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns);
488+
filesWatcher?.runningSubtests.set(path, enqueued);
489+
}
490+
if (filesWatcher) {
491+
return root.processPendingSubtests();
492+
}
493+
return PromisePrototypeThen(root.run(), () => root.postRun());
492494
};
493495

494-
PromisePrototypeThen(PromisePrototypeThen(PromiseResolve(setup?.(root)), runFiles), postRun);
496+
PromisePrototypeThen(PromiseResolve(setup?.(root)), runFiles);
495497

496498
return root.reporter;
497499
}

lib/internal/test_runner/test.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ class SuiteContext {
190190
get name() {
191191
return this.#suite.name;
192192
}
193+
194+
diagnostic(message) {
195+
this.#suite.diagnostic(message);
196+
}
193197
}
194198

195199
class Test extends AsyncResource {
@@ -564,7 +568,7 @@ class Test extends AsyncResource {
564568
}
565569
}
566570

567-
async run(pendingSubtestsError) {
571+
async run() {
568572
this.startTime = hrtime();
569573

570574
if (this[kShouldAbort]()) {
@@ -651,7 +655,7 @@ class Test extends AsyncResource {
651655

652656
// Clean up the test. Then, try to report the results and execute any
653657
// tests that were pending due to available concurrency.
654-
this.postRun(pendingSubtestsError);
658+
this.postRun();
655659
}
656660

657661
postRun(pendingSubtestsError) {
@@ -853,13 +857,12 @@ class Suite extends Test {
853857
this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure));
854858
}),
855859
() => {
856-
this.buildPhaseFinished = true;
860+
this.buildPhaseFinished = this.parent !== null;
857861
},
858862
);
859863
} catch (err) {
860864
this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure));
861-
862-
this.buildPhaseFinished = true;
865+
this.buildPhaseFinished = this.parent !== null;
863866
}
864867
this.fn = () => {};
865868
}
@@ -883,7 +886,7 @@ class Suite extends Test {
883886
return;
884887
}
885888

886-
if (this.parent.hooks.before.length > 0) {
889+
if (this.parent?.hooks.before.length > 0) {
887890
await this.parent.runHook('before', this.parent.getRunArgs());
888891
}
889892

@@ -908,7 +911,9 @@ class Suite extends Test {
908911
stopPromise?.[SymbolDispose]();
909912
}
910913

911-
this.postRun();
914+
if (this.parent !== null) {
915+
this.postRun();
916+
}
912917
}
913918
}
914919

test/fixtures/test-runner/output/abort.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require('../../../common');
44
const test = require('node:test');
55

6-
test('promise timeout signal', { signal: AbortSignal.timeout(1) }, async (t) => {
6+
test('promise timeout signal', { signal: AbortSignal.timeout(100) }, async (t) => {
77
await Promise.all([
88
t.test('ok 1', async () => {}),
99
t.test('ok 2', () => {}),
@@ -23,7 +23,7 @@ test('promise abort signal', { signal: AbortSignal.abort() }, async (t) => {
2323
await t.test('should not appear', () => {});
2424
});
2525

26-
test('callback timeout signal', { signal: AbortSignal.timeout(1) }, (t, done) => {
26+
test('callback timeout signal', { signal: AbortSignal.timeout(100) }, (t, done) => {
2727
t.test('ok 1', async () => {});
2828
t.test('ok 2', () => {});
2929
t.test('ok 3', { signal: t.signal }, async () => {});
@@ -41,7 +41,7 @@ test('callback abort signal', { signal: AbortSignal.abort() }, (t, done) => {
4141
t.test('should not appear', done);
4242
});
4343

44-
// AbortSignal.timeout(1) doesn't prevent process from closing
44+
// AbortSignal.timeout doesn't prevent process from closing
4545
// thus we have to keep the process open to prevent cancelation
4646
// of the entire test tree
4747
setTimeout(() => {}, 1000);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ TAP version 13
2727
failureType: 'cancelledByParent'
2828
error: 'test did not finish before its parent and was cancelled'
2929
code: 'ERR_TEST_FAILURE'
30+
stack: |-
31+
async Promise.all (index 0)
3032
...
3133
# Subtest: not ok 2
3234
not ok 6 - not ok 2
@@ -35,6 +37,8 @@ TAP version 13
3537
failureType: 'cancelledByParent'
3638
error: 'test did not finish before its parent and was cancelled'
3739
code: 'ERR_TEST_FAILURE'
40+
stack: |-
41+
async Promise.all (index 0)
3842
...
3943
# Subtest: not ok 3
4044
not ok 7 - not ok 3
@@ -157,6 +161,8 @@ not ok 2 - promise abort signal
157161
failureType: 'cancelledByParent'
158162
error: 'test did not finish before its parent and was cancelled'
159163
code: 'ERR_TEST_FAILURE'
164+
stack: |-
165+
async Promise.all (index 2)
160166
...
161167
# Subtest: not ok 2
162168
not ok 6 - not ok 2
@@ -165,6 +171,8 @@ not ok 2 - promise abort signal
165171
failureType: 'cancelledByParent'
166172
error: 'test did not finish before its parent and was cancelled'
167173
code: 'ERR_TEST_FAILURE'
174+
stack: |-
175+
async Promise.all (index 2)
168176
...
169177
# Subtest: not ok 3
170178
not ok 7 - not ok 3

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
TAP version 13
12
before
23
2.1
34
2.2
@@ -6,7 +7,6 @@ beforeEach
67
4.1
78
afterEach
89
4.2
9-
TAP version 13
1010
# Subtest: 1 before describe
1111
# Subtest: test 1
1212
not ok 1 - test 1
@@ -15,6 +15,8 @@ TAP version 13
1515
failureType: 'cancelledByParent'
1616
error: 'test did not finish before its parent and was cancelled'
1717
code: 'ERR_TEST_FAILURE'
18+
stack: |-
19+
async Promise.all (index 0)
1820
...
1921
# Subtest: test 2
2022
not ok 2 - test 2
@@ -23,6 +25,8 @@ TAP version 13
2325
failureType: 'cancelledByParent'
2426
error: 'test did not finish before its parent and was cancelled'
2527
code: 'ERR_TEST_FAILURE'
28+
stack: |-
29+
async Promise.all (index 0)
2630
...
2731
1..2
2832
not ok 1 - 1 before describe
@@ -126,6 +130,8 @@ not ok 3 - 3 beforeEach describe
126130
failureType: 'subtestsFailed'
127131
error: '2 subtests failed'
128132
code: 'ERR_TEST_FAILURE'
133+
stack: |-
134+
async Promise.all (index 2)
129135
...
130136
# Subtest: 4 afterEach describe
131137
# Subtest: test 1
@@ -176,6 +182,8 @@ not ok 4 - 4 afterEach describe
176182
failureType: 'subtestsFailed'
177183
error: '2 subtests failed'
178184
code: 'ERR_TEST_FAILURE'
185+
stack: |-
186+
async Promise.all (index 3)
179187
...
180188
1..4
181189
# tests 8

test/fixtures/test-runner/output/abort_suite.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require('../../../common');
44
const { describe, it } = require('node:test');
55

6-
describe('describe timeout signal', { signal: AbortSignal.timeout(1) }, (t) => {
6+
describe('describe timeout signal', { signal: AbortSignal.timeout(100) }, (t) => {
77
it('ok 1', async () => {});
88
it('ok 2', () => {});
99
it('ok 3', { signal: t.signal }, async () => {});
@@ -21,7 +21,7 @@ describe('describe abort signal', { signal: AbortSignal.abort() }, () => {
2121
it('should not appear', () => {});
2222
});
2323

24-
// AbortSignal.timeout(1) doesn't prevent process from closing
24+
// AbortSignal.timeout doesn't prevent process from closing
2525
// thus we have to keep the process open to prevent cancelation
2626
// of the entire test tree
2727
setTimeout(() => {}, 1000);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ TAP version 13
2727
failureType: 'cancelledByParent'
2828
error: 'test did not finish before its parent and was cancelled'
2929
code: 'ERR_TEST_FAILURE'
30+
stack: |-
31+
async Promise.all (index 0)
3032
...
3133
# Subtest: not ok 2
3234
not ok 6 - not ok 2
@@ -35,6 +37,8 @@ TAP version 13
3537
failureType: 'cancelledByParent'
3638
error: 'test did not finish before its parent and was cancelled'
3739
code: 'ERR_TEST_FAILURE'
40+
stack: |-
41+
async Promise.all (index 0)
3842
...
3943
# Subtest: not ok 3
4044
not ok 7 - not ok 3
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as common from '../../../common/index.mjs';
2+
import { describe, test } from "node:test";
3+
import { setTimeout } from "node:timers/promises";
4+
5+
test("test", common.mustCall(() => {}));
6+
describe("suite", common.mustCall(async () => {
7+
test("test", common.mustCall(() => {}));
8+
await setTimeout(10);
9+
test("scheduled async", common.mustCall(() => {}));
10+
}));
11+
12+
await setTimeout(10);
13+
test("scheduled async", common.mustCall(() => {}));
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
TAP version 13
2+
# Subtest: test
3+
ok 1 - test
4+
---
5+
duration_ms: *
6+
...
7+
# Subtest: suite
8+
# Subtest: test
9+
ok 1 - test
10+
---
11+
duration_ms: *
12+
...
13+
# Subtest: scheduled async
14+
ok 2 - scheduled async
15+
---
16+
duration_ms: *
17+
...
18+
1..2
19+
ok 2 - suite
20+
---
21+
duration_ms: *
22+
type: 'suite'
23+
...
24+
# Subtest: scheduled async
25+
ok 3 - scheduled async
26+
---
27+
duration_ms: *
28+
...
29+
1..3
30+
# tests 4
31+
# suites 1
32+
# pass 4
33+
# fail 0
34+
# cancelled 0
35+
# skipped 0
36+
# todo 0
37+
# duration_ms *

0 commit comments

Comments
 (0)