Files
heygen-com__hyperframes/packages/engine/src/utils/processTracker.test.ts
Miguel Ángel 7e4ce96ba8 fix: SIGKILL escalation in killProcessTree + unit tests
Remaining review follow-ups:

- killProcessTree now escalates to SIGKILL after 500ms if SIGTERM
  doesn't kill the process (same pattern as killTrackedProcesses).
  Covers orphan cleanup and dev/local mode tree kill.

- Added unit tests for both new modules:
  - processTracker.test.ts (6 tests): track/remove on exit/error,
    kill running processes, SIGKILL escalation for SIGTERM-resistant
    processes, idempotency.
  - orphanCleanup.test.ts (5 tests): tree kill with children,
    SIGKILL escalation, non-existent PID handling, orphan detection
    returns 0 when clean.
2026-05-23 00:10:30 -04:00

75 lines
2.4 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import { spawn } from "node:child_process";
import { trackChildProcess, killTrackedProcesses } from "./processTracker.js";
// Reset tracked set between tests by killing everything
beforeEach(() => {
killTrackedProcesses();
});
describe("trackChildProcess", () => {
it("tracks a spawned process and removes it after exit", async () => {
const proc = spawn("echo", ["hello"], { stdio: "ignore" });
trackChildProcess(proc);
await new Promise<void>((resolve) => proc.on("close", resolve));
// After exit, killTrackedProcesses should be a no-op (nothing to kill)
killTrackedProcesses();
});
it("removes the process on spawn error", async () => {
const proc = spawn("/nonexistent-binary-that-does-not-exist", { stdio: "ignore" });
trackChildProcess(proc);
await new Promise<void>((resolve) => proc.on("error", () => resolve()));
killTrackedProcesses();
});
});
describe("killTrackedProcesses", () => {
it("kills a running process", async () => {
const proc = spawn("sleep", ["60"], { stdio: "ignore" });
trackChildProcess(proc);
const exitPromise = new Promise<number | null>((resolve) => proc.on("close", resolve));
killTrackedProcesses();
const code = await exitPromise;
// SIGTERM exit: code is null (killed by signal)
expect(code).toBeNull();
});
it("handles already-exited processes gracefully", async () => {
const proc = spawn("true", { stdio: "ignore" });
trackChildProcess(proc);
await new Promise<void>((resolve) => proc.on("close", resolve));
// Should not throw even though process already exited
killTrackedProcesses();
});
it("escalates to SIGKILL for processes that ignore SIGTERM", async () => {
// Spawn a process that traps SIGTERM (bash ignoring it)
const proc = spawn("bash", ["-c", "trap '' TERM; sleep 60"], { stdio: "ignore" });
trackChildProcess(proc);
const exitPromise = new Promise<void>((resolve) => proc.on("close", resolve));
killTrackedProcesses();
// The 500ms SIGKILL escalation should kill it
await exitPromise;
expect(proc.killed).toBe(true);
}, 5000);
it("is idempotent — second call is a no-op", () => {
const proc = spawn("sleep", ["60"], { stdio: "ignore" });
trackChildProcess(proc);
killTrackedProcesses();
killTrackedProcesses();
});
});