diff --git a/package.json b/package.json index fb75ade0f..2f2be33c5 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "server": "bun run browse/src/server.ts", "test": "bun run scripts/test-free-strict.ts", "check:gstack2-generated": "bun run scripts/gstack2/check-generated.ts", - "test:gstack2": "bun run gen:gstack2 && bun run check:gstack2-generated && bun test test/gstack2-*.test.ts", + "test:gstack2": "bun run gen:gstack2 && bun run check:gstack2-generated && bun test --timeout 30000 test/gstack2-*.test.ts", "test:gstack2:install": "bun run scripts/gstack2/test-install-matrix.ts --full", "test:gstack2:parity": "bun run ensure:gstack2-runtime && bun run scripts/gstack2/run-parity.ts", "test:free": "bun run scripts/test-free-shards.ts", diff --git a/test/gstack2-runtime-core.test.ts b/test/gstack2-runtime-core.test.ts index d8cb4fced..7a4b84ca2 100644 --- a/test/gstack2-runtime-core.test.ts +++ b/test/gstack2-runtime-core.test.ts @@ -227,7 +227,11 @@ describe("gstack 2 host-neutral paths and state", () => { const repo = path.join(root, "main repo"); const linked = path.join(root, "linked worktree"); await fs.mkdir(repo); - const git = (args: string[], cwd = repo) => spawnSync("git", args, { cwd, encoding: "utf8" }); + const git = (args: string[], cwd = repo) => spawnSync("git", args, { + cwd, + encoding: "utf8", + timeout: 15_000, + }); expect(git(["init", "--quiet", "-b", "main"]).status).toBe(0); git(["config", "user.email", "runtime@example.com"]); git(["config", "user.name", "Runtime Test"]); diff --git a/test/gstack2-runtime-effect-cli.test.ts b/test/gstack2-runtime-effect-cli.test.ts index c1e760c79..53d2457fb 100644 --- a/test/gstack2-runtime-effect-cli.test.ts +++ b/test/gstack2-runtime-effect-cli.test.ts @@ -27,7 +27,12 @@ async function fixture() { } afterEach(async () => { - await Promise.all(temporaryRoots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true }))); + await Promise.all(temporaryRoots.splice(0).map((root) => fs.rm(root, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 50, + }))); }); describe('gstack state external-effect CLI', () => { @@ -37,6 +42,7 @@ describe('gstack state external-effect CLI', () => { const git = async (args: string[], workingDirectory = cwd) => (await execFile('git', args, { cwd: workingDirectory, encoding: 'utf8', + timeout: 15_000, })).stdout.trim(); await git(['init']); await git(['config', 'user.name', 'GStack Test']); diff --git a/test/gstack2-runtime-install.test.ts b/test/gstack2-runtime-install.test.ts index e8c81638f..3b7a6111b 100644 --- a/test/gstack2-runtime-install.test.ts +++ b/test/gstack2-runtime-install.test.ts @@ -24,6 +24,7 @@ const ENTRIES = [ ]; const CAPABILITIES = { "fixture-tool": "cap/tool" }; const REPO_ROOT = path.resolve(import.meta.dir, ".."); +const FULL_RUNTIME_TEST_TIMEOUT_MS = process.platform === "win32" ? 120_000 : 30_000; describe("GStack 2 managed runtime installer", () => { test("installs, validates, activates, and writes an uninstall-friendly manifest", async () => { @@ -194,7 +195,7 @@ describe("GStack 2 managed runtime installer", () => { ], { capture: true, env: { ...process.env, GSTACK_HOME: home } }); expect(await fs.readFile(path.join(home, "security", "semantic-reviews.jsonl"), "utf8")).toContain('"outcome":"clean"'); }, { createDefaultSource: false }); - }, 30_000); + }, FULL_RUNTIME_TEST_TIMEOUT_MS); test("bounded subprocess execution confirms a timed-out command has exited", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-command-timeout-")); @@ -428,9 +429,13 @@ describe("GStack 2 managed runtime installer", () => { await installFixture(source, home, "1.0.0"); const pointer = await readJson(path.join(home, "versions", "current.json")); const manifest = await fs.readFile(path.join(home, "runtime-install.json")); - const windowsLauncher = await fs.readFile(path.join(home, "bin", "gstack.cmd")); + // Keep the launcher for this host executable so it can enter the shared + // recovery path. The transaction restores the inactive host variant. + const recoverableLauncher = process.platform === "win32" ? "gstack" : "gstack.cmd"; + const launcherPath = path.join(home, "bin", recoverableLauncher); + const launcherBefore = await fs.readFile(launcherPath); await fs.writeFile(path.join(home, "runtime-install.json"), '{"activeVersion":"crashed"}\n'); - await fs.writeFile(path.join(home, "bin", "gstack.cmd"), "candidate launcher\n"); + await fs.writeFile(launcherPath, "candidate launcher\n"); await fs.writeFile(path.join(home, "versions", "current.json"), `${JSON.stringify({ schemaVersion: 2, status: "active", @@ -447,7 +452,7 @@ describe("GStack 2 managed runtime installer", () => { previousPointer: pointer, files: [ { path: "runtime-install.json", existed: true, mode: 0o600, dataBase64: manifest.toString("base64") }, - { path: "bin/gstack.cmd", existed: true, mode: 0o644, dataBase64: windowsLauncher.toString("base64") }, + { path: `bin/${recoverableLauncher}`, existed: true, mode: 0o644, dataBase64: launcherBefore.toString("base64") }, ], }, null, 2)}\n`, { mode: 0o600 }); const orphanedLock = `${home}.runtime-lifecycle.lock`; @@ -462,7 +467,7 @@ describe("GStack 2 managed runtime installer", () => { expect(launched.stdout).toContain("gstack fixture doctor"); expect(await readJson(path.join(home, "versions", "current.json"))).toEqual(pointer); expect(await fs.readFile(path.join(home, "runtime-install.json"))).toEqual(manifest); - expect(await fs.readFile(path.join(home, "bin", "gstack.cmd"))).toEqual(windowsLauncher); + expect(await fs.readFile(launcherPath)).toEqual(launcherBefore); expect(await exists(path.join(home, ".gstack-runtime-transaction.json"))).toBe(false); expect(await exists(orphanedLock)).toBe(false); }); @@ -774,7 +779,7 @@ async function withFixture( if (options.createDefaultSource !== false) await createSource(source); await callback({ root, source, home }); } finally { - await fs.rm(root, { recursive: true, force: true }); + await fs.rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); } }