rea/tests/evaluation/ciTestSelection.test.ts

241 lines
7.0 KiB
TypeScript

import { expect, it } from "vitest";
import {
compareTestSelection,
planTestSelection,
} from "../../scripts/ci/test-selection.mjs";
const sqliteSource = "src/sqlite/SqliteDatabaseProvider.test.ts";
const sqliteBoundary = "tests/boundary/sqlite/sqliteDatabaseProvider.test.ts";
const adbSource = "src/adb/AdbProvider.test.ts";
const adbBoundary =
"tests/boundary/providers/adb/adbBugreportPublication.test.ts";
const cliBoundary = "tests/boundary/cli/apktoolCli.test.ts";
const mcpBoundary = "tests/boundary/mcp/apktoolMcp.test.ts";
const processBoundary =
"tests/boundary/process/processCaptureLifecycle.test.ts";
const acceptance = "tests/acceptance/tools/localToolContracts.test.ts";
const composition = "tests/composition/directAnalysisCleanup.test.ts";
const evaluation = "tests/evaluation/testDiscovery.test.ts";
const passed = (name: string) => ({ name, durationMs: 10, state: "passed" });
it("unions affected leaf owners, keeps changed tests and retains unowned integration tests", () => {
const plan = planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts", adbBoundary],
"pull_request",
"fast",
);
const inventory = [
sqliteSource,
sqliteBoundary,
adbSource,
adbBoundary,
cliBoundary,
mcpBoundary,
processBoundary,
acceptance,
composition,
evaluation,
];
const report = compareTestSelection(plan, inventory, inventory.map(passed));
expect(plan).toMatchObject({
mode: "candidate",
owners: ["adb", "sqlite"],
});
expect(report.selected).toEqual(
expect.arrayContaining([
sqliteSource,
sqliteBoundary,
adbSource,
adbBoundary,
cliBoundary,
mcpBoundary,
processBoundary,
acceptance,
composition,
evaluation,
]),
);
expect(report.omitted).toEqual([]);
});
it("omits only unrelated owned tests while retaining dynamic boundary suites", () => {
const plan = planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts"],
"pull_request",
"fast",
);
const inventory = [
sqliteSource,
sqliteBoundary,
adbSource,
adbBoundary,
cliBoundary,
mcpBoundary,
processBoundary,
acceptance,
composition,
evaluation,
];
const report = compareTestSelection(plan, inventory, inventory.map(passed));
expect(report.selected).toEqual(
expect.arrayContaining([
sqliteSource,
sqliteBoundary,
cliBoundary,
mcpBoundary,
processBoundary,
acceptance,
composition,
evaluation,
]),
);
expect(report.omitted).toEqual([adbSource, adbBoundary]);
expect(report.complete).toBe(true);
});
it.each([
["unowned shared source", "src/application/Workflow.ts"],
["fixture input", "tests/conformance/c/fixture.c"],
["configuration", "vitest.config.ts"],
["documentation", "README.md"],
["CLI implementation", "src/cli/apktoolCommands.ts"],
["MCP implementation", "src/server/registerSqliteTools.ts"],
["process ownership", "src/process/ProcessOwnership.ts"],
["acceptance test", acceptance],
["composition test", composition],
])("uses full fallback for %s changes", (_description, path) => {
expect(planTestSelection([path], "pull_request", "fast")).toMatchObject({
mode: "full",
owners: [],
changedTests: [],
});
});
it("uses full fallback outside ordinary fast pull requests or without diff paths", () => {
expect(
planTestSelection(["src/sqlite/SqliteDatabaseProvider.ts"], "push", "fast")
.mode,
).toBe("full");
expect(
planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts"],
"pull_request",
"full",
).mode,
).toBe("full");
expect(planTestSelection([], "pull_request", "fast").mode).toBe("full");
});
it("keeps new changed tests and falls back when a changed test was removed", () => {
const addedTest = "src/sqlite/NewDatabaseRule.test.ts";
const candidate = planTestSelection([addedTest], "pull_request", "fast");
expect(candidate.changedTests).toEqual([addedTest]);
const inventory = [addedTest, adbSource];
expect(
compareTestSelection(candidate, inventory, inventory.map(passed)).selected,
).toContain(addedTest);
const removedTest = "src/sqlite/RemovedDatabaseRule.test.ts";
const report = compareTestSelection(
planTestSelection([removedTest], "pull_request", "fast"),
[sqliteSource, adbSource],
[passed(sqliteSource), passed(adbSource)],
);
expect(report.plan.mode).toBe("full");
expect(report.selected).toEqual([adbSource, sqliteSource]);
expect(report.omitted).toEqual([]);
});
it("marks incomplete inventories and nonterminal outcomes as incomplete", () => {
const plan = planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts"],
"pull_request",
"fast",
);
const inventory = [sqliteSource, adbSource];
const completeResults = inventory.map(passed);
expect(
compareTestSelection(plan, inventory, [passed(sqliteSource)]).complete,
).toBe(false);
expect(
compareTestSelection(plan, inventory, [
...completeResults,
passed("tests/evaluation/unexpected.test.ts"),
]),
).toMatchObject({
complete: false,
unexpected: ["tests/evaluation/unexpected.test.ts"],
});
expect(
compareTestSelection(plan, inventory, [
...completeResults,
passed(adbSource),
]),
).toMatchObject({ complete: false, duplicateFiles: 1 });
for (const state of ["pending", "skipped"])
expect(
compareTestSelection(plan, inventory, [
passed(sqliteSource),
{ name: adbSource, durationMs: 0, state },
]),
).toMatchObject({ complete: false, unobserved: [adbSource] });
});
it("reports a failure from an omitted test, including collection or module failures", () => {
const plan = planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts"],
"pull_request",
"fast",
);
const report = compareTestSelection(
plan,
[sqliteSource, adbSource],
[passed(sqliteSource), { name: adbSource, durationMs: 1, state: "failed" }],
);
expect(report.omitted).toContain(adbSource);
expect(report.missedFailures).toEqual([adbSource]);
});
it("rejects plans that name unowned owners or malformed changed tests", () => {
const plan = planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts"],
"pull_request",
"fast",
);
expect(() =>
compareTestSelection(
{ ...plan, owners: ["unknown"] },
[sqliteSource],
[passed(sqliteSource)],
),
).toThrow("Invalid observational test selection plan");
expect(() =>
compareTestSelection(
{ ...plan, changedTests: ["src/sqlite/not-a-test.ts"] },
[sqliteSource],
[passed(sqliteSource)],
),
).toThrow("Invalid observational test selection plan");
});
it("uses full fallback when a changed owner has no executable tests", () => {
const report = compareTestSelection(
planTestSelection(
["src/sqlite/SqliteDatabaseProvider.ts"],
"pull_request",
"fast",
),
[adbSource],
[passed(adbSource)],
);
expect(report.plan.mode).toBe("full");
expect(report.omitted).toEqual([]);
});