From b78c61e7d586a66f6b1581db49a346007410b304 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Thu, 30 Jul 2026 21:29:42 +0700 Subject: [PATCH] test: stop the finalize Phase spawning real PowerShell on Windows CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SetupApp`'s finalize Phase calls `installShims()`, which on Windows runs `updateWindowsUserPath()` -> `execFileSync("powershell", ...)`. Both app-level suites mock `node:child_process` but replace only `execFile`, so `execFileSync` passed straight through to the real module: every flow that reached finalize spawned a real, synchronous PowerShell. The runner's first such spawn is cold and blocks the event loop for ~10s, starving Ink's renders and the polling `waitForFrame` (which resolves silently on timeout). The app was still on `Configure tools` when the assertion ran, so `InstallApp > successful flow reaches the done screen` failed on every Windows run with "expected ... to contain 'Happy coding'" — 11-13s for that one test vs ~0.5s on Ubuntu, and 49s vs 13s for the file. The same stall took `ConfigApp > manual-creds happy path` down in run 30540195956. Stub `installShims` in both suites. Nothing there asserts shim files, and `lib/shims.test.ts` already skips its own installShims block on Windows for exactly this reason. It also stops the suite rewriting the PATH registry entry of whatever machine runs it. Co-Authored-By: Claude Opus 5 (1M context) --- tests/ConfigApp.test.tsx | 12 ++++++++++++ tests/InstallApp.test.tsx | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tests/ConfigApp.test.tsx b/tests/ConfigApp.test.tsx index 00e8a6f..e9cb021 100644 --- a/tests/ConfigApp.test.tsx +++ b/tests/ConfigApp.test.tsx @@ -16,6 +16,7 @@ import * as auth from "@/lib/auth.js"; import * as backend from "@/lib/backend.js"; import * as codegraph from "@/lib/codegraph.js"; import * as configure from "@/lib/configure.js"; +import * as shims from "@/lib/shims.js"; // ConfigApp shares its state machine with InstallApp (both render ). // These tests cover only the config-specific deltas: @@ -81,6 +82,17 @@ beforeEach(() => { created: true, }, ]); + // The finalize Phase calls installShims(), and on Windows that shells out to + // a real `powershell -Command` (execFileSync — NOT the mocked execFile) to + // rewrite the user-scope PATH in the registry: a synchronous spawn that + // blocks the event loop long enough to starve waitForFrame, and that mutates + // the PATH of whatever machine runs the suite. See InstallApp.test.tsx. + vi.spyOn(shims, "installShims").mockReturnValue({ + shimDir: join(configAppTempHome, ".codev-hub", "bin"), + shimsWritten: [], + rcFilesUpdated: [], + windowsUserPathUpdated: false, + }); }); type ExecCb = (error: Error | null, stdout: string, stderr: string) => void; diff --git a/tests/InstallApp.test.tsx b/tests/InstallApp.test.tsx index 5f575a4..516cad1 100644 --- a/tests/InstallApp.test.tsx +++ b/tests/InstallApp.test.tsx @@ -18,6 +18,7 @@ import * as codegraph from "@/lib/codegraph.js"; import * as configure from "@/lib/configure.js"; import { FALLBACK_MODEL } from "@/lib/const.js"; import * as npm from "@/lib/npm.js"; +import * as shims from "@/lib/shims.js"; import { vscodeSettingsPath, vscodeUserDataDir, @@ -97,6 +98,20 @@ beforeEach(() => { created: true, }, ]); + // The finalize Phase calls installShims(), and on Windows that shells out to + // a real `powershell -Command` (execFileSync — NOT the mocked execFile) to + // rewrite the user-scope PATH in the registry. That's a synchronous spawn + // which blocks the event loop for ~10s on its cold start, so the first flow + // to reach finalize blows past waitForFrame's budget and never renders the + // done screen — and it mutates the PATH of whatever machine runs the suite. + // lib/shims.test.ts skips its installShims block on Windows for the same + // reason; nothing here asserts shim files, so stub the whole call. + vi.spyOn(shims, "installShims").mockReturnValue({ + shimDir: join(installAppTempHome, ".codev-hub", "bin"), + shimsWritten: [], + rcFilesUpdated: [], + windowsUserPathUpdated: false, + }); }); type ExecCb = (error: Error | null, stdout: string, stderr: string) => void;