Skip to content

Commit fa0a640

Browse files
committed
test_runner: fix diagnostics channel context tracking
Signed-off-by: Moshe Atlow <[email protected]>
1 parent 131fd84 commit fa0a640

3 files changed

Lines changed: 67 additions & 11 deletions

File tree

lib/internal/test_runner/test.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,9 @@ class Test extends AsyncResource {
13081308

13091309
let stopPromise;
13101310

1311+
let publishEnd = () => testChannel.end.publish(channelContext);
1312+
let publishError = (err) => testChannel.error.publish({ __proto__: null, ...channelContext, error: err });
1313+
13111314
try {
13121315
if (this.parent?.hooks.before.length > 0) {
13131316
// This hook usually runs immediately, we need to wait for it to finish
@@ -1326,9 +1329,11 @@ class Test extends AsyncResource {
13261329
// not the runInAsyncScope call itself, to maintain AsyncLocalStorage bindings.
13271330
let testFn = this.fn;
13281331
if (channelContext !== null && testChannel.start.hasSubscribers) {
1329-
testFn = (...fnArgs) => testChannel.start.runStores(channelContext,
1330-
() => ReflectApply(this.fn, this, fnArgs),
1331-
);
1332+
testFn = (...fnArgs) => testChannel.start.runStores(channelContext, () => {
1333+
publishEnd = AsyncResource.bind(publishEnd);
1334+
publishError = AsyncResource.bind(publishError);
1335+
return ReflectApply(this.fn, this, fnArgs);
1336+
});
13321337
}
13331338

13341339
ArrayPrototypeUnshift(runArgs, testFn, ctx);
@@ -1380,9 +1385,8 @@ class Test extends AsyncResource {
13801385
await afterEach();
13811386
await after();
13821387
} catch (err) {
1383-
// Publish diagnostics_channel error event if the channel has subscribers
13841388
if (channelContext !== null && testChannel.error.hasSubscribers) {
1385-
testChannel.error.publish({ __proto__: null, ...channelContext, error: err });
1389+
publishError(err);
13861390
}
13871391
if (isTestFailureError(err)) {
13881392
if (err.failureType === kTestTimeoutFailure) {
@@ -1406,7 +1410,7 @@ class Test extends AsyncResource {
14061410

14071411
// Publish diagnostics_channel end event if the channel has subscribers (in both success and error cases)
14081412
if (channelContext !== null && testChannel.end.hasSubscribers) {
1409-
testChannel.end.publish(channelContext);
1413+
publishEnd();
14101414
}
14111415
}
14121416

@@ -1751,6 +1755,9 @@ class Suite extends Test {
17511755
file: this.entryFile,
17521756
type: this.reportedType,
17531757
};
1758+
let publishEnd = () => testChannel.end.publish(channelContext);
1759+
let publishError = (err) => testChannel.error.publish({ __proto__: null, ...channelContext, error: err });
1760+
17541761
try {
17551762
const { ctx, args } = this.getRunArgs();
17561763

@@ -1762,9 +1769,11 @@ class Suite extends Test {
17621769
let suiteFn = this.fn;
17631770
if (testChannel.start.hasSubscribers) {
17641771
const baseFn = this.fn;
1765-
suiteFn = (...fnArgs) => testChannel.start.runStores(channelContext,
1766-
() => ReflectApply(baseFn, this, fnArgs),
1767-
);
1772+
suiteFn = (...fnArgs) => testChannel.start.runStores(channelContext, () => {
1773+
publishEnd = AsyncResource.bind(publishEnd);
1774+
publishError = AsyncResource.bind(publishError);
1775+
return ReflectApply(baseFn, this, fnArgs);
1776+
});
17681777
}
17691778

17701779
const runArgs = [suiteFn, ctx];
@@ -1773,12 +1782,12 @@ class Suite extends Test {
17731782
await ReflectApply(this.runInAsyncScope, this, runArgs);
17741783
} catch (err) {
17751784
if (testChannel.error.hasSubscribers) {
1776-
testChannel.error.publish({ __proto__: null, ...channelContext, error: err });
1785+
publishError(err);
17771786
}
17781787
this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure));
17791788
} finally {
17801789
if (testChannel.end.hasSubscribers) {
1781-
testChannel.end.publish(channelContext);
1790+
publishEnd();
17821791
}
17831792
}
17841793

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
const dc = require('node:diagnostics_channel');
3+
const { AsyncLocalStorage } = require('node:async_hooks');
4+
const { test } = require('node:test');
5+
6+
const als = new AsyncLocalStorage();
7+
const ch = dc.tracingChannel('node.test');
8+
9+
ch.start.bindStore(als, (data) => data.name);
10+
11+
const storeAtEnd = {};
12+
const storeAtError = {};
13+
14+
ch.end.subscribe((data) => {
15+
storeAtEnd[data.name] = als.getStore();
16+
});
17+
18+
ch.error.subscribe((data) => {
19+
storeAtError[data.name] = als.getStore();
20+
});
21+
22+
test('passing test', () => {});
23+
test('failing test', () => { throw new Error('boom'); });
24+
25+
process.on('exit', () => {
26+
console.log(JSON.stringify({ storeAtEnd, storeAtError }));
27+
});

test/parallel/test-runner-diagnostics-channel.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@ test('context is available in async operations within test', async () => {
119119
assert.strictEqual(valueInTimeout, testName);
120120
});
121121

122+
test('bindStore propagates store to end and error subscribers', async () => {
123+
// Spawn a fixture that records `als.getStore()` at end/error publish time so
124+
// we can assert subscribers see the bound store, not undefined.
125+
const fixturePath = join(__dirname, '../fixtures/test-runner/diagnostics-channel-bindstore-end.js');
126+
const result = spawnSync(process.execPath, [fixturePath], { encoding: 'utf8' });
127+
// The fixture contains an intentionally failing test, so exit is non-zero.
128+
assert.notStrictEqual(result.status, 0);
129+
const line = result.stdout.split('\n').find((l) => l.includes('storeAtEnd'));
130+
assert.ok(line, `expected storeAtEnd line in stdout:\n${result.stdout}`);
131+
const { storeAtEnd, storeAtError } = JSON.parse(line);
132+
assert.deepStrictEqual(storeAtEnd, {
133+
'<root>': '<root>',
134+
'passing test': 'passing test',
135+
'failing test': 'failing test',
136+
});
137+
assert.deepStrictEqual(storeAtError, {
138+
'failing test': 'failing test',
139+
});
140+
});
141+
122142
test('error events fire for failing tests in fixture', async () => {
123143
// Run the fixture test that intentionally fails
124144
const fixturePath = join(__dirname, '../fixtures/test-runner/diagnostics-channel-error-test.js');

0 commit comments

Comments
 (0)