openclaw/scripts/lib/list-test-files.mts
Peter Steinberger 31587f12b6
fix(ci): keep large test inventories on Git
The repository's Git file list exceeds Node's default 1 MiB subprocess
buffer. ENOBUFS sent test discovery into its filesystem fallback, which
traversed generated Vitest caches and raced their replacement with ENOENT.
Move the existing 16 MiB inventory limit to the listing owner, share it
with the other selectors, and retain it in the wrapper's source closure.

A real 6,000-entry Git index regression fails before the fix and preserves
the complete tracked inventory afterward without admitting generated files.
It costs 244 ms of test time and 2.51 s wall time with one worker.
Linux proof passed the original owner file 20 times and its complete CI
shard three times, plus 1,604 owner and sibling tests.
2026-09-23 12:25:26 -07:00

53 lines
1.7 KiB
TypeScript

// Lists tracked test files with a filesystem fallback for non-git contexts.
import { spawnSync } from "node:child_process";
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
// Repository inventories exceed Node's 1 MiB subprocess default; keep Git authoritative.
export const GIT_LS_FILES_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
/** List git-tracked test files below a root, falling back to recursive filesystem discovery. */
export function listTrackedTestFiles(rootDir: string, suffix = ".test.ts"): string[] {
const result = spawnSync("git", ["ls-files", "--", rootDir], {
encoding: "utf8",
maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES,
stdio: ["ignore", "pipe", "ignore"],
});
if (result.status === 0) {
return result.stdout
.split("\n")
.map((line) => line.trim().replaceAll("\\", "/"))
.filter((line) => line.endsWith(suffix))
.toSorted((a, b) => a.localeCompare(b));
}
if (!existsSync(rootDir)) {
return [];
}
const files: string[] = [];
const visit = (dir: string): void => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
visit(path);
continue;
}
if (entry.isFile() && entry.name.endsWith(suffix)) {
files.push(path.replaceAll("\\", "/"));
}
}
};
visit(rootDir);
return files.toSorted((a, b) => a.localeCompare(b));
}
export function isStripeEligibleTestFile(
file: string,
unitFastFiles: ReadonlySet<string>,
): boolean {
return (
!unitFastFiles.has(file) && !file.endsWith(".e2e.test.ts") && !file.endsWith(".live.test.ts")
);
}