| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3423,6 +3423,9 @@ Emitted when code coverage is enabled and all tests have completed. | |||
| 3423 | 3423 | `undefined` if the test was run through the REPL. | |
| 3424 | 3424 | * `name` {string} The test name. | |
| 3425 | 3425 | * `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. | ||
| 3426 | 3429 | * `testNumber` {number} The ordinal number of the test. | |
| 3427 | 3430 | * `todo` {string|boolean|undefined} Present if [`context.todo`][] is called | |
| 3428 | 3431 | * `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'` | |||
| 3443 | 3446 | `undefined` if the test was run through the REPL. | |
| 3444 | 3447 | * `name` {string} The test name. | |
| 3445 | 3448 | * `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. | ||
| 3446 | 3452 | * `type` {string} The test type. Either `'suite'` or `'test'`. | |
| 3447 | 3453 | ||
| 3448 | 3454 | Emitted when a test is dequeued, right before it is executed. | |
@@ -3481,6 +3487,9 @@ defined. | |||
| 3481 | 3487 | `undefined` if the test was run through the REPL. | |
| 3482 | 3488 | * `name` {string} The test name. | |
| 3483 | 3489 | * `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. | ||
| 3484 | 3493 | * `type` {string} The test type. Either `'suite'` or `'test'`. | |
| 3485 | 3494 | ||
| 3486 | 3495 | Emitted when a test is enqueued for execution. | |
@@ -3504,6 +3513,9 @@ Emitted when a test is enqueued for execution. | |||
| 3504 | 3513 | `undefined` if the test was run through the REPL. | |
| 3505 | 3514 | * `name` {string} The test name. | |
| 3506 | 3515 | * `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. | ||
| 3507 | 3519 | * `testNumber` {number} The ordinal number of the test. | |
| 3508 | 3520 | * `todo` {string|boolean|undefined} Present if [`context.todo`][] is called | |
| 3509 | 3521 | * `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 | |||
| 3558 | 3570 | `undefined` if the test was run through the REPL. | |
| 3559 | 3571 | * `name` {string} The test name. | |
| 3560 | 3572 | * `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. | ||
| 3561 | 3576 | * `testNumber` {number} The ordinal number of the test. | |
| 3562 | 3577 | * `todo` {string|boolean|undefined} Present if [`context.todo`][] is called | |
| 3563 | 3578 | * `skip` {string|boolean|undefined} Present if [`context.skip`][] is called | |
@@ -3594,6 +3609,9 @@ defined. | |||
| 3594 | 3609 | `undefined` if the test was run through the REPL. | |
| 3595 | 3610 | * `name` {string} The test name. | |
| 3596 | 3611 | * `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. | ||
| 3597 | 3615 | ||
| 3598 | 3616 | Emitted when a test starts reporting its own and its subtests status. | |
| 3599 | 3617 | This event is guaranteed to be emitted in the same order as the tests are | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -590,6 +590,8 @@ class Test extends AsyncResource { | |||
| 590 | 590 | this.timeout = kDefaultTimeout; | |
| 591 | 591 | this.entryFile = entryFile; | |
| 592 | 592 | this.testDisambiguator = new SafeMap(); | |
| 593 | + this.nextTestId = 1; | ||
| 594 | + this.testId = 0; | ||
| 593 | 595 | } else { | |
| 594 | 596 | const nesting = parent.parent === null ? parent.nesting : | |
| 595 | 597 | parent.nesting + 1; | |
@@ -606,6 +608,7 @@ class Test extends AsyncResource { | |||
| 606 | 608 | this.childNumber = parent.subtests.length + 1; | |
| 607 | 609 | this.timeout = parent.timeout; | |
| 608 | 610 | this.entryFile = parent.entryFile; | |
| 611 | + this.testId = this.root.nextTestId++; | ||
| 609 | 612 | ||
| 610 | 613 | if (isFilteringByName) { | |
| 611 | 614 | this.filteredByName = this.willBeFilteredByName(); | |
@@ -890,7 +893,7 @@ class Test extends AsyncResource { | |||
| 890 | 893 | const deferred = this.dequeuePendingSubtest(); | |
| 891 | 894 | const test = deferred.test; | |
| 892 | 895 | 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); | ||
| 894 | 897 | await test.run(); | |
| 895 | 898 | deferred.resolve(); | |
| 896 | 899 | } | |
@@ -1147,7 +1150,7 @@ class Test extends AsyncResource { | |||
| 1147 | 1150 | // it. Otherwise, return a Promise to the caller and mark the test as | |
| 1148 | 1151 | // pending for later execution. | |
| 1149 | 1152 | 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); | ||
| 1151 | 1154 | if (this.root.harness.buildPromise || !this.parent.hasConcurrency()) { | |
| 1152 | 1155 | const deferred = PromiseWithResolvers(); | |
| 1153 | 1156 | ||
@@ -1170,7 +1173,7 @@ class Test extends AsyncResource { | |||
| 1170 | 1173 | } | |
| 1171 | 1174 | ||
| 1172 | 1175 | 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); | ||
| 1174 | 1177 | return this.run(); | |
| 1175 | 1178 | } | |
| 1176 | 1179 | ||
@@ -1432,7 +1435,10 @@ class Test extends AsyncResource { | |||
| 1432 | 1435 | const report = this.getReportDetails(); | |
| 1433 | 1436 | report.details.passed = this.passed; | |
| 1434 | 1437 | 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 | + ); | ||
| 1436 | 1442 | this.parent.activeSubtests--; | |
| 1437 | 1443 | } | |
| 1438 | 1444 | ||
@@ -1585,9 +1591,15 @@ class Test extends AsyncResource { | |||
| 1585 | 1591 | const report = this.getReportDetails(); | |
| 1586 | 1592 | ||
| 1587 | 1593 | 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 | + ); | ||
| 1589 | 1598 | } 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 | + ); | ||
| 1591 | 1603 | } | |
| 1592 | 1604 | ||
| 1593 | 1605 | for (let i = 0; i < this.diagnostics.length; i++) { | |
@@ -1601,7 +1613,7 @@ class Test extends AsyncResource { | |||
| 1601 | 1613 | } | |
| 1602 | 1614 | this.#reportedSubtest = true; | |
| 1603 | 1615 | 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); | ||
| 1605 | 1617 | } | |
| 1606 | 1618 | ||
| 1607 | 1619 | clearExecutionTime() { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,36 +34,39 @@ class TestsStream extends Readable { | |||
| 34 | 34 | } | |
| 35 | 35 | } | |
| 36 | 36 | ||
| 37 | - fail(nesting, loc, testNumber, name, details, directive) { | ||
| 37 | + fail(nesting, loc, testNumber, name, details, directive, testId) { | ||
| 38 | 38 | this[kEmitMessage]('test:fail', { | |
| 39 | 39 | __proto__: null, | |
| 40 | 40 | name, | |
| 41 | 41 | nesting, | |
| 42 | 42 | testNumber, | |
| 43 | + testId, | ||
| 43 | 44 | details, | |
| 44 | 45 | ...loc, | |
| 45 | 46 | ...directive, | |
| 46 | 47 | }); | |
| 47 | 48 | } | |
| 48 | 49 | ||
| 49 | - ok(nesting, loc, testNumber, name, details, directive) { | ||
| 50 | + ok(nesting, loc, testNumber, name, details, directive, testId) { | ||
| 50 | 51 | this[kEmitMessage]('test:pass', { | |
| 51 | 52 | __proto__: null, | |
| 52 | 53 | name, | |
| 53 | 54 | nesting, | |
| 54 | 55 | testNumber, | |
| 56 | + testId, | ||
| 55 | 57 | details, | |
| 56 | 58 | ...loc, | |
| 57 | 59 | ...directive, | |
| 58 | 60 | }); | |
| 59 | 61 | } | |
| 60 | 62 | ||
| 61 | - complete(nesting, loc, testNumber, name, details, directive) { | ||
| 63 | + complete(nesting, loc, testNumber, name, details, directive, testId) { | ||
| 62 | 64 | this[kEmitMessage]('test:complete', { | |
| 63 | 65 | __proto__: null, | |
| 64 | 66 | name, | |
| 65 | 67 | nesting, | |
| 66 | 68 | testNumber, | |
| 69 | + testId, | ||
| 67 | 70 | details, | |
| 68 | 71 | ...loc, | |
| 69 | 72 | ...directive, | |
@@ -91,31 +94,34 @@ class TestsStream extends Readable { | |||
| 91 | 94 | return { __proto__: null, expectFailure: expectation ?? true }; | |
| 92 | 95 | } | |
| 93 | 96 | ||
| 94 | - enqueue(nesting, loc, name, type) { | ||
| 97 | + enqueue(nesting, loc, name, type, testId) { | ||
| 95 | 98 | this[kEmitMessage]('test:enqueue', { | |
| 96 | 99 | __proto__: null, | |
| 97 | 100 | nesting, | |
| 98 | 101 | name, | |
| 99 | 102 | type, | |
| 103 | + testId, | ||
| 100 | 104 | ...loc, | |
| 101 | 105 | }); | |
| 102 | 106 | } | |
| 103 | 107 | ||
| 104 | - dequeue(nesting, loc, name, type) { | ||
| 108 | + dequeue(nesting, loc, name, type, testId) { | ||
| 105 | 109 | this[kEmitMessage]('test:dequeue', { | |
| 106 | 110 | __proto__: null, | |
| 107 | 111 | nesting, | |
| 108 | 112 | name, | |
| 109 | 113 | type, | |
| 114 | + testId, | ||
| 110 | 115 | ...loc, | |
| 111 | 116 | }); | |
| 112 | 117 | } | |
| 113 | 118 | ||
| 114 | - start(nesting, loc, name) { | ||
| 119 | + start(nesting, loc, name, testId) { | ||
| 115 | 120 | this[kEmitMessage]('test:start', { | |
| 116 | 121 | __proto__: null, | |
| 117 | 122 | nesting, | |
| 118 | 123 | name, | |
| 124 | + testId, | ||
| 119 | 125 | ...loc, | |
| 120 | 126 | }); | |
| 121 | 127 | } | |
| Original file line number | Diff line number | Diff 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 | + }); | ||
| Original file line number | Diff line number | Diff 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 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments