Skip to content

Commit e8acc6a

Browse files
🐛 test(mcp): fix McpHub Windows command wrapping test ordering (#632)
* 🐛 test(mcp): fix McpHub Windows command wrapping test ordering Fix mock initialization race: fs.readFile mock must be set before new McpHub() triggers async initializeGlobalMcpServers(). Also fix duplicate vi.mock('fs/promises') without factory causing auto-mock on line 96. Closes #349 * 🐛 fix(test): add cleanup hook to prevent RPC teardown errors on Windows (#632) * 🐛 fix(ci): cache VS Code test binary to avoid ECONNRESET download failures * 🐛 fix(e2e): update glm-5-turbo max_tokens assertion (#404) * 🐛 fix(providers): clean up abort listeners to prevent MaxListenersExceeded (#404) * 🐛 fix(e2e): fix zai fetch interceptor request capture and timing (#404) * fix: resolve compile TS2532 and e2e glm-5-turbo max_tokens assertion - Fix TS2532 'Object is possibly undefined' by extracting array element to a local variable before accessing .maxTokens - Update glm-5-turbo e2e expected max_tokens from 40000 to 40551 (Math.ceil(202752 * 0.2) = 40551, matching actual context window) * 🐛 fix: address edelauna review feedback (#632) - Pin actions/cache to SHA per repo convention - Add VSCode version to e2e cache key - Remove dead teardown code in zai test interceptor - Replace setTimeout(100) with deterministic assertion in zai tests - Change afterAll to afterEach for spy cleanup * 🐛 fix: replace setTimeout(100) with waitUntilReady() and remove dead code (#632) * refactor(e2e): fetch vscode version dynamically --------- Co-authored-by: Elliott de Launay <edelauna@gmail.com>
1 parent 28c54a7 commit e8acc6a

6 files changed

Lines changed: 59 additions & 19 deletions

File tree

.github/workflows/e2e.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ jobs:
3434
- name: Install xvfb
3535
if: github.event_name != 'pull_request' || steps.e2e-marker.outputs.cache-hit != 'true'
3636
run: sudo apt-get install -y xvfb
37+
38+
- name: Get VS Code version from package.json
39+
if: github.event_name != 'pull_request' || steps.e2e-marker.outputs.cache-hit != 'true'
40+
id: vscode-ver
41+
run: |
42+
VERSION=$(node -p 'require("./apps/vscode-e2e/package.json").devDependencies["@types/vscode"]')
43+
echo "version=$VERSION" >> $GITHUB_OUTPUT
44+
45+
- name: Cache VS Code test binary
46+
if: github.event_name != 'pull_request' || steps.e2e-marker.outputs.cache-hit != 'true'
47+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
48+
with:
49+
path: |
50+
apps/vscode-e2e/.vscode-test/
51+
key: vscode-test-${{ runner.os }}-${{ steps.vscode-ver.outputs.version }}-v1
52+
3753
- name: Run mocked E2E tests
3854
id: run-e2e
3955
# merge_group and workflow_dispatch always run; cache skip is pull_request only

apps/vscode-e2e/src/runTest.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as path from "path"
22
import * as os from "os"
33
import * as fs from "fs/promises"
4+
import { readFileSync } from "fs"
45

56
import { runTests } from "@vscode/test-electron"
67
import { LLMock } from "@copilotkit/aimock"
@@ -156,12 +157,16 @@ async function main() {
156157
}
157158

158159
// Download VS Code, unzip it and run the integration test
160+
// Read VS Code version from package.json to keep in sync with @types/vscode
161+
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"))
162+
const vscodeVersion = process.env.VSCODE_VERSION || pkg.devDependencies["@types/vscode"]
163+
159164
await runTests({
160165
extensionDevelopmentPath,
161166
extensionTestsPath,
162167
launchArgs: [testWorkspace],
163168
extensionTestsEnv,
164-
version: process.env.VSCODE_VERSION || "1.100.0",
169+
version: vscodeVersion,
165170
})
166171
} catch (error) {
167172
console.error("Failed to run tests", error)

apps/vscode-e2e/src/suite/providers/zai.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ suite("Z.ai GLM provider", function () {
220220

221221
await waitUntilCompleted({ api, taskId })
222222
const capturedMaxTokens = requestCapture.maxTokens
223+
assert.ok(
224+
capturedMaxTokens !== undefined,
225+
"max_tokens should have been captured by the fetch interceptor before task completion",
226+
)
223227

224228
const completionMessage = messages.find(
225229
({ say, text }) => (say === "completion_result" || say === "text") && text?.trim() === "4",
@@ -229,8 +233,6 @@ suite("Z.ai GLM provider", function () {
229233

230234
// Verify max_tokens uses the restored default clamp (20% of context window)
231235
// unless the user explicitly overrides it via modelMaxTokens.
232-
// Snapshot immediately after waitUntilCompleted to avoid straggling async calls
233-
// from this task overwriting requestCapture before the assertion runs.
234236
assert.strictEqual(
235237
capturedMaxTokens,
236238
40_000,
@@ -264,6 +266,10 @@ suite("Z.ai GLM provider", function () {
264266

265267
await waitUntilCompleted({ api, taskId })
266268
const capturedMaxTokens = requestCapture.maxTokens
269+
assert.ok(
270+
capturedMaxTokens !== undefined,
271+
"max_tokens should have been captured by the fetch interceptor before task completion",
272+
)
267273

268274
const completionMessage = messages.find(
269275
({ say, text }) => (say === "completion_result" || say === "text") && text?.trim() === "4",
@@ -273,11 +279,10 @@ suite("Z.ai GLM provider", function () {
273279

274280
// Verify max_tokens uses the restored default clamp (20% of context window)
275281
// unless the user explicitly overrides it via modelMaxTokens.
276-
// Snapshot immediately after waitUntilCompleted to avoid straggling async calls
277-
// from the prior test overwriting requestCapture before this assertion runs.
282+
const expectedMaxTokens = 40_551 // Math.ceil(202_752 * 0.2) for glm-5-turbo
278283
assert.strictEqual(
279284
capturedMaxTokens,
280-
40_551,
285+
expectedMaxTokens,
281286
`max_tokens should default to the glm-5-turbo clamp (40_551) but was ${capturedMaxTokens}`,
282287
)
283288
})

src/core/task/Task.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,7 +2690,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
26902690
} else {
26912691
signal.addEventListener("abort", () => {
26922692
reject(new Error("Request cancelled by user"))
2693-
})
2693+
}, { once: true })
26942694
}
26952695
})
26962696
return await Promise.race([nextPromise, abortPromise])
@@ -4194,7 +4194,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
41944194
abortSignal.addEventListener("abort", () => {
41954195
console.log(`[Task#${this.taskId}.${this.instanceId}] AbortSignal triggered for current request`)
41964196
this.currentRequestAbortController = undefined
4197-
})
4197+
}, { once: true })
41984198

41994199
try {
42004200
// Awaiting first chunk to see if it will throw an error.
@@ -4208,7 +4208,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
42084208
} else {
42094209
abortSignal.addEventListener("abort", () => {
42104210
reject(new Error("Request cancelled by user"))
4211-
})
4211+
}, { once: true })
42124212
}
42134213
})
42144214

