From 408fa6e9512ca30d268705a600b4d7c10ba01ac5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 1 Jun 2026 14:50:41 +0200 Subject: [PATCH] fix(test): stabilize watch-node shutdown tests --- scripts/watch-node.mjs | 6 ++++++ src/infra/watch-node.test.ts | 35 +++++++++++++++++------------------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/scripts/watch-node.mjs b/scripts/watch-node.mjs index c8fca7e627a..e78af04417d 100644 --- a/scripts/watch-node.mjs +++ b/scripts/watch-node.mjs @@ -352,6 +352,9 @@ export async function runWatchMain(params = {}) { }); watchProcess.on("exit", (exitCode, exitSignal) => { watchProcess = null; + if (settled) { + return; + } if (settleIfShuttingDown()) { return; } @@ -422,6 +425,9 @@ export async function runWatchMain(params = {}) { }); watchProcess.on("exit", (exitCode, exitSignal) => { watchProcess = null; + if (settled) { + return; + } if (settleIfShuttingDown()) { return; } diff --git a/src/infra/watch-node.test.ts b/src/infra/watch-node.test.ts index e5dbfec94fc..018b6f1f78e 100644 --- a/src/infra/watch-node.test.ts +++ b/src/infra/watch-node.test.ts @@ -34,10 +34,19 @@ const createFakeProcess = () => execPath: "/usr/local/bin/node", }) as unknown as NodeJS.Process; -const createWatchHarness = () => { +const createKillableChild = () => { const child = Object.assign(new EventEmitter(), { - kill: vi.fn(() => {}), + kill: vi.fn(), }); + child.kill.mockImplementation((signal: NodeJS.Signals = "SIGTERM") => { + queueMicrotask(() => child.emit("exit", null, signal)); + return true; + }); + return child; +}; + +const createWatchHarness = () => { + const child = createKillableChild(); const spawn = vi.fn(() => child); const watcher = Object.assign(new EventEmitter(), { close: vi.fn(async () => {}), @@ -220,9 +229,7 @@ describe("watch-node script", () => { }); it("starts the runner before loading chokidar", async () => { - const child = Object.assign(new EventEmitter(), { - kill: vi.fn(() => {}), - }); + const child = createKillableChild(); const spawn = vi.fn(() => child); const watcher = Object.assign(new EventEmitter(), { close: vi.fn(async () => {}), @@ -329,7 +336,7 @@ describe("watch-node script", () => { it("runs doctor once and restarts when gateway exits nonzero", async () => { const gatewayA = Object.assign(new EventEmitter(), { kill: vi.fn() }); const doctor = Object.assign(new EventEmitter(), { kill: vi.fn() }); - const gatewayB = Object.assign(new EventEmitter(), { kill: vi.fn() }); + const gatewayB = createKillableChild(); const spawn = vi .fn() .mockReturnValueOnce(gatewayA) @@ -395,9 +402,7 @@ describe("watch-node script", () => { const childA = Object.assign(new EventEmitter(), { kill: vi.fn(), }); - const childB = Object.assign(new EventEmitter(), { - kill: vi.fn(() => {}), - }); + const childB = createKillableChild(); const spawn = vi.fn().mockReturnValueOnce(childA).mockReturnValueOnce(childB); const { watcher, fakeProcess, runPromise } = startWatchRun({ spawn }); @@ -447,9 +452,7 @@ describe("watch-node script", () => { const childA = createAutoExitChild(); const childB = createAutoExitChild(); const childC = createAutoExitChild(); - const childD = Object.assign(new EventEmitter(), { - kill: vi.fn(() => {}), - }); + const childD = createKillableChild(); const spawn = vi .fn() .mockReturnValueOnce(childA) @@ -538,9 +541,7 @@ describe("watch-node script", () => { ), { code: "ERR_INVALID_PACKAGE_CONFIG" }, ); - const child = Object.assign(new EventEmitter(), { - kill: vi.fn(() => {}), - }); + const child = createKillableChild(); const spawn = vi.fn(() => child); const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); @@ -583,9 +584,7 @@ describe("watch-node script", () => { const error = Object.assign(new Error("Cannot find package 'chokidar'"), { code: "ERR_MODULE_NOT_FOUND", }); - const child = Object.assign(new EventEmitter(), { - kill: vi.fn(() => {}), - }); + const child = createKillableChild(); const spawn = vi.fn(() => child); const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});