Skip to content

Commit 86a4589

Browse files
committed
test_runner: report entryFile in TestStream events
Signed-off-by: Moshe Atlov <moshe@atlow.co.il>
1 parent 4d27d80 commit 86a4589

7 files changed

Lines changed: 131 additions & 11 deletions

File tree

doc/api/test.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3440,6 +3440,10 @@ added:
34403440
- v18.9.0
34413441
- v16.19.0
34423442
changes:
3443+
- version: REPLACEME
3444+
pr-url: https://github.com/nodejs/node/pull/64309
3445+
description: Added `entryFile` to events forwarded from child processes
3446+
when tests run with process isolation.
34433447
- version: v26.3.0
34443448
pr-url: https://github.com/nodejs/node/pull/63435
34453449
description: Added `parentId` to test events that carry a `testId`.
@@ -3524,6 +3528,10 @@ Emitted when code coverage is enabled and all tests have completed.
35243528
* `cause` {Error} The actual error thrown by the test.
35253529
* `type` {string|undefined} The type of the test, used to denote whether
35263530
this is a suite.
3531+
* `entryFile` {string|undefined} The path of the test file that was
3532+
executed as the entry point of the child process that emitted this event.
3533+
Only present when tests run with process isolation. May differ from
3534+
`file` when the test is defined in a module imported by the entry file.
35273535
* `file` {string|undefined} The path of the test file,
35283536
`undefined` if test was run through the REPL.
35293537
* `line` {number|undefined} The line number where the test is defined, or
@@ -3553,6 +3561,10 @@ The corresponding declaration ordered events are `'test:pass'` and `'test:fail'`
35533561
* `data` {Object}
35543562
* `column` {number|undefined} The column number where the test is defined, or
35553563
`undefined` if the test was run through the REPL.
3564+
* `entryFile` {string|undefined} The path of the test file that was
3565+
executed as the entry point of the child process that emitted this event.
3566+
Only present when tests run with process isolation. May differ from
3567+
`file` when the test is defined in a module imported by the entry file.
35563568
* `file` {string|undefined} The path of the test file,
35573569
`undefined` if test was run through the REPL.
35583570
* `line` {number|undefined} The line number where the test is defined, or
@@ -3579,6 +3591,10 @@ defined. The corresponding declaration ordered event is `'test:start'`.
35793591
* `data` {Object}
35803592
* `column` {number|undefined} The column number where the test is defined, or
35813593
`undefined` if the test was run through the REPL.
3594+
* `entryFile` {string|undefined} The path of the test file that was
3595+
executed as the entry point of the child process that emitted this event.
3596+
Only present when tests run with process isolation. May differ from
3597+
`file` when the test is defined in a module imported by the entry file.
35823598
* `file` {string|undefined} The path of the test file,
35833599
`undefined` if test was run through the REPL.
35843600
* `line` {number|undefined} The line number where the test is defined, or
@@ -3600,6 +3616,10 @@ defined.
36003616
* `data` {Object}
36013617
* `column` {number|undefined} The column number where the test is defined, or
36023618
`undefined` if the test was run through the REPL.
3619+
* `entryFile` {string|undefined} The path of the test file that was
3620+
executed as the entry point of the child process that emitted this event.
3621+
Only present when tests run with process isolation. May differ from
3622+
`file` when the test is defined in a module imported by the entry file.
36033623
* `file` {string|undefined} The path of the test file,
36043624
`undefined` if test was run through the REPL.
36053625
* `line` {number|undefined} The line number where the test is defined, or
@@ -3632,6 +3652,10 @@ Emitted when a test is enqueued for execution.
36323652
this is a suite.
36333653
* `attempt` {number|undefined} The attempt number of the test run,
36343654
present only when using the [`--test-rerun-failures`][] flag.
3655+
* `entryFile` {string|undefined} The path of the test file that was
3656+
executed as the entry point of the child process that emitted this event.
3657+
Only present when tests run with process isolation. May differ from
3658+
`file` when the test is defined in a module imported by the entry file.
36353659
* `file` {string|undefined} The path of the test file,
36363660
`undefined` if test was run through the REPL.
36373661
* `line` {number|undefined} The line number where the test is defined, or
@@ -3697,6 +3721,10 @@ since the parent runner only knows about file-level tests. When using
36973721
present only when using the [`--test-rerun-failures`][] flag.
36983722
* `passed_on_attempt` {number|undefined} The attempt number the test passed on,
36993723
present only when using the [`--test-rerun-failures`][] flag.
3724+
* `entryFile` {string|undefined} The path of the test file that was
3725+
executed as the entry point of the child process that emitted this event.
3726+
Only present when tests run with process isolation. May differ from
3727+
`file` when the test is defined in a module imported by the entry file.
37003728
* `file` {string|undefined} The path of the test file,
37013729
`undefined` if test was run through the REPL.
37023730
* `line` {number|undefined} The line number where the test is defined, or
@@ -3726,6 +3754,10 @@ The corresponding execution ordered event is `'test:complete'`.
37263754
* `data` {Object}
37273755
* `column` {number|undefined} The column number where the test is defined, or
37283756
`undefined` if the test was run through the REPL.
3757+
* `entryFile` {string|undefined} The path of the test file that was
3758+
executed as the entry point of the child process that emitted this event.
3759+
Only present when tests run with process isolation. May differ from
3760+
`file` when the test is defined in a module imported by the entry file.
37293761
* `file` {string|undefined} The path of the test file,
37303762
`undefined` if test was run through the REPL.
37313763
* `line` {number|undefined} The line number where the test is defined, or
@@ -3742,6 +3774,10 @@ defined.
37423774
* `data` {Object}
37433775
* `column` {number|undefined} The column number where the test is defined, or
37443776
`undefined` if the test was run through the REPL.
3777+
* `entryFile` {string|undefined} The path of the test file that was
3778+
executed as the entry point of the child process that emitted this event.
3779+
Only present when tests run with process isolation. May differ from
3780+
`file` when the test is defined in a module imported by the entry file.
37453781
* `file` {string|undefined} The path of the test file,
37463782
`undefined` if test was run through the REPL.
37473783
* `line` {number|undefined} The line number where the test is defined, or
@@ -3766,6 +3802,9 @@ The corresponding execution ordered event is `'test:dequeue'`.
37663802
### Event: `'test:stderr'`
37673803

