From e1e20c424b528a9a8340b53ad5b78676d09ab01c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 12 Apr 2026 04:52:29 +0100 Subject: [PATCH] test(process): share supervisor sigkill wait assertions --- src/process/supervisor/adapters/child.test.ts | 39 ++++++++---------- src/process/supervisor/adapters/pty.test.ts | 39 ++++++++---------- .../supervisor/adapters/test-support.ts | 40 +++++++++++++++++++ 3 files changed, 74 insertions(+), 44 deletions(-) create mode 100644 src/process/supervisor/adapters/test-support.ts diff --git a/src/process/supervisor/adapters/child.test.ts b/src/process/supervisor/adapters/child.test.ts index 2d1ef9322fc..038cb214ffe 100644 --- a/src/process/supervisor/adapters/child.test.ts +++ b/src/process/supervisor/adapters/child.test.ts @@ -2,6 +2,10 @@ import type { ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { PassThrough } from "node:stream"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { + expectRealExitWinsOverSigkillFallback, + expectWaitStaysPendingUntilSigkillFallback, +} from "./test-support.js"; const { spawnWithFallbackMock, killProcessTreeMock } = vi.hoisted(() => ({ spawnWithFallbackMock: vi.fn(), @@ -135,20 +139,9 @@ describe("createChildAdapter", () => { vi.useFakeTimers(); const { adapter } = await createAdapterHarness({ pid: 4567 }); - const waitPromise = adapter.wait(); - const settled = vi.fn(); - void waitPromise.then(() => settled()); - - adapter.kill(); - - await Promise.resolve(); - expect(settled).not.toHaveBeenCalled(); - - await vi.advanceTimersByTimeAsync(3999); - expect(settled).not.toHaveBeenCalled(); - - await vi.advanceTimersByTimeAsync(1); - await expect(waitPromise).resolves.toEqual({ code: null, signal: "SIGKILL" }); + await expectWaitStaysPendingUntilSigkillFallback(adapter.wait(), () => { + adapter.kill(); + }); }); it("prefers real child close over the SIGKILL fallback settle", async () => { @@ -166,14 +159,16 @@ describe("createChildAdapter", () => { return { ...stub, adapter }; })(); - const waitPromise = adapter.wait(); - adapter.kill(); - emitClose(0, "SIGKILL"); - - await expect(waitPromise).resolves.toEqual({ code: 0, signal: "SIGKILL" }); - - await vi.advanceTimersByTimeAsync(4_001); - await expect(adapter.wait()).resolves.toEqual({ code: 0, signal: "SIGKILL" }); + await expectRealExitWinsOverSigkillFallback({ + waitPromise: adapter.wait(), + triggerKill: () => { + adapter.kill(); + }, + emitExit: () => { + emitClose(0, "SIGKILL"); + }, + expected: { code: 0, signal: "SIGKILL" }, + }); expect(killMock).toHaveBeenCalledWith("SIGKILL"); }); diff --git a/src/process/supervisor/adapters/pty.test.ts b/src/process/supervisor/adapters/pty.test.ts index 32ca418b533..021bd89a717 100644 --- a/src/process/supervisor/adapters/pty.test.ts +++ b/src/process/supervisor/adapters/pty.test.ts @@ -1,4 +1,8 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { + expectRealExitWinsOverSigkillFallback, + expectWaitStaysPendingUntilSigkillFallback, +} from "./test-support.js"; const { spawnMock, ptyKillMock, killProcessTreeMock } = vi.hoisted(() => ({ spawnMock: vi.fn(), @@ -98,20 +102,9 @@ describe("createPtyAdapter", () => { args: ["-lc", "sleep 10"], }); - const waitPromise = adapter.wait(); - const settled = vi.fn(); - void waitPromise.then(() => settled()); - - adapter.kill(); - - await Promise.resolve(); - expect(settled).not.toHaveBeenCalled(); - - await vi.advanceTimersByTimeAsync(3999); - expect(settled).not.toHaveBeenCalled(); - - await vi.advanceTimersByTimeAsync(1); - await expect(waitPromise).resolves.toEqual({ code: null, signal: "SIGKILL" }); + await expectWaitStaysPendingUntilSigkillFallback(adapter.wait(), () => { + adapter.kill(); + }); }); it("prefers real PTY exit over SIGKILL fallback settle", async () => { @@ -124,14 +117,16 @@ describe("createPtyAdapter", () => { args: ["-lc", "sleep 10"], }); - const waitPromise = adapter.wait(); - adapter.kill(); - stub.emitExit({ exitCode: 0, signal: 9 }); - - await expect(waitPromise).resolves.toEqual({ code: 0, signal: 9 }); - - await vi.advanceTimersByTimeAsync(4_001); - await expect(adapter.wait()).resolves.toEqual({ code: 0, signal: 9 }); + await expectRealExitWinsOverSigkillFallback({ + waitPromise: adapter.wait(), + triggerKill: () => { + adapter.kill(); + }, + emitExit: () => { + stub.emitExit({ exitCode: 0, signal: 9 }); + }, + expected: { code: 0, signal: 9 }, + }); }); it("resolves wait when exit fires before wait is called", async () => { diff --git a/src/process/supervisor/adapters/test-support.ts b/src/process/supervisor/adapters/test-support.ts new file mode 100644 index 00000000000..18a55119b9b --- /dev/null +++ b/src/process/supervisor/adapters/test-support.ts @@ -0,0 +1,40 @@ +import { expect, vi } from "vitest"; + +type WaitResult = { + code: number | null; + signal: number | NodeJS.Signals | null; +}; + +export async function expectWaitStaysPendingUntilSigkillFallback( + waitPromise: Promise, + triggerKill: () => void, +): Promise { + const settled = vi.fn(); + void waitPromise.then(() => settled()); + + triggerKill(); + + await Promise.resolve(); + expect(settled).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(3999); + expect(settled).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(1); + await expect(waitPromise).resolves.toEqual({ code: null, signal: "SIGKILL" }); +} + +export async function expectRealExitWinsOverSigkillFallback(params: { + waitPromise: Promise; + triggerKill: () => void; + emitExit: () => void; + expected: WaitResult; +}): Promise { + params.triggerKill(); + params.emitExit(); + + await expect(params.waitPromise).resolves.toEqual(params.expected); + + await vi.advanceTimersByTimeAsync(4_001); + await expect(params.waitPromise).resolves.toEqual(params.expected); +}