From 99518406715e07f1a78a52f7398ca3aa6446b5b4 Mon Sep 17 00:00:00 2001 From: Luiz Lima Date: Sun, 18 Oct 2026 20:37:12 -0300 Subject: [PATCH] =?UTF-8?q?test(clone):=20a=20planted=20fake=20git=20is=20?= =?UTF-8?q?removed=20with=20retries=20=E2=80=94=20Windows=20may=20hold=20i?= =?UTF-8?q?t=20busy=20(EBUSY)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.33.0 CI run on windows-latest·node24 failed cleanup with EBUSY removing the fake git.exe. Nothing the tests assert changes. --- test/clone.test.ts | 5 +++-- test/helpers/fake-git.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/clone.test.ts b/test/clone.test.ts index f3ad2d4a..750fe28f 100644 --- a/test/clone.test.ts +++ b/test/clone.test.ts @@ -5,6 +5,7 @@ import { cloneRepos, destinationFault, findGit, listRepos, planClone, type Desti import type { RepoEntry } from "../src/core/repos.js"; import type { RunRequest } from "../src/core/proc.js"; import { tmpDir } from "./helpers/forge.js"; +import { removeWithRetry } from "./helpers/fs.js"; // Spec 37 §5.1 *Destination*, §5.2 and §10 — C5 in each of its forms, the listing's states, planClone's refusals // (C3–C5, R1–R3), findGit, and cloneRepos with a recording runner. No test runs git: the runner is injected. @@ -17,7 +18,7 @@ afterEach(async () => { }); async function scratch(prefix = "craftar-clone-"): Promise { const dir = await fs.realpath(await tmpDir(prefix)); - cleanups.push(() => fs.rm(dir, { recursive: true, force: true })); + cleanups.push(() => removeWithRetry(dir)); return dir; } const entry = (name: string, extra: Partial = {}): RepoEntry => ({ name, url: `https://git.example.com/acme/${name}.git`, path: name, ref: null, ...extra }); @@ -238,7 +239,7 @@ describe("findGit — onPath, nothing else", () => { // spelling exists on win32 too — a temp dir may sit on another drive, where path.relative gives an absolute path. const dir = path.join(process.cwd(), "node_modules", ".cache", `craftar-findgit-${process.pid}-${Date.now()}`); await fs.mkdir(dir, { recursive: true }); - cleanups.push(() => fs.rm(dir, { recursive: true, force: true })); + cleanups.push(() => removeWithRetry(dir)); await fs.copyFile(process.execPath, path.join(dir, "git" + EXE)); await fs.chmod(path.join(dir, "git" + EXE), 0o755); const rel = path.relative(process.cwd(), dir); diff --git a/test/helpers/fake-git.ts b/test/helpers/fake-git.ts index 3eef6aef..9eef44ee 100644 --- a/test/helpers/fake-git.ts +++ b/test/helpers/fake-git.ts @@ -1,6 +1,7 @@ import { promises as fs } from "node:fs"; import path from "node:path"; import { tmpDir } from "./forge.js"; +import { removeWithRetry } from "./fs.js"; // Spec 37 §10: no test runs a real `git clone` and none reaches the network. The CLI's clone step is exercised with // a fake git: a copy of node named `git` (`git.exe` on win32) alone on an absolute PATH entry — the trick @@ -54,7 +55,7 @@ export async function fakeGit(): Promise { log, // NODE_OPTIONS reads `\` inside double quotes as an escape: on win32 the hook's path is given with `/`, which node accepts. env: (extra = {}) => ({ PATH: dir, Path: dir, NODE_OPTIONS: `--require "${hook.replace(/\\/g, "/")}"`, CRAFTAR_TEST_CLONE_LOG: log, ...extra }), - cleanup: () => fs.rm(dir, { recursive: true, force: true }), + cleanup: () => removeWithRetry(dir), }; }