GeezerVison is showing github.com — test_runner: report `entryFile` in `TestStream` events · nodejs/node@56ce83b
File treeExpand file treeCollapse file tree Open diff view settings fixtures/test-runner/entry-file
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/test.md +42Lines changed: 42 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -3438,6 +3438,10 @@ added: | 3438 | 3438 | - v18.9.0 | 3439 | 3439 | - v16.19.0 | 3440 | 3440 | changes: | | 3441 | + - version: REPLACEME | | 3442 | + pr-url: https://github.com/nodejs/node/pull/64309 | | 3443 | + description: Added `entryFile` to events forwarded from child processes | | 3444 | + when tests run with process isolation. | 3441 | 3445 | - version: v26.3.0 | 3442 | 3446 | pr-url: https://github.com/nodejs/node/pull/63435 | 3443 | 3447 | description: Added `parentId` to test events that carry a `testId`. | @@ -3522,6 +3526,10 @@ Emitted when code coverage is enabled and all tests have completed. | 3522 | 3526 | * `cause` {Error} The actual error thrown by the test. | 3523 | 3527 | * `type` {string|undefined} The type of the test, used to denote whether | 3524 | 3528 | this is a suite. | | 3529 | + * `entryFile` {string|undefined} The path of the test file that was | | 3530 | + executed as the entry point of the child process that emitted this event. | | 3531 | + Only present when tests run with process isolation. May differ from | | 3532 | + `file` when the test is defined in a module imported by the entry file. | 3525 | 3533 | * `file` {string|undefined} The path of the test file, | 3526 | 3534 | `undefined` if test was run through the REPL. | 3527 | 3535 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3551,6 +3559,10 @@ The corresponding declaration ordered events are `'test:pass'` and `'test:fail'` | 3551 | 3559 | * `data` {Object} | 3552 | 3560 | * `column` {number|undefined} The column number where the test is defined, or | 3553 | 3561 | `undefined` if the test was run through the REPL. | | 3562 | + * `entryFile` {string|undefined} The path of the test file that was | | 3563 | + executed as the entry point of the child process that emitted this event. | | 3564 | + Only present when tests run with process isolation. May differ from | | 3565 | + `file` when the test is defined in a module imported by the entry file. | 3554 | 3566 | * `file` {string|undefined} The path of the test file, | 3555 | 3567 | `undefined` if test was run through the REPL. | 3556 | 3568 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3577,6 +3589,10 @@ defined. The corresponding declaration ordered event is `'test:start'`. | 3577 | 3589 | * `data` {Object} | 3578 | 3590 | * `column` {number|undefined} The column number where the test is defined, or | 3579 | 3591 | `undefined` if the test was run through the REPL. | | 3592 | + * `entryFile` {string|undefined} The path of the test file that was | | 3593 | + executed as the entry point of the child process that emitted this event. | | 3594 | + Only present when tests run with process isolation. May differ from | | 3595 | + `file` when the test is defined in a module imported by the entry file. | 3580 | 3596 | * `file` {string|undefined} The path of the test file, | 3581 | 3597 | `undefined` if test was run through the REPL. | 3582 | 3598 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3598,6 +3614,10 @@ defined. | 3598 | 3614 | * `data` {Object} | 3599 | 3615 | * `column` {number|undefined} The column number where the test is defined, or | 3600 | 3616 | `undefined` if the test was run through the REPL. | | 3617 | + * `entryFile` {string|undefined} The path of the test file that was | | 3618 | + executed as the entry point of the child process that emitted this event. | | 3619 | + Only present when tests run with process isolation. May differ from | | 3620 | + `file` when the test is defined in a module imported by the entry file. | 3601 | 3621 | * `file` {string|undefined} The path of the test file, | 3602 | 3622 | `undefined` if test was run through the REPL. | 3603 | 3623 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3630,6 +3650,10 @@ Emitted when a test is enqueued for execution. | 3630 | 3650 | this is a suite. | 3631 | 3651 | * `attempt` {number|undefined} The attempt number of the test run, | 3632 | 3652 | present only when using the [`--test-rerun-failures`][] flag. | | 3653 | + * `entryFile` {string|undefined} The path of the test file that was | | 3654 | + executed as the entry point of the child process that emitted this event. | | 3655 | + Only present when tests run with process isolation. May differ from | | 3656 | + `file` when the test is defined in a module imported by the entry file. | 3633 | 3657 | * `file` {string|undefined} The path of the test file, | 3634 | 3658 | `undefined` if test was run through the REPL. | 3635 | 3659 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3693,6 +3717,10 @@ since the parent runner only knows about file-level tests. When using | 3693 | 3717 | present only when using the [`--test-rerun-failures`][] flag. | 3694 | 3718 | * `passed_on_attempt` {number|undefined} The attempt number the test passed on, | 3695 | 3719 | present only when using the [`--test-rerun-failures`][] flag. | | 3720 | + * `entryFile` {string|undefined} The path of the test file that was | | 3721 | + executed as the entry point of the child process that emitted this event. | | 3722 | + Only present when tests run with process isolation. May differ from | | 3723 | + `file` when the test is defined in a module imported by the entry file. | 3696 | 3724 | * `file` {string|undefined} The path of the test file, | 3697 | 3725 | `undefined` if test was run through the REPL. | 3698 | 3726 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3722,6 +3750,10 @@ The corresponding execution ordered event is `'test:complete'`. | 3722 | 3750 | * `data` {Object} | 3723 | 3751 | * `column` {number|undefined} The column number where the test is defined, or | 3724 | 3752 | `undefined` if the test was run through the REPL. | | 3753 | + * `entryFile` {string|undefined} The path of the test file that was | | 3754 | + executed as the entry point of the child process that emitted this event. | | 3755 | + Only present when tests run with process isolation. May differ from | | 3756 | + `file` when the test is defined in a module imported by the entry file. | 3725 | 3757 | * `file` {string|undefined} The path of the test file, | 3726 | 3758 | `undefined` if test was run through the REPL. | 3727 | 3759 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3738,6 +3770,10 @@ defined. | 3738 | 3770 | * `data` {Object} | 3739 | 3771 | * `column` {number|undefined} The column number where the test is defined, or | 3740 | 3772 | `undefined` if the test was run through the REPL. | | 3773 | + * `entryFile` {string|undefined} The path of the test file that was | | 3774 | + executed as the entry point of the child process that emitted this event. | | 3775 | + Only present when tests run with process isolation. May differ from | | 3776 | + `file` when the test is defined in a module imported by the entry file. | 3741 | 3777 | * `file` {string|undefined} The path of the test file, | 3742 | 3778 | `undefined` if test was run through the REPL. | 3743 | 3779 | * `line` {number|undefined} The line number where the test is defined, or | @@ -3762,6 +3798,9 @@ The corresponding execution ordered event is `'test:dequeue'`. | 3762 | 3798 | ### Event: `'test:stderr'` | 3763 | 3799 |
| 3764 | 3800 | * `data` {Object} | | 3801 | + * `entryFile` {string|undefined} The path of the test file that was | | 3802 | + executed as the entry point of the child process that emitted this event. | | 3803 | + Only present when tests run with process isolation. | 3765 | 3804 | * `file` {string} The path of the test file. | 3766 | 3805 | * `message` {string} The message written to `stderr`. | 3767 | 3806 |
| @@ -3773,6 +3812,9 @@ defined. | 3773 | 3812 | ### Event: `'test:stdout'` | 3774 | 3813 |
| 3775 | 3814 | * `data` {Object} | | 3815 | + * `entryFile` {string|undefined} The path of the test file that was | | 3816 | + executed as the entry point of the child process that emitted this event. | | 3817 | + Only present when tests run with process isolation. | 3776 | 3818 | * `file` {string} The path of the test file. | 3777 | 3819 | * `message` {string} The message written to `stdout`. | 3778 | 3820 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/runner.js +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -295,6 +295,10 @@ class FileTest extends Test { | 295 | 295 | ArrayPrototypeIncludes(kDiagnosticsFilterArgs, StringPrototypeSlice(comment, 0, firstSpaceIndex)); | 296 | 296 | } | 297 | 297 | #handleReportItem(item) { | | 298 | + // The name is empty when a single child process runs all test files. | | 299 | + if (this.name !== '') { | | 300 | + item.data.entryFile = this.loc.file; | | 301 | + } | 298 | 302 | const isTopLevel = item.data.nesting === 0; | 299 | 303 | if (isTopLevel) { | 300 | 304 | if (item.type === 'test:plan' && this.#skipReporting()) { | |
Collapse file Copy file name to clipboard +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import { test } from 'node:test'; | | 2 | +import { runShared } from './helper.mjs'; | | 3 | +test('backup A', async (t) => { await runShared(t, 'A'); }); |
Collapse file Copy file name to clipboard +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import { test } from 'node:test'; | | 2 | +import { runShared } from './helper.mjs'; | | 3 | +test('backup B', async (t) => { await runShared(t, 'B'); }); |
Collapse file Copy file name to clipboard +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +export async function runShared(t, target) { | | 2 | + await t.test(`restore ${target}`, async () => {}); | | 3 | +} |
Collapse file Copy file name to clipboard +59Lines changed: 59 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import '../common/index.mjs'; | | 2 | +import * as fixtures from '../common/fixtures.mjs'; | | 3 | +import { describe, it, run } from 'node:test'; | | 4 | +import assert from 'node:assert'; | | 5 | + | | 6 | +const aPath = fixtures.path('test-runner', 'entry-file', 'a.test.mjs'); | | 7 | +const bPath = fixtures.path('test-runner', 'entry-file', 'b.test.mjs'); | | 8 | +const helperPath = fixtures.path('test-runner', 'entry-file', 'helper.mjs'); | | 9 | + | | 10 | +async function collectEvents(options) { | | 11 | + const events = []; | | 12 | + const stream = run({ files: [aPath, bPath], ...options }); | | 13 | + stream.on('test:fail', () => {}); | | 14 | + for await (const event of stream) { | | 15 | + events.push(event); | | 16 | + } | | 17 | + return events; | | 18 | +} | | 19 | + | | 20 | +describe('entryFile attribution in reporter events', { concurrency: false }, () => { | | 21 | + it('stamps entryFile on events forwarded from child processes', async () => { | | 22 | + const events = await collectEvents({ isolation: 'process' }); | | 23 | + const checked = { __proto__: null, A: 0, B: 0 }; | | 24 | + | | 25 | + for (const { type, data } of events) { | | 26 | + if (data?.name === 'restore A' || data?.name === 'restore B') { | | 27 | + const target = data.name === 'restore A' ? 'A' : 'B'; | | 28 | + const expectedEntry = target === 'A' ? aPath : bPath; | | 29 | + assert.strictEqual(data.file, helperPath, | | 30 | + `${type} file should be the definition site`); | | 31 | + assert.strictEqual(data.entryFile, expectedEntry, | | 32 | + `${type} entryFile should be the entry file`); | | 33 | + checked[target]++; | | 34 | + } | | 35 | + } | | 36 | + | | 37 | + // Each subtest emits at least enqueue/dequeue/start/pass/complete. | | 38 | + assert.ok(checked.A >= 4, `expected events for restore A, got ${checked.A}`); | | 39 | + assert.ok(checked.B >= 4, `expected events for restore B, got ${checked.B}`); | | 40 | + }); | | 41 | + | | 42 | + it('stamps entryFile on top-level tests forwarded from child processes', async () => { | | 43 | + const events = await collectEvents({ isolation: 'process' }); | | 44 | + const pass = events.filter(({ type }) => type === 'test:pass'); | | 45 | + const backupA = pass.find(({ data }) => data.name === 'backup A'); | | 46 | + const backupB = pass.find(({ data }) => data.name === 'backup B'); | | 47 | + assert.strictEqual(backupA.data.entryFile, aPath); | | 48 | + assert.strictEqual(backupB.data.entryFile, bPath); | | 49 | + }); | | 50 | + | | 51 | + it('does not stamp entryFile with isolation none', async () => { | | 52 | + const events = await collectEvents({ isolation: 'none' }); | | 53 | + for (const { data } of events) { | | 54 | + if (data?.name === 'restore A' || data?.name === 'restore B') { | | 55 | + assert.strictEqual(data.entryFile, undefined); | | 56 | + } | | 57 | + } | | 58 | + }); | | 59 | +}); |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-runner-v8-deserializer.mjs +17-11Lines changed: 17 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -5,6 +5,7 @@ import { describe, it, beforeEach } from 'node:test'; | 5 | 5 | import assert from 'node:assert'; | 6 | 6 | import { finished } from 'node:stream/promises'; | 7 | 7 | import { DefaultSerializer } from 'node:v8'; | | 8 | +import { resolve } from 'node:path'; | 8 | 9 | import serializer from 'internal/test_runner/reporter/v8-serializer'; | 9 | 10 | import runner from 'internal/test_runner/runner'; | 10 | 11 |
| @@ -14,10 +15,15 @@ async function toArray(chunks) { | 14 | 15 | return arr; | 15 | 16 | } | 16 | 17 |
| | 18 | +const entryFile = resolve('filetest'); | 17 | 19 | const diagnosticEvent = { | 18 | 20 | type: 'test:diagnostic', | 19 | 21 | data: { nesting: 0, details: {}, message: 'diagnostic' }, | 20 | 22 | }; | | 23 | +const reportedDiagnosticEvent = { | | 24 | + type: 'test:diagnostic', | | 25 | + data: { ...diagnosticEvent.data, entryFile }, | | 26 | +}; | 21 | 27 | const chunks = await toArray(serializer([diagnosticEvent])); | 22 | 28 | const defaultSerializer = new DefaultSerializer(); | 23 | 29 | defaultSerializer.writeHeader(); | @@ -67,28 +73,28 @@ describe('v8 deserializer', common.mustCall(() => { | 67 | 73 | it('should deserialize a chunk with no serialization', async () => { | 68 | 74 | const reported = await collectReported([Buffer.from('unknown')]); | 69 | 75 | assert.deepStrictEqual(reported, [ | 70 | | - { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | | 76 | + { data: { __proto__: null, entryFile, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | 71 | 77 | ]); | 72 | 78 | }); | 73 | 79 |
| 74 | 80 | it('should deserialize a serialized chunk', async () => { | 75 | 81 | const reported = await collectReported(chunks); | 76 | | - assert.deepStrictEqual(reported, [diagnosticEvent]); | | 82 | + assert.deepStrictEqual(reported, [reportedDiagnosticEvent]); | 77 | 83 | }); | 78 | 84 |
| 79 | 85 | it('should deserialize a serialized chunk after non-serialized chunk', async () => { | 80 | 86 | const reported = await collectReported([Buffer.concat([Buffer.from('unknown'), ...chunks])]); | 81 | 87 | assert.deepStrictEqual(reported, [ | 82 | | - { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | 83 | | - diagnosticEvent, | | 88 | + { data: { __proto__: null, entryFile, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | | 89 | + reportedDiagnosticEvent, | 84 | 90 | ]); | 85 | 91 | }); | 86 | 92 |
| 87 | 93 | it('should deserialize a serialized chunk before non-serialized output', async () => { | 88 | 94 | const reported = await collectReported([Buffer.concat([ ...chunks, Buffer.from('unknown')])]); | 89 | 95 | assert.deepStrictEqual(reported, [ | 90 | | - diagnosticEvent, | 91 | | - { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | | 96 | + reportedDiagnosticEvent, | | 97 | + { data: { __proto__: null, entryFile, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | 92 | 98 | ]); | 93 | 99 | }); | 94 | 100 |
| @@ -131,7 +137,7 @@ describe('v8 deserializer', common.mustCall(() => { | 131 | 137 | oversizedLengthHeader, | 132 | 138 | ...chunks, | 133 | 139 | ]); | 134 | | - assert.deepStrictEqual(reported.at(-1), diagnosticEvent); | | 140 | + assert.deepStrictEqual(reported.at(-1), reportedDiagnosticEvent); | 135 | 141 | assert.strictEqual(reported.filter((event) => event.type === 'test:diagnostic').length, 1); | 136 | 142 | assert.strictEqual(collectStdout(reported), oversizedLengthStdout); | 137 | 143 | }); | @@ -152,7 +158,7 @@ describe('v8 deserializer', common.mustCall(() => { | 152 | 158 | const data = chunks[0]; | 153 | 159 | const reported = await collectReported([data.subarray(0, i), data.subarray(i)]); | 154 | 160 | assert.deepStrictEqual(reported, [ | 155 | | - diagnosticEvent, | | 161 | + reportedDiagnosticEvent, | 156 | 162 | ]); | 157 | 163 | }); | 158 | 164 |
| @@ -163,9 +169,9 @@ describe('v8 deserializer', common.mustCall(() => { | 163 | 169 | Buffer.concat([data.subarray(i), Buffer.from('unknown')]), | 164 | 170 | ]); | 165 | 171 | assert.deepStrictEqual(reported, [ | 166 | | - { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | 167 | | - diagnosticEvent, | 168 | | - { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | | 172 | + { data: { __proto__: null, entryFile, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | | 173 | + reportedDiagnosticEvent, | | 174 | + { data: { __proto__: null, entryFile, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | 169 | 175 | ]); | 170 | 176 | } | 171 | 177 | ); | |
|