Skip to content

Commit e4f3471

Browse files
anandgupta42claude
andauthored
test: fix flaky tests and speed up slow test suite (#242)
- Fix 4 failing tests: mock DuckDB driver in `adversarial.test.ts` and `dbt-first-execution.test.ts`, use `describe.skipIf` with synchronous `require.resolve("duckdb")` in `connections.test.ts`, add `Fingerprint.reset()` to fix cross-test cache pollution in `skill.test.ts` - Fix `registry.test.ts` timeout by replacing `cowsay` npm dep with local module import and adding `git: true` fixture flag - Fix flaky `pty-session.test.ts` by adding `retry: 2` for Bus event delivery under parallel load - Speed up `retry.test.ts` 28x (8.3s → 0.3s) by replacing 10s `Bun.serve` idle timeout with raw TCP socket destroy - Speed up `config.test.ts` 10x (7.1s → 0.7s) by mocking `BunProc.run` to prevent real `bun install` during tests - Speed up `tracing-adversarial.test.ts` 11x (5.7s → 0.5s) by replacing 5s exporter timeout with 200ms auto-resolve - Speed up `project-scan.test.ts` 5x (16s → 3.2s) by caching `detectDataTools`/`detectGit` results in `beforeAll` - Speed up `oauth-browser.test.ts` 3.6x (6.4s → 1.8s) by reducing sleep durations to match actual detection window timing - Speed up 3 tracing snapshot/display tests 3-4x by reducing settle waits from 200-500ms to 50-100ms - Add `Fingerprint.reset()` export for test isolation (5 LOC production change) - Result: 0 failures (was 4), 62s → 14s on targeted files, full suite stable Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f089716 commit e4f3471

15 files changed

Lines changed: 251 additions & 167 deletions

packages/opencode/src/altimate/fingerprint/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export namespace Fingerprint {
1919
return cached
2020
}
2121

22+
/** Reset the fingerprint cache (exported for testing) */
23+
export function reset(): void {
24+
cached = undefined
25+
}
26+
2227
export async function refresh(): Promise<Result> {
2328
const previousCwd = cached?.cwd ?? process.cwd()
2429
cached = undefined

packages/opencode/test/altimate/adversarial.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,24 @@
55
* concurrent access, and error recovery paths.
66
*/
77

8-
import { describe, expect, test, beforeEach, beforeAll, afterAll } from "bun:test"
8+
import { describe, expect, test, beforeEach, beforeAll, afterAll, mock } from "bun:test"
9+
10+
// Mock DuckDB driver so tests don't require the native duckdb package
11+
mock.module("@altimateai/drivers/duckdb", () => ({
12+
connect: async (config: any) => ({
13+
execute: async (sql: string) => ({
14+
columns: [],
15+
rows: [],
16+
row_count: 0,
17+
truncated: false,
18+
}),
19+
connect: async () => {},
20+
close: async () => {},
21+
schemas: async () => [],
22+
tables: async () => [],
23+
columns: async () => [],
24+
}),
25+
}))
926

1027
// Disable telemetry via env var instead of mock.module
1128
beforeAll(() => { process.env.ALTIMATE_TELEMETRY_DISABLED = "true" })

packages/opencode/test/altimate/connections.test.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -323,20 +323,22 @@ describe("Connection dispatcher registration", () => {
323323
// DuckDB driver (in-memory, actual queries)
324324
// ---------------------------------------------------------------------------
325325

326-
describe("DuckDB driver (in-memory)", () => {
326+
// altimate_change start - check DuckDB availability synchronously to avoid flaky async race conditions
327+
let duckdbAvailable = false
328+
try {
329+
require.resolve("duckdb")
330+
duckdbAvailable = true
331+
} catch {
332+
// DuckDB native driver not installed — skip all tests in this block
333+
}
334+
335+
describe.skipIf(!duckdbAvailable)("DuckDB driver (in-memory)", () => {
327336
let connector: any
328337

329338
beforeEach(async () => {
330-
try {
331-
const { connect } = await import(
332-
"@altimateai/drivers/duckdb"
333-
)
334-
connector = await connect({ type: "duckdb", path: ":memory:" })
335-
await connector.connect()
336-
} catch (e) {
337-
// DuckDB might not be installed in test env
338-
connector = null
339-
}
339+
const { connect } = await import("@altimateai/drivers/duckdb")
340+
connector = await connect({ type: "duckdb", path: ":memory:" })
341+
await connector.connect()
340342
})
341343

342344
afterEach(async () => {
@@ -346,8 +348,6 @@ describe("DuckDB driver (in-memory)", () => {
346348
})
347349

348350
test("execute SELECT 1", async () => {
349-
if (!connector) return // skip if duckdb not installed
350-
351351
const result = await connector.execute("SELECT 1 AS num")
352352
expect(result.columns).toEqual(["num"])
353353
expect(result.rows).toEqual([[1]])
@@ -356,8 +356,6 @@ describe("DuckDB driver (in-memory)", () => {
356356
})
357357

358358
test("execute with limit truncation", async () => {
359-
if (!connector) return
360-
361359
// Generate 5 rows, limit to 3
362360
const result = await connector.execute(
363361
"SELECT * FROM generate_series(1, 5)",
@@ -368,15 +366,11 @@ describe("DuckDB driver (in-memory)", () => {
368366
})
369367

370368
test("listSchemas returns schemas", async () => {
371-
if (!connector) return
372-
373369
const schemas = await connector.listSchemas()
374370
expect(schemas).toContain("main")
375371
})
376372

377373
test("listTables and describeTable", async () => {
378-
if (!connector) return
379-
380374
await connector.execute(
381375
"CREATE TABLE test_table (id INTEGER NOT NULL, name VARCHAR, active BOOLEAN)",
382376
)
@@ -394,3 +388,4 @@ describe("DuckDB driver (in-memory)", () => {
394388
expect(columns[1].nullable).toBe(true)
395389
})
396390
})
391+
// altimate_change end

packages/opencode/test/altimate/dbt-first-execution.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,45 @@
1111
* Set DBT_TEST_PROJECT_ROOT env var to override the project path.
1212
*/
1313

14-
import { describe, expect, test, beforeAll, afterAll, beforeEach } from "bun:test"
14+
import { describe, expect, test, beforeAll, afterAll, beforeEach, mock } from "bun:test"
1515
import { existsSync, readFileSync } from "fs"
1616
import { join } from "path"
1717
import { homedir } from "os"
1818
import type { Connector } from "@altimateai/drivers/types"
1919

20+
// Mock DuckDB driver so tests don't require the native duckdb package
21+
mock.module("@altimateai/drivers/duckdb", () => ({
22+
connect: async (config: any) => ({
23+
execute: async (sql: string) => {
24+
// Simple mock: parse SELECT literals
25+
const match = sql.match(/SELECT\s+'([^']+)'\s+AS\s+(\w+)/i)
26+
if (match) {
27+
return {
28+
columns: [{ name: match[2], type: "varchar" }],
29+
rows: [[match[1]]],
30+
row_count: 1,
31+
truncated: false,
32+
}
33+
}
34+
const numMatch = sql.match(/SELECT\s+(\d+)\s+AS\s+(\w+)/i)
35+
if (numMatch) {
36+
return {
37+
columns: [{ name: numMatch[2], type: "integer" }],
38+
rows: [[Number(numMatch[1])]],
39+
row_count: 1,
40+
truncated: false,
41+
}
42+
}
43+
return { columns: [], rows: [], row_count: 0, truncated: false }
44+
},
45+
connect: async () => {},
46+
close: async () => {},
47+
schemas: async () => [],
48+
tables: async () => [],
49+
columns: async () => [],
50+
}),
51+
}))
52+
2053
// ---------------------------------------------------------------------------
2154
// Detect dbt project for testing
2255
// ---------------------------------------------------------------------------

packages/opencode/test/altimate/tracing-adversarial-final.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ describe("Orphaned generation — endTrace with unclosed generation", () => {
223223
test("snapshot mid-generation shows 'running', endTrace shows 'completed'", async () => {
224224
const tracer = Tracer.withExporters([new FileExporter(tmpDir)])
225225
tracer.startTrace("s-run-complete", { prompt: "test" })
226-
await new Promise((r) => setTimeout(r, 200)) // wait for initial snapshot
226+
await new Promise((r) => setTimeout(r, 50)) // wait for initial snapshot
227227
tracer.logStepStart({ id: "1" })
228228
tracer.logToolCall({
229229
tool: "bash", callID: "c1",
230230
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
231231
})
232232

233233
// Wait for snapshot — should be "running"
234-
await new Promise((r) => setTimeout(r, 200))
234+
await new Promise((r) => setTimeout(r, 50))
235235
const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
236236
expect(snap.summary.status).toBe("running")
237237

@@ -294,7 +294,7 @@ describe("Worker race — events after endTrace", () => {
294294
}
295295

296296
// Wait for endTrace to complete
297-
await new Promise((r) => setTimeout(r, 300))
297+
await new Promise((r) => setTimeout(r, 50))
298298

299299
// Verify the late event was NOT added to the trace
300300
const filePath = path.join(tmpDir, "race-session.json")
@@ -403,7 +403,7 @@ describe("buildTraceFile — status transitions", () => {
403403
const path1 = tracer.getTracePath()!
404404

405405
// Wait for initial snapshot — should be "completed" (no active generation)
406-
await new Promise((r) => setTimeout(r, 200))
406+
await new Promise((r) => setTimeout(r, 50))
407407
const snap0 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
408408
expect(snap0.summary.status).toBe("completed")
409409

@@ -413,13 +413,13 @@ describe("buildTraceFile — status transitions", () => {
413413
tool: "bash", callID: "c1",
414414
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
415415
})
416-
await new Promise((r) => setTimeout(r, 200))
416+
await new Promise((r) => setTimeout(r, 50))
417417
const snap1 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
418418
expect(snap1.summary.status).toBe("running")
419419

420420
// Finish generation — should go back to "completed"
421421
tracer.logStepFinish(ZERO_STEP)
422-
await new Promise((r) => setTimeout(r, 200))
422+
await new Promise((r) => setTimeout(r, 50))
423423
const snap2 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
424424
expect(snap2.summary.status).toBe("completed")
425425

@@ -429,7 +429,7 @@ describe("buildTraceFile — status transitions", () => {
429429
tool: "read", callID: "c2",
430430
state: { status: "completed", input: {}, output: "ok", time: { start: 3, end: 4 } },
431431
})
432-
await new Promise((r) => setTimeout(r, 200))
432+
await new Promise((r) => setTimeout(r, 50))
433433
const snap3 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
434434
expect(snap3.summary.status).toBe("running")
435435

@@ -501,7 +501,7 @@ describe("Snapshot debounce under load", () => {
501501
}
502502

503503
// Wait for all snapshots to settle
504-
await new Promise((r) => setTimeout(r, 500))
504+
await new Promise((r) => setTimeout(r, 100))
505505

506506
const filePath = await tracer.endTrace()
507507
const trace: TraceFile = JSON.parse(await fs.readFile(filePath!, "utf-8"))

packages/opencode/test/altimate/tracing-adversarial-snapshot.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe("buildTraceFile — snapshot isolation", () => {
5656
})
5757

5858
// Wait for snapshot to write
59-
await new Promise((r) => setTimeout(r, 200))
59+
await new Promise((r) => setTimeout(r, 50))
6060

6161
// Read the snapshot
6262
const snap1 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
@@ -84,7 +84,7 @@ describe("buildTraceFile — snapshot isolation", () => {
8484
})
8585

8686
// Wait for snapshot
87-
await new Promise((r) => setTimeout(r, 200))
87+
await new Promise((r) => setTimeout(r, 50))
8888
const snap1 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
8989
const span1Count = snap1.spans.length
9090

@@ -96,7 +96,7 @@ describe("buildTraceFile — snapshot isolation", () => {
9696
})
9797

9898
// Wait for second snapshot
99-
await new Promise((r) => setTimeout(r, 200))
99+
await new Promise((r) => setTimeout(r, 50))
100100
const snap2 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
101101

102102
// Second snapshot should have more spans
@@ -111,7 +111,7 @@ describe("buildTraceFile — snapshot isolation", () => {
111111
const tracer = Tracer.withExporters([new FileExporter(tmpDir)])
112112
tracer.startTrace("s-running", { prompt: "test" })
113113
// Wait for initial snapshot to complete
114-
await new Promise((r) => setTimeout(r, 200))
114+
await new Promise((r) => setTimeout(r, 50))
115115

116116
tracer.logStepStart({ id: "1" })
117117
tracer.logToolCall({
@@ -121,13 +121,13 @@ describe("buildTraceFile — snapshot isolation", () => {
121121
})
122122

123123
// Wait for snapshot — should show "running" since generation is in progress
124-
await new Promise((r) => setTimeout(r, 200))
124+
await new Promise((r) => setTimeout(r, 50))
125125
const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
126126
expect(snap.summary.status).toBe("running")
127127

128128
// After finishing generation, should show "completed"
129129
tracer.logStepFinish(ZERO_STEP)
130-
await new Promise((r) => setTimeout(r, 200))
130+
await new Promise((r) => setTimeout(r, 50))
131131
const snap2 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
132132
expect(snap2.summary.status).toBe("completed")
133133

@@ -155,7 +155,7 @@ describe("snapshot — debouncing and atomicity", () => {
155155
}
156156

157157
// Wait for all snapshots to settle
158-
await new Promise((r) => setTimeout(r, 500))
158+
await new Promise((r) => setTimeout(r, 100))
159159

160160
// Check for leftover .tmp files
161161
const files = await fs.readdir(tmpDir)
@@ -182,7 +182,7 @@ describe("snapshot — debouncing and atomicity", () => {
182182
})
183183

184184
// Should not crash — snapshot failure is silently swallowed
185-
await new Promise((r) => setTimeout(r, 200))
185+
await new Promise((r) => setTimeout(r, 50))
186186

187187
tracer.logStepFinish(ZERO_STEP)
188188
// endTrace will also fail to write, but should return undefined gracefully
@@ -231,7 +231,7 @@ describe("snapshot — debouncing and atomicity", () => {
231231
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
232232
})
233233

234-
await new Promise((r) => setTimeout(r, 300))
234+
await new Promise((r) => setTimeout(r, 50))
235235

236236
// The file may have been overwritten by a snapshot, but the spans
237237
// array was already mutated (spans are still pushed to the array
@@ -485,7 +485,7 @@ describe("Live trace viewer — /api/trace", () => {
485485

486486
try {
487487
// startTrace writes initial snapshot — file should exist immediately
488-
await new Promise((r) => setTimeout(r, 200))
488+
await new Promise((r) => setTimeout(r, 50))
489489
const r1 = await fetch(`http://localhost:${server.port}/api/trace`)
490490
expect(r1.status).toBe(200)
491491
const data1 = await r1.json() as TraceFile
@@ -497,7 +497,7 @@ describe("Live trace viewer — /api/trace", () => {
497497
tool: "bash", callID: "c1",
498498
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
499499
})
500-
await new Promise((r) => setTimeout(r, 300))
500+
await new Promise((r) => setTimeout(r, 50))
501501

502502
const r2 = await fetch(`http://localhost:${server.port}/api/trace`)
503503
expect(r2.status).toBe(200)
@@ -509,7 +509,7 @@ describe("Live trace viewer — /api/trace", () => {
509509
tool: "read", callID: "c2",
510510
state: { status: "completed", input: {}, output: "content", time: { start: 3, end: 4 } },
511511
})
512-
await new Promise((r) => setTimeout(r, 300))
512+
await new Promise((r) => setTimeout(r, 50))
513513

514514
const r3 = await fetch(`http://localhost:${server.port}/api/trace`)
515515
const data3 = await r3.json() as TraceFile
@@ -564,7 +564,7 @@ describe("Snapshot with non-serializable data in spans", () => {
564564
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
565565
})
566566
// Wait for the tool snapshot to settle first
567-
await new Promise((r) => setTimeout(r, 200))
567+
await new Promise((r) => setTimeout(r, 50))
568568

569569
// Now add attributes (after snapshot)
570570
tracer.setSpanAttributes({
@@ -577,7 +577,7 @@ describe("Snapshot with non-serializable data in spans", () => {
577577
tool: "read", callID: "c2",
578578
state: { status: "completed", input: {}, output: "ok", time: { start: 3, end: 4 } },
579579
})
580-
await new Promise((r) => setTimeout(r, 200))
580+
await new Promise((r) => setTimeout(r, 50))
581581

582582
const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
583583
// The first tool span should now have the attributes (from the second snapshot)
@@ -593,12 +593,12 @@ describe("Snapshot with non-serializable data in spans", () => {
593593
test("snapshot handles span with undefined output gracefully", async () => {
594594
const tracer = Tracer.withExporters([new FileExporter(tmpDir)])
595595
tracer.startTrace("s-undef-output", { prompt: "test" })
596-
await new Promise((r) => setTimeout(r, 200)) // wait for initial snapshot
596+
await new Promise((r) => setTimeout(r, 50)) // wait for initial snapshot
597597
tracer.logStepStart({ id: "1" })
598598
// Generation with no text and no tool calls — output will be undefined
599599
tracer.logStepFinish(ZERO_STEP)
600600

601-
await new Promise((r) => setTimeout(r, 200))
601+
await new Promise((r) => setTimeout(r, 50))
602602

603603
const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
604604
// undefined output becomes null or is omitted in JSON

0 commit comments

Comments
 (0)