Skip to content

Commit c817f8a

Browse files
committed
test(e2e): route every context.db open through openTestDb with busy_timeout
The e2e suite runs many files in parallel against a shared context.db while the plugin under test writes to it. Handles opened with the default busy_timeout=0 fail immediately with SQLITE_BUSY the instant another connection holds the write lock, surfacing as flaky 'database is locked' failures (memory-injection, cache-stability cascade) rather than real regressions. Most write-handle opens set the pragma inline, but that left the footgun live: memory-injection and all the pi-* direct opens had no timeout, and one pi-todo-synthesis open was missing it too. Add one openTestDb() helper that always sets busy_timeout and route every direct open (reader and writer) through it, deleting the now-redundant inline pragmas. A bare new Database in a test can no longer reintroduce the flake.
1 parent 5af0283 commit c817f8a

16 files changed

Lines changed: 63 additions & 36 deletions

packages/e2e-tests/src/test-db.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Database } from "bun:sqlite";
2+
3+
/**
4+
* Open a SQLite handle for an e2e test with a non-zero `busy_timeout` always set.
5+
*
6+
* The e2e suite runs many files in parallel against a per-test shared
7+
* `context.db` while the plugin under test writes to the same file. A handle
8+
* opened with the default `busy_timeout = 0` fails immediately with SQLITE_BUSY
9+
* the instant any other connection holds the write lock, which surfaces as
10+
* flaky "database is locked" failures under load rather than a real regression.
11+
* Setting a timeout makes the handle WAIT for the lock instead of failing.
12+
*
13+
* Every test that opens the context DB directly (reader or writer) must go
14+
* through this helper so the timeout can never be forgotten — a bare
15+
* `new Database(...)` in a test reintroduces the flake.
16+
*/
17+
export function openTestDb(
18+
path: string,
19+
options?: { readonly?: boolean; readwrite?: boolean },
20+
): Database {
21+
const db = new Database(path, options);
22+
db.exec("PRAGMA busy_timeout=5000");
23+
return db;
24+
}

packages/e2e-tests/tests/cache-invariants.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
mainAgentRequests,
4242
} from "../src/cache-analysis";
4343
import { TestHarness } from "../src/harness";
44+
import { openTestDb } from "../src/test-db";
4445
import type { MockUsage } from "../src/mock-provider/server";
4546

