Skip to content

Commit 40a86b1

Browse files
committed
Debug test flakes
1 parent e413a59 commit 40a86b1

File tree

3 files changed

+122
-26
lines changed

3 files changed

+122
-26
lines changed

src/core/Cline.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,9 @@ export class Cline {
129129
images?: string[] | undefined,
130130
historyItem?: HistoryItem | undefined,
131131
experiments?: Record<string, boolean>,
132+
startTask = true,
132133
) {
133-
if (!task && !images && !historyItem) {
134+
if (startTask && !task && !images && !historyItem) {
134135
throw new Error("Either historyItem or task/images must be provided")
135136
}
136137

@@ -153,11 +154,32 @@ export class Cline {
153154
// Initialize diffStrategy based on current state
154155
this.updateDiffStrategy(Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY))
155156

156-
if (task || images) {
157-
this.startTask(task, images)
158-
} else if (historyItem) {
159-
this.resumeTaskFromHistory()
157+
if (startTask) {
158+
if (task || images) {
159+
this.startTask(task, images)
160+
} else if (historyItem) {
161+
this.resumeTaskFromHistory()
162+
} else {
163+
throw new Error("Either historyItem or task/images must be provided")
164+
}
165+
}
166+
}
167+
168+
static create(...args: ConstructorParameters<typeof Cline>): [Cline, Promise<void>] {
169+
args[10] = false // startTask
170+
const instance = new Cline(...args)
171+
172+
let task
173+
174+
if (args[6] || args[7]) {
175+
task = instance.startTask(args[6], args[7])
176+
} else if (args[8]) {
177+
task = instance.resumeTaskFromHistory()
178+
} else {
179+
throw new Error("Either historyItem or task/images must be provided")
160180
}
181+
182+
return [instance, task]
161183
}
162184

163185
// Add method to update diffStrategy
@@ -745,8 +767,12 @@ export class Cline {
745767
}
746768
}
747769

748-
async abortTask() {
770+
async abortTask(isAbandoned = false) {
749771
// Will stop any autonomously running promises.
772+
if (isAbandoned) {
773+
this.abandoned = true
774+
}
775+
750776
this.abort = true
751777

752778
this.terminalManager.disposeAll()
@@ -2967,7 +2993,7 @@ export class Cline {
29672993
}
29682994

29692995
// need to call here in case the stream was aborted
2970-
if (this.abort) {
2996+
if (this.abort || this.abandoned) {
29712997
throw new Error("Roo Code instance aborted")
29722998
}
29732999

src/core/__tests__/Cline.test.ts

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// npx jest src/core/__tests__/Cline.test.ts
2+
13
import { Cline } from "../Cline"
24
import { ClineProvider } from "../webview/ClineProvider"
35
import { ApiConfiguration, ModelInfo } from "../../shared/api"
@@ -324,8 +326,8 @@ describe("Cline", () => {
324326
})
325327

326328
describe("constructor", () => {
327-
it("should respect provided settings", () => {
328-
const cline = new Cline(
329+
it("should respect provided settings", async () => {
330+
const [cline, task] = Cline.create(
329331
mockProvider,
330332
mockApiConfig,
331333
"custom instructions",
@@ -337,10 +339,13 @@ describe("Cline", () => {
337339

338340
expect(cline.customInstructions).toBe("custom instructions")
339341
expect(cline.diffEnabled).toBe(false)
342+
343+
await cline.abortTask(true)
344+
await task.catch(() => {})
340345
})
341346

342-
it("should use default fuzzy match threshold when not provided", () => {
343-
const cline = new Cline(
347+
it("should use default fuzzy match threshold when not provided", async () => {
348+
const [cline, task] = await Cline.create(
344349
mockProvider,
345350
mockApiConfig,
346351
"custom instructions",
@@ -353,12 +358,15 @@ describe("Cline", () => {
353358
expect(cline.diffEnabled).toBe(true)
354359
// The diff strategy should be created with default threshold (1.0)
355360
expect(cline.diffStrategy).toBeDefined()
361+
362+
await cline.abortTask(true)
363+
await task.catch(() => {})
356364
})
357365

358-
it("should use provided fuzzy match threshold", () => {
366+
it("should use provided fuzzy match threshold", async () => {
359367
const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy")
360368

361-
const cline = new Cline(
369+
const [cline, task] = Cline.create(
362370
mockProvider,
363371
mockApiConfig,
364372
"custom instructions",
@@ -373,12 +381,15 @@ describe("Cline", () => {
373381
expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 0.9, false)
374382

375383
getDiffStrategySpy.mockRestore()
384+
385+
await cline.abortTask(true)
386+
await task.catch(() => {})
376387
})
377388

378-
it("should pass default threshold to diff strategy when not provided", () => {
389+
it("should pass default threshold to diff strategy when not provided", async () => {
379390
const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy")
380391

381-
const cline = new Cline(
392+
const [cline, task] = Cline.create(
382393
mockProvider,
383394
mockApiConfig,
384395
"custom instructions",
@@ -393,6 +404,9 @@ describe("Cline", () => {
393404
expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 1.0, false)
394405

395406
getDiffStrategySpy.mockRestore()
407+
408+
await cline.abortTask(true)
409+
await task.catch(() => {})
396410
})
397411

398412
it("should require either task or historyItem", () => {
@@ -455,7 +469,15 @@ describe("Cline", () => {
455469
})
456470

457471
it("should include timezone information in environment details", async () => {
458-
const cline = new Cline(mockProvider, mockApiConfig, undefined, false, false, undefined, "test task")
472+
const [cline, task] = Cline.create(
473+
mockProvider,
474+
mockApiConfig,
475+
undefined,
476+
false,
477+
false,
478+
undefined,
479+
"test task",
480+
)
459481

460482
const details = await cline["getEnvironmentDetails"](false)
461483

@@ -464,11 +486,24 @@ describe("Cline", () => {
464486
expect(details).toMatch(/UTC-7:00/) // Fixed offset for America/Los_Angeles
465487
expect(details).toContain("# Current Time")
466488
expect(details).toMatch(/1\/1\/2024.*5:00:00 AM.*\(America\/Los_Angeles, UTC-7:00\)/) // Full time string format
489+
490+
await cline.abortTask(true)
491+
await task.catch(() => {})
467492
})
468493

469494
describe("API conversation handling", () => {
470495
it("should clean conversation history before sending to API", async () => {
471-
const cline = new Cline(mockProvider, mockApiConfig, undefined, false, false, undefined, "test task")
496+
const [cline, task] = Cline.create(
497+
mockProvider,
498+
mockApiConfig,
499+
undefined,
500+
false,
501+
false,
502+
undefined,
503+
"test task",
504+
)
505+
cline.abandoned = true
506+
await task
472507

473508
// Mock the API's createMessage method to capture the conversation history
474509
const createMessageSpy = jest.fn()
@@ -576,7 +611,7 @@ describe("Cline", () => {
576611
]
577612

578613
// Test with model that supports images
579-
const clineWithImages = new Cline(
614+
const [clineWithImages, taskWithImages] = Cline.create(
580615
mockProvider,
581616
configWithImages,
582617
undefined,
@@ -585,6 +620,7 @@ describe("Cline", () => {
585620
undefined,
586621
"test task",
587622
)
623+
588624
// Mock the model info to indicate image support
589625
jest.spyOn(clineWithImages.api, "getModel").mockReturnValue({
590626
id: "claude-3-sonnet",
@@ -598,10 +634,11 @@ describe("Cline", () => {
598634
outputPrice: 0.75,
599635
} as ModelInfo,
600636
})
637+
601638
clineWithImages.apiConversationHistory = conversationHistory
602639

603640
// Test with model that doesn't support images
604-
const clineWithoutImages = new Cline(
641+
const [clineWithoutImages, taskWithoutImages] = Cline.create(
605642
mockProvider,
606643
configWithoutImages,
607644
undefined,
@@ -610,6 +647,7 @@ describe("Cline", () => {
610647
undefined,
611648
"test task",
612649
)
650+
613651
// Mock the model info to indicate no image support
614652
jest.spyOn(clineWithoutImages.api, "getModel").mockReturnValue({
615653
id: "gpt-3.5-turbo",
@@ -623,6 +661,7 @@ describe("Cline", () => {
623661
outputPrice: 0.2,
624662
} as ModelInfo,
625663
})
664+
626665
clineWithoutImages.apiConversationHistory = conversationHistory
627666

628667
// Mock abort state for both instances
@@ -631,6 +670,7 @@ describe("Cline", () => {
631670
set: () => {},
632671
configurable: true,
633672
})
673+
634674
Object.defineProperty(clineWithoutImages, "abort", {
635675
get: () => false,
636676
set: () => {},
@@ -645,6 +685,7 @@ describe("Cline", () => {
645685
content,
646686
"",
647687
])
688+
648689
// Set up mock streams
649690
const mockStreamWithImages = (async function* () {
650691
yield { type: "text", text: "test response" }
@@ -672,6 +713,12 @@ describe("Cline", () => {
672713
},
673714
]
674715

716+
clineWithImages.abandoned = true
717+
await taskWithImages.catch(() => {})
718+
719+
clineWithoutImages.abandoned = true
720+
await taskWithoutImages.catch(() => {})
721+
675722
// Trigger API requests
676723
await clineWithImages.recursivelyMakeClineRequests([{ type: "text", text: "test request" }])
677724
await clineWithoutImages.recursivelyMakeClineRequests([{ type: "text", text: "test request" }])
@@ -695,7 +742,15 @@ describe("Cline", () => {
695742
})
696743

697744
it.skip("should handle API retry with countdown", async () => {
698-
const cline = new Cline(mockProvider, mockApiConfig, undefined, false, false, undefined, "test task")
745+
const [cline, task] = Cline.create(
746+
mockProvider,
747+
mockApiConfig,
748+
undefined,
749+
false,
750+
false,
751+
undefined,
752+
"test task",
753+
)
699754

700755
// Mock delay to track countdown timing
701756
const mockDelay = jest.fn().mockResolvedValue(undefined)
@@ -809,10 +864,21 @@ describe("Cline", () => {
809864
expect(errorMessage).toBe(
810865
`${mockError.message}\n\nRetry attempt 1\nRetrying in ${baseDelay} seconds...`,
811866
)
867+
868+
await cline.abortTask(true)
869+
await task.catch(() => {})
812870
})
813871

814872
it.skip("should not apply retry delay twice", async () => {
815-
const cline = new Cline(mockProvider, mockApiConfig, undefined, false, false, undefined, "test task")
873+
const [cline, task] = Cline.create(
874+
mockProvider,
875+
mockApiConfig,
876+
undefined,
877+
false,
878+
false,
879+
undefined,
880+
"test task",
881+
)
816882

817883
// Mock delay to track countdown timing
818884
const mockDelay = jest.fn().mockResolvedValue(undefined)
@@ -925,11 +991,14 @@ describe("Cline", () => {
925991
undefined,
926992
false,
927993
)
994+
995+
await cline.abortTask(true)
996+
await task.catch(() => {})
928997
})
929998

930999
describe("loadContext", () => {
9311000
it("should process mentions in task and feedback tags", async () => {
932-
const cline = new Cline(
1001+
const [cline, task] = Cline.create(
9331002
mockProvider,
9341003
mockApiConfig,
9351004
undefined,
@@ -1002,6 +1071,9 @@ describe("Cline", () => {
10021071
const toolResult2 = processedContent[3] as Anthropic.ToolResultBlockParam
10031072
const content2 = Array.isArray(toolResult2.content) ? toolResult2.content[0] : toolResult2.content
10041073
expect((content2 as Anthropic.TextBlockParam).text).toBe("Regular tool result with @/path")
1074+
1075+
await cline.abortTask(true)
1076+
await task.catch(() => {})
10051077
})
10061078
})
10071079
})

src/core/diff/strategies/new-unified/__tests__/edit-strategies.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference types="jest" />
2-
31
import { applyContextMatching, applyDMP, applyGitFallback } from "../edit-strategies"
42
import { Hunk } from "../types"
53

@@ -277,7 +275,7 @@ describe("applyGitFallback", () => {
277275
expect(result.result.join("\n")).toEqual("line1\nnew line2\nline3")
278276
expect(result.confidence).toBe(1)
279277
expect(result.strategy).toBe("git-fallback")
280-
}, 10_000)
278+
})
281279

282280
it("should return original content with 0 confidence when changes cannot be applied", async () => {
283281
const hunk = {
@@ -293,5 +291,5 @@ describe("applyGitFallback", () => {
293291
expect(result.result).toEqual(content)
294292
expect(result.confidence).toBe(0)
295293
expect(result.strategy).toBe("git-fallback")
296-
}, 10_000)
294+
})
297295
})

0 commit comments

Comments
 (0)