Files
reverse-engineering-anything/src/process/ProviderDeadline.test.ts
T
Kaoru0822andClaude Opus 5.5 dfdaa0ca74 fix(process): report no remaining time after a provider deadline fires
Node timers can fire while performance.now() is still a fraction of a
millisecond short of the monotonic deadline. ProviderStartupDeadline then
reported interruption "timeout" and an aborted signal while remainingMs()
returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0
(Hopper and Ghidra startup) could run past an elapsed deadline, and the
real-timer test "aborts a bounded wait when its real monotonic deadline
expires" failed intermittently in CI (expected 1 to be 0).

Once the deadline's own timer has classified a timeout, report zero
remaining time. A deterministic regression test fires the timer with the
monotonic clock 0.4 ms short of the deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:52:29 +00:00

53 lines
1.8 KiB
TypeScript

import { afterEach, expect, it, vi } from "vitest";
import { ProviderStartupDeadline } from "./ProviderDeadline.js";
afterEach(() => {
vi.restoreAllMocks();
});
it("keeps remaining time and cancellation classification stable across wall-clock jumps", () => {
const dateNow = vi.spyOn(Date, "now").mockReturnValue(10_000);
const caller = new AbortController();
const deadline = new ProviderStartupDeadline(10_000, caller.signal);
try {
dateNow.mockReturnValue(9_000_000_000_000);
const remaining = deadline.remainingMs();
expect(remaining).toBeGreaterThan(0);
expect(remaining).toBeLessThanOrEqual(10_000);
expect(Number.isInteger(remaining)).toBe(true);
expect(deadline.interruption).toBeUndefined();
caller.abort(new Error("caller stopped startup"));
expect(deadline.interruption).toBe("cancelled");
} finally {
deadline.dispose();
}
});
it("aborts a bounded wait when its real monotonic deadline expires", async () => {
const deadline = new ProviderStartupDeadline(40);
try {
await expect(deadline.wait(500)).resolves.toBe("aborted");
expect(deadline.interruption).toBe("timeout");
expect(deadline.remainingMs()).toBe(0);
} finally {
deadline.dispose();
}
});
it("reports no remaining time once its timer fires before the monotonic deadline", () => {
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
const now = vi.spyOn(performance, "now").mockReturnValue(1_000);
const deadline = new ProviderStartupDeadline(40);
try {
// Timers may fire while the monotonic clock is still just short of the deadline.
now.mockReturnValue(1_039.6);
vi.advanceTimersByTime(40);
expect(deadline.signal.aborted).toBe(true);
expect(deadline.interruption).toBe("timeout");
expect(deadline.remainingMs()).toBe(0);
} finally {
deadline.dispose();
vi.useRealTimers();
}
});