From 7aebac697ea7f8903fe6f16ba3ab4a5d2eb0b507 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 19:15:31 +0100 Subject: [PATCH] ci: split remaining slow test shards --- docs/ci.md | 2 +- scripts/lib/channel-contract-test-plan.mjs | 30 ++++++-------- scripts/lib/ci-node-test-plan.mjs | 36 ++++++++--------- .../channel-contract-test-plan.test.ts | 20 ++++++++++ test/scripts/ci-node-test-plan.test.ts | 40 +++++++++++++++---- 5 files changed, 84 insertions(+), 44 deletions(-) diff --git a/docs/ci.md b/docs/ci.md index 3b5e3951aac..f981309aed6 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -51,7 +51,7 @@ Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by On pushes, the `checks` matrix adds the push-only `compat-node22` lane. On pull requests, that lane is skipped and the matrix stays focused on the normal test/channel lanes. -The slowest Node test families are split into include-file shards so each job stays small: channel contracts split registry/core/extension coverage into focused shards, and auto-reply reply tests split each large prefix group into two include-pattern shards. `check-additional` also separates package-boundary compile/canary work from runtime topology gateway/architecture work. +The slowest Node test families are split into include-file shards so each job stays small: channel contracts split registry and core coverage into four balanced shards each, auto-reply reply command tests split into four include-pattern shards, and the other large auto-reply reply prefix groups split into two shards each. `check-additional` also separates package-boundary compile/canary work from runtime topology gateway/architecture work. ## Runners diff --git a/scripts/lib/channel-contract-test-plan.mjs b/scripts/lib/channel-contract-test-plan.mjs index 55a47385c89..52c6db8e286 100644 --- a/scripts/lib/channel-contract-test-plan.mjs +++ b/scripts/lib/channel-contract-test-plan.mjs @@ -14,31 +14,27 @@ function listContractTestFiles(rootDir = "src/channels/plugins/contracts") { 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": [], + const suffixes = ["a", "b", "c", "d"]; + const groups = Object.fromEntries( + ["registry", "core"].flatMap((family) => + suffixes.map((suffix) => [`checks-fast-contracts-channels-${family}-${suffix}`, []]), + ), + ); + const groupKeys = { + core: suffixes.map((suffix) => `checks-fast-contracts-channels-core-${suffix}`), + registry: suffixes.map((suffix) => `checks-fast-contracts-channels-registry-${suffix}`), }; - const pushBalanced = (firstKey, secondKey, file) => { - const target = groups[firstKey].length <= groups[secondKey].length ? firstKey : secondKey; + const pushBalanced = (keys, file) => { + const target = keys.toSorted((a, b) => groups[a].length - groups[b].length)[0]; groups[target].push(file); }; for (const file of listContractTestFiles(rootDir)) { const name = relative(rootDir, file).replaceAll("\\", "/"); if (name.startsWith("plugins-core.") || name.startsWith("plugin.")) { - pushBalanced( - "checks-fast-contracts-channels-core-a", - "checks-fast-contracts-channels-core-b", - file, - ); + pushBalanced(groupKeys.core, file); } else { - pushBalanced( - "checks-fast-contracts-channels-registry-a", - "checks-fast-contracts-channels-registry-b", - file, - ); + pushBalanced(groupKeys.registry, file); } } diff --git a/scripts/lib/ci-node-test-plan.mjs b/scripts/lib/ci-node-test-plan.mjs index d0ef0b51eab..0eb18bd07d8 100644 --- a/scripts/lib/ci-node-test-plan.mjs +++ b/scripts/lib/ci-node-test-plan.mjs @@ -66,21 +66,13 @@ function createAutoReplyReplySplitShards() { } 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 shardCount = groupName === "auto-reply-reply-commands" ? 4 : 2; + return Array.from({ length: shardCount }, (_, index) => ({ + shardName: `${groupName}-${String.fromCharCode(97 + index)}`, + configs: ["test/vitest/vitest.auto-reply-reply.config.ts"], + includePatterns: includePatterns.filter((_, fileIndex) => fileIndex % shardCount === index), + requiresDist: false, + })).filter((shard) => shard.includePatterns.length > 0); }); } @@ -164,13 +156,21 @@ const SPLIT_NODE_SHARDS = new Map([ requiresDist: false, }, { - shardName: "agentic-agents-plugins", + shardName: "agentic-agents", + configs: ["test/vitest/vitest.agents.config.ts"], + requiresDist: false, + }, + { + shardName: "agentic-plugin-sdk", configs: [ - "test/vitest/vitest.agents.config.ts", "test/vitest/vitest.plugin-sdk-light.config.ts", "test/vitest/vitest.plugin-sdk.config.ts", - "test/vitest/vitest.plugins.config.ts", ], + requiresDist: false, + }, + { + shardName: "agentic-plugins", + configs: ["test/vitest/vitest.plugins.config.ts"], requiresDist: true, }, ], diff --git a/test/scripts/channel-contract-test-plan.test.ts b/test/scripts/channel-contract-test-plan.test.ts index 37fab59a5c0..7a3a34a9a5a 100644 --- a/test/scripts/channel-contract-test-plan.test.ts +++ b/test/scripts/channel-contract-test-plan.test.ts @@ -33,6 +33,16 @@ describe("scripts/lib/channel-contract-test-plan.mjs", () => { runtime: "node", task: "contracts-channels", }, + { + checkName: "checks-fast-contracts-channels-registry-c", + runtime: "node", + task: "contracts-channels", + }, + { + checkName: "checks-fast-contracts-channels-registry-d", + runtime: "node", + task: "contracts-channels", + }, { checkName: "checks-fast-contracts-channels-core-a", runtime: "node", @@ -43,6 +53,16 @@ describe("scripts/lib/channel-contract-test-plan.mjs", () => { runtime: "node", task: "contracts-channels", }, + { + checkName: "checks-fast-contracts-channels-core-c", + runtime: "node", + task: "contracts-channels", + }, + { + checkName: "checks-fast-contracts-channels-core-d", + runtime: "node", + task: "contracts-channels", + }, ]); }); diff --git a/test/scripts/ci-node-test-plan.test.ts b/test/scripts/ci-node-test-plan.test.ts index bfce7667382..6d9569d47cf 100644 --- a/test/scripts/ci-node-test-plan.test.ts +++ b/test/scripts/ci-node-test-plan.test.ts @@ -58,7 +58,7 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { "core-runtime-infra", "core-runtime-media-ui", "core-runtime-shared", - "agentic-agents-plugins", + "agentic-plugins", ]); }); @@ -109,11 +109,13 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { ]); }); - it("splits the agentic lane into control-plane, commands, and agent/plugin shards", () => { + it("splits the agentic lane into control-plane, commands, agent, SDK, and plugin shards", () => { const shards = createNodeTestShards(); const controlPlaneShard = shards.find((shard) => shard.shardName === "agentic-control-plane"); const commandsShard = shards.find((shard) => shard.shardName === "agentic-commands"); - const agentPluginShard = shards.find((shard) => shard.shardName === "agentic-agents-plugins"); + const agentShard = shards.find((shard) => shard.shardName === "agentic-agents"); + const pluginSdkShard = shards.find((shard) => shard.shardName === "agentic-plugin-sdk"); + const pluginsShard = shards.find((shard) => shard.shardName === "agentic-plugins"); expect(controlPlaneShard).toEqual({ checkName: "checks-node-agentic-control-plane", @@ -137,15 +139,25 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { ], requiresDist: false, }); - expect(agentPluginShard).toEqual({ - checkName: "checks-node-agentic-agents-plugins", - shardName: "agentic-agents-plugins", + expect(agentShard).toEqual({ + checkName: "checks-node-agentic-agents", + shardName: "agentic-agents", + configs: ["test/vitest/vitest.agents.config.ts"], + requiresDist: false, + }); + expect(pluginSdkShard).toEqual({ + checkName: "checks-node-agentic-plugin-sdk", + shardName: "agentic-plugin-sdk", configs: [ - "test/vitest/vitest.agents.config.ts", "test/vitest/vitest.plugin-sdk-light.config.ts", "test/vitest/vitest.plugin-sdk.config.ts", - "test/vitest/vitest.plugins.config.ts", ], + requiresDist: false, + }); + expect(pluginsShard).toEqual({ + checkName: "checks-node-agentic-plugins", + shardName: "agentic-plugins", + configs: ["test/vitest/vitest.plugins.config.ts"], requiresDist: true, }); }); @@ -198,6 +210,18 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { requiresDist: false, shardName: "auto-reply-reply-commands-b", }, + { + checkName: "checks-node-auto-reply-reply-commands-c", + configs: ["test/vitest/vitest.auto-reply-reply.config.ts"], + requiresDist: false, + shardName: "auto-reply-reply-commands-c", + }, + { + checkName: "checks-node-auto-reply-reply-commands-d", + configs: ["test/vitest/vitest.auto-reply-reply.config.ts"], + requiresDist: false, + shardName: "auto-reply-reply-commands-d", + }, { checkName: "checks-node-auto-reply-reply-dispatch-a", configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],