import type { ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { afterEach, describe, expect, it, vi } from "vitest"; const spawnMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { const actual = await vi.importActual("node:child_process"); return { ...actual, spawn: spawnMock, }; }); import { runCommandWithTimeout } from "./exec.js"; function createFakeSpawnedChild() { const child = new EventEmitter() as EventEmitter & ChildProcess; const stdout = new EventEmitter(); const stderr = new EventEmitter(); let killed = false; const kill = vi.fn<(signal?: NodeJS.Signals) => boolean>(() => { killed = true; return true; }); Object.defineProperty(child, "killed", { get: () => killed, configurable: true, }); Object.defineProperty(child, "pid", { value: 12345, configurable: true, }); child.stdout = stdout as ChildProcess["stdout"]; child.stderr = stderr as ChildProcess["stderr"]; child.stdin = null; child.kill = kill as ChildProcess["kill"]; return { child, stdout, stderr, kill }; } describe("runCommandWithTimeout no-output timer", () => { afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); }); it("resets no-output timeout when spawned child keeps emitting stdout", async () => { vi.useFakeTimers(); const fake = createFakeSpawnedChild(); spawnMock.mockReturnValue(fake.child); const runPromise = runCommandWithTimeout(["node", "-e", "ignored"], { timeoutMs: 1_000, noOutputTimeoutMs: 80, }); fake.stdout.emit("data", Buffer.from(".")); await vi.advanceTimersByTimeAsync(40); fake.stdout.emit("data", Buffer.from(".")); await vi.advanceTimersByTimeAsync(40); fake.stdout.emit("data", Buffer.from(".")); await vi.advanceTimersByTimeAsync(20); fake.child.emit("close", 0, null); const result = await runPromise; expect(result.code ?? 0).toBe(0); expect(result.termination).toBe("exit"); expect(result.noOutputTimedOut).toBe(false); expect(result.stdout).toBe("..."); expect(fake.kill).not.toHaveBeenCalled(); }); });