Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6fa62b7

Browse filesBrowse files
MoLowaduh95
authored andcommitted
test_runner: add testId to test events
Signed-off-by: Moshe Atlow <moshe@atlow.co.il> PR-URL: #62772 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 40a4337 commit 6fa62b7
Copy full SHA for 6fa62b7

5 files changed

+146-13Lines changed: 146 additions & 13 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/test.md‎

Copy file name to clipboardExpand all lines: doc/api/test.md
+18Lines changed: 18 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3431,6 +3431,9 @@ Emitted when code coverage is enabled and all tests have completed.
34313431
`undefined` if the test was run through the REPL.
34323432
* `name` {string} The test name.
34333433
* `nesting` {number} The nesting level of the test.
3434+
* `testId` {number} A numeric identifier for this test instance, unique
3435+
within the test file's process. Consistent across all events for the same
3436+
test instance, enabling reliable correlation in custom reporters.
34343437
* `testNumber` {number} The ordinal number of the test.
34353438
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
34363439
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
@@ -3451,6 +3454,9 @@ The corresponding declaration ordered events are `'test:pass'` and `'test:fail'`
34513454
`undefined` if the test was run through the REPL.
34523455
* `name` {string} The test name.
34533456
* `nesting` {number} The nesting level of the test.
3457+
* `testId` {number} A numeric identifier for this test instance, unique
3458+
within the test file's process. Consistent across all events for the same
3459+
test instance, enabling reliable correlation in custom reporters.
34543460
* `type` {string} The test type. Either `'suite'` or `'test'`.
34553461

34563462
Emitted when a test is dequeued, right before it is executed.
@@ -3489,6 +3495,9 @@ defined.
34893495
`undefined` if the test was run through the REPL.
34903496
* `name` {string} The test name.
34913497
* `nesting` {number} The nesting level of the test.
3498+
* `testId` {number} A numeric identifier for this test instance, unique
3499+
within the test file's process. Consistent across all events for the same
3500+
test instance, enabling reliable correlation in custom reporters.
34923501
* `type` {string} The test type. Either `'suite'` or `'test'`.
34933502

34943503
Emitted when a test is enqueued for execution.
@@ -3512,6 +3521,9 @@ Emitted when a test is enqueued for execution.
35123521
`undefined` if the test was run through the REPL.
35133522
* `name` {string} The test name.
35143523
* `nesting` {number} The nesting level of the test.
3524+
* `testId` {number} A numeric identifier for this test instance, unique
3525+
within the test file's process. Consistent across all events for the same
3526+
test instance, enabling reliable correlation in custom reporters.
35153527
* `testNumber` {number} The ordinal number of the test.
35163528
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
35173529
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
@@ -3566,6 +3578,9 @@ since the parent runner only knows about file-level tests. When using
35663578
`undefined` if the test was run through the REPL.
35673579
* `name` {string} The test name.
35683580
* `nesting` {number} The nesting level of the test.
3581+
* `testId` {number} A numeric identifier for this test instance, unique
3582+
within the test file's process. Consistent across all events for the same
3583+
test instance, enabling reliable correlation in custom reporters.
35693584
* `testNumber` {number} The ordinal number of the test.
35703585
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
35713586
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
@@ -3602,6 +3617,9 @@ defined.
36023617
`undefined` if the test was run through the REPL.
36033618
* `name` {string} The test name.
36043619
* `nesting` {number} The nesting level of the test.
3620+
* `testId` {number} A numeric identifier for this test instance, unique
3621+
within the test file's process. Consistent across all events for the same
3622+
test instance, enabling reliable correlation in custom reporters.
36053623

36063624
Emitted when a test starts reporting its own and its subtests status.
36073625
This event is guaranteed to be emitted in the same order as the tests are
Collapse file

