From 928c80d8fbe5b8153c9dfabc0f5f04d2ed557417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catriel=20M=C3=BCller?= Date: Mon, 18 May 2026 14:07:02 -0300 Subject: [PATCH] test(cli): avoid global timer patching --- packages/opencode/src/session/network.ts | 28 ++++++++----- .../opencode/test/session/network.test.ts | 40 +------------------ 2 files changed, 21 insertions(+), 47 deletions(-) diff --git a/packages/opencode/src/session/network.ts b/packages/opencode/src/session/network.ts index c3bc2ef9066..e50121f3264 100644 --- a/packages/opencode/src/session/network.ts +++ b/packages/opencode/src/session/network.ts @@ -108,6 +108,8 @@ export namespace SessionNetwork { { info: Types.Mutable abort: AbortSignal + ctl: AbortController + delay: number resolve: () => void reject: (e: unknown) => void } @@ -217,23 +219,23 @@ export namespace SessionNetwork { ).catch(() => false) } - async function delay(abort: AbortSignal) { - if (abort.aborted) return false + async function delay(input: { abort: AbortSignal; ms: number }) { + if (input.abort.aborted) return false return new Promise((resolve) => { const timer = setTimeout(() => { - abort.removeEventListener("abort", onAbort) + input.abort.removeEventListener("abort", onAbort) resolve(true) - }, RESUME_MS) + }, input.ms) function onAbort() { clearTimeout(timer) resolve(false) } - abort.addEventListener("abort", onAbort, { once: true }) + input.abort.addEventListener("abort", onAbort, { once: true }) }) } - async function resume(input: { requestID: QuestionID; abort: AbortSignal }) { - if (!(await delay(input.abort))) return + async function resume(input: { requestID: QuestionID; abort: AbortSignal; ms: number }) { + if (!(await delay(input))) return const s = await state() const req = s.pending.get(input.requestID) if (!req || !req.info.restored) return @@ -254,9 +256,10 @@ export namespace SessionNetwork { } } - export async function ask(input: { sessionID: SessionID; message: string; abort: AbortSignal }) { + export async function ask(input: { sessionID: SessionID; message: string; abort: AbortSignal; resumeMs?: number }) { const s = await state() const id = QuestionID.ascending() + const ctl = new AbortController() const info: Wait = { id, sessionID: input.sessionID, @@ -270,6 +273,7 @@ export namespace SessionNetwork { const promise = new Promise((resolve, reject) => { const onAbort = () => { if (!s.pending.has(id)) return + ctl.abort() input.abort.removeEventListener("abort", onAbort) s.pending.delete(id) Bus.publish(Event.Rejected, { @@ -281,11 +285,15 @@ export namespace SessionNetwork { s.pending.set(id, { info, abort: input.abort, + ctl, + delay: input.resumeMs ?? RESUME_MS, resolve: () => { + ctl.abort() input.abort.removeEventListener("abort", onAbort) resolve() }, reject: (err) => { + ctl.abort() input.abort.removeEventListener("abort", onAbort) reject(err) }, @@ -322,7 +330,7 @@ export namespace SessionNetwork { requestID: req.info.id, time, }) - void resume({ requestID, abort: req.abort }).catch((err) => { + void resume({ requestID, abort: AbortSignal.any([req.abort, req.ctl.signal]), ms: req.delay }).catch((err) => { log.error("auto resume failed", { err, requestID }) }) }, @@ -340,6 +348,7 @@ export namespace SessionNetwork { log.warn("reply for unknown request", { requestID }) return } + req.ctl.abort() s.pending.delete(requestID) // kilocode_change start — reconnect failed remote MCP servers after network recovery void MCP.status() @@ -376,6 +385,7 @@ export namespace SessionNetwork { log.warn("reject for unknown request", { requestID }) return } + req.ctl.abort() s.pending.delete(requestID) Bus.publish(Event.Rejected, { sessionID: req.info.sessionID, diff --git a/packages/opencode/test/session/network.test.ts b/packages/opencode/test/session/network.test.ts index 1a1009514d0..4b13dfd995b 100644 --- a/packages/opencode/test/session/network.test.ts +++ b/packages/opencode/test/session/network.test.ts @@ -1,42 +1,11 @@ // kilocode_change - new file -import { afterEach, describe, expect, test } from "bun:test" +import { describe, expect, test } from "bun:test" import { Bus } from "../../src/bus" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { SessionNetwork } from "../../src/session/network" import { SessionID } from "../../src/session/schema" -const timer = globalThis.setTimeout -const clear = globalThis.clearTimeout - -afterEach(() => { - globalThis.setTimeout = timer - globalThis.clearTimeout = clear -}) - -function manual() { - const state = { - next: 0, - jobs: new Map(), - } - globalThis.setTimeout = ((cb: TimerHandler) => { - const id = state.next + 1 - state.next = id - state.jobs.set(id, cb) - return id as unknown as ReturnType - }) as unknown as typeof setTimeout - globalThis.clearTimeout = ((id: ReturnType) => { - state.jobs.delete(id as unknown as number) - }) as unknown as typeof clearTimeout - return () => { - const jobs = Array.from(state.jobs.values()) - state.jobs.clear() - for (const job of jobs) { - if (typeof job === "function") job() - } - } -} - describe("session.network", () => { test("detects common network disconnect codes", () => { expect(SessionNetwork.disconnected({ code: "ECONNREFUSED" })).toBe(true) @@ -110,18 +79,17 @@ describe("session.network", () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const run = manual() const { promise } = await SessionNetwork.ask({ sessionID: SessionID.make("ses_test"), message: "Connection refused", abort: new AbortController().signal, + resumeMs: 0, }) const pending = await SessionNetwork.list() expect(pending).toHaveLength(1) const req = pending[0]! await SessionNetwork.restore({ requestID: req.id }) expect((await SessionNetwork.list())[0]?.restored).toBe(true) - run() await expect(promise).resolves.toBeUndefined() expect(await SessionNetwork.list()).toHaveLength(0) }, @@ -133,7 +101,6 @@ describe("session.network", () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const run = manual() const { promise } = await SessionNetwork.ask({ sessionID: SessionID.make("ses_test"), message: "Connection timed out", @@ -143,7 +110,6 @@ describe("session.network", () => { await SessionNetwork.restore({ requestID: req.id }) await SessionNetwork.reject({ requestID: req.id }) await expect(promise).rejects.toBeInstanceOf(SessionNetwork.RejectedError) - run() expect(await SessionNetwork.list()).toHaveLength(0) }, }) @@ -154,7 +120,6 @@ describe("session.network", () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const run = manual() const abort = new AbortController() const { promise } = await SessionNetwork.ask({ sessionID: SessionID.make("ses_test"), @@ -165,7 +130,6 @@ describe("session.network", () => { await SessionNetwork.restore({ requestID: req.id }) abort.abort() await expect(promise).rejects.toBeInstanceOf(DOMException) - run() expect(await SessionNetwork.list()).toHaveLength(0) }, })