nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
211 lines (199 loc) • 6.26 kB
JavaScript
// 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, ""),
""",
),
);
}
function escapeContent(s = "") {
return RegExp.prototype[Symbol.replace].call(
/</g,
RegExp.prototype[Symbol.replace].call(/&/g, s, "&"),
"<",
);
}
function escapeComment(s = "") {
return RegExp.prototype[Symbol.replace].call(/--/g, s, "--");
}
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";
});