feat(FN-3366): document CI shard contract in contributing guide
Documentation update to `docs/contributing.md` adding details on the CI shard contract (FN-3366 Step 3). Fusion-Task-Id: FN-3366
This commit is contained in:
@@ -22,7 +22,8 @@ function loadWorkflow(name: string): any {
|
||||
describe("CI workflow (.github/workflows/ci.yml)", () => {
|
||||
let workflow: any;
|
||||
let content: string;
|
||||
let ciSteps: any[];
|
||||
let buildSteps: any[];
|
||||
let testShardJob: any;
|
||||
let contributingContent: string;
|
||||
let readmeContent: string;
|
||||
let cliPackageJsonContent: string;
|
||||
@@ -34,7 +35,8 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
|
||||
const result = loadWorkflow("ci.yml");
|
||||
workflow = result.parsed;
|
||||
content = result.content;
|
||||
ciSteps = workflow.jobs?.ci?.steps ?? [];
|
||||
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");
|
||||
@@ -52,13 +54,8 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
|
||||
);
|
||||
});
|
||||
|
||||
const findStepByRun = (runSnippet: string) => ciSteps.find((step) => typeof step.run === "string" && step.run.includes(runSnippet));
|
||||
|
||||
const findStepByRunExact = (runCommand: string) =>
|
||||
ciSteps.find((step) => typeof step.run === "string" && step.run.trim() === runCommand);
|
||||
|
||||
const findStepIndexByRun = (runSnippet: string) =>
|
||||
ciSteps.findIndex((step) => typeof step.run === "string" && step.run.includes(runSnippet));
|
||||
const findBuildStepByRun = (runSnippet: string) =>
|
||||
buildSteps.find((step) => typeof step.run === "string" && step.run.includes(runSnippet));
|
||||
|
||||
it("is valid YAML", () => {
|
||||
expect(workflow).toBeDefined();
|
||||
@@ -80,26 +77,21 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
|
||||
expect(content).not.toContain("--no-frozen-lockfile");
|
||||
});
|
||||
|
||||
it("uses verify:workspace as the single lint/test/build contract", () => {
|
||||
const verifyStep = findStepByRun("pnpm verify:workspace");
|
||||
expect(verifyStep).toBeDefined();
|
||||
expect(verifyStep.name).toContain("bootstrap contract");
|
||||
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();
|
||||
|
||||
const directLintStep = findStepByRunExact("pnpm lint");
|
||||
const directTestStep = findStepByRunExact("pnpm test");
|
||||
const directBuildStep = findStepByRunExact("pnpm build");
|
||||
expect(directLintStep).toBeUndefined();
|
||||
expect(directTestStep).toBeUndefined();
|
||||
expect(directBuildStep).toBeUndefined();
|
||||
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 workspace verification before slow lane and binary packaging", () => {
|
||||
const verifyIdx = findStepIndexByRun("pnpm verify:workspace");
|
||||
const slowLaneIdx = findStepIndexByRun("pnpm test:slow-cli");
|
||||
const buildExeIdx = findStepIndexByRun("build:exe");
|
||||
expect(verifyIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(slowLaneIdx).toBeGreaterThan(verifyIdx);
|
||||
expect(buildExeIdx).toBeGreaterThan(slowLaneIdx);
|
||||
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", () => {
|
||||
@@ -158,13 +150,11 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
|
||||
describe("PR checks workflow (.github/workflows/pr-checks.yml)", () => {
|
||||
let workflow: any;
|
||||
let content: string;
|
||||
let steps: any[];
|
||||
|
||||
beforeAll(() => {
|
||||
const result = loadWorkflow("pr-checks.yml");
|
||||
workflow = result.parsed;
|
||||
content = result.content;
|
||||
steps = workflow.jobs?.checks?.steps ?? [];
|
||||
});
|
||||
|
||||
it("is valid YAML", () => {
|
||||
@@ -176,10 +166,12 @@ describe("PR checks workflow (.github/workflows/pr-checks.yml)", () => {
|
||||
expect(workflow.on?.pull_request?.branches).toContain("main");
|
||||
});
|
||||
|
||||
it("runs explicit full-suite tests (not changed-only root test)", () => {
|
||||
const testStep = steps.find((step: any) => step.name?.includes("Test"));
|
||||
expect(testStep).toBeDefined();
|
||||
expect(testStep.run).toBe("pnpm test:full");
|
||||
it("uses the same deterministic test sharding command as manual CI", () => {
|
||||
expect(workflow.jobs?.lint).toBeDefined();
|
||||
expect(workflow.jobs?.typecheck).toBeDefined();
|
||||
expect(workflow.jobs?.["test-shards"]).toBeDefined();
|
||||
expect(workflow.jobs?.["test-shards"]?.strategy?.matrix?.shard).toEqual([1, 2, 3]);
|
||||
expect(content).toContain("pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3");
|
||||
expect(content).not.toContain("run: pnpm test\n");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,10 +122,11 @@ describe("Scoped @fusion/* packages publishing config", () => {
|
||||
describe("Workspace bootstrap script contract", () => {
|
||||
const rootPkg = loadRootPackageJson();
|
||||
|
||||
it("makes root test changed-only while keeping explicit full-suite command", () => {
|
||||
it("makes root test changed-only while keeping explicit full-suite and CI-shard commands", () => {
|
||||
expect(rootPkg.scripts?.test).toBe("node scripts/test-changed.mjs");
|
||||
expect(rootPkg.scripts?.["test:full"]).toContain("pnpm -r --workspace-concurrency=2 test");
|
||||
expect(rootPkg.scripts?.["test:full"]).not.toContain("pnpm build");
|
||||
expect(rootPkg.scripts?.["test:ci:shard"]).toBe("node scripts/ci-test-shard.mjs");
|
||||
});
|
||||
|
||||
it("defines verify:workspace in lint -> test:full -> build order", () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
resolveAffectedPackages,
|
||||
shouldForceFullSuite,
|
||||
} from "../../../../scripts/test-changed.mjs";
|
||||
import { parseShardArgs, selectShardPackages } from "../../../../scripts/ci-test-shard.mjs";
|
||||
|
||||
describe("root test command changed-only planning", () => {
|
||||
it("uses changed mode when package-only changes are detected", () => {
|
||||
@@ -55,3 +56,25 @@ describe("root test command changed-only planning", () => {
|
||||
expect(shouldForceFullSuite(["packages/core/src/store.ts"])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CI shard test planner", () => {
|
||||
it("parses valid shard args", () => {
|
||||
expect(parseShardArgs(["--shard", "2", "--total", "3"], {} as NodeJS.ProcessEnv)).toEqual({
|
||||
shard: 2,
|
||||
total: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid shard args", () => {
|
||||
expect(() => parseShardArgs(["--shard", "4", "--total", "3"], {} as NodeJS.ProcessEnv)).toThrow(
|
||||
"Usage: node scripts/ci-test-shard.mjs --shard <1..N> --total <N>",
|
||||
);
|
||||
});
|
||||
|
||||
it("selects deterministic package partitions", () => {
|
||||
const packages = ["a", "b", "c", "d", "e"];
|
||||
expect(selectShardPackages(packages, 1, 3)).toEqual(["a", "d"]);
|
||||
expect(selectShardPackages(packages, 2, 3)).toEqual(["b", "e"]);
|
||||
expect(selectShardPackages(packages, 3, 3)).toEqual(["c"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user