From 64c66c1e5c4aac23f49e52035c1414a7c63817c4 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 10 May 2026 10:29:49 -0700 Subject: [PATCH] feat(FN-3933): coordinate temp dir cleanup to harden testing suite isolatio Documents and tests a temp directory isolation fix in the core test suite, coordinating cleanup to prevent cross-test contamination. Fusion-Task-Id: FN-3933 --- docs/bugs/testing-suite-hardening.md | 1 + packages/core/src/__tests__/db.test.ts | 31 +++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/bugs/testing-suite-hardening.md b/docs/bugs/testing-suite-hardening.md index f36f80760..fde60dd7e 100644 --- a/docs/bugs/testing-suite-hardening.md +++ b/docs/bugs/testing-suite-hardening.md @@ -29,3 +29,4 @@ why it broke, how it was fixed, and what command verified the fix. | TSH-011 | PR build coverage | While reducing PR shard minutes, the workflow temporarily had no standalone `pnpm build` job. | The old workflow only had lint, typecheck, and test shards, so removing the shard-local build also removed PR build coverage. | Added an explicit `build` job to `pr-checks.yml` and updated the workflow contract test to require it. | `pnpm --filter @runfusion/fusion exec vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot` and `pnpm build` | | TSH-012 | Warning filtering | The first SQLite warning filter also installed a `process.on("warning")` listener that could duplicate unrelated warnings. | Node still prints non-filtered warnings by default, so manually writing them again made future warning output noisier. | The shared setup now only wraps `process.emitWarning` for the known SQLite experimental warning, and the dashboard noisy-output marker list no longer suppresses generic trace-warning guidance. | `pnpm --filter @fusion/core typecheck`, `pnpm --filter @fusion/core exec vitest run src/__tests__/central-db.test.ts --silent=passed-only --reporter=dot`, and `pnpm test:full` | | TSH-013 | Dashboard default runtime | The default dashboard package test still took about 9 minutes after the noise cleanup. | `pnpm --filter @fusion/dashboard test` continued to run every app/jsdom and API/node file, including exhaustive modal/view permutations and broad route matrices intended for deeper sweeps. | Added curated `dashboard-app-quality` and `dashboard-api-quality` Vitest projects for the default package gate, kept exhaustive coverage behind `test:deep`, `test:app`, and `test:api`, and documented when to run each lane. | `/usr/bin/time -p pnpm --filter @fusion/dashboard test` (148 files, 3642 tests, `real 91.73`), `/usr/bin/time -p pnpm --filter @fusion/dashboard test:deep` (419 files, 10747 tests, `real 335.47`), and `/usr/bin/time -p pnpm test:full` (`real 308.34`) | +| TSH-014 | Core temp-dir cleanup | `kb-db-test-*` directories could leak after `db.test.ts` runs and trip isolation checks. | `afterEach(async)` and module-level `afterAll(async)` both cleared `createdTmpDirs` before async `rm(...)` completed, so hook timing races could hide leftovers from final teardown during worker shutdown. | Coordinated cleanup bookkeeping by deleting set entries only after per-dir removal, kept `afterEach` as async best-effort cleanup, and switched the final `afterAll` fallback to defensive synchronous `rmSync(..., { recursive: true, force: true })`. | `pnpm --filter @fusion/core test -- src/__tests__/db.test.ts` and `node scripts/check-test-isolation.mjs --before && pnpm --filter @fusion/core test && node scripts/check-test-isolation.mjs` | diff --git a/packages/core/src/__tests__/db.test.ts b/packages/core/src/__tests__/db.test.ts index e8c4ea393..78c2e5b65 100644 --- a/packages/core/src/__tests__/db.test.ts +++ b/packages/core/src/__tests__/db.test.ts @@ -26,10 +26,31 @@ function makeTmpDir(): string { return dir; } -afterAll(async () => { +async function cleanupTmpDirsAsync(): Promise { const cleanup = Array.from(createdTmpDirs); - createdTmpDirs.clear(); - await Promise.all(cleanup.map((dir) => rm(dir, { recursive: true, force: true }))); + await Promise.all( + cleanup.map(async (dir) => { + await rm(dir, { recursive: true, force: true }); + createdTmpDirs.delete(dir); + }), + ); +} + +function cleanupTmpDirsSync(): void { + const cleanup = Array.from(createdTmpDirs); + for (const dir of cleanup) { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // best-effort fallback during teardown + } finally { + createdTmpDirs.delete(dir); + } + } +} + +afterAll(() => { + cleanupTmpDirsSync(); }); describe("Database", () => { @@ -50,9 +71,7 @@ describe("Database", () => { } catch { // already closed } - const cleanup = Array.from(createdTmpDirs); - createdTmpDirs.clear(); - await Promise.all(cleanup.map((dir) => rm(dir, { recursive: true, force: true }))); + await cleanupTmpDirsAsync(); }); describe("initialization", () => {