src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,4 +685,8 @@ describe("ClineProvider flicker-free cancel", () => {
685685
expect(createTaskWithHistoryItemSpy).not.toHaveBeenCalled()
686686
expect((provider as any).cancelledDelegationChildIds.has("child-1")).toBe(true)
687687
})
688+
689+
afterAll(() => {
690+
vi.restoreAllMocks()
691+
})
688692
})

src/services/mcp/__tests__/McpHub.spec.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,7 +2112,7 @@ describe("McpHub", () => {
21122112
}
21132113
})
21142114

2115-
// Mock the config file read BEFORE creating McpHub
2115+
// Mock the config file read
21162116
vi.mocked(fs.readFile).mockResolvedValue(
21172117
JSON.stringify({
21182118
mcpServers: {
@@ -2124,8 +2124,10 @@ describe("McpHub", () => {
21242124
}),
21252125
)
21262126

2127-
// Create a new McpHub instance and wait for initialization
2127+
// Create a new McpHub instance
21282128
const mcpHub = new McpHub(mockProvider as ClineProvider)
2129+
2130+
// Wait for initialization
21292131
await mcpHub.waitUntilReady()
21302132

21312133
// Verify StdioClientTransport was called with wrapped command
@@ -2174,7 +2176,7 @@ describe("McpHub", () => {
21742176
}
21752177
})
21762178

2177-
// Mock the config file read BEFORE creating McpHub
2179+
// Mock the config file read
21782180
vi.mocked(fs.readFile).mockResolvedValue(
21792181
JSON.stringify({
21802182
mcpServers: {
@@ -2186,8 +2188,10 @@ describe("McpHub", () => {
21862188
}),
21872189
)
21882190

2189-
// Create a new McpHub instance and wait for initialization
2191+
// Create a new McpHub instance
21902192
const mcpHub = new McpHub(mockProvider as ClineProvider)
2193+
2194+
// Wait for initialization
21912195
await mcpHub.waitUntilReady()
21922196

21932197
// Verify StdioClientTransport was called without wrapping
@@ -2236,7 +2240,7 @@ describe("McpHub", () => {
22362240
}
22372241
})
22382242

2239-
// Mock the config file read BEFORE creating McpHub
2243+
// Mock the config file read with cmd.exe already as command
22402244
vi.mocked(fs.readFile).mockResolvedValue(
22412245
JSON.stringify({
22422246
mcpServers: {
@@ -2248,8 +2252,10 @@ describe("McpHub", () => {
22482252
}),
22492253
)
22502254

2251-
// Create a new McpHub instance and wait for initialization
2255+
// Create a new McpHub instance
22522256
const mcpHub = new McpHub(mockProvider as ClineProvider)
2257+
2258+
// Wait for initialization
22532259
await mcpHub.waitUntilReady()
22542260

22552261
// Verify StdioClientTransport was called without double-wrapping
@@ -2305,7 +2311,7 @@ describe("McpHub", () => {
23052311
}
23062312
})
23072313

2308-
// Mock the config file read BEFORE creating McpHub - simulating fnm/nvm-windows scenario
2314+
// Mock the config file read - simulating fnm/nvm-windows scenario
23092315
vi.mocked(fs.readFile).mockResolvedValue(
23102316
JSON.stringify({
23112317
mcpServers: {
@@ -2323,8 +2329,10 @@ describe("McpHub", () => {
23232329
}),
23242330
)
23252331

2326-
// Create a new McpHub instance and wait for initialization
2332+
// Create a new McpHub instance
23272333
const mcpHub = new McpHub(mockProvider as ClineProvider)
2334+
2335+
// Wait for initialization
23282336
await mcpHub.waitUntilReady()
23292337

23302338
// Verify that the command was wrapped with cmd.exe
@@ -2378,7 +2386,7 @@ describe("McpHub", () => {
23782386
}
23792387
})
23802388

2381-
// Mock the config file read BEFORE creating McpHub
2389+
// Mock the config file read with CMD (uppercase) as command
23822390
vi.mocked(fs.readFile).mockResolvedValue(
23832391
JSON.stringify({
23842392
mcpServers: {
@@ -2390,8 +2398,10 @@ describe("McpHub", () => {
23902398
}),
23912399
)
23922400

2393-
// Create a new McpHub instance and wait for initialization
2401+
// Create a new McpHub instance
23942402
const mcpHub = new McpHub(mockProvider as ClineProvider)
2403+
2404+
// Wait for initialization
23952405
await mcpHub.waitUntilReady()
23962406

23972407
// Verify StdioClientTransport was called without double-wrapping

0 commit comments

Comments
 (0)