test(resolution): close graphs and connections, and always remove the temp project (#1786)

On Windows this file leaked 171 temp directories per run and failed 4 tests, both from the same cause: things that hold the SQLite database open outlive the code that removes their directory.

afterEach removed tempDir only in the else branch of \if (cg)\, and cg is describe-scoped and never reset, so from the first test that assigns it every later test kept its directory. destroy() is a deprecated alias for close(): it releases the database but does not remove the project, so removal cannot be its alternative -- uninitialize() is the method that removes. Removal is now unconditional.

That alone turns the silent leak into EPERM, because five DatabaseConnection.open calls were never closed and four nested tests removed their own tempProject while the outer cg still held it open. Each connection is now closed once its rows are materialised, and each nested finally closes the graph before removing its directory.

Measured on Windows 11 with TEMP redirected to a private directory: before, 171 leaked directories and 4 failed / 196 passed; after, 0 leaked directories and 200 passed. Fixes #1777.
This commit is contained in:
Aaron Queen
2026-09-27 06:23:16 -06:00
committed by GitHub
parent 6f4ec88350
commit 5aa7193f7a
+39 -3
View File
@@ -28,12 +28,15 @@ describe('Resolution Module', () => {
});
afterEach(() => {
// Clean up
// destroy() is an alias for close(): it releases the database but leaves
// the project directory on disk, so removing tempDir cannot be the
// alternative to it. Both must run, on every test. maxRetries covers
// Windows releasing the SQLite handles slightly after close() returns.
if (cg) {
cg.destroy();
} else if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true });
cg = undefined as unknown as CodeGraph;
}
fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5 });
});
describe('Name Matcher', () => {
@@ -3947,6 +3950,7 @@ int run() {
and src.kind = 'file'
and src.file_path = 'src/main.cpp'
`).all() as Array<{ dstKind: string; dstPath: string }>;
db.close();
const resolvedToHeader = rows.find(
(r) => r.dstKind === 'file' && r.dstPath === 'include/utils.h'
);
@@ -3957,6 +3961,13 @@ int run() {
);
expect(stdlibFile).toBeUndefined();
} finally {
// The graph opened on tempProject has to be closed here: the outer
// afterEach runs after this finally, so on Windows the still-open
// database makes the removal fail with EPERM.
if (cg) {
cg.close();
cg = undefined as unknown as CodeGraph;
}
fs.rmSync(tempProject, { recursive: true, force: true });
}
});
@@ -3994,6 +4005,7 @@ class Both : public Base<char>, public Plain {}; // templated + plain in one cla
where e.kind = 'extends'`
)
.all() as Array<{ fromName: string; toName: string }>;
db.close();
const has = (from: string, to: string) =>
edges.some((r) => r.fromName === from && r.toName === to);
@@ -4056,11 +4068,19 @@ class Both : public Base<char>, public Plain {}; // templated + plain in one cla
and src.kind = 'file'
and src.file_path = 'src/page.php'
`).all() as Array<{ dstKind: string; dstPath: string }>;
db.close();
const resolved = rows.find(
(r) => r.dstKind === 'file' && r.dstPath === 'src/lib.php'
);
expect(resolved, 'page.php → src/lib.php imports edge missing').toBeDefined();
} finally {
// The graph opened on tempProject has to be closed here: the outer
// afterEach runs after this finally, so on Windows the still-open
// database makes the removal fail with EPERM.
if (cg) {
cg.close();
cg = undefined as unknown as CodeGraph;
}
fs.rmSync(tempProject, { recursive: true, force: true });
}
});
@@ -4090,11 +4110,19 @@ class Both : public Base<char>, public Plain {}; // templated + plain in one cla
and src.kind = 'file'
and src.file_path = 'index.php'
`).all() as Array<{ dstKind: string; dstPath: string }>;
db.close();
expect(
rows.find((r) => r.dstKind === 'file' && r.dstPath === 'inc/db.php'),
'index.php → inc/db.php imports edge missing'
).toBeDefined();
} finally {
// The graph opened on tempProject has to be closed here: the outer
// afterEach runs after this finally, so on Windows the still-open
// database makes the removal fail with EPERM.
if (cg) {
cg.close();
cg = undefined as unknown as CodeGraph;
}
fs.rmSync(tempProject, { recursive: true, force: true });
}
});
@@ -4129,11 +4157,19 @@ class Both : public Base<char>, public Plain {}; // templated + plain in one cla
and src.kind = 'file'
and src.file_path = 'app/page.php'
`).all() as Array<{ dstKind: string; dstPath: string }>;
db.close();
expect(
rows.find((r) => r.dstKind === 'file' && r.dstPath === 'lib/inc/db.php'),
'app/page.php must NOT mis-connect to unrelated lib/inc/db.php'
).toBeUndefined();
} finally {
// The graph opened on tempProject has to be closed here: the outer
// afterEach runs after this finally, so on Windows the still-open
// database makes the removal fail with EPERM.
if (cg) {
cg.close();
cg = undefined as unknown as CodeGraph;
}
fs.rmSync(tempProject, { recursive: true, force: true });
}
});