Files
node/test/parallel/test-runner-log.mjs
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

81 lines
2.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Flags: --no-warnings
import '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { spawnSync } from 'node:child_process';
import { test, run } from 'node:test';
const fixture = fixtures.path('test-runner', 'log-events', 'basic.mjs');
test('t.log() validates its arguments', (t) => {
assert.throws(() => t.log(123), { code: 'ERR_INVALID_ARG_TYPE' });
});
test('built-in reporters render test:log like diagnostics', () => {
const tap = spawnSync(process.execPath, ['--test', '--test-reporter=tap', fixture]);
assert.match(tap.stdout.toString(), /# hello/);
assert.match(tap.stdout.toString(), /# suite message/);
const spec = spawnSync(process.execPath, ['--test', '--test-reporter=spec', fixture]);
assert.match(spec.stdout.toString(), /ℹ hello/);
assert.match(spec.stdout.toString(), /ℹ warned/);
const junit = spawnSync(process.execPath, ['--test', '--test-reporter=junit', fixture]);
assert.match(junit.stdout.toString(), /<!-- hello -->/);
});
test('test:log is emitted immediately with the expected payload', async () => {
const stream = run({
files: [fixture],
isolation: 'none',
});
const logs = [];
const events = [];
stream.on('test:log', (data) => {
logs.push(data);
events.push(`log:${data.message}`);
});
stream.on('test:pass', (data) => {
events.push(`pass:${data.name}`);
});
// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
assert.strictEqual(logs.length, 3);
const suiteLog = logs.find((log) => log.message === 'suite message');
assert.strictEqual(suiteLog.name, 'my suite');
assert.strictEqual(suiteLog.data, undefined);
const hello = logs.find((log) => log.message === 'hello');
assert.strictEqual(hello.name, 'logger');
assert.ok(!('level' in hello));
assert.deepStrictEqual(hello.data, { foo: 1 });
assert.strictEqual(typeof hello.nesting, 'number');
assert.strictEqual(typeof hello.testId, 'number');
assert.strictEqual(typeof hello.parentId, 'number');
assert.strictEqual(typeof hello.file, 'string');
assert.strictEqual(typeof hello.line, 'number');
assert.strictEqual(typeof hello.column, 'number');
// The runner does not interpret data; conventions like a level live there.
const warned = logs.find((log) => log.message === 'warned');
assert.ok(!('level' in warned));
assert.deepStrictEqual(warned.data, { level: 'warn', attempt: 2 });
// 'logger' logs immediately while its earlier-declared sibling 'slow' is
// still running, so the log must arrive before slow's buffered test:pass.
const logIndex = events.indexOf('log:hello');
const slowPassIndex = events.indexOf('pass:slow');
assert.notStrictEqual(logIndex, -1);
assert.notStrictEqual(slowPassIndex, -1);
assert.ok(
logIndex < slowPassIndex,
`test:log should arrive before slow's buffered test:pass; events=${events.join(', ')}`,
);
});