Skip to content

Commit 5fc447e

Browse files
ci: add per-file handle-leak diagnostic to inprocess Node legs
Runs the inprocess transport legs with --no-file-parallelism plus a setup file that logs active-resource deltas per test file, so cumulative native handle growth can be attributed to the leaking file. Diagnostic only; gated by VITEST_LEAK_BY_FILE and scoped to the inprocess matrix legs.
1 parent 84fa68c commit 5fc447e

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

.github/workflows/nodejs-sdk-tests.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,14 @@ jobs:
8080
if: matrix.transport == 'inprocess'
8181
run: |
8282
echo "COPILOT_SDK_DEFAULT_CONNECTION=inprocess" >> "$GITHUB_ENV"
83+
echo "VITEST_LEAK_BY_FILE=1" >> "$GITHUB_ENV"
8384
8485
- name: Run Node.js SDK tests
8586
env:
8687
COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }}
87-
run: npm test
88+
run: |
89+
if [ "${{ matrix.transport }}" = "inprocess" ]; then
90+
npm test -- --no-file-parallelism --reporter=default --reporter=hanging-process
91+
else
92+
npm test
93+
fi
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { afterAll, beforeAll } from "vitest";
2+
3+
// Diagnostic (opt-in via VITEST_LEAK_BY_FILE=1): with --no-file-parallelism all
4+
// files share one worker, so logging the active-resource counts before/after each
5+
// file attributes cumulative native-handle growth to the file that caused it.
6+
7+
function countByType(): Record<string, number> {
8+
const info = (process as unknown as { getActiveResourcesInfo?: () => string[] }).getActiveResourcesInfo?.() ?? [];
9+
const counts: Record<string, number> = {};
10+
for (const t of info) counts[t] = (counts[t] ?? 0) + 1;
11+
return counts;
12+
}
13+
14+
function total(counts: Record<string, number>): number {
15+
return Object.values(counts).reduce((a, b) => a + b, 0);
16+
}
17+
18+
const testFile = (() => {
19+
try {
20+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
21+
const fp = (globalThis as any).__vitest_worker__?.filepath as string | undefined;
22+
return fp ? fp.replace(/\\/g, "/").split("/").slice(-2).join("/") : "<unknown>";
23+
} catch {
24+
return "<unknown>";
25+
}
26+
})();
27+
28+
let before: Record<string, number> = {};
29+
30+
beforeAll(() => {
31+
before = countByType();
32+
});
33+
34+
afterAll(() => {
35+
const after = countByType();
36+
const delta = total(after) - total(before);
37+
const typeDeltas: string[] = [];
38+
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
39+
for (const k of keys) {
40+
const d = (after[k] ?? 0) - (before[k] ?? 0);
41+
if (d !== 0) typeDeltas.push(`${k}:${d > 0 ? "+" : ""}${d}`);
42+
}
43+
// eslint-disable-next-line no-console
44+
console.log(
45+
`[LEAKDIAG] ${testFile} deltaTotal=${delta} nowTotal=${total(after)} ${typeDeltas.join(" ")}`
46+
);
47+
});

nodejs/vitest.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export default defineConfig({
99
teardownTimeout: 10000,
1010
isolate: true, // Run each test file in isolation
1111
pool: "forks", // Use process forking for better isolation
12+
setupFiles:
13+
process.env.VITEST_LEAK_BY_FILE === "1" ? ["./test/diagnostics/leakByFile.ts"] : [],
1214
// Exclude our ad-hoc test files that aren't vitest-based
1315
exclude: [
1416
"**/node_modules/**",

0 commit comments

Comments
 (0)