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,54 @@
import { existsSync, readdirSync } from "node:fs";
import { join, relative } from "node:path";
function listContractTestFiles(rootDir = "src/channels/plugins/contracts") {
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));
}
export function createChannelContractTestShards() {
const rootDir = "src/channels/plugins/contracts";
const groups = {
"checks-fast-contracts-channels-registry-a": [],
"checks-fast-contracts-channels-registry-b": [],
"checks-fast-contracts-channels-core-a": [],
"checks-fast-contracts-channels-core-b": [],
"checks-fast-contracts-channels-extensions": [],
};
const pushBalanced = (firstKey, secondKey, file) => {
const target = groups[firstKey].length <= groups[secondKey].length ? firstKey : secondKey;
groups[target].push(file);
};
for (const file of listContractTestFiles(rootDir)) {
const name = relative(rootDir, file).replaceAll("\\", "/");
if (name.startsWith("plugins-core-extension.")) {
groups["checks-fast-contracts-channels-extensions"].push(file);
} else if (name.startsWith("plugins-core.") || name.startsWith("plugin.")) {
pushBalanced(
"checks-fast-contracts-channels-core-a",
"checks-fast-contracts-channels-core-b",
file,
);
} else {
pushBalanced(
"checks-fast-contracts-channels-registry-a",
"checks-fast-contracts-channels-registry-b",
file,
);
}
}
return Object.entries(groups).map(([checkName, includePatterns]) => ({
checkName,
includePatterns,
task: "contracts-channels",
runtime: "node",
}));
}

View File

@@ -1,3 +1,5 @@
import { existsSync, readdirSync } from "node:fs";
import { join, relative } from "node:path";
import { fullSuiteVitestShards } from "../../test/vitest/vitest.test-shards.mjs";
const EXCLUDED_FULL_SUITE_SHARDS = new Set([
@@ -7,7 +9,137 @@ const EXCLUDED_FULL_SUITE_SHARDS = new Set([
]);
const EXCLUDED_PROJECT_CONFIGS = new Set(["test/vitest/vitest.channels.config.ts"]);
function listTestFiles(rootDir) {
if (!existsSync(rootDir)) {
return [];
}
const files = [];
const visit = (dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
visit(path);
continue;
}
if (entry.isFile() && entry.name.endsWith(".test.ts")) {
files.push(path.replaceAll("\\", "/"));
}
}
};
visit(rootDir);
return files.toSorted((a, b) => a.localeCompare(b));
}
function createAutoReplyReplySplitShards() {
const files = listTestFiles("src/auto-reply/reply");
const groups = {
"auto-reply-reply-agent-runner": [],
"auto-reply-reply-commands": [],
"auto-reply-reply-dispatch": [],
"auto-reply-reply-state-routing": [],
};
for (const file of files) {
const name = relative("src/auto-reply/reply", file).replaceAll("\\", "/");
if (
name.startsWith("agent-runner") ||
name.startsWith("acp-") ||
name === "abort.test.ts" ||
name === "bash-command.stop.test.ts" ||
name.startsWith("block-")
) {
groups["auto-reply-reply-agent-runner"].push(file);
} else if (name.startsWith("commands")) {
groups["auto-reply-reply-commands"].push(file);
} else if (
name.startsWith("directive-") ||
name.startsWith("dispatch") ||
name.startsWith("followup-") ||
name.startsWith("get-reply")
) {
groups["auto-reply-reply-dispatch"].push(file);
} else {
groups["auto-reply-reply-state-routing"].push(file);
}
}
return Object.entries(groups).flatMap(([groupName, includePatterns]) => {
const midpoint = Math.ceil(includePatterns.length / 2);
return [
{
shardName: `${groupName}-a`,
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
includePatterns: includePatterns.slice(0, midpoint),
requiresDist: false,
},
{
shardName: `${groupName}-b`,
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
includePatterns: includePatterns.slice(midpoint),
requiresDist: false,
},
].filter((shard) => shard.includePatterns.length > 0);
});
}
const SPLIT_NODE_SHARDS = new Map([
[
"core-runtime",
[
{
shardName: "core-runtime-infra",
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-media-ui",
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-shared",
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,
},
],
],
[
"auto-reply",
[
{
shardName: "auto-reply-core",
configs: ["test/vitest/vitest.auto-reply-core.config.ts"],
requiresDist: false,
},
{
shardName: "auto-reply-top-level",
configs: ["test/vitest/vitest.auto-reply-top-level.config.ts"],
requiresDist: false,
},
...createAutoReplyReplySplitShards(),
],
],
[
"agentic",
[
@@ -77,6 +209,7 @@ export function createNodeTestShards() {
checkName: formatNodeTestShardCheckName(splitShard.shardName),
shardName: splitShard.shardName,
configs: splitConfigs,
...(splitShard.includePatterns ? { includePatterns: splitShard.includePatterns } : {}),
requiresDist: splitShard.requiresDist,
},
];