From 0dad584784016e122fc815739b70e2d6c9d41d93 Mon Sep 17 00:00:00 2001 From: Johnson K C Date: Mon, 15 Jun 2026 00:58:35 -0400 Subject: [PATCH] test(resolution): close SQLite handles before rmSync so Windows teardown doesn't EPERM The C/C++ include and PHP require_once end-to-end tests open a second read-only DatabaseConnection to inspect edges directly, but never close it. They also remove their per-test tempProject in a `finally` that runs before afterEach's cg.destroy(), so the CodeGraph instance's own handle is still open at removal time too. On POSIX an open file can be unlinked, so cleanup silently succeeds. On Windows an open SQLite handle locks codegraph.db, and fs.rmSync(tempProject) throws EPERM, failing all four tests in their finally block (the assertions themselves pass). Close the local reader and the CodeGraph instance before rmSync. DatabaseConnection.close() is idempotent, so afterEach's later cg.destroy() remains safe. Verified on Windows 11 (wasm SQLite backend): the four resolution.test.ts tests go from failing with EPERM to passing (80 -> 84 passed, 4 -> 0 failed), with no other test affected. --- __tests__/resolution.test.ts | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/__tests__/resolution.test.ts b/__tests__/resolution.test.ts index caf41c7df..ea7c190ca 100644 --- a/__tests__/resolution.test.ts +++ b/__tests__/resolution.test.ts @@ -1964,6 +1964,7 @@ func main() { // feature can't silently regress to a no-op in the indexing flow. it('connects #include to the real header file via include-dir scan (end-to-end)', async () => { const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-e2e-')); + let db: DatabaseConnection | undefined; try { fs.mkdirSync(path.join(tempProject, 'include'), { recursive: true }); fs.mkdirSync(path.join(tempProject, 'src'), { recursive: true }); @@ -1986,7 +1987,7 @@ func main() { // The `#include "utils.h"` edge should target the real // `include/utils.h` file node — not a floating `import` node // living inside main.cpp. - const db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); + db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); const rows = db.getDb().prepare(` select dst.kind as dstKind, dst.file_path as dstPath from edges e @@ -2006,6 +2007,12 @@ func main() { ); expect(stdlibFile).toBeUndefined(); } finally { + // Close every SQLite handle on tempProject before removing it — the + // local reader AND the CodeGraph instance (afterEach's cg.destroy() + // runs too late, after this rmSync). On Windows an open handle locks + // the file and rmSync throws EPERM; POSIX silently unlinks it. + db?.close(); + cg?.close(); fs.rmSync(tempProject, { recursive: true, force: true }); } }); @@ -2034,6 +2041,7 @@ func main() { it('resolves require_once to a file→file imports edge (#660)', async () => { const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-php-e2e-')); + let db: DatabaseConnection | undefined; try { fs.mkdirSync(path.join(tempProject, 'src'), { recursive: true }); fs.writeFileSync( @@ -2050,7 +2058,7 @@ func main() { // reporter's repro: page.php's `require_once("lib.php")` must resolve // to the real src/lib.php file node — a file→file `imports` edge, so // callers(lib.php) now includes page.php. - const db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); + db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); const rows = db.getDb().prepare(` select dst.kind as dstKind, dst.file_path as dstPath from edges e @@ -2065,12 +2073,17 @@ func main() { ); expect(resolved, 'page.php → src/lib.php imports edge missing').toBeDefined(); } finally { + // Close the local reader and the CodeGraph instance before rmSync so + // Windows doesn't EPERM on a still-open SQLite handle (see above). + db?.close(); + cg?.close(); fs.rmSync(tempProject, { recursive: true, force: true }); } }); it('resolves a subdirectory include path to the correct file (#660)', async () => { const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-php-subdir-')); + let db: DatabaseConnection | undefined; try { fs.mkdirSync(path.join(tempProject, 'inc'), { recursive: true }); fs.writeFileSync( @@ -2084,7 +2097,7 @@ func main() { cg = await CodeGraph.init(tempProject, { index: true }); - const db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); + db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); const rows = db.getDb().prepare(` select dst.kind as dstKind, dst.file_path as dstPath from edges e @@ -2099,12 +2112,17 @@ func main() { 'index.php → inc/db.php imports edge missing' ).toBeDefined(); } finally { + // Close the local reader and the CodeGraph instance before rmSync so + // Windows doesn't EPERM on a still-open SQLite handle (see above). + db?.close(); + cg?.close(); fs.rmSync(tempProject, { recursive: true, force: true }); } }); it('does not mis-connect an unresolvable include to a same-named file elsewhere (#660)', async () => { const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-php-misresolve-')); + let db: DatabaseConnection | undefined; try { // app/page.php's `require "inc/db.php"` resolves relative to app/, where // inc/db.php does NOT exist. A same-named lib/inc/db.php exists elsewhere @@ -2123,7 +2141,7 @@ func main() { cg = await CodeGraph.init(tempProject, { index: true }); - const db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); + db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); const rows = db.getDb().prepare(` select dst.kind as dstKind, dst.file_path as dstPath from edges e @@ -2138,6 +2156,10 @@ func main() { 'app/page.php must NOT mis-connect to unrelated lib/inc/db.php' ).toBeUndefined(); } finally { + // Close the local reader and the CodeGraph instance before rmSync so + // Windows doesn't EPERM on a still-open SQLite handle (see above). + db?.close(); + cg?.close(); fs.rmSync(tempProject, { recursive: true, force: true }); } });