import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { resolve } from "node:path"; import { collectPolyglotFiles, measureWorkingSetForRepo, } from "./c14_working_set_walker.ts"; // Hermetic fixture: build a tiny fake repo in a tmpdir, walk it, // assert what comes back. The CLI script's real-world use against // /tmp/dive and /tmp/ripgrep is exercised via the measurement step // in this PR, not via unit tests; this file pins the algorithm. const FIXTURE_ROOT = mkdtempSync(resolve(tmpdir(), "tdd-md-wswalker-")); const writeFile = (relPath: string, lineCount: number): void => { const abs = resolve(FIXTURE_ROOT, relPath); mkdirSync(abs.split("/").slice(0, -1).join("/"), { recursive: true }); const lines = Array.from({ length: lineCount }, (_, i) => `// line ${i}`); writeFileSync(abs, lines.join("\n")); }; beforeAll(() => { // Top-level Go sources (one in-band, one out-of-band, one test file). writeFile("a.go", 100); // in band writeFile("b.go", 600); // out (over) writeFile("c_test.go", 200); // excluded for Go // Nested. writeFile("pkg/inner.go", 60); // in band, inside subdir writeFile("pkg/tiny.go", 10); // out (under) // Rust sources (separate sub-tree). writeFile("rs/src/lib.rs", 120); // in band writeFile("rs/src/big.rs", 700); // out (over) writeFile("rs/src/tests.rs", 75); // included (Rust has no path test rule) // Skip directories that should NOT be walked. writeFile(".git/HEAD.go", 100); // .git is skipped writeFile("target/build.rs", 100); // target/ is skipped writeFile("vendor/pkg.go", 100); // vendor/ is skipped writeFile("node_modules/dep.go", 100); // node_modules/ skipped }); afterAll(() => { rmSync(FIXTURE_ROOT, { recursive: true, force: true }); }); describe("collectPolyglotFiles — Go", () => { test("walks recursively and finds the right .go files", () => { const files = collectPolyglotFiles(FIXTURE_ROOT, "go"); const paths = files.map((f) => f.path); // Excluded: .git/*, target/*, vendor/*, node_modules/*. // Included: a.go, b.go, c_test.go (the helper RETURNS it; the // metric helper drops it during the count — separation of concerns). expect(paths).toContain("a.go"); expect(paths).toContain("b.go"); expect(paths).toContain("c_test.go"); expect(paths).toContain("pkg/inner.go"); expect(paths).toContain("pkg/tiny.go"); expect(paths).not.toContain(".git/HEAD.go"); expect(paths).not.toContain("vendor/pkg.go"); expect(paths).not.toContain("node_modules/dep.go"); }); test("LOC counts match content.split('\\n').length", () => { const files = collectPolyglotFiles(FIXTURE_ROOT, "go"); const a = files.find((f) => f.path === "a.go"); // We wrote 100 lines joined by "\n" → split("\n").length === 100. expect(a?.locCount).toBe(100); }); test("returns files in deterministic sorted order", () => { const a = collectPolyglotFiles(FIXTURE_ROOT, "go").map((f) => f.path); const b = collectPolyglotFiles(FIXTURE_ROOT, "go").map((f) => f.path); expect(a).toEqual(b); const sorted = [...a].sort((x, y) => x.localeCompare(y)); expect(a).toEqual(sorted); }); }); describe("collectPolyglotFiles — Rust", () => { test("finds only .rs files; ignores .go", () => { const files = collectPolyglotFiles(FIXTURE_ROOT, "rust"); const paths = files.map((f) => f.path); expect(paths).toContain("rs/src/lib.rs"); expect(paths).toContain("rs/src/big.rs"); expect(paths).toContain("rs/src/tests.rs"); expect(paths.every((p) => p.endsWith(".rs"))).toBe(true); }); test("target/build.rs is excluded (skipped dir)", () => { const files = collectPolyglotFiles(FIXTURE_ROOT, "rust"); const paths = files.map((f) => f.path); expect(paths).not.toContain("target/build.rs"); }); }); describe("measureWorkingSetForRepo — end-to-end", () => { test("Go fixture: 2 in band (a.go=100, pkg/inner.go=60) of 4 source files (excluding c_test.go) = 0.5", () => { const r = measureWorkingSetForRepo(FIXTURE_ROOT, "go"); expect(r.total).toBe(4); // a, b, pkg/inner, pkg/tiny (c_test excluded) expect(r.included).toBe(2); // a, pkg/inner expect(r.ratio).toBe(0.5); }); test("Rust fixture: 2 in band (lib.rs=120, tests.rs=75) of 3 .rs files = 2/3", () => { const r = measureWorkingSetForRepo(FIXTURE_ROOT, "rust"); expect(r.total).toBe(3); expect(r.included).toBe(2); expect(r.ratio).toBeCloseTo(2 / 3, 6); }); test("echoes the bounds back so callers can audit which numbers produced the ratio", () => { const r = measureWorkingSetForRepo(FIXTURE_ROOT, "go"); expect(r.minLoc).toBe(50); expect(r.maxLoc).toBe(500); }); });