Public: anyone can read this; the crew can change it.
A check's failure that belongs to no test is named with the error it threw, not "(unnamed)" alone #5728
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5727-a-check-s-failure-that-belongs-to-no-tes → main 3 commits fork 1 thread · 1 unresolved
3 files
changed +80
−7
src/checks/bun-test.ts +36 −5
| @@ -21,32 +21,63 @@ const HEADER = /^(\S+\.test\.[cm]?[jt]sx?):$/; | ||
| 21 | 21 | const UNNAMED_FAIL = /^\(fail\) ((?:.* > )?)\(unnamed\)/; |
| 22 | 22 | /** bun's own caret line under a hook that timed out, printed right after its `(fail)`. */ |
| 23 | 23 | const HOOK_TIMEOUT = /^\s*\^ a (\S+) hook timed out/; |
| 24 | /** bun prints what a hook or stray promise threw just above its `(fail)`. */ | |
| 25 | const THROWN = /^(?:[A-Za-z]*Error|error): .+/; | |
| 26 | const RESULT_LINE = /^\((?:fail|pass)\) /; | |
| 27 | const MAX_THROWN = 120; | |
| 24 | 28 | /** The tail that ends a file's report; bun's recap of `(fail)` lines can follow, headerless. */ |
| 25 | 29 | const SUMMARY = /^\s*\d+ pass$|^\d+ tests? failed:$/; |
| 26 | 30 | |
| 31 | /** | |
| 32 | * The first error bun printed since the previous result line, printable only, | |
| 33 | * with any directory prefix of a path dropped so no machine path is shown. | |
| 34 | */ | |
| 35 | function thrownSince(lines: readonly string[], from: number, to: number): string { | |
| 36 | for (let j = from; j < to; j++) { | |
| 37 | if (!THROWN.test(lines[j]!)) continue; | |
| 38 | const text = lines[j]! | |
| 39 | // eslint-disable-next-line no-control-regex | |
| 40 | .replace(/[\u0000-\u001f\u007f-\u009f]/g, "") | |
| 41 | .replace(/(?<![\w.])\/(?:[\w.@-]+\/)+/g, "") | |
| 42 | .trim(); | |
| 43 | return `: ${text.slice(0, MAX_THROWN)}`; | |
| 44 | } | |
| 45 | return ""; | |
| 46 | } | |
| 47 | ||
| 27 | 48 | /** |
| 28 | 49 | * bun labels a failure that belongs to no test — a hook that timed out or |
| 29 | 50 | * threw — `(unnamed)`, so every such red looked like one. Each is renamed |
| 30 | 51 | * for the file whose header precedes it, and for the hook when bun's caret |
| 31 | 52 | * line under it says one: `a.test.ts > beforeEach/afterEach (timeout)`. |
| 32 | * A hook that threw keeps bun's own describe prefix: `a.test.ts > s > (unnamed)`. | |
| 53 | * A hook that threw keeps bun's own describe prefix and gains the error bun | |
| 54 | * printed above it: `a.test.ts > s > (unnamed): error: database is closed`. | |
| 33 | 55 | * Once the summary starts no file is current until the next header, so a |
| 34 | 56 | * recap's `(fail)` lines are never given the last file's name. |
| 35 | 57 | */ |
| 36 | 58 | export function nameUnnamed(lines: readonly string[]): string[] { |
| 37 | 59 | let file: string | undefined; |
| 60 | let segment = 0; | |
| 38 | 61 | return lines.map((line, i) => { |
| 39 | 62 | const header = HEADER.exec(line); |
| 40 | 63 | if (header) { |
| 41 | 64 | file = header[1]; |
| 65 | segment = i + 1; | |
| 42 | 66 | return line; |
| 43 | 67 | } |
| 44 | 68 | if (SUMMARY.test(line)) file = undefined; |
| 45 | 69 | const unnamed = UNNAMED_FAIL.exec(line); |
| 46 | if (!unnamed || file === undefined) return line; | |
| 47 | const timeout = HOOK_TIMEOUT.exec(lines[i + 1] ?? ""); | |
| 48 | const what = timeout ? `${timeout[1]} (timeout)` : `${unnamed[1]}(unnamed)`; | |
| 49 | return `(fail) ${file} > ${what}${line.slice(unnamed[0].length)}`; | |
| 70 | if (RESULT_LINE.test(line)) { | |
| 71 | const from = segment; | |
| 72 | segment = i + 1; | |
| 73 | if (!unnamed || file === undefined) return line; | |
| 74 | const timeout = HOOK_TIMEOUT.exec(lines[i + 1] ?? ""); | |
| 75 | const what = timeout | |
| 76 | ? `${timeout[1]} (timeout)` | |
| 77 | : `${unnamed[1]}(unnamed)${thrownSince(lines, from, i)}`; | |
| 78 | return `(fail) ${file} > ${what}${line.slice(unnamed[0].length)}`; | |
| 79 | } | |
| 80 | return line; | |
| 50 | 81 | }); |
| 51 | 82 | } |
| 52 | 83 |
new above
offline
This widens the exclusion from the exact string to any name containing the marker, a behaviour change to shared-red counting. No test fails if it is reverted: the shared-red tests only cover nameUnnamed output. Add one that goes through pullsPerTest/the shared-red summary.