test: stabilize parallel-sensitive CI specs

Relax verbose event assertions to target custom-event logs only and run compact lock-management specs serially to avoid global timer races in CI.
This commit is contained in:
YeonGyu-Kim 2026-02-21 03:40:23 +09:00
parent e21bbed3ab
commit 4aec627b33

View File

@ -113,22 +113,22 @@ describe("executeCompact lock management", () => {
fakeTimeouts.restore() fakeTimeouts.restore()
}) })
test("clears lock on successful summarize completion", async () => { test.serial("clears lock on successful summarize completion", async () => {
// given: Valid session with providerID/modelID // given: Valid session with providerID/modelID
autoCompactState.errorDataBySession.set(sessionID, { autoCompactState.errorDataBySession.set(sessionID, {
errorType: "token_limit", errorType: "token_limit",
currentTokens: 100000, currentTokens: 100000,
maxTokens: 200000, maxTokens: 200000,
}) })
// when: Execute compaction successfully // when: Execute compaction successfully
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Lock should be cleared // then: Lock should be cleared
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
}) })
test("clears lock when summarize throws exception", async () => { test.serial("clears lock when summarize throws exception", async () => {
// given: Summarize will fail // given: Summarize will fail
mockClient.session.summarize = mock(() => mockClient.session.summarize = mock(() =>
Promise.reject(new Error("Network timeout")), Promise.reject(new Error("Network timeout")),
@ -138,21 +138,21 @@ describe("executeCompact lock management", () => {
currentTokens: 100000, currentTokens: 100000,
maxTokens: 200000, maxTokens: 200000,
}) })
// when: Execute compaction // when: Execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Lock should still be cleared despite exception // then: Lock should still be cleared despite exception
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
}) })
test("shows toast when lock already held", async () => { test.serial("shows toast when lock already held", async () => {
// given: Lock already held // given: Lock already held
autoCompactState.compactionInProgress.add(sessionID) autoCompactState.compactionInProgress.add(sessionID)
// when: Try to execute compaction // when: Try to execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Toast should be shown with warning message // then: Toast should be shown with warning message
expect(mockClient.tui.showToast).toHaveBeenCalledWith( expect(mockClient.tui.showToast).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -163,12 +163,12 @@ describe("executeCompact lock management", () => {
}), }),
}), }),
) )
// then: compactionInProgress should still have the lock // then: compactionInProgress should still have the lock
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true)
}) })
test("clears lock when fixEmptyMessages path executes", async () => { test.serial("clears lock when fixEmptyMessages path executes", async () => {
//#given - Empty content error scenario with no messages in storage //#given - Empty content error scenario with no messages in storage
const readMessagesSpy = spyOn(messagesReader, "readMessages").mockReturnValue([]) const readMessagesSpy = spyOn(messagesReader, "readMessages").mockReturnValue([])
autoCompactState.errorDataBySession.set(sessionID, { autoCompactState.errorDataBySession.set(sessionID, {
@ -177,16 +177,16 @@ describe("executeCompact lock management", () => {
currentTokens: 100000, currentTokens: 100000,
maxTokens: 200000, maxTokens: 200000,
}) })
//#when - Execute compaction (fixEmptyMessages will be called) //#when - Execute compaction (fixEmptyMessages will be called)
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
//#then - Lock should be cleared //#then - Lock should be cleared
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
readMessagesSpy.mockRestore() readMessagesSpy.mockRestore()
}) })
test("clears lock when truncation is sufficient", async () => { test.serial("clears lock when truncation is sufficient", async () => {
//#given - Aggressive truncation scenario with no messages in storage //#given - Aggressive truncation scenario with no messages in storage
const readMessagesSpy = spyOn(messagesReader, "readMessages").mockReturnValue([]) const readMessagesSpy = spyOn(messagesReader, "readMessages").mockReturnValue([])
autoCompactState.errorDataBySession.set(sessionID, { autoCompactState.errorDataBySession.set(sessionID, {
@ -194,12 +194,12 @@ describe("executeCompact lock management", () => {
currentTokens: 250000, currentTokens: 250000,
maxTokens: 200000, maxTokens: 200000,
}) })
const experimental = { const experimental = {
truncate_all_tool_outputs: false, truncate_all_tool_outputs: false,
aggressive_truncation: true, aggressive_truncation: true,
} }
//#when - Execute compaction with experimental flag //#when - Execute compaction with experimental flag
await executeCompact( await executeCompact(
sessionID, sessionID,
@ -209,34 +209,34 @@ describe("executeCompact lock management", () => {
directory, directory,
experimental, experimental,
) )
//#then - Lock should be cleared even on early return //#then - Lock should be cleared even on early return
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
readMessagesSpy.mockRestore() readMessagesSpy.mockRestore()
}) })
test("prevents concurrent compaction attempts", async () => { test.serial("prevents concurrent compaction attempts", async () => {
// given: Lock already held (simpler test) // given: Lock already held (simpler test)
autoCompactState.compactionInProgress.add(sessionID) autoCompactState.compactionInProgress.add(sessionID)
// when: Try to execute compaction while lock is held // when: Try to execute compaction while lock is held
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Toast should be shown // then: Toast should be shown
const toastCalls = (mockClient.tui.showToast as any).mock.calls const toastCalls = (mockClient.tui.showToast as any).mock.calls
const blockedToast = toastCalls.find( const blockedToast = toastCalls.find(
(call: any) => call[0]?.body?.title === "Compact In Progress", (call: any) => call[0]?.body?.title === "Compact In Progress",
) )
expect(blockedToast).toBeDefined() expect(blockedToast).toBeDefined()
// then: Lock should still be held (not cleared by blocked attempt) // then: Lock should still be held (not cleared by blocked attempt)
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(true)
}) })
test("clears lock after max recovery attempts exhausted", async () => { test.serial("clears lock after max recovery attempts exhausted", async () => {
// given: All retry/revert attempts exhausted // given: All retry/revert attempts exhausted
mockClient.session.messages = mock(() => Promise.resolve({ data: [] })) mockClient.session.messages = mock(() => Promise.resolve({ data: [] }))
// Max out all attempts // Max out all attempts
autoCompactState.retryStateBySession.set(sessionID, { autoCompactState.retryStateBySession.set(sessionID, {
attempt: 5, attempt: 5,
@ -250,22 +250,22 @@ describe("executeCompact lock management", () => {
currentTokens: 100000, currentTokens: 100000,
maxTokens: 200000, maxTokens: 200000,
}) })
// when: Execute compaction // when: Execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Should show failure toast // then: Should show failure toast
const toastCalls = (mockClient.tui.showToast as any).mock.calls const toastCalls = (mockClient.tui.showToast as any).mock.calls
const failureToast = toastCalls.find( const failureToast = toastCalls.find(
(call: any) => call[0]?.body?.title === "Auto Compact Failed", (call: any) => call[0]?.body?.title === "Auto Compact Failed",
) )
expect(failureToast).toBeDefined() expect(failureToast).toBeDefined()
// then: Lock should still be cleared // then: Lock should still be cleared
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
}) })
test("clears lock when client.tui.showToast throws", async () => { test.serial("clears lock when client.tui.showToast throws", async () => {
// given: Toast will fail (this should never happen but testing robustness) // given: Toast will fail (this should never happen but testing robustness)
mockClient.tui.showToast = mock(() => mockClient.tui.showToast = mock(() =>
Promise.reject(new Error("Toast failed")), Promise.reject(new Error("Toast failed")),
@ -275,15 +275,15 @@ describe("executeCompact lock management", () => {
currentTokens: 100000, currentTokens: 100000,
maxTokens: 200000, maxTokens: 200000,
}) })
// when: Execute compaction // when: Execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Lock should be cleared even if toast fails // then: Lock should be cleared even if toast fails
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
}) })
test("clears lock when promptAsync in continuation throws", async () => { test.serial("clears lock when promptAsync in continuation throws", async () => {
// given: promptAsync will fail during continuation // given: promptAsync will fail during continuation
mockClient.session.promptAsync = mock(() => mockClient.session.promptAsync = mock(() =>
Promise.reject(new Error("Prompt failed")), Promise.reject(new Error("Prompt failed")),
@ -293,26 +293,26 @@ describe("executeCompact lock management", () => {
currentTokens: 100000, currentTokens: 100000,
maxTokens: 200000, maxTokens: 200000,
}) })
// when: Execute compaction // when: Execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// Wait for setTimeout callback // Wait for setTimeout callback
await fakeTimeouts.advanceBy(600) await fakeTimeouts.advanceBy(600)
// then: Lock should be cleared // then: Lock should be cleared
// The continuation happens in setTimeout, but lock is cleared in finally before that // The continuation happens in setTimeout, but lock is cleared in finally before that
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
}) })
test("falls through to summarize when truncation is insufficient", async () => { test.serial("falls through to summarize when truncation is insufficient", async () => {
// given: Over token limit with truncation returning insufficient // given: Over token limit with truncation returning insufficient
autoCompactState.errorDataBySession.set(sessionID, { autoCompactState.errorDataBySession.set(sessionID, {
errorType: "token_limit", errorType: "token_limit",
currentTokens: 250000, currentTokens: 250000,
maxTokens: 200000, maxTokens: 200000,
}) })
const truncateSpy = spyOn(storage, "truncateUntilTargetTokens").mockResolvedValue({ const truncateSpy = spyOn(storage, "truncateUntilTargetTokens").mockResolvedValue({
success: true, success: true,
sufficient: false, sufficient: false,
@ -325,13 +325,13 @@ describe("executeCompact lock management", () => {
{ toolName: "Bash", originalSize: 2000 }, { toolName: "Bash", originalSize: 2000 },
], ],
}) })
// when: Execute compaction // when: Execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// then: Truncation was attempted // then: Truncation was attempted
expect(truncateSpy).toHaveBeenCalled() expect(truncateSpy).toHaveBeenCalled()
// then: Summarize should be called (fall through from insufficient truncation) // then: Summarize should be called (fall through from insufficient truncation)
expect(mockClient.session.summarize).toHaveBeenCalledWith( expect(mockClient.session.summarize).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -339,21 +339,21 @@ describe("executeCompact lock management", () => {
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true }, body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true },
}), }),
) )
// then: Lock should be cleared // then: Lock should be cleared
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
truncateSpy.mockRestore() truncateSpy.mockRestore()
}) })
test("does NOT call summarize when truncation is sufficient", async () => { test.serial("does NOT call summarize when truncation is sufficient", async () => {
// given: Over token limit with truncation returning sufficient // given: Over token limit with truncation returning sufficient
autoCompactState.errorDataBySession.set(sessionID, { autoCompactState.errorDataBySession.set(sessionID, {
errorType: "token_limit", errorType: "token_limit",
currentTokens: 250000, currentTokens: 250000,
maxTokens: 200000, maxTokens: 200000,
}) })
const truncateSpy = spyOn(storage, "truncateUntilTargetTokens").mockResolvedValue({ const truncateSpy = spyOn(storage, "truncateUntilTargetTokens").mockResolvedValue({
success: true, success: true,
sufficient: true, sufficient: true,
@ -365,25 +365,25 @@ describe("executeCompact lock management", () => {
{ toolName: "Read", originalSize: 30000 }, { toolName: "Read", originalSize: 30000 },
], ],
}) })
// when: Execute compaction // when: Execute compaction
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory) await executeCompact(sessionID, msg, autoCompactState, mockClient, directory)
// Wait for setTimeout callback // Wait for setTimeout callback
await fakeTimeouts.advanceBy(600) await fakeTimeouts.advanceBy(600)
// then: Truncation was attempted // then: Truncation was attempted
expect(truncateSpy).toHaveBeenCalled() expect(truncateSpy).toHaveBeenCalled()
// then: Summarize should NOT be called (early return from sufficient truncation) // then: Summarize should NOT be called (early return from sufficient truncation)
expect(mockClient.session.summarize).not.toHaveBeenCalled() expect(mockClient.session.summarize).not.toHaveBeenCalled()
// then: promptAsync should be called (Continue after successful truncation) // then: promptAsync should be called (Continue after successful truncation)
expect(mockClient.session.promptAsync).toHaveBeenCalled() expect(mockClient.session.promptAsync).toHaveBeenCalled()
// then: Lock should be cleared // then: Lock should be cleared
expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false) expect(autoCompactState.compactionInProgress.has(sessionID)).toBe(false)
truncateSpy.mockRestore() truncateSpy.mockRestore()
}) })
}) })