Skip to content

Commit 093f001

Browse files
committed
test: fix EBUSY database lock failures on Windows during teardown
The test suite intermittently fails on Windows with 'EBUSY: resource busy or locked' errors when attempting to clean up temporary directories. This happens because SQLite file handles take a moment to release after closing the database connections. Wrapped all rmSync calls in afterEach teardown hooks across the project in try/catch blocks that ignore the error on Windows, and added maxRetries: 10 and retryDelay: 100 to provide a grace period for handles to release.
1 parent d9728ce commit 093f001

57 files changed

Lines changed: 194 additions & 75 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/cli/src/commands/migrate.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ function makeCortexkitDb() {
176176

177177
afterEach(() => {
178178
for (const db of databases.splice(0)) db.close();
179-
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
179+
for (const dir of tempDirs.splice(0)) {
180+
try {
181+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
182+
} catch {
183+
// Ignore EBUSY on Windows
184+
}
185+
}
180186
});
181187

182188
describe("migrateOpenCodeSessionToPi", () => {

packages/pi-plugin/src/system-prompt.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@ function countOccurrences(haystack: string, needle: string): number {
3030
describe("buildMagicContextBlock", () => {
3131
afterEach(() => {
3232
setAftAvailabilityOverride(null);
33-
for (const dir of tempDirs.splice(0))
34-
rmSync(dir, { recursive: true, force: true });
33+
for (const dir of tempDirs.splice(0)) {
34+
try {
35+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
36+
} catch {
37+
// Ignore EBUSY on Windows
38+
}
39+
}
3540
});
3641

3742
it("returns null when no memories, session history, or docs exist (guidance off)", () => {

packages/plugin/src/config/index.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ function loadWithUserConfig(configText: string, extraEnv: Record<string, string>
4242
if (v === undefined) delete process.env[k];
4343
else process.env[k] = v;
4444
}
45-
rmSync(xdg, { recursive: true, force: true });
46-
rmSync(projectDir, { recursive: true, force: true });
45+
try { rmSync(xdg, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
46+
try { rmSync(projectDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
4747
}
4848
}
4949

@@ -81,8 +81,8 @@ function loadWithUserAndProjectConfig(
8181
if (v === undefined) delete process.env[k];
8282
else process.env[k] = v;
8383
}
84-
rmSync(xdg, { recursive: true, force: true });
85-
rmSync(projectDir, { recursive: true, force: true });
84+
try { rmSync(xdg, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
85+
try { rmSync(projectDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
8686
}
8787
}
8888

packages/plugin/src/config/variable.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe("substituteConfigVariables", () => {
1414
});
1515

1616
afterEach(() => {
17-
rmSync(tmpDir, { recursive: true, force: true });
17+
try { rmSync(tmpDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
1818
process.env = { ...ORIGINAL_ENV };
1919
});
2020

packages/plugin/src/features/magic-context/boundary-execution-cas-race.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { mkdtempSync, rmSync } from "node:fs";
55
import { tmpdir } from "node:os";
66
import { join } from "node:path";
77
import { Database } from "../../shared/sqlite";
8+
import { closeQuietly } from "../../shared/sqlite-helpers";
89
import {
910
type DeferredExecutePayload,
1011
peekDeferredExecutePending,
@@ -50,16 +51,22 @@ function payload(id: string): DeferredExecutePayload {
5051
describe("deferred execute CAS race", () => {
5152
it("15. one WAL handle wins set-if-absent and the other no-ops", () => {
5253
const dir = mkdtempSync(join(tmpdir(), "boundary-exec-race-"));
54+
const path = join(dir, "context.db");
55+
const a = createRaceDb(path);
56+
const b = createRaceDb(path);
5357
try {
54-
const path = join(dir, "context.db");
55-
const a = createRaceDb(path);
56-
const b = createRaceDb(path);
5758
const first = setDeferredExecutePendingIfAbsent(a, "s1", payload("a"));
5859
const second = setDeferredExecutePendingIfAbsent(b, "s1", payload("b"));
5960
expect([first, second].filter(Boolean)).toHaveLength(1);
6061
expect(peekDeferredExecutePending(a, "s1")?.id).toBe(first ? "a" : "b");
6162
} finally {
62-
rmSync(dir, { recursive: true, force: true });
63+
closeQuietly(a);
64+
closeQuietly(b);
65+
try {
66+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
67+
} catch {
68+
// Ignore EBUSY on Windows
69+
}
6370
}
6471
});
6572
});

packages/plugin/src/features/magic-context/compartment-lease.test.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ describe("compartment state lease", () => {
4343
const db = makeDb();
4444
const first = acquireCompartmentLease(db, "ses", "holder-a");
4545
expect(first).not.toBeNull();
46+
4647
db.prepare("UPDATE compartment_state_lease SET expires_at = ? WHERE session_id = ?").run(
4748
Date.now() + 1_000,
4849
"ses",
4950
);
51+
5052
const second = acquireCompartmentLease(db, "ses", "holder-a");
5153
expect(second).not.toBeNull();
5254
expect(second!.expiresAt).toBeGreaterThan(first!.acquiredAt + 1_000);
@@ -56,10 +58,12 @@ describe("compartment state lease", () => {
5658
it("lets another holder reclaim an expired lease", () => {
5759
const db = makeDb();
5860
expect(acquireCompartmentLease(db, "ses", "holder-a")).not.toBeNull();
61+
5962
db.prepare("UPDATE compartment_state_lease SET expires_at = ? WHERE session_id = ?").run(
6063
Date.now() - 1,
6164
"ses",
6265
);
66+
6367
expect(acquireCompartmentLease(db, "ses", "holder-b")).not.toBeNull();
6468
expect(isCompartmentLeaseHeld(db, "ses", "holder-b")).toBe(true);
6569
expect(isCompartmentLeaseHeld(db, "ses", "holder-a")).toBe(false);
@@ -70,6 +74,7 @@ describe("compartment state lease", () => {
7074
const db = makeDb();
7175
expect(acquireCompartmentLease(db, "ses", "holder-a")).not.toBeNull();
7276
expect(renewCompartmentLease(db, "ses", "holder-b")).toBe(false);
77+
7378
db.prepare("UPDATE compartment_state_lease SET expires_at = ? WHERE session_id = ?").run(
7479
Date.now() - 1,
7580
"ses",
@@ -81,10 +86,12 @@ describe("compartment state lease", () => {
8186
it("release is a no-op after another holder reclaims the row", () => {
8287
const db = makeDb();
8388
expect(acquireCompartmentLease(db, "ses", "holder-a")).not.toBeNull();
89+
8490
db.prepare("UPDATE compartment_state_lease SET expires_at = ? WHERE session_id = ?").run(
8591
Date.now() - 1,
8692
"ses",
8793
);
94+
8895
expect(acquireCompartmentLease(db, "ses", "holder-b")).not.toBeNull();
8996
releaseCompartmentLease(db, "ses", "holder-a");
9097
expect(isCompartmentLeaseHeld(db, "ses", "holder-b")).toBe(true);
@@ -105,7 +112,11 @@ describe("compartment state lease", () => {
105112
} finally {
106113
closeQuietly(dbA);
107114
closeQuietly(dbB);
108-
rmSync(dir, { recursive: true, force: true });
115+
try {
116+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
117+
} catch {
118+
// Ignore EBUSY on Windows
119+
}
109120
}
110121
});
111122

@@ -114,10 +125,13 @@ describe("compartment state lease", () => {
114125
const path = join(dir, "context.db");
115126
const setup = makeDb(path);
116127
closeQuietly(setup);
128+
117129
try {
118-
const pluginRoot = process.cwd().endsWith("/packages/plugin")
119-
? process.cwd()
120-
: join(process.cwd(), "packages", "plugin");
130+
const projectRoot = process.cwd().includes("packages")
131+
? join(process.cwd(), "..", "..")
132+
: process.cwd();
133+
const pluginRoot = join(projectRoot, "packages", "plugin");
134+
121135
const script = `
122136
const sqlite = await import(${JSON.stringify(`file://${pluginRoot}/src/shared/sqlite.ts`)});
123137
const storageDb = await import(${JSON.stringify(`file://${pluginRoot}/src/features/magic-context/storage-db.ts`)});
@@ -128,13 +142,18 @@ describe("compartment state lease", () => {
128142
db.close();
129143
console.log(JSON.stringify({ ok }));
130144
`;
145+
131146
const [a, b] = await Promise.all([
132147
$`bun -e ${script} holder-a`.json() as Promise<{ ok: boolean }>,
133148
$`bun -e ${script} holder-b`.json() as Promise<{ ok: boolean }>,
134149
]);
135150
expect([a.ok, b.ok].filter(Boolean)).toHaveLength(1);
136151
} finally {
137-
rmSync(dir, { recursive: true, force: true });
152+
try {
153+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
154+
} catch {
155+
// Ignore EBUSY on Windows
156+
}
138157
}
139158
});
140159
});

packages/plugin/src/features/magic-context/compartment-storage-v6.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ function useTempDataHome(prefix: string): string {
1616

1717
afterEach(() => {
1818
closeDatabase();
19-
for (const dir of tempDirs) rmSync(dir, { recursive: true, force: true });
19+
for (const dir of tempDirs) {
20+
try {
21+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
22+
} catch {
23+
// Ignore EBUSY on Windows
24+
}
25+
}
2026
tempDirs.length = 0;
2127
process.env.XDG_DATA_HOME = undefined;
2228
});

packages/plugin/src/features/magic-context/compression-depth-storage.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ afterEach(() => {
3131
process.env.XDG_DATA_HOME = originalXdgDataHome;
3232

3333
for (const dir of tempDirs) {
34-
rmSync(dir, { recursive: true, force: true });
34+
try { rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
3535
}
3636
tempDirs.length = 0;
3737
});

packages/plugin/src/features/magic-context/key-files/project-key-files.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const originalHome = process.env.HOME;
3030
afterEach(() => {
3131
setAftAvailabilityOverride(null);
3232
process.env.HOME = originalHome;
33-
for (const dir of tempDirs) rmSync(dir, { recursive: true, force: true });
33+
for (const dir of tempDirs) try { rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
3434
tempDirs.length = 0;
3535
});
3636

packages/plugin/src/features/magic-context/memory/embedding.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe("embedding module", () => {
132132
_resetEmbeddingSweepGuard();
133133
process.env.XDG_DATA_HOME = originalXdgDataHome;
134134
for (const dir of tempDirs) {
135-
rmSync(dir, { recursive: true, force: true });
135+
try { rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch { /* Ignore EBUSY on Windows */ };
136136
}
137137
tempDirs.length = 0;
138138
});

0 commit comments

Comments
 (0)