241 lines
7.0 KiB
TypeScript
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([]);
|
|
});
|