‎lib/internal/test_runner/test.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/test.js
+19-7Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,8 @@ class Test extends AsyncResource {
590590
this.timeout = kDefaultTimeout;
591591
this.entryFile = entryFile;
592592
this.testDisambiguator = new SafeMap();
593+
this.nextTestId = 1;
594+
this.testId = 0;
593595
} else {
594596
const nesting = parent.parent === null ? parent.nesting :
595597
parent.nesting + 1;
@@ -606,6 +608,7 @@ class Test extends AsyncResource {
606608
this.childNumber = parent.subtests.length + 1;
607609
this.timeout = parent.timeout;
608610
this.entryFile = parent.entryFile;
611+
this.testId = this.root.nextTestId++;
609612

610613
if (isFilteringByName) {
611614
this.filteredByName = this.willBeFilteredByName();
@@ -884,7 +887,7 @@ class Test extends AsyncResource {
884887
const deferred = this.dequeuePendingSubtest();
885888
const test = deferred.test;
886889
this.assignReportOrder(test);
887-
test.reporter.dequeue(test.nesting, test.loc, test.name, this.reportedType);
890+
test.reporter.dequeue(test.nesting, test.loc, test.name, this.reportedType, test.testId);
888891
await test.run();
889892
deferred.resolve();
890893
}
@@ -1141,7 +1144,7 @@ class Test extends AsyncResource {
11411144
// it. Otherwise, return a Promise to the caller and mark the test as
11421145
// pending for later execution.
11431146
this.parent.unfinishedSubtests.add(this);
1144-
this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType);
1147+
this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType, this.testId);
11451148
if (this.root.harness.buildPromise || !this.parent.hasConcurrency()) {
11461149
const deferred = PromiseWithResolvers();
11471150

@@ -1164,7 +1167,7 @@ class Test extends AsyncResource {
11641167
}
11651168

11661169
this.parent.assignReportOrder(this);
1167-
this.reporter.dequeue(this.nesting, this.loc, this.name, this.reportedType);
1170+
this.reporter.dequeue(this.nesting, this.loc, this.name, this.reportedType, this.testId);
11681171
return this.run();
11691172
}
11701173

@@ -1426,7 +1429,10 @@ class Test extends AsyncResource {
14261429
const report = this.getReportDetails();
14271430
report.details.passed = this.passed;
14281431
this.testNumber ||= ++this.parent.outputSubtestCount;
1429-
this.reporter.complete(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive);
1432+
this.reporter.complete(
1433+
this.nesting, this.loc, this.testNumber, this.name,
1434+
report.details, report.directive, this.testId,
1435+
);
14301436
this.parent.activeSubtests--;
14311437
}
14321438

@@ -1579,9 +1585,15 @@ class Test extends AsyncResource {
15791585
const report = this.getReportDetails();
15801586

15811587
if (this.passed) {
1582-
this.reporter.ok(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive);
1588+
this.reporter.ok(
1589+
this.nesting, this.loc, this.testNumber, this.name,
1590+
report.details, report.directive, this.testId,
1591+
);
15831592
} else {
1584-
this.reporter.fail(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive);
1593+
this.reporter.fail(
1594+
this.nesting, this.loc, this.testNumber, this.name,
1595+
report.details, report.directive, this.testId,
1596+
);
15851597
}
15861598

15871599
for (let i = 0; i < this.diagnostics.length; i++) {
@@ -1595,7 +1607,7 @@ class Test extends AsyncResource {
15951607
}
15961608
this.#reportedSubtest = true;
15971609
this.parent.reportStarted();
1598-
this.reporter.start(this.nesting, this.loc, this.name);
1610+
this.reporter.start(this.nesting, this.loc, this.name, this.testId);
15991611
}
16001612

16011613
clearExecutionTime() {
Collapse file

‎lib/internal/test_runner/tests_stream.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/tests_stream.js
+12-6Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,39 @@ class TestsStream extends Readable {
3434
}
3535
}
3636

37-
fail(nesting, loc, testNumber, name, details, directive) {
37+
fail(nesting, loc, testNumber, name, details, directive, testId) {
3838
this[kEmitMessage]('test:fail', {
3939
__proto__: null,
4040
name,
4141
nesting,
4242
testNumber,
43+
testId,
4344
details,
4445
...loc,
4546
...directive,
4647
});
4748
}
4849

49-
ok(nesting, loc, testNumber, name, details, directive) {
50+
ok(nesting, loc, testNumber, name, details, directive, testId) {
5051
this[kEmitMessage]('test:pass', {
5152
__proto__: null,
5253
name,
5354
nesting,
5455
testNumber,
56+
testId,
5557
details,
5658
...loc,
5759
...directive,
5860
});
5961
}
6062

61-
complete(nesting, loc, testNumber, name, details, directive) {
63+
complete(nesting, loc, testNumber, name, details, directive, testId) {
6264
this[kEmitMessage]('test:complete', {
6365
__proto__: null,
6466
name,
6567
nesting,
6668
testNumber,
69+
testId,
6770
details,
6871
...loc,
6972
...directive,
@@ -91,31 +94,34 @@ class TestsStream extends Readable {
9194
return { __proto__: null, expectFailure: expectation ?? true };
9295
}
9396

94-
enqueue(nesting, loc, name, type) {
97+
enqueue(nesting, loc, name, type, testId) {
9598
this[kEmitMessage]('test:enqueue', {
9699
__proto__: null,
97100
nesting,
98101
name,
99102
type,
103+
testId,
100104
...loc,
101105
});
102106
}
103107

104-
dequeue(nesting, loc, name, type) {
108+
dequeue(nesting, loc, name, type, testId) {
105109
this[kEmitMessage]('test:dequeue', {
106110
__proto__: null,
107111
nesting,
108112
name,
109113
type,
114+
testId,
110115
...loc,
111116
});
112117
}
113118

114-
start(nesting, loc, name) {
119+
start(nesting, loc, name, testId) {
115120
this[kEmitMessage]('test:start', {
116121
__proto__: null,
117122
nesting,
118123
name,
124+
testId,
119125
...loc,
120126
});
121127
}
Collapse file
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
const { describe, it } = require('node:test');
3+
const assert = require('node:assert');
4+
5+
// Factory that creates subtests at the SAME source location.
6+
// Multiple concurrent `it` blocks calling this will have subtests
7+
// sharing file:line:column — but each should get a distinct testId.
8+
function makeSubtest(shouldFail) {
9+
return async function(t) {
10+
await t.test('e2e', async () => {
11+
if (shouldFail) assert.fail('intentional');
12+
});
13+
};
14+
}
15+
16+
describe('suite', { concurrency: 10_000 }, () => {
17+
it('test-A (passes)', makeSubtest(false));
18+
it('test-B (passes)', makeSubtest(false));
19+
it('test-C (fails)', makeSubtest(true));
20+
it('test-D (passes)', makeSubtest(false));
21+
});
Collapse file
+76Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('node:assert');
4+
const { run } = require('node:test');
5+
const fixtures = require('../common/fixtures');
6+
7+
async function collectEvents() {
8+
const events = [];
9+
const stream = run({
10+
files: [fixtures.path('test-runner/test-id-fixture.js')],
11+
isolation: 'none',
12+
});
13+
for await (const event of stream) {
14+
events.push(event);
15+
}
16+
return events;
17+
}
18+
19+
async function main() {
20+
const events = await collectEvents();
21+
22+
// 1. Every per-test event should have a numeric testId.
23+
const perTestTypes = new Set([
24+
'test:start', 'test:complete', 'test:fail',
25+
'test:pass', 'test:enqueue', 'test:dequeue',
26+
]);
27+
for (const event of events) {
28+
if (perTestTypes.has(event.type)) {
29+
assert.strictEqual(typeof event.data.testId, 'number',
30+
`${event.type} for "${event.data.name}" should have numeric testId`);
31+
}
32+
}
33+
34+
// 2. test:start and test:fail for the same instance should share testId.
35+
const failEvent = events.find(
36+
(e) => e.type === 'test:fail' && e.data.name === 'e2e',
37+
);
38+
assert.ok(failEvent, 'should have a test:fail for "e2e"');
39+
40+
const startEvent = events.find(
41+
(e) => e.type === 'test:start' &&
42+
e.data.testId === failEvent.data.testId,
43+
);
44+
assert.ok(startEvent, 'should have a test:start with matching testId');
45+
assert.strictEqual(startEvent.data.name, 'e2e');
46+
47+
// 3. Concurrent instances at the same source location get distinct testIds.
48+
const e2eStarts = events.filter(
49+
(e) => e.type === 'test:start' && e.data.name === 'e2e',
50+
);
51+
assert.strictEqual(e2eStarts.length, 4);
52+
53+
const testIds = e2eStarts.map((e) => e.data.testId);
54+
const uniqueIds = new Set(testIds);
55+
assert.strictEqual(uniqueIds.size, 4,
56+
`all 4 "e2e" instances should have distinct testIds, got: ${testIds}`);
57+
58+
// 4. test:complete for the same instance shares testId with test:start.
59+
const completeEvents = events.filter(
60+
(e) => e.type === 'test:complete' && e.data.name === 'e2e',
61+
);
62+
for (const complete of completeEvents) {
63+
const matchingStart = e2eStarts.find(
64+
(s) => s.data.testId === complete.data.testId,
65+
);
66+
assert.ok(matchingStart,
67+
`test:complete (testId=${complete.data.testId}) should match a test:start`);
68+
}
69+
70+
console.log('All testId assertions passed');
71+
}
72+
73+
main().catch((err) => {
74+
console.error(err);
75+
process.exit(1);
76+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.