From 16f688b726578ddf26456a291e0f39d686e844cb Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:30:30 +0000 Subject: [PATCH] test(server): stop pending setup before removing its fixture (#8624) * test(server): diagnose recurring Windows setup timeout * test(server): stop pending setup before removing its fixture A test deadline does not cancel background setup. Abort and join it before deleting its working directory, and cover teardown with a deliberately live setup child. Keep the existing subdirectory shell assertions and timeout. * test(server): preserve setup readiness filesystem errors --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../src/server/worktree-session.test.ts | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/server/src/server/worktree-session.test.ts b/packages/server/src/server/worktree-session.test.ts index 16a3c60a8d..a5119faa5a 100644 --- a/packages/server/src/server/worktree-session.test.ts +++ b/packages/server/src/server/worktree-session.test.ts @@ -550,14 +550,17 @@ function createGitHubPrRemoteRepo() { describe("runWorktreeSetupInBackground", () => { const cleanupPaths: string[] = []; + let stopSetup: (() => Promise) | undefined; - afterEach(() => { + afterEach(async () => { + await stopSetup?.(); + stopSetup = undefined; for (const target of cleanupPaths.splice(0)) { rmSync(target, { recursive: true, force: true }); } }); - test("runs setup from an exact workspace subdirectory", async () => { + function startSubdirectorySetup(command: string) { const tempDir = realpathSync.native(mkdtempSync(path.join(tmpdir(), "worktree-setup-test-"))); cleanupPaths.push(tempDir); const repoDir = path.join(tempDir, "source"); @@ -578,13 +581,14 @@ describe("runWorktreeSetupInBackground", () => { path.join(workspaceCwd, "paseo.json"), JSON.stringify({ worktree: { - setup: ["pwd > setup-cwd.txt"], + setup: [command], }, }), ); const paseoHome = path.join(tempDir, ".paseo"); - await runWorktreeSetupInBackground( + const controller = new AbortController(); + const completion = runWorktreeSetupInBackground( { paseoHome, emitWorkspaceUpdateForWorkspaceId: async () => {}, @@ -603,10 +607,56 @@ describe("runWorktreeSetupInBackground", () => { worktreePath: worktree.worktreePath, workspaceCwd, }, + controller.signal, ); + const stop = async () => { + controller.abort(); + await completion; + }; + stopSetup = stop; + return { tempDir, workspaceCwd, worktreePath: worktree.worktreePath, completion, stop }; + } - expect(existsSync(path.join(workspaceCwd, "setup-cwd.txt"))).toBe(true); - expect(existsSync(path.join(worktree.worktreePath, "setup-cwd.txt"))).toBe(false); + test("runs setup from an exact workspace subdirectory", async () => { + const setup = startSubdirectorySetup("pwd > setup-cwd.txt"); + await setup.completion; + + expect(existsSync(path.join(setup.workspaceCwd, "setup-cwd.txt"))).toBe(true); + expect(existsSync(path.join(setup.worktreePath, "setup-cwd.txt"))).toBe(false); + }); + + test("teardown stops unfinished setup before removing its workspace", async ({ + onTestFinished, + }) => { + const setup = startSubdirectorySetup( + `node -e "require('fs').writeFileSync('setup-pid.txt', String(process.pid)); setInterval(()=>{},1000)"`, + ); + let pid = 0; + const probeChild = () => process.kill(pid, 0); + onTestFinished(async () => { + try { + expect(pid).toBeGreaterThan(0); + expect(existsSync(setup.tempDir)).toBe(false); + expect(probeChild).toThrow(expect.objectContaining({ code: "ESRCH" })); + } finally { + // Keep a failing cleanup regression from leaking its deliberately live child. + await setup.stop(); + } + }); + await expect + .poll( + () => { + try { + pid = Number(readFileSync(path.join(setup.workspaceCwd, "setup-pid.txt"), "utf8")); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + pid = 0; + } + return pid; + }, + { timeout: 30_000 }, + ) + .toBeGreaterThan(0); }); test("emits running then completed snapshots for no-setup workspaces without auto-starting scripts", async () => {