ci: split remaining slow CI lanes

This commit is contained in:
Peter Steinberger
2026-04-20 17:26:42 +01:00
parent b3963e847e
commit b225d31179
6 changed files with 856 additions and 109 deletions

View File

@@ -0,0 +1,62 @@
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { createChannelContractTestShards } from "../../scripts/lib/channel-contract-test-plan.mjs";
function listContractTests(rootDir = "src/channels/plugins/contracts"): string[] {
if (!existsSync(rootDir)) {
return [];
}
return readdirSync(rootDir, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".test.ts"))
.map((entry) => join(rootDir, entry.name).replaceAll("\\", "/"))
.toSorted((a, b) => a.localeCompare(b));
}
describe("scripts/lib/channel-contract-test-plan.mjs", () => {
it("splits channel contracts into focused shards", () => {
expect(
createChannelContractTestShards().map((shard) => ({
checkName: shard.checkName,
runtime: shard.runtime,
task: shard.task,
})),
).toEqual([
{
checkName: "checks-fast-contracts-channels-registry-a",
runtime: "node",
task: "contracts-channels",
},
{
checkName: "checks-fast-contracts-channels-registry-b",
runtime: "node",
task: "contracts-channels",
},
{
checkName: "checks-fast-contracts-channels-core-a",
runtime: "node",
task: "contracts-channels",
},
{
checkName: "checks-fast-contracts-channels-core-b",
runtime: "node",
task: "contracts-channels",
},
{
checkName: "checks-fast-contracts-channels-extensions",
runtime: "node",
task: "contracts-channels",
},
]);
});
it("covers every channel contract test exactly once", () => {
const actual = createChannelContractTestShards()
.flatMap((shard) => shard.includePatterns)
.toSorted((a, b) => a.localeCompare(b));
expect(actual).toEqual(listContractTests());
expect(new Set(actual).size).toBe(actual.length);
});
});

View File

@@ -1,6 +1,29 @@
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { createNodeTestShards } from "../../scripts/lib/ci-node-test-plan.mjs";
function listTestFiles(rootDir: string): string[] {
if (!existsSync(rootDir)) {
return [];
}
const files: string[] = [];
const visit = (dir: string) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
visit(path);
} else if (entry.isFile() && entry.name.endsWith(".test.ts")) {
files.push(path.replaceAll("\\", "/"));
}
}
};
visit(rootDir);
return files.toSorted((a, b) => a.localeCompare(b));
}
describe("scripts/lib/ci-node-test-plan.mjs", () => {
it("names the node shard checks as core test lanes", () => {
const shards = createNodeTestShards();
@@ -32,11 +55,60 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
expect(requiresDistShardNames).toEqual([
"core-support-boundary",
"core-runtime",
"core-runtime-infra",
"core-runtime-media-ui",
"core-runtime-shared",
"agentic-agents-plugins",
]);
});
it("splits core runtime configs into smaller dist-dependent shards", () => {
const runtimeShards = createNodeTestShards()
.filter((shard) => shard.shardName.startsWith("core-runtime-"))
.map((shard) => ({
configs: shard.configs,
requiresDist: shard.requiresDist,
shardName: shard.shardName,
}));
expect(runtimeShards).toEqual([
{
configs: [
"test/vitest/vitest.infra.config.ts",
"test/vitest/vitest.hooks.config.ts",
"test/vitest/vitest.runtime-config.config.ts",
"test/vitest/vitest.secrets.config.ts",
"test/vitest/vitest.logging.config.ts",
"test/vitest/vitest.process.config.ts",
],
requiresDist: true,
shardName: "core-runtime-infra",
},
{
configs: [
"test/vitest/vitest.media.config.ts",
"test/vitest/vitest.media-understanding.config.ts",
"test/vitest/vitest.tui.config.ts",
"test/vitest/vitest.ui.config.ts",
"test/vitest/vitest.wizard.config.ts",
],
requiresDist: true,
shardName: "core-runtime-media-ui",
},
{
configs: [
"test/vitest/vitest.acp.config.ts",
"test/vitest/vitest.cron.config.ts",
"test/vitest/vitest.shared-core.config.ts",
"test/vitest/vitest.tasks.config.ts",
"test/vitest/vitest.utils.config.ts",
],
requiresDist: true,
shardName: "core-runtime-shared",
},
]);
});
it("splits the agentic lane into control-plane, commands, and agent/plugin shards", () => {
const shards = createNodeTestShards();
const controlPlaneShard = shards.find((shard) => shard.shardName === "agentic-control-plane");
@@ -77,4 +149,89 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
requiresDist: true,
});
});
it("splits auto-reply into independent core, top-level, and reply subtree shards", () => {
const shards = createNodeTestShards();
const autoReplyShards = shards
.filter((shard) => shard.shardName.startsWith("auto-reply"))
.map((shard) => ({
checkName: shard.checkName,
configs: shard.configs,
requiresDist: shard.requiresDist,
shardName: shard.shardName,
}));
expect(autoReplyShards).toEqual([
{
checkName: "checks-node-auto-reply-core",
configs: ["test/vitest/vitest.auto-reply-core.config.ts"],
requiresDist: false,
shardName: "auto-reply-core",
},
{
checkName: "checks-node-auto-reply-top-level",
configs: ["test/vitest/vitest.auto-reply-top-level.config.ts"],
requiresDist: false,
shardName: "auto-reply-top-level",
},
{
checkName: "checks-node-auto-reply-reply-agent-runner-a",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-agent-runner-a",
},
{
checkName: "checks-node-auto-reply-reply-agent-runner-b",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-agent-runner-b",
},
{
checkName: "checks-node-auto-reply-reply-commands-a",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-commands-a",
},
{
checkName: "checks-node-auto-reply-reply-commands-b",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-commands-b",
},
{
checkName: "checks-node-auto-reply-reply-dispatch-a",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-dispatch-a",
},
{
checkName: "checks-node-auto-reply-reply-dispatch-b",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-dispatch-b",
},
{
checkName: "checks-node-auto-reply-reply-state-routing-a",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-state-routing-a",
},
{
checkName: "checks-node-auto-reply-reply-state-routing-b",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-state-routing-b",
},
]);
});
it("covers every auto-reply reply test exactly once across split shards", () => {
const actual = createNodeTestShards()
.filter((shard) => shard.shardName.startsWith("auto-reply-reply-"))
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
expect(actual).toEqual(listTestFiles("src/auto-reply/reply"));
expect(new Set(actual).size).toBe(actual.length);
});
});