import { appendFileSync } from "node:fs"; import { resolve, basename } from "node:path"; import { a, h, count, duration, table, br, ul, code } from "html"; import { TestError, TestStatus, printTest } from "runner"; import { runTests } from "runner"; const cwd = resolve(import.meta.dir, "..", "..", "..", "test"); const filters = process.argv.slice(2); // TODO let result; const tests = runTests({ cwd, filters: ["*.test.ts", "*.test.js", "*.test.cjs", "*.test.mjs", "*.test.jsx", "*.test.tsx"], env: { // "BUN_GARBAGE_COLLECTOR_LEVEL": "2" }, timeout: 30_000, }); while (true) { const { value, done } = await tests.next(); if (done) { result = value; break; } else { printTest(value); } } const summaryPath = process.env["GITHUB_STEP_SUMMARY"]; const outputPath = process.env["GITHUB_OUTPUT"]; if (summaryPath) { const server = process.env["GITHUB_SERVER_URL"] ?? "https://github.com"; const repository = process.env["GITHUB_REPOSITORY"] ?? "oven-sh/bun"; const baseUrl = `${server}/${repository}/tree/${result.info.revision}/test/`; let failures: string = ""; let summaries: string[][] = []; let totalSummary = [ icon("pass") + " " + result.summary.pass, icon("fail") + " " + result.summary.fail, icon("skip") + " " + result.summary.skip, icon("todo") + " " + result.summary.todo, duration(result.summary.duration), ]; const sortedFiles = result.files.sort((a, b) => { if (a.status === b.status) { return a.file.localeCompare(b.file); } const order = { fail: 10, pass: 0, skip: -1, todo: -2, }; return order[b.status] - order[a.status]; }); for (const { file, status, summary } of sortedFiles) { summaries.push([ a(basename(file), baseUrl, file), icon(status), count(summary.pass), count(summary.fail), count(summary.skip), count(summary.todo), duration(summary.duration), ]); } const failedFiles = sortedFiles.filter(({ status }) => status === "fail"); for (const { file, tests, errors } of failedFiles) { const testErrors: TestError[] = []; if (errors?.length) { testErrors.push(...errors); } for (const { errors } of tests) { if (errors?.length) { testErrors.push(...errors); } } const failedTests = tests.filter(({ status }) => status === "fail"); const lines: string[] = []; for (const { name, errors } of failedTests) { let line = a(name, link(baseUrl, file, errors)); if (!errors?.length) { lines.push(line); continue; } line += br(2); for (const error of errors) { line += preview(error); } lines.push(line); } failures += h(3, a(file, link(baseUrl, file, testErrors))); failures += ul(lines); } let summary = h(2, "Summary") + table(["Passed", "Failed", "Skipped", "Todo", "Duration"], [totalSummary]) + table(["File", "Status", "Passed", "Failed", "Skipped", "Todo", "Duration"], summaries) + h(2, "Errors") + failures; appendFileSync(summaryPath, summary, "utf-8"); if (outputPath && failedFiles.length) { appendFileSync(outputPath, `\nfailing_tests_count=${failedFiles.length}`, "utf-8"); const rng = Math.ceil(Math.random() * 10_000); const value = failedFiles.map(({ file }) => ` - \`${file}\``).join("\n"); appendFileSync(outputPath, `\nfailing_tests<<${rng}\n${value}\n${rng}`, "utf-8"); } } function icon(status: TestStatus) { switch (status) { case "pass": return "✅"; case "fail": return "❌"; case "skip": return "⏭️"; case "todo": return "📝"; } } function link(baseUrl: string, fileName: string, errors?: TestError[]): string { const url = new URL(fileName, baseUrl); loop: for (const { stack } of errors ?? []) { for (const location of stack ?? []) { if (location.file.endsWith(fileName)) { url.hash = `L${location.line}`; break loop; } } } return url.toString(); } function preview(error: TestError): string { const { name, message, preview } = error; let result = code(`${name}: ${message}`, "diff"); if (preview) { result += code(preview, "typescript"); } return result; }