import { execFile } from "node:child_process"; import { access, mkdtemp, readFile, rm, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { afterEach, describe, expect, it } from "vitest"; import { assertV8CoverageCounterSemantics, checkV8CoverageCounterSemantics, } from "../../scripts/lib/v8-coverage-counter-semantics.ts"; const roots: string[] = []; const execFileAsync = promisify(execFile); const zeroCounter = Object.freeze({ total: 0, covered: 0, skipped: 0, pct: 100, }); const zeroMetrics = Object.freeze({ lines: zeroCounter, statements: zeroCounter, functions: zeroCounter, branches: zeroCounter, }); const counterlessPaths = Object.freeze([ "src/import-empty.ts", "src/import-side-effect.ts", "src/import-type-empty.ts", "src/import-value.ts", "src/reexport-named.ts", "src/reexport-star.ts", "src/type-only.ts", ]); function fixtureSummary(root: string): Record { return Object.fromEntries([ [ "total", { lines: { total: 1, covered: 1, skipped: 0, pct: 100 }, statements: { total: 1, covered: 1, skipped: 0, pct: 100 }, functions: zeroCounter, branches: zeroCounter, }, ], [ path.join(root, "src/runtime.ts"), { lines: { total: 1, covered: 1, skipped: 0, pct: 100 }, statements: { total: 1, covered: 1, skipped: 0, pct: 100 }, functions: zeroCounter, branches: zeroCounter, }, ], ...counterlessPaths.map((modulePath) => [path.join(root, modulePath), zeroMetrics]), ]); } async function captureFailure(operation: Promise): Promise { try { await operation; } catch (error) { if (error instanceof Error) return error; throw error; } throw new Error("expected operation to fail"); } afterEach(async () => { await Promise.all( roots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); describe("V8 coverage counter semantics", () => { it("accepts one counter-bearing row and seven exact counterless rows", () => { const root = "/owned-fixture"; expect(assertV8CoverageCounterSemantics(fixtureSummary(root), root)).toEqual({ counterBearingModules: ["src/runtime.ts"], counterlessModules: counterlessPaths, }); }); it("rejects a missing or additional producer row", () => { const root = "/owned-fixture"; const missing = fixtureSummary(root); delete missing[path.join(root, "src/import-empty.ts")]; expect(() => assertV8CoverageCounterSemantics(missing, root)).toThrow( /row set.*missing.*import-empty/u, ); const additional = fixtureSummary(root); additional[path.join(root, "src/unexpected.ts")] = zeroMetrics; expect(() => assertV8CoverageCounterSemantics(additional, root)).toThrow( /row set.*additional.*unexpected/u, ); }); it("rejects counterless nonzero and runtime all-zero drift", () => { const root = "/owned-fixture"; const counterlessDrift = fixtureSummary(root); counterlessDrift[path.join(root, "src/import-value.ts")] = { ...zeroMetrics, lines: { total: 1, covered: 1, skipped: 0, pct: 100 }, }; expect(() => assertV8CoverageCounterSemantics(counterlessDrift, root), ).toThrow(/counterless.*exact all-zero.*import-value/u); const runtimeDrift = fixtureSummary(root); runtimeDrift[path.join(root, "src/runtime.ts")] = zeroMetrics; expect(() => assertV8CoverageCounterSemantics(runtimeDrift, root)).toThrow( /runtime.*counter-bearing/u, ); }); it("rejects producer percentages that do not exactly match their counts", () => { const root = "/owned-fixture"; const totalDrift = fixtureSummary(root); totalDrift["total"] = { ...totalDrift["total"] as Record, lines: { total: 1, covered: 1, skipped: 0, pct: -500 }, }; expect(() => assertV8CoverageCounterSemantics(totalDrift, root)).toThrow( /total\.lines.*invalid coverage counters/u, ); const runtimeDrift = fixtureSummary(root); runtimeDrift[path.join(root, "src/runtime.ts")] = { ...runtimeDrift[path.join(root, "src/runtime.ts")] as Record< string, unknown >, lines: { total: 1, covered: 1, skipped: 0, pct: 99.99 }, }; expect(() => assertV8CoverageCounterSemantics(runtimeDrift, root)).toThrow( /runtime.*lines.*invalid coverage counters/u, ); }); it("bounds child diagnostics and cleans its owned root on child exit", async () => { const ownedRoot = await mkdtemp(path.join(tmpdir(), "v8-counter-exit-")); roots.push(ownedRoot); const childFailure = Object.assign(new Error("child exit 1"), { stdout: "o".repeat(20_000), stderr: "e".repeat(20_000), }); const failure = await captureFailure( checkV8CoverageCounterSemantics({ repositoryRoot: process.cwd(), createOwnedRoot: async () => ownedRoot, runVitest: async () => Promise.reject(childFailure), }), ); expect(failure.message).toMatch(/child Vitest failed/u); expect(failure.message).toMatch(/truncated/u); expect(failure.message.length).toBeLessThan(5_000); await expect(access(ownedRoot)).rejects.toMatchObject({ code: "ENOENT" }); }); it("fails closed on a missing summary and cleans its owned root", async () => { const ownedRoot = await mkdtemp(path.join(tmpdir(), "v8-counter-summary-")); roots.push(ownedRoot); await expect( checkV8CoverageCounterSemantics({ repositoryRoot: process.cwd(), createOwnedRoot: async () => ownedRoot, runVitest: async () => ({ stdout: "", stderr: "" }), }), ).rejects.toThrow(/coverage summary is missing/u); await expect(access(ownedRoot)).rejects.toMatchObject({ code: "ENOENT" }); }); it("keeps the child fixture outside main Vitest discovery", async () => { const outputRoot = await mkdtemp(path.join(tmpdir(), "v8-counter-list-")); roots.push(outputRoot); const outputPath = path.join(outputRoot, "listed.json"); await execFileAsync( process.execPath, [ path.join(process.cwd(), "node_modules/vitest/vitest.mjs"), "list", "tests/fixtures/v8-coverage-counter-semantics", "--config", path.join(process.cwd(), "vitest.config.ts"), "--filesOnly", "--passWithNoTests", "--json", outputPath, "--no-color", ], { cwd: process.cwd(), encoding: "utf8", timeout: 30_000, maxBuffer: 256 * 1024, }, ); expect(JSON.parse(await readFile(outputPath, "utf8"))).toEqual([]); }); });