From 52090d4b4e239bfa19e3047c209a6f4c3262b5b4 Mon Sep 17 00:00:00 2001 From: devswha Date: Fri, 30 Oct 2026 08:19:28 +0900 Subject: [PATCH] test(portal): wait for an unrelated child's READY and exit past a loaded runner's start, not 2 s "running Portal > does not treat an arbitrary interpreter as a Portal script" starts a stand-in Bun process and waits for its READY line, racing it against a 2 s bound. On a host at load ~70 a Bun start takes longer: it timed out at ~2.0 s five times in local check fast runs across four lanes. The wait stays on the child's own signal (READY, exit); the bound (CHILD_MS, 20 s) only names a hung child, under the case's own timeout (CASE_MS). The shell case next to it, with the same pattern, gets the same. Fail-first with a scratch 2.5 s sleep before READY: old 2/2 timed out, new 3/3 passed. portal.test.ts passed 5/5 (38 tests); bun run check fast ok. --- changelog.d/unit-flake-portal-child-start.md | 3 +++ server/portal.test.ts | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 changelog.d/unit-flake-portal-child-start.md diff --git a/changelog.d/unit-flake-portal-child-start.md b/changelog.d/unit-flake-portal-child-start.md new file mode 100644 index 0000000000..a5340e145f --- /dev/null +++ b/changelog.d/unit-flake-portal-child-start.md @@ -0,0 +1,3 @@ +### Maintenance +- The two Portal unit tests that spawn an unrelated process (a shell, a Bun script) give it 20 s to + print READY and to exit, instead of 2 s, which a loaded runner's Bun start exceeded. diff --git a/server/portal.test.ts b/server/portal.test.ts index 74aa9930da..33a6683d7f 100644 --- a/server/portal.test.ts +++ b/server/portal.test.ts @@ -105,6 +105,12 @@ async function bounded(promise: Promise, what: string, timeoutMs = 2_000): } } +// A child process starting or stopping is waited for by its own signal (its READY line, its exit). The +// bound only names a child that hangs: a loaded runner can take seconds to start a Bun process, so it is +// far above a loaded runner's start time. A case that uses it sets its own timeout above it (CASE_MS). +const CHILD_MS = 20_000; +const CASE_MS = CHILD_MS + 10_000; + async function waitFor(done: () => boolean, what: string): Promise { const deadline = Date.now() + 2_000; while (!done()) { @@ -498,16 +504,16 @@ describe("running Portal", () => { // until the shell runs its script, /proc//cmdline can still show the parent's argv or be empty mid-exec const reader = unrelated.stdout.getReader(); try { - const ready = await bounded(reader.read(), "unrelated shell executed its script"); + const ready = await bounded(reader.read(), "unrelated shell executed its script", CHILD_MS); expect(new TextDecoder().decode(ready.value)).toBe("READY\n"); } finally { reader.releaseLock(); } await service.resume({ port: 7317, tokenSet: false, serveOnly: false }); expect(alive(unrelated.pid)).toBe(true); } finally { unrelated.kill("SIGTERM"); - await bounded(unrelated.exited, "unrelated process stopped"); + await bounded(unrelated.exited, "unrelated process stopped", CHILD_MS); } - }); + }, CASE_MS); it("does not treat an arbitrary interpreter as a Portal script", async () => { const stateDir = mkdtempSync(join(root, "state-")); @@ -525,7 +531,7 @@ describe("running Portal", () => { try { const reader = unrelated.stdout.getReader(); try { - const ready = await bounded(reader.read(), "unrelated interpreter executed its script"); + const ready = await bounded(reader.read(), "unrelated interpreter executed its script", CHILD_MS); expect(new TextDecoder().decode(ready.value)).toBe("READY\n"); } finally { reader.releaseLock(); } expect((await internals.processArgv(unrelated.pid))?.slice(1, 3)).toEqual([scriptPath, "expose"]); @@ -533,9 +539,9 @@ describe("running Portal", () => { expect(alive(unrelated.pid)).toBe(true); } finally { if (alive(unrelated.pid)) unrelated.kill("SIGTERM"); - await bounded(unrelated.exited, "unrelated interpreter stopped"); + await bounded(unrelated.exited, "unrelated interpreter stopped", CHILD_MS); } - }); + }, CASE_MS); it("rejects resume when a live saved PID has unreadable identity", async () => { const stateDir = mkdtempSync(join(root, "state-"));