◐ Shell
clean mode source ↗

test_runner: publish to TracingChannel for OTel instrumentation · nodejs/node@5c27704

1+

'use strict';

2+

require('../common');

3+

const assert = require('node:assert');

4+

const { AsyncLocalStorage } = require('node:async_hooks');

5+

const dc = require('node:diagnostics_channel');

6+

const { describe, it, test } = require('node:test');

7+

const { spawnSync } = require('child_process');

8+

const { join } = require('path');

9+10+

const events = [];

11+12+

dc.subscribe('tracing:node.test:start', (data) => events.push({ event: 'start', name: data.name }));

13+

dc.subscribe('tracing:node.test:end', (data) => events.push({ event: 'end', name: data.name }));

14+

dc.subscribe('tracing:node.test:error', (data) => events.push({ event: 'error', name: data.name }));

15+16+

test('passing test fires start and end', async () => {});

17+18+

// Validate events were emitted (check after all tests via process.on('exit'))

19+

process.on('exit', () => {

20+

// Check passing test

21+

const testName1 = 'passing test fires start and end';

22+

const startEvents = events.filter((e) => e.event === 'start' && e.name === testName1);

23+

const endEvents = events.filter((e) => e.event === 'end' && e.name === testName1);

24+

assert.strictEqual(startEvents.length, 1);

25+

assert.strictEqual(endEvents.length, 1);

26+27+

// Check nested tests fire events

28+

const nested1Start = events.filter((e) => e.event === 'start' && e.name === 'nested test 1');

29+

const nested1End = events.filter((e) => e.event === 'end' && e.name === 'nested test 1');

30+

const nested2Start = events.filter((e) => e.event === 'start' && e.name === 'nested test 2');

31+

const nested2End = events.filter((e) => e.event === 'end' && e.name === 'nested test 2');

32+

assert.strictEqual(nested1Start.length, 1);

33+

assert.strictEqual(nested1End.length, 1);

34+

assert.strictEqual(nested2Start.length, 1);

35+

assert.strictEqual(nested2End.length, 1);

36+37+

// Check describe block tests fire events

38+

const describeStart = events.filter((e) => e.event === 'start' && e.name === 'test inside describe');

39+

const describeEnd = events.filter((e) => e.event === 'end' && e.name === 'test inside describe');

40+

const describeStart2 = events.filter(

41+

(e) => e.event === 'start' && e.name === 'another test inside describe',

42+

);

43+

const describeEnd2 = events.filter(

44+

(e) => e.event === 'end' && e.name === 'another test inside describe',

45+

);

46+

assert.strictEqual(describeStart.length, 1);

47+

assert.strictEqual(describeEnd.length, 1);

48+

assert.strictEqual(describeStart2.length, 1);

49+

assert.strictEqual(describeEnd2.length, 1);

50+51+

// Check async operations test fires events

52+

const asyncTestName = 'context is available in async operations within test';

53+

const asyncStart = events.filter((e) => e.event === 'start' && e.name === asyncTestName);

54+

const asyncEnd = events.filter((e) => e.event === 'end' && e.name === asyncTestName);

55+

assert.strictEqual(asyncStart.length, 1);

56+

assert.strictEqual(asyncEnd.length, 1);

57+

});

58+59+

// Test bindStore context propagation

60+

const testStorage = new AsyncLocalStorage();

61+62+

// bindStore on the start channel: whenever a test fn runs, set testStorage to the test name

63+

dc.channel('tracing:node.test:start').bindStore(testStorage, (data) => data.name);

64+65+

const expectedName = 'bindStore propagates into test body via start channel';

66+

test(expectedName, async () => {

67+

const storedValueDuringTest = testStorage.getStore();

68+

assert.strictEqual(storedValueDuringTest, expectedName);

69+70+

// Propagates into async operations inside the test

71+

const valueInSetImmediate = await new Promise((resolve) => {

72+

setImmediate(() => resolve(testStorage.getStore()));

73+

});

74+

assert.strictEqual(valueInSetImmediate, expectedName);

75+

});

76+77+

test('bindStore value is isolated between tests', async () => {

78+

assert.strictEqual(testStorage.getStore(), 'bindStore value is isolated between tests');

79+

});

80+81+

test('nested tests fire events with correct names', async (t) => {

82+

await t.test('nested test 1', async () => {

83+

const stored = testStorage.getStore();

84+

assert.strictEqual(stored, 'nested test 1');

85+

});

86+87+

await t.test('nested test 2', async () => {

88+

const stored = testStorage.getStore();

89+

assert.strictEqual(stored, 'nested test 2');

90+

});

91+

});

92+93+

describe('describe block with tests', () => {

94+

it('test inside describe', async () => {

95+

const stored = testStorage.getStore();

96+

assert.strictEqual(stored, 'test inside describe');

97+

});

98+99+

it('another test inside describe', async () => {

100+

const stored = testStorage.getStore();

101+

assert.strictEqual(stored, 'another test inside describe');

102+

});

103+

});

104+105+

test('context is available in async operations within test', async () => {

106+

const testName = 'context is available in async operations within test';

107+

assert.strictEqual(testStorage.getStore(), testName);

108+109+

// Verify context is available in setImmediate

110+

const valueInImmediate = await new Promise((resolve) => {

111+

setImmediate(() => resolve(testStorage.getStore()));

112+

});

113+

assert.strictEqual(valueInImmediate, testName);

114+115+

// Verify context is available in setTimeout

116+

const valueInTimeout = await new Promise((resolve) => {

117+

setTimeout(() => resolve(testStorage.getStore()), 0);

118+

});

119+

assert.strictEqual(valueInTimeout, testName);

120+

});

121+122+

test('error events fire for failing tests in fixture', async () => {

123+

// Run the fixture test that intentionally fails

124+

const fixturePath = join(__dirname, '../fixtures/test-runner/diagnostics-channel-error-test.js');

125+

const result = spawnSync(process.execPath, [fixturePath], { encoding: 'utf8' });

126+127+

// The fixture test intentionally fails, so exit code should be non-zero

128+

assert.notStrictEqual(result.status, 0);

129+130+

// Extract and verify error events from fixture output

131+

// The fixture outputs JSON with errorEvents array on exit

132+

const lines = result.stdout.split('\n');

133+

const eventLine = lines.find((line) => line.includes('errorEvents'));

134+

assert.ok(eventLine, 'Expected errorEvents line in fixture output');

135+

const { errorEvents } = JSON.parse(eventLine);

136+

assert.strictEqual(errorEvents.includes('test that intentionally fails'), true);

137+

});