fix: reject empty removal fixture scans
This commit is contained in:
@@ -78,6 +78,9 @@ export async function runtimeImportGraph(
|
|||||||
const files = (await filesBelow(root))
|
const files = (await filesBelow(root))
|
||||||
.filter((file) => /\.(?:[cm]?ts|tsx)$/u.test(file))
|
.filter((file) => /\.(?:[cm]?ts|tsx)$/u.test(file))
|
||||||
.map((file) => path.resolve(file));
|
.map((file) => path.resolve(file));
|
||||||
|
if (files.length === 0) {
|
||||||
|
throw new Error("removal fixture scanned module universe is empty");
|
||||||
|
}
|
||||||
const sourceSet = new Set(files);
|
const sourceSet = new Set(files);
|
||||||
const runtimeRoots = runtimeSourceRoots.map((entry) => path.resolve(root, entry));
|
const runtimeRoots = runtimeSourceRoots.map((entry) => path.resolve(root, entry));
|
||||||
const imports = new Map<string, readonly string[]>();
|
const imports = new Map<string, readonly string[]>();
|
||||||
@@ -124,6 +127,9 @@ export async function removeRuntimeDependentTests(
|
|||||||
runtimeSourceRoots: readonly string[],
|
runtimeSourceRoots: readonly string[],
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const graph = await runtimeImportGraph(root, runtimeSourceRoots);
|
const graph = await runtimeImportGraph(root, runtimeSourceRoots);
|
||||||
|
if (graph.dependentTests.length === 0) {
|
||||||
|
throw new Error("removal fixture: no runtime-dependent tests discovered");
|
||||||
|
}
|
||||||
await Promise.all(graph.dependentTests.map((file) => rm(file, { force: true })));
|
await Promise.all(graph.dependentTests.map((file) => rm(file, { force: true })));
|
||||||
return graph.dependentTests.length;
|
return graph.dependentTests.length;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,26 @@ describe("CI gate contract", () => {
|
|||||||
await writeFile(path.join(root, "package.json"), await readFile("package.json"));
|
await writeFile(path.join(root, "package.json"), await readFile("package.json"));
|
||||||
|
|
||||||
await expect(loadCiGateContract(root)).rejects.toThrow(
|
await expect(loadCiGateContract(root)).rejects.toThrow(
|
||||||
/canonical gate semantic shape|lacks a bound producer command/i,
|
/lacks a bound producer command/i,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects canonical gate name drift through the semantic-shape digest", async () => {
|
||||||
|
const root = await mkdtemp(path.join(tmpdir(), "ci-contract-gate-name-shape-"));
|
||||||
|
temporaryRoots.push(root);
|
||||||
|
await mkdir(path.join(root, "config/ci"), { recursive: true });
|
||||||
|
const contract = JSON.parse(
|
||||||
|
JSON.stringify(await loadCiGateContract(process.cwd())),
|
||||||
|
) as Record<string, any>;
|
||||||
|
const gate = contract.gates.find(
|
||||||
|
(candidate: Record<string, any>) => candidate.id === "FE-GATE-013",
|
||||||
|
);
|
||||||
|
gate.name = `${gate.name}-renamed`;
|
||||||
|
await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`);
|
||||||
|
await writeFile(path.join(root, "package.json"), await readFile("package.json"));
|
||||||
|
|
||||||
|
await expect(loadCiGateContract(root)).rejects.toThrow(
|
||||||
|
/canonical gate semantic shape/i,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { afterEach, expect, it } from "vitest";
|
|||||||
|
|
||||||
import { loadCiGateContract } from "../../scripts/contracts/ci-gates.ts";
|
import { loadCiGateContract } from "../../scripts/contracts/ci-gates.ts";
|
||||||
import {
|
import {
|
||||||
|
assertNoRuntimeImports,
|
||||||
pruneRemovalFixtureCiContract,
|
pruneRemovalFixtureCiContract,
|
||||||
|
removeRuntimeDependentTests,
|
||||||
|
runtimeImportGraph,
|
||||||
} from "../../scripts/lib/removal-fixture.ts";
|
} from "../../scripts/lib/removal-fixture.ts";
|
||||||
|
|
||||||
const temporaryRoots: string[] = [];
|
const temporaryRoots: string[] = [];
|
||||||
@@ -17,6 +20,39 @@ afterEach(async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects an empty scanned module universe", async () => {
|
||||||
|
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-empty-modules-"));
|
||||||
|
temporaryRoots.push(root);
|
||||||
|
|
||||||
|
await expect(runtimeImportGraph(root, ["src/runtime"])).rejects.toThrow(
|
||||||
|
/scanned module universe is empty/i,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects a runtime removal scan that discovers no dependent tests", async () => {
|
||||||
|
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-zero-dependent-tests-"));
|
||||||
|
temporaryRoots.push(root);
|
||||||
|
await mkdir(path.join(root, "src/runtime"), { recursive: true });
|
||||||
|
await mkdir(path.join(root, "tests"), { recursive: true });
|
||||||
|
await writeFile(path.join(root, "src/runtime/index.ts"), "export const runtime = true;\n");
|
||||||
|
await writeFile(path.join(root, "tests/unrelated.test.ts"), "export const unrelated = true;\n");
|
||||||
|
|
||||||
|
await expect(removeRuntimeDependentTests(root, ["src/runtime"])).rejects.toThrow(
|
||||||
|
/no runtime-dependent tests discovered/i,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows zero runtime imports after the runtime has been removed", async () => {
|
||||||
|
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-post-removal-"));
|
||||||
|
temporaryRoots.push(root);
|
||||||
|
await mkdir(path.join(root, "tests"), { recursive: true });
|
||||||
|
await writeFile(path.join(root, "tests/unrelated.test.ts"), "export const unrelated = true;\n");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
assertNoRuntimeImports(root, ["src/runtime"], "fixture runtime"),
|
||||||
|
).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("prunes removed command and evidence references from a reduced CI contract", async () => {
|
it("prunes removed command and evidence references from a reduced CI contract", async () => {
|
||||||
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-contract-"));
|
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-contract-"));
|
||||||
temporaryRoots.push(root);
|
temporaryRoots.push(root);
|
||||||
|
|||||||
Reference in New Issue
Block a user