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 f68189b

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 5c27704 commit f68189b
Copy full SHA for f68189b

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
@@ -3423,6 +3423,9 @@ Emitted when code coverage is enabled and all tests have completed.
34233423
`undefined` if the test was run through the REPL.
34243424
* `name` {string} The test name.
34253425
* `nesting` {number} The nesting level of the test.
3426+
* `testId` {number} A numeric identifier for this test instance, unique
3427+
within the test file's process. Consistent across all events for the same
3428+
test instance, enabling reliable correlation in custom reporters.
34263429
* `testNumber` {number} The ordinal number of the test.
34273430
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
34283431
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
@@ -3443,6 +3446,9 @@ The corresponding declaration ordered events are `'test:pass'` and `'test:fail'`
34433446
`undefined` if the test was run through the REPL.
34443447
* `name` {string} The test name.
34453448
* `nesting` {number} The nesting level of the test.
3449+
* `testId` {number} A numeric identifier for this test instance, unique
3450+
within the test file's process. Consistent across all events for the same
3451+
test instance, enabling reliable correlation in custom reporters.
34463452
* `type` {string} The test type. Either `'suite'` or `'test'`.
34473453

34483454
Emitted when a test is dequeued, right before it is executed.
@@ -3481,6 +3487,9 @@ defined.
34813487
`undefined` if the test was run through the REPL.
34823488
* `name` {string} The test name.
34833489
* `nesting` {number} The nesting level of the test.
3490+
* `testId` {number} A numeric identifier for this test instance, unique
3491+
within the test file's process. Consistent across all events for the same
3492+
test instance, enabling reliable correlation in custom reporters.
34843493
* `type` {string} The test type. Either `'suite'` or `'test'`.
34853494

34863495
Emitted when a test is enqueued for execution.
@@ -3504,6 +3513,9 @@ Emitted when a test is enqueued for execution.
35043513
`undefined` if the test was run through the REPL.
35053514
* `name` {string} The test name.
35063515
* `nesting` {number} The nesting level of the test.
3516+
* `testId` {number} A numeric identifier for this test instance, unique
3517+
within the test file's process. Consistent across all events for the same
3518+
test instance, enabling reliable correlation in custom reporters.
35073519
* `testNumber` {number} The ordinal number of the test.
35083520
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
35093521
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
@@ -3558,6 +3570,9 @@ since the parent runner only knows about file-level tests. When using
35583570
`undefined` if the test was run through the REPL.
35593571
* `name` {string} The test name.
35603572
* `nesting` {number} The nesting level of the test.
3573+
* `testId` {number} A numeric identifier for this test instance, unique
3574+
within the test file's process. Consistent across all events for the same
3575+
test instance, enabling reliable correlation in custom reporters.
35613576
* `testNumber` {number} The ordinal number of the test.
35623577
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
35633578
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
@@ -3594,6 +3609,9 @@ defined.
35943609
`undefined` if the test was run through the REPL.
35953610
* `name` {string} The test name.
35963611
* `nesting` {number} The nesting level of the test.
3612+
* `testId` {number} A numeric identifier for this test instance, unique
3613+
within the test file's process. Consistent across all events for the same
3614+
test instance, enabling reliable correlation in custom reporters.
35973615

35983616
Emitted when a test starts reporting its own and its subtests status.
35993617
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();
@@ -890,7 +893,7 @@ class Test extends AsyncResource {
890893
const deferred = this.dequeuePendingSubtest();
891894
const test = deferred.test;
892895
this.assignReportOrder(test);
893-
test.reporter.dequeue(test.nesting, test.loc, test.name, this.reportedType);
896+
test.reporter.dequeue(test.nesting, test.loc, test.name, this.reportedType, test.testId);
894897
await test.run();
895898
deferred.resolve();
896899
}
@@ -1147,7 +1150,7 @@ class Test extends AsyncResource {
11471150
// it. Otherwise, return a Promise to the caller and mark the test as
11481151
// pending for later execution.
11491152
this.parent.unfinishedSubtests.add(this);
1150-
this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType);
1153+
this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType, this.testId);
11511154
if (this.root.harness.buildPromise || !this.parent.hasConcurrency()) {
11521155
const deferred = PromiseWithResolvers();
11531156

@@ -1170,7 +1173,7 @@ class Test extends AsyncResource {
11701173
}
11711174

11721175
this.parent.assignReportOrder(this);
1173-
this.reporter.dequeue(this.nesting, this.loc, this.name, this.reportedType);
1176+
this.reporter.dequeue(this.nesting, this.loc, this.name, this.reportedType, this.testId);
11741177
return this.run();
11751178
}
11761179

@@ -1432,7 +1435,10 @@ class Test extends AsyncResource {
14321435
const report = this.getReportDetails();
14331436
report.details.passed = this.passed;
14341437
this.testNumber ||= ++this.parent.outputSubtestCount;
1435-
this.reporter.complete(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive);
1438+
this.reporter.complete(
1439+
this.nesting, this.loc, this.testNumber, this.name,
1440+
report.details, report.directive, this.testId,
1441+
);
14361442
this.parent.activeSubtests--;
14371443
}
14381444

@@ -1585,9 +1591,15 @@ class Test extends AsyncResource {
15851591
const report = this.getReportDetails();
15861592

15871593
if (this.passed) {
1588-
this.reporter.ok(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive);
1594+
this.reporter.ok(
1595+
this.nesting, this.loc, this.testNumber, this.name,
1596+
report.details, report.directive, this.testId,
1597+
);
15891598
} else {
1590-
this.reporter.fail(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive);
1599+
this.reporter.fail(
1600+
this.nesting, this.loc, this.testNumber, this.name,
1601+
report.details, report.directive, this.testId,
1602+
);
15911603
}
15921604

15931605
for (let i = 0; i < this.diagnostics.length; i++) {
@@ -1601,7 +1613,7 @@ class Test extends AsyncResource {
16011613
}
16021614
this.#reportedSubtest = true;
16031615
this.parent.reportStarted();
1604-
this.reporter.start(this.nesting, this.loc, this.name);
1616+
this.reporter.start(this.nesting, this.loc, this.name, this.testId);
16051617
}
16061618

16071619
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.