From d7a659b0a7c09da0cc6f0c27725213dd3d5ccfc9 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Mon, 3 Aug 2026 14:11:05 +0000 Subject: [PATCH] test(helpers): add the shared temp-repo lifecycle helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `createTempDirPool` gives a suite one owner for its temp fixture repos — create on demand, remove them all in one `afterAll` — instead of a hand-rolled mkdtemp/rmSync pair per file. The PDG receiver pin added in the next commit uses it. Cherry-picked verbatim from ec36c6dda on the #2802 branch, where it was extracted to collapse five hand-rolled cleanups. Identical content, so if both branches land the add resolves as a duplicate rather than a divergence. Refs #2807 Co-Authored-By: Claude Opus 5 (1M context) --- gitnexus/test/helpers/temp-dir-pool.ts | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 gitnexus/test/helpers/temp-dir-pool.ts diff --git a/gitnexus/test/helpers/temp-dir-pool.ts b/gitnexus/test/helpers/temp-dir-pool.ts new file mode 100644 index 000000000..cd6749c82 --- /dev/null +++ b/gitnexus/test/helpers/temp-dir-pool.ts @@ -0,0 +1,62 @@ +/** + * Temp-directory lifecycle for pipeline-level integration tests. + * + * The pipeline mutates the repo it is handed (parse caches, `.gitnexus/`), so + * these tests each run against a throwaway copy of a fixture. Every consumer + * had hand-rolled the SAME three parts — a `string[]` of created dirs, a + * `mkdtempSync` that pushes onto it, and an `afterAll` that `rmSync`s the lot. + * Extracted at the fourth consumer (`pipeline-pdg`, `pipeline-pdg-streaming`, + * `interproc-taint`, `pdg-chained-receiver-callees`); the copies had already + * drifted — `pipeline-pdg` registered two cleanup hooks over one array. + * + * Only the LIFECYCLE is shared, deliberately: seeding differs per test (a + * recursive fixture copy, a single file, an inline-written source, or nothing + * at all), so `dir()` hands back an empty registered directory and the caller + * fills it however it likes. `fromFixture()` is the common case. + * + * `createTempDirPool` calls `afterAll` itself, so it must be called from a + * test file's module scope (not from this module's top level — ESM caching + * would register the hook once, for whichever file imported it first). + * Directories are registered at creation, before any seeding runs, so a + * fixture copy or a pipeline run that throws still leaves them cleaned up. + */ + +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { afterAll } from 'vitest'; + +export interface TempDirPool { + /** A fresh empty temp dir, registered for cleanup. Seed it yourself. */ + dir(): string; + /** A fresh temp dir seeded with a recursive copy of `fixture`. */ + fromFixture(fixture: string): string; +} + +/** + * Create a pool of temp directories that are removed after the calling test + * file finishes. `prefix` is the `mkdtemp` prefix (e.g. `'gn-pdg-'`), kept + * per-pool so a leaked directory still names the suite that made it. + */ +export function createTempDirPool(prefix: string): TempDirPool { + const created: string[] = []; + + const dir = (): string => { + const made = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + created.push(made); + return made; + }; + + afterAll(() => { + for (const d of created) fs.rmSync(d, { recursive: true, force: true }); + }); + + return { + dir, + fromFixture(fixture: string): string { + const made = dir(); + fs.cpSync(fixture, made, { recursive: true }); + return made; + }, + }; +}