From 00c20dcd56c5d7e7926d14cfbe2111b7885af15e Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Sun, 24 May 2026 23:34:46 +0900 Subject: [PATCH] test: isolate fixture state by copying per getFixture() call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each getFixture() call now copies the source fixture to a unique fixtures/.tmp// directory. The copy becomes the fixtureRoot, so outDir, .astro, node_modules/.vite, and the CLI's default dist/ all land inside that unique dir — nothing is ever shared between tests. clean() becomes a no-op, which removes the rm-during-vite-shutdown race entirely: the vite optimizer (esbuild) keeps writing async to node_modules/.vite after viteServer.close() returns, so the previous rm() in afterEach could race with it. With per-test directories there is no longer any need to clean up between tests; a globalSetup hook wipes fixtures/.tmp/ once before each test run instead. Drops the workarounds from the previous PR: - maxRetries/retryDelay on the rm() calls - fileParallelism: false in the root vitest config Test parallelism is re-enabled (~33s → ~20s for the full suite). --- .gitignore | 3 ++ packages/core/tests/fixture.ts | 57 +++++++++++------------------- packages/core/tests/globalSetup.ts | 8 +++++ packages/core/vitest.config.ts | 1 + packages/hono/tests/fixture.ts | 54 +++++++++------------------- packages/hono/tests/globalSetup.ts | 8 +++++ packages/hono/vitest.config.ts | 1 + vitest.config.ts | 1 - 8 files changed, 59 insertions(+), 74 deletions(-) create mode 100644 packages/core/tests/globalSetup.ts create mode 100644 packages/hono/tests/globalSetup.ts diff --git a/.gitignore b/.gitignore index 971622d..a3488d1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Astro-specific files .astro + +# Test fixture temp directories +.tmp diff --git a/packages/core/tests/fixture.ts b/packages/core/tests/fixture.ts index aed792d..6bc8653 100644 --- a/packages/core/tests/fixture.ts +++ b/packages/core/tests/fixture.ts @@ -1,4 +1,5 @@ -import { rm } from "node:fs/promises"; +import { randomBytes } from "node:crypto"; +import { cpSync, mkdirSync } from "node:fs"; import path from "node:path"; import type { AstroInlineConfig } from "astro"; import { mergeConfig } from "astro/config"; @@ -18,6 +19,9 @@ export type GetFromViteMiddlewareFunction = ( path: string, ) => Promise; +const FIXTURES_DIR = path.join(import.meta.dirname, "..", "fixtures"); +const TMP_DIR = path.join(FIXTURES_DIR, ".tmp"); + export function getFixture(fixtureName: string): { fixtureRoot: string; outDir: string; @@ -33,38 +37,24 @@ export function getFixture(fixtureName: string): { }>; clean: () => Promise; } { - const fixtureRoot = path.join( - import.meta.dirname, - "..", - "fixtures", + const sourceFixtureRoot = path.join( + FIXTURES_DIR, ...dotStringToPath(fixtureName), ); - const outDir = path.join( - fixtureRoot, - `dist-${Math.random().toString(36).substring(2, 15)}`, - ); + const uniqueId = randomBytes(6).toString("hex"); + const fixtureRoot = path.join(TMP_DIR, uniqueId); + mkdirSync(TMP_DIR, { recursive: true }); + cpSync(sourceFixtureRoot, fixtureRoot, { recursive: true }); - const clean = async () => { - await rm(outDir, { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - await rm(path.join(fixtureRoot, ".astro"), { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - await rm(path.join(fixtureRoot, "node_modules"), { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - }; + const outDir = path.join(fixtureRoot, "dist"); + + // Each getFixture() call creates a unique fixtureRoot under fixtures/.tmp/, + // so outDir, .astro, and node_modules/.vite are never shared between tests. + // The whole .tmp/ directory is wiped before each test run via globalSetup, + // so no cleanup is needed (and skipping it avoids racing with the vite + // optimizer that may still be writing to .vite after viteServer.close()). + const clean = async () => {}; const DEV_TEST_ADDRESS = "http://host-placeholder.test"; @@ -110,10 +100,7 @@ export function getFixture(fixtureName: string): { toFetchResponse(res).then(resolve); }); }, - stop: async () => { - await stopDevServer(); - await clean(); - }, + stop: stopDevServer, }; }, build: async (params?: MightyServerOptions) => { @@ -147,9 +134,7 @@ export function getFixture(fixtureName: string): { return { render, - stop: async () => { - await clean(); - }, + stop: async () => {}, }; }, clean, diff --git a/packages/core/tests/globalSetup.ts b/packages/core/tests/globalSetup.ts new file mode 100644 index 0000000..68cdde0 --- /dev/null +++ b/packages/core/tests/globalSetup.ts @@ -0,0 +1,8 @@ +import { rmSync } from "node:fs"; +import path from "node:path"; + +const TMP_DIR = path.join(import.meta.dirname, "..", "fixtures", ".tmp"); + +export default function setup(): void { + rmSync(TMP_DIR, { recursive: true, force: true }); +} diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 53fea7a..f90b23d 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -9,5 +9,6 @@ export default defineConfig({ "@tests/": `${path.resolve(import.meta.dirname, "tests")}/`, }, testTimeout: 30000, + globalSetup: ["./tests/globalSetup.ts"], }, }); diff --git a/packages/hono/tests/fixture.ts b/packages/hono/tests/fixture.ts index 3628436..7a56cc8 100644 --- a/packages/hono/tests/fixture.ts +++ b/packages/hono/tests/fixture.ts @@ -1,4 +1,5 @@ -import { rm } from "node:fs/promises"; +import { randomBytes } from "node:crypto"; +import { cpSync, mkdirSync } from "node:fs"; import path from "node:path"; import { build } from "@gomighty/core/build"; import type { MightyServerOptions } from "@gomighty/core/types"; @@ -15,6 +16,9 @@ type AppEnv = { }; }; +const FIXTURES_DIR = path.join(import.meta.dirname, "..", "fixtures"); +const TMP_DIR = path.join(FIXTURES_DIR, ".tmp"); + export function getFixture(fixtureName: string): { fixtureRoot: string; outDir: string; @@ -30,44 +34,20 @@ export function getFixture(fixtureName: string): { }; clean: () => Promise; } { - const fixtureRoot = path.join( - import.meta.dirname, - "..", - "fixtures", - fixtureName, - ); + const sourceFixtureRoot = path.join(FIXTURES_DIR, fixtureName); - const outDir = path.join( - fixtureRoot, - `dist-${Math.random().toString(36).substring(2, 15)}`, - ); + const uniqueId = randomBytes(6).toString("hex"); + const fixtureRoot = path.join(TMP_DIR, uniqueId); + mkdirSync(TMP_DIR, { recursive: true }); + cpSync(sourceFixtureRoot, fixtureRoot, { recursive: true }); - const clean = async (): Promise => { - await rm(outDir, { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - await rm(path.join(fixtureRoot, "dist"), { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - await rm(path.join(fixtureRoot, ".astro"), { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - await rm(path.join(fixtureRoot, "node_modules"), { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); - }; + const outDir = path.join(fixtureRoot, "dist"); + + // Each getFixture() call creates a unique fixtureRoot under fixtures/.tmp/, + // so outDir, .astro, dist, and node_modules/.vite are never shared between + // tests. The whole .tmp/ directory is wiped before each test run via + // globalSetup, so no cleanup is needed. + const clean = async (): Promise => {}; return { fixtureRoot, diff --git a/packages/hono/tests/globalSetup.ts b/packages/hono/tests/globalSetup.ts new file mode 100644 index 0000000..68cdde0 --- /dev/null +++ b/packages/hono/tests/globalSetup.ts @@ -0,0 +1,8 @@ +import { rmSync } from "node:fs"; +import path from "node:path"; + +const TMP_DIR = path.join(import.meta.dirname, "..", "fixtures", ".tmp"); + +export default function setup(): void { + rmSync(TMP_DIR, { recursive: true, force: true }); +} diff --git a/packages/hono/vitest.config.ts b/packages/hono/vitest.config.ts index 20a75d0..c10f3a8 100644 --- a/packages/hono/vitest.config.ts +++ b/packages/hono/vitest.config.ts @@ -8,5 +8,6 @@ export default defineConfig({ "@/": `${path.resolve(import.meta.dirname, "src")}/`, }, testTimeout: 30000, + globalSetup: ["./tests/globalSetup.ts"], }, }); diff --git a/vitest.config.ts b/vitest.config.ts index 21a1202..7959d17 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,5 @@ export default defineConfig({ test: { projects: ["packages/*"], testTimeout: 30000, - fileParallelism: false, }, });