// 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= /** * @param {AsyncIterable<{type: string, data: any}>} source - node:test events * @returns {AsyncGenerator} 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`; } }