Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,9 @@ class Test extends AsyncResource {

let stopPromise;

let publishEnd = () => testChannel.end.publish(channelContext);
let publishError = (err) => testChannel.error.publish({ __proto__: null, ...channelContext, error: err });

try {
if (this.parent?.hooks.before.length > 0) {
// This hook usually runs immediately, we need to wait for it to finish
Expand All @@ -1326,9 +1329,11 @@ class Test extends AsyncResource {
// not the runInAsyncScope call itself, to maintain AsyncLocalStorage bindings.
let testFn = this.fn;
if (channelContext !== null && testChannel.start.hasSubscribers) {
testFn = (...fnArgs) => testChannel.start.runStores(channelContext,
() => ReflectApply(this.fn, this, fnArgs),
);
testFn = (...fnArgs) => testChannel.start.runStores(channelContext, () => {
publishEnd = AsyncResource.bind(publishEnd);
publishError = AsyncResource.bind(publishError);
return ReflectApply(this.fn, this, fnArgs);
});
}

ArrayPrototypeUnshift(runArgs, testFn, ctx);
Expand Down Expand Up @@ -1380,9 +1385,8 @@ class Test extends AsyncResource {
await afterEach();
await after();
} catch (err) {
// Publish diagnostics_channel error event if the channel has subscribers
if (channelContext !== null && testChannel.error.hasSubscribers) {
testChannel.error.publish({ __proto__: null, ...channelContext, error: err });
publishError(err);
}
if (isTestFailureError(err)) {
if (err.failureType === kTestTimeoutFailure) {
Expand All @@ -1406,7 +1410,7 @@ class Test extends AsyncResource {

// Publish diagnostics_channel end event if the channel has subscribers (in both success and error cases)
if (channelContext !== null && testChannel.end.hasSubscribers) {
testChannel.end.publish(channelContext);
publishEnd();
}
}

Expand Down Expand Up @@ -1751,6 +1755,9 @@ class Suite extends Test {
file: this.entryFile,
type: this.reportedType,
};
let publishEnd = () => testChannel.end.publish(channelContext);
let publishError = (err) => testChannel.error.publish({ __proto__: null, ...channelContext, error: err });

try {
const { ctx, args } = this.getRunArgs();

Expand All @@ -1762,9 +1769,11 @@ class Suite extends Test {
let suiteFn = this.fn;
if (testChannel.start.hasSubscribers) {
const baseFn = this.fn;
suiteFn = (...fnArgs) => testChannel.start.runStores(channelContext,
() => ReflectApply(baseFn, this, fnArgs),
);
suiteFn = (...fnArgs) => testChannel.start.runStores(channelContext, () => {
publishEnd = AsyncResource.bind(publishEnd);
publishError = AsyncResource.bind(publishError);
return ReflectApply(baseFn, this, fnArgs);
});
}

const runArgs = [suiteFn, ctx];
Expand All @@ -1773,12 +1782,12 @@ class Suite extends Test {
await ReflectApply(this.runInAsyncScope, this, runArgs);
} catch (err) {
if (testChannel.error.hasSubscribers) {
testChannel.error.publish({ __proto__: null, ...channelContext, error: err });
publishError(err);
}
this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure));
} finally {
if (testChannel.end.hasSubscribers) {
testChannel.end.publish(channelContext);
publishEnd();
}
}

Expand Down
27 changes: 27 additions & 0 deletions test/fixtures/test-runner/diagnostics-channel-bindstore-end.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';
const dc = require('node:diagnostics_channel');
const { AsyncLocalStorage } = require('node:async_hooks');
const { test } = require('node:test');

const als = new AsyncLocalStorage();
const ch = dc.tracingChannel('node.test');

ch.start.bindStore(als, (data) => data.name);

const storeAtEnd = {};
const storeAtError = {};

ch.end.subscribe((data) => {
storeAtEnd[data.name] = als.getStore();
});

ch.error.subscribe((data) => {
storeAtError[data.name] = als.getStore();
});

test('passing test', () => {});
test('failing test', () => { throw new Error('boom'); });

process.on('exit', () => {
console.log(JSON.stringify({ storeAtEnd, storeAtError }));
});
20 changes: 20 additions & 0 deletions test/parallel/test-runner-diagnostics-channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,26 @@ test('context is available in async operations within test', async () => {
assert.strictEqual(valueInTimeout, testName);
});

test('bindStore propagates store to end and error subscribers', async () => {
// Spawn a fixture that records `als.getStore()` at end/error publish time so
// we can assert subscribers see the bound store, not undefined.
const fixturePath = join(__dirname, '../fixtures/test-runner/diagnostics-channel-bindstore-end.js');
const result = spawnSync(process.execPath, [fixturePath], { encoding: 'utf8' });
// The fixture contains an intentionally failing test, so exit is non-zero.
assert.notStrictEqual(result.status, 0);
const line = result.stdout.split('\n').find((l) => l.includes('storeAtEnd'));
assert.ok(line, `expected storeAtEnd line in stdout:\n${result.stdout}`);
const { storeAtEnd, storeAtError } = JSON.parse(line);
assert.deepStrictEqual(storeAtEnd, {
'<root>': '<root>',
'passing test': 'passing test',
'failing test': 'failing test',
});
assert.deepStrictEqual(storeAtError, {
'failing test': 'failing test',
});
});

test('error events fire for failing tests in fixture', async () => {
// Run the fixture test that intentionally fails
const fixturePath = join(__dirname, '../fixtures/test-runner/diagnostics-channel-error-test.js');
Expand Down
Loading