UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

211 lines (199 loc) 6.26 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/test_runner/reporter/junit.js import { inspectWithNoCustomRetry } from "nstdlib/lib/internal/errors"; import { hostname } from "nstdlib/lib/os"; const inspectOptions = { __proto__: null, colors: false, breakLength: Infinity, }; const HOSTNAME = hostname(); function escapeAttribute(s = "") { return ((...args) => globalThis.escapeContent(...args))( RegExp.prototype[Symbol.replace].call( /"/g, RegExp.prototype[Symbol.replace].call(/\n/g, s, ""), "&quot;", ), ); } function escapeContent(s = "") { return RegExp.prototype[Symbol.replace].call( /</g, RegExp.prototype[Symbol.replace].call(/&/g, s, "&amp;"), "&lt;", ); } function escapeComment(s = "") { return RegExp.prototype[Symbol.replace].call(/--/g, s, "&#45;&#45;"); } function treeToXML(tree) { if (typeof tree === "string") { return `${((...args) => globalThis.escapeContent(...args))(tree)}\n`; } const { tag, attrs, nesting, children, comment } = tree; const indent = String.prototype.repeat.call("\t", nesting + 1); if (comment) { return `${indent}<!-- ${((...args) => globalThis.escapeComment(...args))(comment)} -->\n`; } const attrsString = Array.prototype.join.call( Array.prototype.map.call( Object.entries(attrs), ({ 0: key, 1: value }) => `${key}="${((...args) => globalThis.escapeAttribute(...args))(String(value))}"`, ), " ", ); if (!children?.length) { return `${indent}<${tag} ${attrsString}/>\n`; } const childrenString = Array.prototype.join.call( Array.prototype.map.call(children ?? [], treeToXML), "", ); return `${indent}<${tag} ${attrsString}>\n${childrenString}${indent}</${tag}>\n`; } function isFailure(node) { return ( (node?.children && Array.prototype.some.call(node.children, (c) => c.tag === "failure")) || node?.attrs?.failures ); } function isSkipped(node) { return ( (node?.children && Array.prototype.some.call(node.children, (c) => c.tag === "skipped")) || node?.attrs?.failures ); } export default (async function* junitReporter(source) { yield '<?xml version="1.0" encoding="utf-8"?>\n'; yield "<testsuites>\n"; let currentSuite = null; const roots = []; function startTest(event) { const originalSuite = currentSuite; currentSuite = { __proto__: null, attrs: { __proto__: null, name: event.data.name }, nesting: event.data.nesting, parent: currentSuite, children: [], }; if (originalSuite?.children) { Array.prototype.push.call(originalSuite.children, currentSuite); } if (!currentSuite.parent) { Array.prototype.push.call(roots, currentSuite); } } for await (const event of source) { switch (event.type) { case "test:start": { startTest(event); break; } case "test:pass": case "test:fail": { if (!currentSuite) { startTest({ __proto__: null, data: { __proto__: null, name: "root", nesting: 0 }, }); } if ( currentSuite.attrs.name !== event.data.name || currentSuite.nesting !== event.data.nesting ) { startTest(event); } const currentTest = currentSuite; if (currentSuite?.nesting === event.data.nesting) { currentSuite = currentSuite.parent; } currentTest.attrs.time = Number.prototype.toFixed.call( event.data.details.duration_ms / 1000, 6, ); const nonCommentChildren = Array.prototype.filter.call( currentTest.children, (c) => c.comment == null, ); if (nonCommentChildren.length > 0) { currentTest.tag = "testsuite"; currentTest.attrs.disabled = 0; currentTest.attrs.errors = 0; currentTest.attrs.tests = nonCommentChildren.length; currentTest.attrs.failures = Array.prototype.filter.call( currentTest.children, isFailure, ).length; currentTest.attrs.skipped = Array.prototype.filter.call( currentTest.children, isSkipped, ).length; currentTest.attrs.hostname = HOSTNAME; } else { currentTest.tag = "testcase"; currentTest.attrs.classname = event.data.classname ?? "test"; if (event.data.skip) { Array.prototype.push.call(currentTest.children, { __proto__: null, nesting: event.data.nesting + 1, tag: "skipped", attrs: { __proto__: null, type: "skipped", message: event.data.skip, }, }); } if (event.data.todo) { Array.prototype.push.call(currentTest.children, { __proto__: null, nesting: event.data.nesting + 1, tag: "skipped", attrs: { __proto__: null, type: "todo", message: event.data.todo, }, }); } if (event.type === "test:fail") { const error = event.data.details?.error; currentTest.children.push({ __proto__: null, nesting: event.data.nesting + 1, tag: "failure", attrs: { __proto__: null, type: error?.failureType || error?.code, message: error?.message ?? "", }, children: [inspectWithNoCustomRetry(error, inspectOptions)], }); currentTest.failures = 1; currentTest.attrs.failure = error?.message ?? ""; } } break; } case "test:diagnostic": { const parent = currentSuite?.children ?? roots; Array.prototype.push.call(parent, { __proto__: null, nesting: event.data.nesting, comment: event.data.message, }); break; } default: break; } } for (const suite of roots) { yield treeToXML(suite); } yield "</testsuites>\n"; });