Files
node/lib/internal/test_runner/tests_stream.js
T
Moshe Atlow 892976d17a test_runner: add context.log() and test:log event
Add a log(message[, data]) method to TestContext and SuiteContext that
emits a new test:log event. Unlike test:diagnostic, which is buffered
so it is emitted in the order tests are defined, test:log is emitted
immediately, in the order tests execute, including under process
isolation where it bypasses the per-file declaration order buffer.
This gives reporters that render the test tree unbuffered a live,
attributed logging channel that captured stdout cannot provide under
concurrency.

The event carries the message, an optional opaque structured payload
that the runner passes through untouched, and the emitting test's
name, testId, parentId, nesting, and location. Built-in reporters
render it the same way they render test:diagnostic.

Signed-off-by: Moses Atlow <moshe@atlow.co.il>
PR-URL: https://github.com/nodejs/node/pull/64389
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
2026-07-12 11:45:05 +00:00

214 lines
4.1 KiB
JavaScript

'use strict';
const {
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeSlice,
NumberMAX_SAFE_INTEGER,
Symbol,
} = primordials;
const Readable = require('internal/streams/readable');
const kEmitMessage = Symbol('kEmitMessage');
class TestsStream extends Readable {
#buffer;
#canPush;
constructor() {
super({
__proto__: null,
objectMode: true,
highWaterMark: NumberMAX_SAFE_INTEGER,
});
this.#buffer = [];
this.#canPush = true;
}
_read() {
this.#canPush = true;
while (this.#buffer.length > 0) {
const obj = ArrayPrototypeShift(this.#buffer);
if (!this.#tryPush(obj)) {
return;
}
}
}
fail(nesting, loc, testNumber, name, details, directive, testId, parentId, tags) {
this[kEmitMessage]('test:fail', {
__proto__: null,
name,
nesting,
testNumber,
testId,
parentId,
details,
tags: ArrayPrototypeSlice(tags),
...loc,
...directive,
});
}
ok(nesting, loc, testNumber, name, details, directive, testId, parentId, tags) {
this[kEmitMessage]('test:pass', {
__proto__: null,
name,
nesting,
testNumber,
testId,
parentId,
details,
tags: ArrayPrototypeSlice(tags),
...loc,
...directive,
});
}
complete(nesting, loc, testNumber, name, details, directive, testId, parentId, tags) {
this[kEmitMessage]('test:complete', {
__proto__: null,
name,
nesting,
testNumber,
testId,
parentId,
details,
tags: ArrayPrototypeSlice(tags),
...loc,
...directive,
});
}
plan(nesting, loc, count) {
this[kEmitMessage]('test:plan', {
__proto__: null,
nesting,
count,
...loc,
});
}
getSkip(reason = undefined) {
return { __proto__: null, skip: reason ?? true };
}
getTodo(reason = undefined) {
return { __proto__: null, todo: reason ?? true };
}
getXFail(expectation = undefined) {
return { __proto__: null, expectFailure: expectation ?? true };
}
enqueue(nesting, loc, name, type, testId, parentId, tags) {
this[kEmitMessage]('test:enqueue', {
__proto__: null,
nesting,
name,
type,
testId,
parentId,
tags: ArrayPrototypeSlice(tags),
...loc,
});
}
dequeue(nesting, loc, name, type, testId, parentId, tags) {
this[kEmitMessage]('test:dequeue', {
__proto__: null,
nesting,
name,
type,
testId,
parentId,
tags: ArrayPrototypeSlice(tags),
...loc,
});
}
start(nesting, loc, name, testId, parentId, tags) {
this[kEmitMessage]('test:start', {
__proto__: null,
nesting,
name,
testId,
parentId,
tags: ArrayPrototypeSlice(tags),
...loc,
});
}
log(nesting, loc, message, data, name, testId, parentId) {
this[kEmitMessage]('test:log', {
__proto__: null,
name,
nesting,
testId,
parentId,
message,
data,
...loc,
});
}
diagnostic(nesting, loc, message, level = 'info') {
this[kEmitMessage]('test:diagnostic', {
__proto__: null,
nesting,
message,
level,
...loc,
});
}
coverage(nesting, loc, summary) {
this[kEmitMessage]('test:coverage', {
__proto__: null,
nesting,
summary,
...loc,
});
}
summary(nesting, file, success, counts, duration_ms) {
this[kEmitMessage]('test:summary', {
__proto__: null,
success,
counts,
duration_ms,
file,
});
}
interrupted(tests) {
this[kEmitMessage]('test:interrupted', {
__proto__: null,
tests,
});
}
end() {
this.#tryPush(null);
}
[kEmitMessage](type, data) {
this.emit(type, data);
// Disabling as this going to the user-land
// eslint-disable-next-line node-core/set-proto-to-null-in-object
this.#tryPush({ type, data });
}
#tryPush(message) {
if (this.#canPush) {
this.#canPush = this.push(message);
} else {
ArrayPrototypePush(this.#buffer, message);
}
return this.#canPush;
}
}
module.exports = { TestsStream, kEmitMessage };