mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 07:33:18 +00:00
test(muse): drive the failed-repair retry test on the fake clock
The test polled the fake's request log and then slept 50 ms before advancing the clock again, so a slow host could advance time before the plugin had handled the failed page and re-armed its watchdog. The fake now never answers the first page, so the plugin's own request timeout fails the repair on the fake clock, and the test steps through the timeout and the re-armed watchdog one timer at a time. Waiting for the first request moves into a harness helper.
This commit is contained in:
@@ -30,7 +30,7 @@ const catalogRows = readFixture("catalog-controls");
|
||||
const replacements = new Map();
|
||||
const counts = new Map();
|
||||
const receipts = new Set();
|
||||
let pageFailed = false;
|
||||
let pageHung = false;
|
||||
const lines = createInterface({ input: process.stdin });
|
||||
lines.on("line", receive);
|
||||
function receive(line) {
|
||||
@@ -143,9 +143,9 @@ function scriptedResponse(frame) {
|
||||
respond(frame, { status: "accepted", commandId: frame.params.commandId });
|
||||
return true;
|
||||
}
|
||||
return frame.method === "view/page" && rejectedPage(frame);
|
||||
return frame.method === "view/page" && failedPage(frame);
|
||||
}
|
||||
function rejectedPage(frame) {
|
||||
function failedPage(frame) {
|
||||
const anchor = frame.params.cursor;
|
||||
// Real Muse rejects an anchor from another session's view the same way (live probe, Muse 1.4.7).
|
||||
if (
|
||||
@@ -164,9 +164,9 @@ function rejectedPage(frame) {
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (process.env.MUSE_TEST_PAGE_FAIL_ONCE && !pageFailed) {
|
||||
pageFailed = true;
|
||||
rpcError(frame, -32603, "internal", "view range read failed");
|
||||
// Never answering lets the plugin's own request timeout fail the repair on the test's fake clock.
|
||||
if (process.env.MUSE_TEST_PAGE_HANG_ONCE && !pageHung) {
|
||||
pageHung = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -138,6 +138,15 @@ async function harness(
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line));
|
||||
}
|
||||
async function waitForRequests(method: string, count: number) {
|
||||
const deadline = Date.now() + 5000;
|
||||
while (Date.now() < deadline) {
|
||||
const sent = (await recorded()).filter((frame) => frame.method === method);
|
||||
if (sent.length >= count) return sent;
|
||||
await delay(10);
|
||||
}
|
||||
throw new Error(`Muse never received ${count} ${method} request(s)`);
|
||||
}
|
||||
return {
|
||||
provider,
|
||||
launch,
|
||||
@@ -148,6 +157,7 @@ async function harness(
|
||||
open,
|
||||
prompt,
|
||||
recorded,
|
||||
waitForRequests,
|
||||
root,
|
||||
usageSource,
|
||||
};
|
||||
@@ -1318,25 +1328,24 @@ test("a subagent approval does not move the parent's silence repair onto the chi
|
||||
test("a failed silence repair keeps the session and retries at the next silence", async () => {
|
||||
const h = await harness("text-reasoning", {
|
||||
MUSE_TEST_SILENT: "1",
|
||||
MUSE_TEST_PAGE_FAIL_ONCE: "1",
|
||||
MUSE_TEST_PAGE_HANG_ONCE: "1",
|
||||
});
|
||||
await h.open();
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
||||
await h.prompt();
|
||||
await h.wait((e) => e.type === "session.prompt_result");
|
||||
// The first repair's page is never answered. The silence, that page's request timeout and the
|
||||
// re-armed watchdog all run on the fake clock, one timer per step, whatever the timeout length.
|
||||
await vi.advanceTimersByTimeAsync(120000);
|
||||
const deadline = Date.now() + 5000;
|
||||
while ((await h.recorded()).filter((f) => f.method === "view/page").length === 0) {
|
||||
if (Date.now() > deadline) throw new Error("The first repair never paged");
|
||||
await delay(10);
|
||||
}
|
||||
await delay(50);
|
||||
await h.waitForRequests("view/page", 1);
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
await h.wait(
|
||||
(e) =>
|
||||
(e.type === "session.turn" && e.state === "completed") || e.type === "session.runtime_failed",
|
||||
);
|
||||
expect(h.events.filter((e) => e.type === "session.runtime_failed")).toEqual([]);
|
||||
expect(h.events.filter((e) => e.type === "session.turn" && e.state === "completed")).toEqual([]);
|
||||
await vi.advanceTimersByTimeAsync(120000);
|
||||
await h.wait((e) => e.type === "session.turn" && e.state === "completed");
|
||||
expect((await h.recorded()).filter((f) => f.method === "view/page")).toHaveLength(2);
|
||||
expect(h.events.filter((e) => e.type === "session.runtime_failed")).toEqual([]);
|
||||
expect(h.events.filter((e) => e.type === "session.turn" && e.state === "completed")).toHaveLength(
|
||||
1,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user