37683804
* `data` {Object}
3805+
* `entryFile` {string|undefined} The path of the test file that was
3806+
executed as the entry point of the child process that emitted this event.
3807+
Only present when tests run with process isolation.
37693808
* `file` {string} The path of the test file.
37703809
* `message` {string} The message written to `stderr`.
37713810

@@ -3777,6 +3816,9 @@ defined.
37773816
### Event: `'test:stdout'`
37783817

37793818
* `data` {Object}
3819+
* `entryFile` {string|undefined} The path of the test file that was
3820+
executed as the entry point of the child process that emitted this event.
3821+
Only present when tests run with process isolation.
37803822
* `file` {string} The path of the test file.
37813823
* `message` {string} The message written to `stdout`.
37823824

lib/internal/test_runner/runner.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ class FileTest extends Test {
295295
ArrayPrototypeIncludes(kDiagnosticsFilterArgs, StringPrototypeSlice(comment, 0, firstSpaceIndex));
296296
}
297297
#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+
}
298302
const isTopLevel = item.data.nesting === 0;
299303
if (isTopLevel) {
300304
if (item.type === 'test:plan' && this.#skipReporting()) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test } from 'node:test';
2+
import { runShared } from './helper.mjs';
3+
test('backup A', async (t) => { await runShared(t, 'A'); });
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test } from 'node:test';
2+
import { runShared } from './helper.mjs';
3+
test('backup B', async (t) => { await runShared(t, 'B'); });
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export async function runShared(t, target) {
2+
await t.test(`restore ${target}`, async () => {});
3+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
});

test/parallel/test-runner-v8-deserializer.mjs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, it, beforeEach } from 'node:test';
55
import assert from 'node:assert';
66
import { finished } from 'node:stream/promises';
77
import { DefaultSerializer } from 'node:v8';
8+
import { resolve } from 'node:path';
89
import serializer from 'internal/test_runner/reporter/v8-serializer';
910
import runner from 'internal/test_runner/runner';
1011

@@ -14,10 +15,15 @@ async function toArray(chunks) {
1415
return arr;
1516
}
1617

18+
const entryFile = resolve('filetest');
1719
const diagnosticEvent = {
1820
type: 'test:diagnostic',
1921
data: { nesting: 0, details: {}, message: 'diagnostic' },
2022
};
23+
const reportedDiagnosticEvent = {
24+
type: 'test:diagnostic',
25+
data: { ...diagnosticEvent.data, entryFile },
26+
};
2127
const chunks = await toArray(serializer([diagnosticEvent]));
2228
const defaultSerializer = new DefaultSerializer();
2329
defaultSerializer.writeHeader();
@@ -67,28 +73,28 @@ describe('v8 deserializer', common.mustCall(() => {
6773
it('should deserialize a chunk with no serialization', async () => {
6874
const reported = await collectReported([Buffer.from('unknown')]);
6975
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' },
7177
]);
7278
});
7379

7480
it('should deserialize a serialized chunk', async () => {
7581
const reported = await collectReported(chunks);
76-
assert.deepStrictEqual(reported, [diagnosticEvent]);
82+
assert.deepStrictEqual(reported, [reportedDiagnosticEvent]);
7783
});
7884

7985
it('should deserialize a serialized chunk after non-serialized chunk', async () => {
8086
const reported = await collectReported([Buffer.concat([Buffer.from('unknown'), ...chunks])]);
8187
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,
8490
]);
8591
});
8692

8793
it('should deserialize a serialized chunk before non-serialized output', async () => {
8894
const reported = await collectReported([Buffer.concat([ ...chunks, Buffer.from('unknown')])]);
8995
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' },
9298
]);
9399
});
94100

@@ -131,7 +137,7 @@ describe('v8 deserializer', common.mustCall(() => {
131137
oversizedLengthHeader,
132138
...chunks,
133139
]);
134-
assert.deepStrictEqual(reported.at(-1), diagnosticEvent);
140+
assert.deepStrictEqual(reported.at(-1), reportedDiagnosticEvent);
135141
assert.strictEqual(reported.filter((event) => event.type === 'test:diagnostic').length, 1);
136142
assert.strictEqual(collectStdout(reported), oversizedLengthStdout);
137143
});
@@ -152,7 +158,7 @@ describe('v8 deserializer', common.mustCall(() => {
152158
const data = chunks[0];
153159
const reported = await collectReported([data.subarray(0, i), data.subarray(i)]);
154160
assert.deepStrictEqual(reported, [
155-
diagnosticEvent,
161+
reportedDiagnosticEvent,
156162
]);
157163
});
158164

@@ -163,9 +169,9 @@ describe('v8 deserializer', common.mustCall(() => {
163169
Buffer.concat([data.subarray(i), Buffer.from('unknown')]),
164170
]);
165171
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' },
169175
]);
170176
}
171177
);

0 commit comments

Comments
 (0)