voyage/tests/helpers/red-reporter.mjs
2026-10-24 19:23:59 +02:00

39 lines
1.9 KiB
JavaScript

// tests/helpers/red-reporter.mjs
// A node:test custom reporter for the red script of fix round 4-5a
// (tests/fix-round-4-5a-red.sh). It writes one JSON line per test:pass and
// test:fail event, for tests/helpers/red-judge.mjs to read:
// {name, file, nesting, ok, skip, todo, code, cname, failureType}
// `code` and `cname` come from the error's cause (the error node:test reports
// is its own ERR_TEST_FAILURE wrapper): ERR_ASSERTION / AssertionError for an
// assertion. A cause with no code (a thrown TypeError) gives the wrapper's
// code; a cause that is not an object (a file that failed to load, a suite
// whose subtests failed) gives a null `cname`. A pass gives null for both.
// `failureType` is node:test's own (testCodeFailure, subtestsFailed, ...).
// Use: node --test --test-reporter=./tests/helpers/red-reporter.mjs
// --test-reporter-destination=<file> <files>
/**
* @param {AsyncIterable<{type: string, data: any}>} source - node:test events
* @returns {AsyncGenerator<string>} one JSON line per pass or fail
*/
export default async function* redReporter(source) {
for await (const event of source) {
if (event.type !== 'test:pass' && event.type !== 'test:fail') continue;
const d = event.data || {};
const error = d.details && d.details.error;
const cause = error && error.cause;
const causeObj = cause !== null && typeof cause === 'object' ? cause : null;
const line = {
name: d.name,
file: d.file ?? null,
nesting: d.nesting,
ok: event.type === 'test:pass',
skip: Boolean(d.skip),
todo: Boolean(d.todo),
code: causeObj && causeObj.code != null ? String(causeObj.code) : (error && error.code != null ? String(error.code) : null),
cname: causeObj && causeObj.name != null ? String(causeObj.name) : null,
failureType: error && error.failureType !== undefined ? error.failureType : null,
};
yield `${JSON.stringify(line)}\n`;
}
}