test(hooks): repair stale retry harnesses

This commit is contained in:
YeonGyu-Kim
2026-05-16 15:43:15 +09:00
parent cf7bf9d02d
commit d974cd3d3b
3 changed files with 22 additions and 12 deletions
+12 -4
View File
@@ -7,6 +7,7 @@ import type { PluginInput } from "@opencode-ai/plugin"
import { createAtlasHook } from "./atlas-hook" import { createAtlasHook } from "./atlas-hook"
import { clearBoulderState, writeBoulderState } from "../../features/boulder-state" import { clearBoulderState, writeBoulderState } from "../../features/boulder-state"
import { _resetForTesting, clearSessionAgent, registerAgentName, setSessionAgent } from "../../features/claude-code-session-state" import { _resetForTesting, clearSessionAgent, registerAgentName, setSessionAgent } from "../../features/claude-code-session-state"
import { DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS } from "../../shared/prompt-async-gate"
import { unsafeTestValue } from "../../../test-support/unsafe-test-value" import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
// Force process isolation in CI runner (globalThis.setTimeout override conflicts with other atlas tests) // Force process isolation in CI runner (globalThis.setTimeout override conflicts with other atlas tests)
@@ -24,6 +25,8 @@ describe("atlas background task retry", () => {
let nextFakeTimerId = 1000 let nextFakeTimerId = 1000
const originalSetTimeout = globalThis.setTimeout const originalSetTimeout = globalThis.setTimeout
const originalClearTimeout = globalThis.clearTimeout const originalClearTimeout = globalThis.clearTimeout
const originalDateNow = Date.now
let fakeNow = 0
async function flushMicrotasks(): Promise<void> { async function flushMicrotasks(): Promise<void> {
await Promise.resolve() await Promise.resolve()
@@ -52,6 +55,7 @@ describe("atlas background task retry", () => {
} }
capturedTimers.delete(id) capturedTimers.delete(id)
fakeNow += 6000
await entry.callback() await entry.callback()
} }
await flushMicrotasks() await flushMicrotasks()
@@ -67,6 +71,8 @@ describe("atlas background task retry", () => {
capturedTimers.clear() capturedTimers.clear()
nextFakeTimerId = 1000 nextFakeTimerId = 1000
fakeNow = 10_000
Date.now = () => fakeNow
globalThis.setTimeout = ((callback: Parameters<typeof setTimeout>[0], delay?: number, ...args: unknown[]) => { globalThis.setTimeout = ((callback: Parameters<typeof setTimeout>[0], delay?: number, ...args: unknown[]) => {
const normalizedDelay = typeof delay === "number" ? delay : 0 const normalizedDelay = typeof delay === "number" ? delay : 0
@@ -74,7 +80,7 @@ describe("atlas background task retry", () => {
return originalSetTimeout(callback, delay, ...args) return originalSetTimeout(callback, delay, ...args)
} }
if (normalizedDelay >= 5000) { if (normalizedDelay >= 5000 && normalizedDelay !== DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS) {
const id = nextFakeTimerId++ const id = nextFakeTimerId++
capturedTimers.set(id, { capturedTimers.set(id, {
callback: () => (callback as LongTimerCallback)(...args), callback: () => (callback as LongTimerCallback)(...args),
@@ -87,8 +93,9 @@ describe("atlas background task retry", () => {
}) as typeof setTimeout }) as typeof setTimeout
globalThis.clearTimeout = ((id?: number | ReturnType<typeof setTimeout>) => { globalThis.clearTimeout = ((id?: number | ReturnType<typeof setTimeout>) => {
if (typeof id === "number" && capturedTimers.has(id)) { const timerEntry = typeof id === "number" ? capturedTimers.get(id) : undefined
capturedTimers.get(id)!.cleared = true if (timerEntry) {
timerEntry.cleared = true
capturedTimers.delete(id) capturedTimers.delete(id)
return return
} }
@@ -100,6 +107,7 @@ describe("atlas background task retry", () => {
afterEach(() => { afterEach(() => {
globalThis.setTimeout = originalSetTimeout globalThis.setTimeout = originalSetTimeout
globalThis.clearTimeout = originalClearTimeout globalThis.clearTimeout = originalClearTimeout
Date.now = originalDateNow
_resetForTesting() _resetForTesting()
clearBoulderState(testDir) clearBoulderState(testDir)
if (existsSync(testDir)) { if (existsSync(testDir)) {
@@ -423,7 +431,7 @@ describe("atlas background task retry", () => {
agent: "atlas", agent: "atlas",
}) })
const deferredPrompt = createDeferred<{}>() const deferredPrompt = createDeferred<unknown>()
const promptAsyncMock = mock(() => deferredPrompt.promise) const promptAsyncMock = mock(() => deferredPrompt.promise)
const hook = createAtlasHook(unsafeTestValue<PluginInput>({ const hook = createAtlasHook(unsafeTestValue<PluginInput>({
directory: testDir, directory: testDir,
+6 -4
View File
@@ -11,6 +11,7 @@ import {
} from "../../features/boulder-state" } from "../../features/boulder-state"
import type { BoulderState } from "../../features/boulder-state" import type { BoulderState } from "../../features/boulder-state"
import { _resetForTesting, registerAgentName, subagentSessions, updateSessionAgent } from "../../features/claude-code-session-state" import { _resetForTesting, registerAgentName, subagentSessions, updateSessionAgent } from "../../features/claude-code-session-state"
import { DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS } from "../../shared/prompt-async-gate"
import type { AtlasHookOptions, PendingTaskRef } from "./types" import type { AtlasHookOptions, PendingTaskRef } from "./types"
import { createAtlasHook } from "./index" import { createAtlasHook } from "./index"
import { createToolExecuteAfterHandler } from "./tool-execute-after" import { createToolExecuteAfterHandler } from "./tool-execute-after"
@@ -1702,7 +1703,7 @@ session_id: ses_untrusted_999
// then - stale idle is consumed, not converted into another scheduled continuation // then - stale idle is consumed, not converted into another scheduled continuation
expect(mockInput._promptMock).toHaveBeenCalledTimes(1) expect(mockInput._promptMock).toHaveBeenCalledTimes(1)
expect(scheduledDelays.filter((delay) => delay >= 5_000)).toHaveLength(0) expect(scheduledDelays.filter((delay) => delay >= 5_000 && delay !== DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS)).toHaveLength(0)
} finally { } finally {
globalThis.setTimeout = originalSetTimeout globalThis.setTimeout = originalSetTimeout
} }
@@ -2498,7 +2499,7 @@ session_id: ses_untrusted_999
globalThis.setTimeout = ((callback: Parameters<typeof setTimeout>[0], delay?: number, ...args: unknown[]) => { globalThis.setTimeout = ((callback: Parameters<typeof setTimeout>[0], delay?: number, ...args: unknown[]) => {
const normalized = typeof delay === "number" ? delay : 0 const normalized = typeof delay === "number" ? delay : 0
if (normalized >= 5000) { if (normalized >= 5000 && normalized !== DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS) {
const timerID = originalSetTimeout(() => undefined, 0) const timerID = originalSetTimeout(() => undefined, 0)
const capturedCallback = typeof callback === "function" const capturedCallback = typeof callback === "function"
? () => callback(...args) ? () => callback(...args)
@@ -2512,8 +2513,9 @@ session_id: ses_untrusted_999
}) as typeof setTimeout }) as typeof setTimeout
globalThis.clearTimeout = ((id?: ReturnType<typeof setTimeout>) => { globalThis.clearTimeout = ((id?: ReturnType<typeof setTimeout>) => {
if (id && capturedTimers.has(id)) { const timerEntry = id ? capturedTimers.get(id) : undefined
capturedTimers.get(id)!.cleared = true if (timerEntry) {
timerEntry.cleared = true
capturedTimers.delete(id) capturedTimers.delete(id)
return return
} }
@@ -4,9 +4,9 @@ import { readFileSync } from "node:fs"
describe("experimental.session.compacting", () => { describe("experimental.session.compacting", () => {
test("does not hardcode a model and uses output.context", () => { test("does not hardcode a model and uses output.context", () => {
//#given //#given
const indexUrl = new URL("./index.ts", import.meta.url) const moduleUrl = new URL("./testing/create-plugin-module.ts", import.meta.url)
const compactionUrl = new URL("./plugin/session-compacting.ts", import.meta.url) const compactionUrl = new URL("./plugin/session-compacting.ts", import.meta.url)
const content = readFileSync(indexUrl, "utf-8") const content = readFileSync(moduleUrl, "utf-8")
const compactionContent = readFileSync(compactionUrl, "utf-8") const compactionContent = readFileSync(compactionUrl, "utf-8")
//#when //#when
@@ -22,9 +22,9 @@ describe("experimental.session.compacting", () => {
test("registers autocontinue restores before OpenCode synthetic continue", () => { test("registers autocontinue restores before OpenCode synthetic continue", () => {
//#given //#given
const indexUrl = new URL("./index.ts", import.meta.url) const moduleUrl = new URL("./testing/create-plugin-module.ts", import.meta.url)
const compactionUrl = new URL("./plugin/session-compacting.ts", import.meta.url) const compactionUrl = new URL("./plugin/session-compacting.ts", import.meta.url)
const content = readFileSync(indexUrl, "utf-8") const content = readFileSync(moduleUrl, "utf-8")
const compactionContent = readFileSync(compactionUrl, "utf-8") const compactionContent = readFileSync(compactionUrl, "utf-8")
//#when //#when