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:
parent
e21bbed3ab
commit
4aec627b33
@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user