Signed-off-by: Moshe Atlow <moshe@atlow.co.il> PR-URL: https://github.com/nodejs/node/pull/63533 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: James M Snell <jasnell@gmail.com>
175 lines
7.3 KiB
JavaScript
175 lines
7.3 KiB
JavaScript
'use strict';
|
|
require('../common');
|
|
const assert = require('node:assert');
|
|
const { AsyncLocalStorage } = require('node:async_hooks');
|
|
const dc = require('node:diagnostics_channel');
|
|
const { describe, it, test } = require('node:test');
|
|
const { spawnSync } = require('child_process');
|
|
const { join } = require('path');
|
|
|
|
const events = [];
|
|
|
|
dc.subscribe('tracing:node.test:start', (data) => events.push({ event: 'start', name: data.name, type: data.type }));
|
|
dc.subscribe('tracing:node.test:end', (data) => events.push({ event: 'end', name: data.name, type: data.type }));
|
|
dc.subscribe('tracing:node.test:error', (data) => events.push({ event: 'error', name: data.name, type: data.type }));
|
|
|
|
describe('suite end ordering', () => {
|
|
it('child a', async () => { await new Promise((r) => setTimeout(r, 5)); });
|
|
it('child b', () => {});
|
|
});
|
|
|
|
test('passing test fires start and end', async () => {});
|
|
|
|
// Validate events were emitted (check after all tests via process.on('exit'))
|
|
process.on('exit', () => {
|
|
// Check passing test
|
|
const testName1 = 'passing test fires start and end';
|
|
const startEvents = events.filter((e) => e.event === 'start' && e.name === testName1);
|
|
const endEvents = events.filter((e) => e.event === 'end' && e.name === testName1);
|
|
assert.strictEqual(startEvents.length, 1);
|
|
assert.strictEqual(endEvents.length, 1);
|
|
|
|
// Check nested tests fire events
|
|
const nested1Start = events.filter((e) => e.event === 'start' && e.name === 'nested test 1');
|
|
const nested1End = events.filter((e) => e.event === 'end' && e.name === 'nested test 1');
|
|
const nested2Start = events.filter((e) => e.event === 'start' && e.name === 'nested test 2');
|
|
const nested2End = events.filter((e) => e.event === 'end' && e.name === 'nested test 2');
|
|
assert.strictEqual(nested1Start.length, 1);
|
|
assert.strictEqual(nested1End.length, 1);
|
|
assert.strictEqual(nested2Start.length, 1);
|
|
assert.strictEqual(nested2End.length, 1);
|
|
|
|
// Check describe block tests fire events
|
|
const describeStart = events.filter((e) => e.event === 'start' && e.name === 'test inside describe');
|
|
const describeEnd = events.filter((e) => e.event === 'end' && e.name === 'test inside describe');
|
|
const describeStart2 = events.filter(
|
|
(e) => e.event === 'start' && e.name === 'another test inside describe',
|
|
);
|
|
const describeEnd2 = events.filter(
|
|
(e) => e.event === 'end' && e.name === 'another test inside describe',
|
|
);
|
|
assert.strictEqual(describeStart.length, 1);
|
|
assert.strictEqual(describeEnd.length, 1);
|
|
assert.strictEqual(describeStart2.length, 1);
|
|
assert.strictEqual(describeEnd2.length, 1);
|
|
|
|
// Check async operations test fires events
|
|
const asyncTestName = 'context is available in async operations within test';
|
|
const asyncStart = events.filter((e) => e.event === 'start' && e.name === asyncTestName);
|
|
const asyncEnd = events.filter((e) => e.event === 'end' && e.name === asyncTestName);
|
|
assert.strictEqual(asyncStart.length, 1);
|
|
assert.strictEqual(asyncEnd.length, 1);
|
|
|
|
const suiteNames = new Set(['suite end ordering', 'child a', 'child b']);
|
|
const suiteSequence = events
|
|
.filter((e) => suiteNames.has(e.name))
|
|
.map((e) => `${e.event}:${e.name}`);
|
|
assert.deepStrictEqual(suiteSequence, [
|
|
'start:suite end ordering',
|
|
'start:child a',
|
|
'end:child a',
|
|
'start:child b',
|
|
'end:child b',
|
|
'end:suite end ordering',
|
|
]);
|
|
});
|
|
|
|
// Test bindStore context propagation
|
|
const testStorage = new AsyncLocalStorage();
|
|
|
|
// bindStore on the start channel: whenever a test fn runs, set testStorage to the test name
|
|
dc.channel('tracing:node.test:start').bindStore(testStorage, (data) => data.name);
|
|
|
|
const expectedName = 'bindStore propagates into test body via start channel';
|
|
test(expectedName, async () => {
|
|
const storedValueDuringTest = testStorage.getStore();
|
|
assert.strictEqual(storedValueDuringTest, expectedName);
|
|
|
|
// Propagates into async operations inside the test
|
|
const valueInSetImmediate = await new Promise((resolve) => {
|
|
setImmediate(() => resolve(testStorage.getStore()));
|
|
});
|
|
assert.strictEqual(valueInSetImmediate, expectedName);
|
|
});
|
|
|
|
test('bindStore value is isolated between tests', async () => {
|
|
assert.strictEqual(testStorage.getStore(), 'bindStore value is isolated between tests');
|
|
});
|
|
|
|
test('nested tests fire events with correct names', async (t) => {
|
|
await t.test('nested test 1', async () => {
|
|
const stored = testStorage.getStore();
|
|
assert.strictEqual(stored, 'nested test 1');
|
|
});
|
|
|
|
await t.test('nested test 2', async () => {
|
|
const stored = testStorage.getStore();
|
|
assert.strictEqual(stored, 'nested test 2');
|
|
});
|
|
});
|
|
|
|
describe('describe block with tests', () => {
|
|
it('test inside describe', async () => {
|
|
const stored = testStorage.getStore();
|
|
assert.strictEqual(stored, 'test inside describe');
|
|
});
|
|
|
|
it('another test inside describe', async () => {
|
|
const stored = testStorage.getStore();
|
|
assert.strictEqual(stored, 'another test inside describe');
|
|
});
|
|
});
|
|
|
|
test('context is available in async operations within test', async () => {
|
|
const testName = 'context is available in async operations within test';
|
|
assert.strictEqual(testStorage.getStore(), testName);
|
|
|
|
// Verify context is available in setImmediate
|
|
const valueInImmediate = await new Promise((resolve) => {
|
|
setImmediate(() => resolve(testStorage.getStore()));
|
|
});
|
|
assert.strictEqual(valueInImmediate, testName);
|
|
|
|
// Verify context is available in setTimeout
|
|
const valueInTimeout = await new Promise((resolve) => {
|
|
setTimeout(() => resolve(testStorage.getStore()), 0);
|
|
});
|
|
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');
|
|
const result = spawnSync(process.execPath, [fixturePath], { encoding: 'utf8' });
|
|
|
|
// The fixture test intentionally fails, so exit code should be non-zero
|
|
assert.notStrictEqual(result.status, 0);
|
|
|
|
// Extract and verify error events from fixture output
|
|
// The fixture outputs JSON with errorEvents array on exit
|
|
const lines = result.stdout.split('\n');
|
|
const eventLine = lines.find((line) => line.includes('errorEvents'));
|
|
assert.ok(eventLine, 'Expected errorEvents line in fixture output');
|
|
const { errorEvents } = JSON.parse(eventLine);
|
|
assert.strictEqual(errorEvents.includes('test that intentionally fails'), true);
|
|
});
|