From e3cb0416ee52615c52e152304efb2e1cb56249b3 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 15 Aug 2026 18:33:40 -0700 Subject: [PATCH] test(windows): curate skill-census + browser-manager-unit; surface unhandled errors in the epilogue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2 Windows census (zero failing TESTS — the first curation wave held): shard 1 failed on an unhandled module-load throw in skill-census (the skills-tree symlink layout needs Developer Mode CI runners lack) and shard 2 wedged to its wall deadline inside browser-manager-unit — both get receipted exclusions; macOS + Linux lanes keep covering the files. The unhandled-error class also exposed an epilogue gap: it fails the shard via the strict classifier but produces no (fail) lines, so the epilogue read 'FAIL — 0 failing test(s)' with no culprit. The reporter now attributes each '# Unhandled error between tests' marker to its chunk and the FAIL line carries the count. --- scripts/test-free-shards.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/test-free-shards.ts b/scripts/test-free-shards.ts index 3e921bc7b..c8491f0c4 100755 --- a/scripts/test-free-shards.ts +++ b/scripts/test-free-shards.ts @@ -197,6 +197,15 @@ export const KNOWN_WINDOWS_INCOMPATIBLE: Array<{ file: string; reason: string }> file: 'design/test/variants-retry-after.test.ts', reason: 'wall-clock retry-timing assertions — flaky on the slow windows-latest runner even with widened bounds', }, + // Round-2 census (PR #2593 run 31919227507) after the first seven: + { + file: 'test/skill-census.test.ts', + reason: 'census walk throws at module load on Windows (skill-census.ts:63) — the skills-tree symlink layout needs Developer Mode that CI runners lack', + }, + { + file: 'browse/test/browser-manager-unit.test.ts', + reason: 'wedges the shard to its wall deadline on windows-latest (in-flight at kill); needs a Windows repro to diagnose — macOS + Linux lanes cover the file', + }, ]; // Force-include overrides: files a WINDOWS_FRAGILE_PATTERNS regex excludes for @@ -574,6 +583,14 @@ export interface FreeRunReport { failures: FreeRunFailure[]; /** Files that crashed a worker (bun retries once; a second crash is final). Deduped. */ crashedFiles: string[]; + /** + * "# Unhandled error between tests" markers, attributed to the chunk they + * appeared in. These fail the shard via the strict classifier but produce + * NO (fail) lines — without surfacing them here, the epilogue reads + * "FAIL — 0 failing test(s)" and the culprit is undiscoverable from CI + * output (first Windows lane run: a module-load throw in skill-census). + */ + unhandledErrors: Array<{ file: string | null }>; /** * Wedge-suspect heuristic for a wall-timeout kill: files whose header was * seen but whose chunk never ENDED (chunk end = the next file's header, or @@ -620,6 +637,7 @@ export class FreeRunReporter { private readonly crashed = new Set(); private currentFile: string | null = null; private inRecap = false; + private readonly unhandled: Array<{ file: string | null }> = []; private testsRan: number | null = null; private filesRan: number | null = null; private sawSummary = false; @@ -665,6 +683,7 @@ export class FreeRunReporter { sawTerminalSummary: this.sawSummary, failures: [...this.failures], crashedFiles: [...this.crashed].sort(), + unhandledErrors: [...this.unhandled], inFlight, filesWithNoOutput: this.plannedFiles.filter((f) => !this.progress.has(normalizeRelativePath(f))).length, }; @@ -689,6 +708,10 @@ export class FreeRunReporter { this.currentFile = null; } + if (line === '# Unhandled error between tests') { + this.unhandled.push({ file: this.currentFile }); + } + const header = FILE_HEADER_RE.exec(line); if (header) { const file = this.canonicalize(header[1]); @@ -788,7 +811,7 @@ export function buildRunEpilogue( const failingFiles = new Set(report.failures.map((f) => f.file ?? '(unattributed)')); const lines = [ `[test:free] FAIL — ${report.failures.length} failing test(s) in ${failingFiles.size} file(s), ` - + `${report.crashedFiles.length} crashed worker(s). Full log: ${logPath}`, + + `${report.crashedFiles.length} crashed worker(s)${report.unhandledErrors.length > 0 ? `, ${report.unhandledErrors.length} unhandled error(s) between tests` : ''}. Full log: ${logPath}`, ]; for (const failure of report.failures) { lines.push(` ✗ ${failure.file ?? '(unattributed)'} — ${failure.testName}`); @@ -796,6 +819,9 @@ export function buildRunEpilogue( for (const file of report.crashedFiles) { lines.push(` ⚠ crashed+retried: ${file}`); } + for (const u of report.unhandledErrors) { + lines.push(` ⚠ unhandled error between tests (around ${u.file ?? 'unknown file'})`); + } if (status === 'timed-out') { if (report.inFlight.length > 0) { lines.push(` ⏱ in flight at kill: ${report.inFlight.join(', ')}`);