Merge pull request #1453 from Runfusion/gsxdsm/fast-tests

refactor(ci): thin trusted merge gate with flaky-test deletion ratchet
This commit is contained in:
gsxdsm
2026-06-05 14:52:12 -07:00
committed by GitHub
22 changed files with 1292 additions and 415 deletions

View File

@@ -50,6 +50,7 @@
"build:exe:all": "bun run build.ts --all",
"typecheck": "tsc --noEmit",
"test": "vitest run --silent=passed-only --reporter=dot",
"test:ci-shape": "vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot",
"test:slow-cli": "cross-env FUSION_TEST_SLOW_CLI=1 vitest run src/commands/__tests__/agent-export.test.ts --silent=passed-only --reporter=dot",
"test:extension-integration": "cross-env FUSION_TEST_EXTENSION_INTEGRATION=1 vitest run src/__tests__/extension-integration.test.ts --silent=passed-only --reporter=dot",
"test:build-exe": "cross-env FUSION_TEST_BUILD_EXE=1 vitest run --config vitest.build-exe.config.ts --silent=passed-only --reporter=dot",

View File

@@ -27,12 +27,10 @@ function findCompositeSetupStep(steps: any[]) {
return steps.find((step) => step.uses === "./.github/actions/setup-node-pnpm");
}
describe("CI workflow (.github/workflows/ci.yml)", () => {
describe("Merge gate (.github/workflows/pr-checks.yml)", () => {
let workflow: any;
let content: string;
let compositeAction: any;
let buildSteps: any[];
let testShardJob: any;
let contributingContent: string;
let readmeContent: string;
let cliPackageJsonContent: string;
@@ -41,12 +39,10 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
let buildExeSuiteContent: string;
beforeAll(() => {
const result = loadWorkflow("ci.yml");
const result = loadWorkflow("pr-checks.yml");
workflow = result.parsed;
content = result.content;
compositeAction = loadYamlFile(".github", "actions", "setup-node-pnpm", "action.yml").parsed;
buildSteps = workflow.jobs?.build?.steps ?? [];
testShardJob = workflow.jobs?.["test-shards"];
contributingContent = readFileSync(join(workspaceRoot, "docs", "contributing.md"), "utf-8");
readmeContent = readFileSync(join(workspaceRoot, "README.md"), "utf-8");
cliPackageJsonContent = readFileSync(join(workspaceRoot, "packages", "cli", "package.json"), "utf-8");
@@ -64,134 +60,56 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
);
});
const findBuildStepByRun = (runSnippet: string) =>
buildSteps.find((step) => typeof step.run === "string" && step.run.includes(runSnippet));
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
});
it("uses workflow_dispatch trigger (auto CI disabled)", () => {
expect(workflow.on).toHaveProperty("workflow_dispatch");
it("runs on pull requests targeting main and ONLY there", () => {
expect(workflow.on?.pull_request?.branches).toContain("main");
// Post-merge signal lives in full-suite.yml; the gate workflow must not
// double-run on push (that conflates blocking and non-blocking surfaces).
expect(workflow.on?.push).toBeUndefined();
});
it("does not auto-trigger on push/pull_request", () => {
expect(workflow.on.push).toBeUndefined();
expect(workflow.on.pull_request).toBeUndefined();
it("blocks PRs on exactly lint, typecheck, build, and gate", () => {
expect(Object.keys(workflow.jobs ?? {}).sort()).toEqual(["build", "gate", "lint", "typecheck"]);
});
it("pins dependency bootstrap to frozen lockfile", () => {
const jobs = [workflow.jobs?.lint, workflow.jobs?.["test-shards"], workflow.jobs?.build];
for (const job of jobs) {
expect(findCompositeSetupStep(job?.steps ?? [])).toBeDefined();
it("contains no shard matrix or full-suite invocation (demoted to full-suite.yml)", () => {
expect(workflow.jobs?.["test-shards"]).toBeUndefined();
expect(workflow.jobs?.["test-slow"]).toBeUndefined();
expect(workflow.jobs?.["test-inventory-guard"]).toBeUndefined();
expect(content).not.toContain("test:ci:shard");
expect(content).not.toContain("run: pnpm test\n");
expect(content).not.toContain("pnpm verify:workspace");
});
it("gate job runs boot smoke and the dedicated test:gate command", () => {
const gateSteps = workflow.jobs?.gate?.steps ?? [];
expect(
gateSteps.some(
(step: any) => typeof step.run === "string" && step.run.includes("node scripts/boot-smoke.mjs"),
),
).toBe(true);
// The gate must use the dedicated command — `pnpm test` routes through
// scripts/test-changed.mjs whose selection semantics are for local runs.
expect(
gateSteps.some(
(step: any) => typeof step.run === "string" && step.run.includes("pnpm test:gate"),
),
).toBe(true);
});
it("pins dependency bootstrap to frozen lockfile in every job", () => {
for (const jobName of ["lint", "typecheck", "build", "gate"]) {
expect(findCompositeSetupStep(workflow.jobs?.[jobName]?.steps ?? [])).toBeDefined();
}
expect(content).not.toContain("run: pnpm install\n");
expect(content).not.toContain("--no-frozen-lockfile");
expect(compositeAction.inputs?.["install-args"]?.default).toBe("--frozen-lockfile");
});
it("uses deterministic test sharding and keeps lint/build as explicit jobs", () => {
expect(workflow.jobs?.lint).toBeDefined();
expect(testShardJob).toBeDefined();
expect(workflow.jobs?.build).toBeDefined();
expect(testShardJob.strategy?.matrix?.shard).toEqual([1, 2, 3]);
expect(content).toContain("pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3");
expect(content).not.toContain("pnpm verify:workspace");
});
it("runs build job after lint and sharded tests, then executes slow lane and binary packaging", () => {
expect(workflow.jobs?.build?.needs).toEqual(["lint", "test-shards"]);
expect(findBuildStepByRun("pnpm build")).toBeDefined();
expect(findBuildStepByRun("pnpm test:slow-cli")).toBeDefined();
expect(findBuildStepByRun("build:exe")).toBeDefined();
});
it("keeps contributing docs aligned with verification and slow-lane contracts", () => {
expect(contributingContent).toContain("pnpm test:full` must be runnable in a clean worktree without requiring a prior `pnpm build`.");
expect(contributingContent).toContain("`pnpm verify:workspace` is the canonical pre-merge gate");
expect(contributingContent).toContain("1. `pnpm lint`");
expect(contributingContent).toContain("2. `pnpm test:full`");
expect(contributingContent).toContain("3. `pnpm build`");
expect(contributingContent).toContain("`pnpm test` now uses a changed-only entrypoint");
expect(contributingContent).toContain("pnpm test:slow-cli");
expect(contributingContent).toContain("test:pre-release");
expect(contributingContent).toContain("test:extension-integration");
});
it("keeps docs aligned with default and explicit build commands", () => {
expect(readmeContent).toContain("pnpm build # Build default workspace packages (excludes desktop/mobile)");
expect(readmeContent).toContain("pnpm build:all # Build all packages (including desktop/mobile)");
expect(contributingContent).toContain("pnpm build # default build (excludes desktop/mobile)");
expect(contributingContent).toContain("pnpm build:all # full recursive build including desktop/mobile");
});
it("includes binary build step", () => {
expect(content).toContain("build:exe");
});
it("keeps explicit gating for audited CLI integration suites", () => {
expect(cliPackageJsonContent).toContain('"test:slow-cli"');
expect(cliPackageJsonContent).toContain("FUSION_TEST_SLOW_CLI=1");
expect(cliPackageJsonContent).toContain('"test:extension-integration"');
expect(cliPackageJsonContent).toContain("FUSION_TEST_EXTENSION_INTEGRATION=1");
expect(cliPackageJsonContent).toContain("extension-integration.test.ts");
expect(cliPackageJsonContent).toContain('"test:build-exe"');
expect(cliPackageJsonContent).toContain("FUSION_TEST_BUILD_EXE=1");
expect(extensionSuiteContent).toContain("describe.skipIf(!SHOULD_RUN_EXTENSION_INTEGRATION)");
expect(extensionSuiteContent).toContain("FUSION_TEST_EXTENSION_INTEGRATION");
expect(extensionSuiteContent).toContain("dist/extension.js");
expect(agentExportSuiteContent).toContain("describe.skipIf(!SHOULD_RUN_SLOW_CLI)");
expect(agentExportSuiteContent).toContain("FUSION_TEST_SLOW_CLI");
expect(buildExeSuiteContent).toContain('process.env.FUSION_TEST_BUILD_EXE === "1"');
expect(buildExeSuiteContent).toContain('process.env.FUSION_TEST_BUILD_EXE === "true"');
expect(buildExeSuiteContent).not.toContain("Boolean(process.env.FUSION_TEST_BUILD_EXE)");
});
it("includes Bun setup", () => {
expect(content).toContain("oven-sh/setup-bun");
});
it("verifies binary exists after build", () => {
expect(content).toContain("test -f packages/cli/dist/fn");
});
});
describe("PR checks workflow (.github/workflows/pr-checks.yml)", () => {
let workflow: any;
let content: string;
beforeAll(() => {
const result = loadWorkflow("pr-checks.yml");
workflow = result.parsed;
content = result.content;
});
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
});
it("runs on pull requests targeting main", () => {
expect(workflow.on?.pull_request?.branches).toContain("main");
});
it("uses the same deterministic test sharding command as manual CI", () => {
expect(workflow.jobs?.lint).toBeDefined();
expect(workflow.jobs?.typecheck).toBeDefined();
expect(workflow.jobs?.build).toBeDefined();
expect(workflow.jobs?.["test-shards"]).toBeDefined();
expect(workflow.jobs?.["test-shards"]?.strategy?.matrix?.shard).toEqual([1, 2, 3, 4]);
expect(content).toContain("pnpm test:ci:shard --shard ${{ matrix.shard }} --total 4");
expect(content).not.toContain("run: pnpm test\n");
});
it("keeps lint as install + lint only, without Bun/setup build coupling", () => {
const lintSteps = workflow.jobs?.lint?.steps ?? [];
expect(
@@ -229,7 +147,99 @@ describe("PR checks workflow (.github/workflows/pr-checks.yml)", () => {
).toBe(true);
});
it("does not spend PR action minutes on a pre-test workspace build", () => {
it("keeps contributing docs aligned with the gate contract", () => {
expect(contributingContent).toContain("pnpm test:full` must be runnable in a clean worktree without requiring a prior `pnpm build`.");
expect(contributingContent).toContain("`pnpm test:gate` is the merge gate");
expect(contributingContent).toContain("`pnpm verify:workspace` is the deep opt-in verification (not the merge gate)");
expect(contributingContent).toContain("1. `pnpm lint`");
expect(contributingContent).toContain("2. `pnpm test:full`");
expect(contributingContent).toContain("3. `pnpm build`");
expect(contributingContent).toContain("`pnpm test` now uses a changed-only entrypoint");
expect(contributingContent).toContain("pnpm test:slow-cli");
expect(contributingContent).toContain("test:pre-release");
expect(contributingContent).toContain("test:extension-integration");
});
it("keeps docs aligned with default and explicit build commands", () => {
expect(readmeContent).toContain("pnpm build # Build default workspace packages (excludes desktop/mobile)");
expect(readmeContent).toContain("pnpm build:all # Build all packages (including desktop/mobile)");
expect(contributingContent).toContain("pnpm build # default build (excludes desktop/mobile)");
expect(contributingContent).toContain("pnpm build:all # full recursive build including desktop/mobile");
});
it("keeps explicit gating for audited CLI integration suites", () => {
expect(cliPackageJsonContent).toContain('"test:slow-cli"');
expect(cliPackageJsonContent).toContain("FUSION_TEST_SLOW_CLI=1");
expect(cliPackageJsonContent).toContain('"test:extension-integration"');
expect(cliPackageJsonContent).toContain("FUSION_TEST_EXTENSION_INTEGRATION=1");
expect(cliPackageJsonContent).toContain("extension-integration.test.ts");
expect(cliPackageJsonContent).toContain('"test:build-exe"');
expect(cliPackageJsonContent).toContain("FUSION_TEST_BUILD_EXE=1");
expect(extensionSuiteContent).toContain("describe.skipIf(!SHOULD_RUN_EXTENSION_INTEGRATION)");
expect(extensionSuiteContent).toContain("FUSION_TEST_EXTENSION_INTEGRATION");
expect(extensionSuiteContent).toContain("dist/extension.js");
expect(agentExportSuiteContent).toContain("describe.skipIf(!SHOULD_RUN_SLOW_CLI)");
expect(agentExportSuiteContent).toContain("FUSION_TEST_SLOW_CLI");
expect(buildExeSuiteContent).toContain('process.env.FUSION_TEST_BUILD_EXE === "1"');
expect(buildExeSuiteContent).toContain('process.env.FUSION_TEST_BUILD_EXE === "true"');
expect(buildExeSuiteContent).not.toContain("Boolean(process.env.FUSION_TEST_BUILD_EXE)");
});
it("the deleted manual CI workflow stays deleted", () => {
// ci.yml was the trigger-disabled (FN-1541) 3-shard manual workflow; the
// merge-gate redesign removed it. Reintroducing it would resurrect a
// second, drift-prone definition of the test pipeline.
expect(() => loadWorkflow("ci.yml")).toThrow();
});
});
describe("Full suite workflow (.github/workflows/full-suite.yml)", () => {
let workflow: any;
let content: string;
beforeAll(() => {
const result = loadWorkflow("full-suite.yml");
workflow = result.parsed;
content = result.content;
});
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
});
it("runs ONLY on push to main — never as a PR gate", () => {
expect(workflow.on?.push?.branches).toEqual(["main"]);
expect(workflow.on?.pull_request).toBeUndefined();
});
it("carries the demoted tier: 4-way shards, engine slow, inventory guard", () => {
expect(workflow.jobs?.["test-shards"]?.strategy?.matrix?.shard).toEqual([1, 2, 3, 4]);
expect(content).toContain("pnpm test:ci:shard --shard ${{ matrix.shard }} --total 4");
expect(workflow.jobs?.["test-slow"]).toBeDefined();
expect(workflow.jobs?.["test-inventory-guard"]).toBeDefined();
});
it("keeps full clones where real-git tests need history", () => {
const shardSteps = workflow.jobs?.["test-shards"]?.steps ?? [];
const slowSteps = workflow.jobs?.["test-slow"]?.steps ?? [];
for (const steps of [shardSteps, slowSteps]) {
expect(
steps.some((step: any) => step.uses?.includes("actions/checkout") && step.with?.["fetch-depth"] === 0),
).toBe(true);
}
});
it("still uploads per-shard timing artifacts for snapshot refresh", () => {
expect(content).toContain("test-timings-shard-${{ matrix.shard }}");
});
it("does not spend action minutes on a pre-test workspace build", () => {
const testSteps = workflow.jobs?.["test-shards"]?.steps ?? [];
expect(
testSteps.some(

View File

@@ -299,10 +299,16 @@ describe("Workspace bootstrap script contract", () => {
});
describe("Workflow YAML validity", () => {
it("ci.yml is valid YAML", () => {
const parsed = loadWorkflowYaml("ci.yml");
it("pr-checks.yml is valid YAML", () => {
const parsed = loadWorkflowYaml("pr-checks.yml");
expect(parsed).toBeDefined();
expect(parsed.name).toBe("CI");
expect(parsed.name).toBe("PR Checks");
});
it("full-suite.yml is valid YAML", () => {
const parsed = loadWorkflowYaml("full-suite.yml");
expect(parsed).toBeDefined();
expect(parsed.name).toBe("Full Suite (non-blocking)");
});
it("version.yml is valid YAML", () => {

View File

@@ -3,7 +3,7 @@ import {
decideExecutionPlan,
normalizeForwardedArgs,
resolveAffectedPackages,
shouldForceFullSuite,
isSharedInfraChange,
} from "../../../../scripts/test-changed.mjs";
import { computeSplitPlan, parseShardArgs, planShardAssignments, selectShardPackages } from "../../../../scripts/ci-test-shard.mjs";
@@ -24,7 +24,7 @@ describe("root test command changed-only planning", () => {
expect(plan).toEqual({ mode: "changed", packages: ["@fusion/core", "@fusion/engine"] });
});
it("falls back to full suite when shared test infra changes", () => {
it("routes to gate mode when shared test infra changes (no implicit full suite)", () => {
const plan = decideExecutionPlan({
forceFullSuite: false,
comparisonBase: "abc123",
@@ -32,10 +32,10 @@ describe("root test command changed-only planning", () => {
packageNameByDir: new Map([["packages/core", "@fusion/core"]]),
});
expect(plan).toEqual({ mode: "full", reason: "shared-infra-changed" });
expect(plan).toEqual({ mode: "gate", reason: "shared-infra-changed" });
});
it("falls back to full suite when comparison base cannot be resolved", () => {
it("routes to gate mode when comparison base cannot be resolved", () => {
const plan = decideExecutionPlan({
forceFullSuite: false,
comparisonBase: null,
@@ -43,18 +43,18 @@ describe("root test command changed-only planning", () => {
packageNameByDir: new Map(),
});
expect(plan).toEqual({ mode: "full", reason: "missing-comparison-base" });
expect(plan).toEqual({ mode: "gate", reason: "missing-comparison-base" });
});
it("treats unknown package directories as full-suite fallback", () => {
it("treats unknown package directories as gate-mode fallback (resolver returns null)", () => {
const resolved = resolveAffectedPackages(["packages/unknown/src/index.ts"], new Map());
expect(resolved).toBeNull();
});
it("marks root workflow/config changes as full-suite triggers", () => {
expect(shouldForceFullSuite([".github/workflows/ci.yml"])).toBe(true);
expect(shouldForceFullSuite(["package.json"])).toBe(true);
expect(shouldForceFullSuite(["packages/core/src/store.ts"])).toBe(false);
it("marks root workflow/config changes as shared-infra (gate-mode) triggers", () => {
expect(isSharedInfraChange([".github/workflows/pr-checks.yml"])).toBe(true);
expect(isSharedInfraChange(["package.json"])).toBe(true);
expect(isSharedInfraChange(["packages/core/src/store.ts"])).toBe(false);
});
it("strips forwarded silent flags so package vitest scripts do not receive duplicates", () => {