4647
const HISTORIAN_SYSTEM_MARKER = "the hippocampus of a long-running coding agent";
@@ -185,9 +186,8 @@ function projectIdentity(): string {
185186

186187
function writeContextDb<T>(fn: (db: Database) => T): T {
187188
const dbPath = join(h.opencode.env.dataDir, "cortexkit", "magic-context", "context.db");
188-
const db = new Database(dbPath);
189+
const db = openTestDb(dbPath);
189190
try {
190-
db.query("PRAGMA busy_timeout = 5000").run();
191191
return fn(db);
192192
} finally {
193193
db.close();

packages/e2e-tests/tests/long-running-session.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { resolveProjectIdentity } from "../../plugin/src/features/magic-context/
99
import { computeSyntheticCallId } from "../../plugin/src/hooks/magic-context/todo-view";
1010
import { TestHarness } from "../src/harness";
1111
import type { MockUsage } from "../src/mock-provider/server";
12+
import { openTestDb } from "../src/test-db";
1213

1314
const HISTORIAN_SYSTEM_MARKER = "the hippocampus of a long-running coding agent";
1415

@@ -197,9 +198,8 @@ function contextDbPath(): string {
197198
}
198199

199200
function writeDb(fn: (db: Database) => void): void {
200-
const db = new Database(contextDbPath(), { readwrite: true });
201+
const db = openTestDb(contextDbPath(), { readwrite: true });
201202
try {
202-
db.query("PRAGMA busy_timeout = 5000").run();
203203
fn(db);
204204
} finally {
205205
db.close();
@@ -275,8 +275,7 @@ async function send(sessionId: string, prompt: string, text: string, usage: Mock
275275
// Open opencode's session DB directly (Database is imported at top of file)
276276
try {
277277
const ocDbPath = join(h.opencode.env.dataDir, "opencode", "opencode.db");
278-
const ocDb = new Database(ocDbPath, { readonly: true });
279-
ocDb.query("PRAGMA busy_timeout = 1000").run();
278+
const ocDb = openTestDb(ocDbPath, { readonly: true });
280279
// Get latest messages in the session
281280
const rows = ocDb.prepare(
282281
"SELECT id, json_extract(data, '$.role') AS role, json_extract(data, '$.finish') AS finish, json_extract(data, '$.summary') AS summary FROM message WHERE session_id = ? ORDER BY id DESC LIMIT 6",
@@ -289,8 +288,7 @@ async function send(sessionId: string, prompt: string, text: string, usage: Mock
289288
// Dump parts of the TOP 3 newest messages — reveals source of mystery user messages
290289
// (compaction marker? autocontinue text? tool result? synthetic ignored notification?)
291290
try {
292-
const ocDb2 = new Database(ocDbPath, { readonly: true });
293-
ocDb2.query("PRAGMA busy_timeout = 1000").run();
291+
const ocDb2 = openTestDb(ocDbPath, { readonly: true });
294292
const topIds = rows.slice(0, 3).map((r) => r.id);
295293
for (const msgId of topIds) {
296294
const parts = ocDb2.prepare(

packages/e2e-tests/tests/memory-injection.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { TestHarness } from "../src/harness";
1111
// test aligned with whatever the plugin does at runtime.
1212
import { resolveProjectIdentity } from "../../plugin/src/features/magic-context/memory/project-identity";
1313
import { computeNormalizedHash } from "../../plugin/src/features/magic-context/memory/normalize-hash";
14+
import { openTestDb } from "../src/test-db";
1415

1516
/**
1617
* Memory injection — regression test for v0.9.1.
@@ -61,7 +62,7 @@ function computeDirIdentity(directory: string): string {
6162
function seedMemory(h: TestHarness, projectIdentity: string, content: string): void {
6263
// Plugin v0.16+ — shared cortexkit/magic-context path.
6364
const dbPath = join(h.opencode.env.dataDir, "cortexkit", "magic-context", "context.db");
64-
const db = new Database(dbPath);
65+
const db = openTestDb(dbPath);
6566
try {
6667
const now = Date.now();
6768
// Use the production hash helper so this matches the value the plugin

packages/e2e-tests/tests/pi-cache-stability.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, it } from "bun:test";
55
import { writeFileSync } from "node:fs";
66
import { join } from "node:path";
77
import { PiTestHarness } from "../src/pi-harness";
8+
import { openTestDb } from "../src/test-db";
89

910
interface HarnessOptions {
1011
magicContextConfig?: Record<string, unknown>;
@@ -24,11 +25,11 @@ async function withPiHarness<T>(
2425
}
2526

2627
function openWritableDb(h: PiTestHarness): Database {
27-
return new Database(h.contextDbPath(), { readwrite: true });
28+
return openTestDb(h.contextDbPath(), { readwrite: true });
2829
}
2930

3031
function readDb<T>(h: PiTestHarness, fn: (db: Database) => T): T {
31-
const db = new Database(h.contextDbPath(), { readonly: true });
32+
const db = openTestDb(h.contextDbPath(), { readonly: true });
3233
try {
3334
return fn(db);
3435
} finally {

packages/e2e-tests/tests/pi-cross-harness.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { computeNormalizedHash } from "../../plugin/src/features/magic-context/m
88
import { resolveProjectIdentity } from "../../plugin/src/features/magic-context/memory/project-identity";
99
import { TestHarness } from "../src/harness";
1010
import { PiTestHarness } from "../src/pi-harness";
11+
import { openTestDb } from "../src/test-db";
1112

1213
let oc: TestHarness | null = null;
1314
let pi: PiTestHarness | null = null;
@@ -20,9 +21,8 @@ afterAll(async () => {
2021
async function insertMemory(dbPath: string, projectIdentity: string, sessionId: string | null, content: string) {
2122
const deadline = Date.now() + 10_000;
2223
while (true) {
23-
const db = new Database(dbPath);
24+
const db = openTestDb(dbPath);
2425
try {
25-
db.exec("PRAGMA busy_timeout = 1000");
2626
const now = Date.now();
2727
db.prepare(
2828
`INSERT INTO memories (

packages/e2e-tests/tests/pi-deferred-compaction-marker.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, it } from "bun:test";
55
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
66
import { join } from "node:path";
77
import { PiTestHarness } from "../src/pi-harness";
8+
import { openTestDb } from "../src/test-db";
89

910
/**
1011
* Pi compaction marker behavior (Phase 2 deferred-marker design).
@@ -78,7 +79,7 @@ function findOrdinalRange(body: Record<string, unknown>): { start: number; end:
7879
}
7980

8081
function readMarkerRow(h: PiTestHarness, sessionId: string): MarkerRow | null {
81-
const db = new Database(h.contextDbPath(), { readonly: true });
82+
const db = openTestDb(h.contextDbPath(), { readonly: true });
8283
try {
8384
return db
8485
.prepare(
@@ -126,7 +127,7 @@ function readCompactionEntries(h: PiTestHarness): Array<Record<string, unknown>>
126127
* a reliable indicator that the publish transaction committed.
127128
*/
128129
function readCompartmentCount(h: PiTestHarness, sessionId: string): number {
129-
const db = new Database(h.contextDbPath(), { readonly: true });
130+
const db = openTestDb(h.contextDbPath(), { readonly: true });
130131
try {
131132
const row = db
132133
.prepare(

packages/e2e-tests/tests/pi-drops.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Database } from "bun:sqlite";
44
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
55
import { PiTestHarness } from "../src/pi-harness";
6+
import { openTestDb } from "../src/test-db";
67

78
let h: PiTestHarness;
89

@@ -38,7 +39,7 @@ describe("pi drops", () => {
3839
expect(first.sessionId).toBeTruthy();
3940
await h.waitFor(() => h.countTags(first.sessionId!) > 0, { label: "tag ready" });
4041

41-
const writable = new Database(h.contextDbPath());
42+
const writable = openTestDb(h.contextDbPath());
4243
try {
4344
writable
4445
.prepare(

packages/e2e-tests/tests/pi-long-running-session.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { resolveProjectIdentity } from "../../plugin/src/features/magic-context/
99
import { computeSyntheticCallId } from "../../plugin/src/hooks/magic-context/todo-view";
1010
import { PiTestHarness } from "../src/pi-harness";
1111
import type { MockUsage } from "../src/mock-provider/server";
12+
import { openTestDb } from "../src/test-db";
1213

1314
const HISTORIAN_SYSTEM_MARKER = "the hippocampus of a long-running coding agent";
1415

@@ -155,9 +156,8 @@ function readMeta<T>(h: PiTestHarness, sessionId: string, columns: string): T |
155156
}
156157

157158
function writeDb(h: PiTestHarness, fn: (db: Database) => void): void {
158-
const db = new Database(h.contextDbPath(), { readwrite: true });
159+
const db = openTestDb(h.contextDbPath(), { readwrite: true });
159160
try {
160-
db.query("PRAGMA busy_timeout = 5000").run();
161161
fn(db);
162162
} finally {
163163
db.close();

packages/e2e-tests/tests/pi-memory-injection.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { resolve as pathResolve } from "node:path";
77
import { computeNormalizedHash } from "../../plugin/src/features/magic-context/memory/normalize-hash";
88
import { resolveProjectIdentity } from "../../plugin/src/features/magic-context/memory/project-identity";
99
import { PiTestHarness } from "../src/pi-harness";
10+
import { openTestDb } from "../src/test-db";
1011

1112
let h: PiTestHarness;
1213

@@ -19,7 +20,7 @@ afterAll(async () => {
1920
});
2021

2122
function seedMemory(content: string): void {
22-
const db = new Database(h.contextDbPath());
23+
const db = openTestDb(h.contextDbPath());
2324
try {
2425
const now = Date.now();
2526
db.prepare(

0 commit comments

Comments
 (0)