fix(test): add opt-in vitest no-output watchdog

This commit is contained in:
Vincent Koc
2026-04-12 04:30:21 +01:00
parent 9c7c360fed
commit f466435529
2 changed files with 168 additions and 1 deletions

View File

@@ -1,6 +1,9 @@
import { describe, expect, it } from "vitest";
import { EventEmitter } from "node:events";
import { describe, expect, it, vi } from "vitest";
import {
installVitestNoOutputWatchdog,
resolveVitestNodeArgs,
resolveVitestNoOutputTimeoutMs,
resolveVitestSpawnParams,
shouldSuppressVitestStderrLine,
} from "../../scripts/run-vitest.mjs";
@@ -19,6 +22,16 @@ describe("scripts/run-vitest", () => {
).toEqual([]);
});
it("parses the optional no-output timeout env", () => {
expect(resolveVitestNoOutputTimeoutMs({})).toBeNull();
expect(resolveVitestNoOutputTimeoutMs({ OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "2500" })).toBe(
2500,
);
expect(
resolveVitestNoOutputTimeoutMs({ OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "0" }),
).toBeNull();
});
it("spawns vitest in a detached process group on Unix hosts", () => {
expect(resolveVitestSpawnParams({ PATH: "/usr/bin" }, "darwin")).toEqual({
env: { PATH: "/usr/bin" },
@@ -40,4 +53,48 @@ describe("scripts/run-vitest", () => {
).toBe(true);
expect(shouldSuppressVitestStderrLine("real failure output\n")).toBe(false);
});
it("kills silent vitest runs after the configured idle timeout", () => {
vi.useFakeTimers();
try {
const stdout = new EventEmitter();
const timeoutSpy = vi.fn();
const forceKillSpy = vi.fn();
const logSpy = vi.fn();
const teardown = installVitestNoOutputWatchdog({
streams: [stdout],
timeoutMs: 1000,
forceKillAfterMs: 5000,
log: logSpy,
onTimeout: timeoutSpy,
onForceKill: forceKillSpy,
setTimeoutFn: setTimeout,
clearTimeoutFn: clearTimeout,
});
vi.advanceTimersByTime(900);
expect(timeoutSpy).not.toHaveBeenCalled();
stdout.emit("data", "still alive");
vi.advanceTimersByTime(900);
expect(timeoutSpy).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(timeoutSpy).toHaveBeenCalledTimes(1);
expect(logSpy).toHaveBeenCalledWith(
"[vitest] no output for 1000ms; terminating stalled Vitest process group.",
);
vi.advanceTimersByTime(5000);
expect(forceKillSpy).toHaveBeenCalledTimes(1);
expect(logSpy).toHaveBeenCalledWith(
"[vitest] process group still alive after 5000ms; sending SIGKILL.",
);
teardown();
} finally {
vi.useRealTimers();
}
});
});