test_runner: publish to TracingChannel node.test for OTel instrumentation#62502
test_runner: publish to TracingChannel node.test for OTel instrumentation#62502
Conversation
|
Review requested:
|
There was a problem hiding this comment.
Pull request overview
Adds diagnostics channel tracing hooks to the built-in Node.js test runner to enable OpenTelemetry-style instrumentation via diagnostics_channel.tracingChannel('node.test').
Changes:
- Introduce a shared
TracingChannel('node.test')for the internal test runner and export it from test-runner utils. - Wrap test/suite execution with
start.runStores(...)and publishend/errorlifecycle events. - Add documentation and new tests/fixtures validating lifecycle events and
AsyncLocalStoragepropagation viabindStore().
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/parallel/test-runner-diagnostics-channel.js | New parallel test covering start/end/error events and bindStore propagation. |
| test/fixtures/test-runner/diagnostics-channel-error-test.js | Fixture that intentionally fails to validate error event emission. |
| lib/internal/test_runner/utils.js | Creates and exports testChannel via diagnostics_channel.tracingChannel('node.test'). |
| lib/internal/test_runner/test.js | Instruments Test.run() and Suite.createBuild() to emit tracing lifecycle events and run stores. |
| doc/api/test.md | Documents the new tracing channels and provides an OpenTelemetry integration example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { diagnosticsChannel } from 'node:diagnostics_channel'; | ||
| import { AsyncLocalStorage } from 'node:async_hooks'; | ||
| import { context, trace } from '@opentelemetry/api'; | ||
|
|
||
| const testStorage = new AsyncLocalStorage(); | ||
| const testChannel = diagnosticsChannel.tracingChannel('node.test'); | ||
| const tracer = trace.getTracer('test-tracer'); |
There was a problem hiding this comment.
The OpenTelemetry example imports diagnosticsChannel from node:diagnostics_channel, but that module's public API exports functions like tracingChannel (and does not export a diagnosticsChannel binding). As written, the example code will throw at import time / diagnosticsChannel.tracingChannel(...) will be undefined. Update the example to use the actual node:diagnostics_channel exports (e.g., import tracingChannel directly or import the module namespace and call .tracingChannel).
| if (channelContext !== null && testChannel.end.hasSubscribers) { | ||
| testChannel.end.publish(channelContext); | ||
| } | ||
| } catch (err) { | ||
| if (channelContext !== null && testChannel.error.hasSubscribers) { | ||
| testChannel.error.publish({ __proto__: null, ...channelContext, error: err }); | ||
| } | ||
| this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); | ||
| } |
There was a problem hiding this comment.
In Suite.createBuild(), the tracing:node.test:end event is only published on the success path. If the suite function throws, tracing:node.test:error is published but end is never emitted, which makes lifecycle instrumentation incomplete/inconsistent with Test.run() (where end is always emitted in finally). Consider publishing end in a finally block so it fires for both success and error cases.
| let suiteFn = this.fn; | ||
| if (channelContext !== null && testChannel.start.hasSubscribers) { | ||
| suiteFn = (...fnArgs) => testChannel.start.runStores(channelContext, | ||
| () => ReflectApply(this.fn, this, fnArgs), | ||
| ); |
There was a problem hiding this comment.
The wrapped suiteFn calls ReflectApply(this.fn, ...), but Suite mutates this.fn to noop in the constructor after kicking off createBuild(). This makes the wrapper fragile and easy to break if execution order changes. Prefer invoking the originally captured suite function (e.g., via a local baseFn) inside the wrapper instead of referencing this.fn.
| * `data` {Object} | ||
| * `name` {string} The name of the test. | ||
| * `nesting` {number} The nesting level of the test. | ||
| * `file` {string} The path to the test file. |
There was a problem hiding this comment.
in most places we also say something around the lines of "or undefined when in REPL", I think the 3 occurrences here should too?
| ```mjs | ||
| import { diagnosticsChannel } from 'node:diagnostics_channel'; | ||
| import { AsyncLocalStorage } from 'node:async_hooks'; | ||
| import { context, trace } from '@opentelemetry/api'; |
There was a problem hiding this comment.
Is it common/customary for the node docs to show examples with external libraries?
c3b10e5 to
75fe3a3
Compare
| test('bindStore propagates into test body via start channel', async () => { | ||
| const expectedName = 'bindStore propagates into test body via start channel'; |
There was a problem hiding this comment.
why not:
| test('bindStore propagates into test body via start channel', async () => { | |
| const expectedName = 'bindStore propagates into test body via start channel'; | |
| const expectedName = 'bindStore propagates into test body via start channel'; | |
| test(expectedName, async () => { |
| }); | ||
| }); | ||
|
|
||
| const { describe, it } = require('node:test'); |
There was a problem hiding this comment.
Any reason for this to be down here and not alongside the test in line 5?
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #62502 +/- ##
=======================================
Coverage 89.69% 89.70%
=======================================
Files 692 692
Lines 214039 214104 +65
Branches 41064 41078 +14
=======================================
+ Hits 191985 192062 +77
Misses 14134 14134
+ Partials 7920 7908 -12
🚀 New features to boost your workflow:
|
No description provided.