test: ignore ESRCH during process cleanup (#106880)

This commit is contained in:
Peter Steinberger
2026-07-13 17:13:16 -07:00
committed by GitHub
parent cac32bb5d1
commit 3dd7322d22
3 changed files with 64 additions and 2 deletions
+34
View File
@@ -0,0 +1,34 @@
// Tests race-safe process cleanup helpers.
import { afterEach, describe, expect, it, vi } from "vitest";
import { killPidIfAlive } from "./process-tree.js";
describe("killPidIfAlive", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("ignores ESRCH when a live process exits before SIGKILL", () => {
const killError = Object.assign(new Error("kill ESRCH"), { code: "ESRCH" });
const kill = vi
.spyOn(process, "kill")
.mockReturnValueOnce(true)
.mockImplementationOnce(() => {
throw killError;
});
expect(() => killPidIfAlive(123)).not.toThrow();
expect(kill).toHaveBeenNthCalledWith(1, 123, 0);
expect(kill).toHaveBeenNthCalledWith(2, 123, "SIGKILL");
});
it("rethrows other SIGKILL failures", () => {
const killError = Object.assign(new Error("kill EPERM"), { code: "EPERM" });
vi.spyOn(process, "kill")
.mockReturnValueOnce(true)
.mockImplementationOnce(() => {
throw killError;
});
expect(() => killPidIfAlive(123)).toThrow(killError);
});
});
+8 -1
View File
@@ -52,5 +52,12 @@ export function killPidIfAlive(pid: number | undefined): void {
if (pid === undefined || !isPidAlive(pid)) {
return;
}
process.kill(pid, "SIGKILL");
try {
process.kill(pid, "SIGKILL");
} catch (error) {
// The process can exit after the liveness probe; ESRCH already satisfies cleanup.
if ((error as NodeJS.ErrnoException | undefined)?.code !== "ESRCH") {
throw error;
}
}
}
@@ -215,7 +215,14 @@ function killPidIfAlive(pid: number | undefined): void {
if (pid === undefined || !pidIsAlive(pid)) {
return;
}
process.kill(pid, "SIGKILL");
try {
process.kill(pid, "SIGKILL");
} catch (error) {
// The process can exit after the liveness probe; ESRCH already satisfies cleanup.
if ((error as NodeJS.ErrnoException | undefined)?.code !== "ESRCH") {
throw error;
}
}
}
afterEach(() => {
@@ -249,6 +256,20 @@ describe("bundled plugin install/uninstall probe", () => {
expect(kill).not.toHaveBeenCalled();
});
it("ignores ESRCH when a probed process exits before cleanup", () => {
const killError = Object.assign(new Error("kill ESRCH"), { code: "ESRCH" });
const kill = vi
.spyOn(process, "kill")
.mockReturnValueOnce(true)
.mockImplementationOnce(() => {
throw killError;
});
expect(() => killPidIfAlive(123)).not.toThrow();
expect(kill).toHaveBeenNthCalledWith(1, 123, 0);
expect(kill).toHaveBeenNthCalledWith(2, 123, "SIGKILL");
});
it("keeps the sweep script compatible with macOS Bash 3", () => {
const sweep = fs.readFileSync(sweepPath, "utf8");