perf(ci): start long node shards first (#106522)

This commit is contained in:
Peter Steinberger
2026-07-13 09:56:30 -07:00
committed by GitHub
parent 2ec0299f9f
commit 8814e75cd6
3 changed files with 21 additions and 1 deletions

View File

@@ -103,6 +103,7 @@ The slowest Node test families are split or balanced so each job stays small wit
- Agentic gateway/server (control-plane) configs split across chat, auth, model, HTTP/plugin, runtime, and startup lanes instead of waiting on built artifacts.
- Normal CI packs only isolated infra include-pattern shards into deterministic bundles of at most 64 test files, reducing the Node matrix without merging non-isolated command/cron, stateful agents-core, or gateway/server suites. Heavy fixed suites stay on 8 vCPU while the bundled and lower-weight lanes use 4 vCPU.
- Pull requests on the canonical repository use a compact admission plan: the same per-config groups run in isolated subprocesses, currently 14 Node test jobs instead of the 74-job full matrix. Whole-config batches retain their 120-minute timeout, and the serial tooling config is striped across three PR-only groups; `main` pushes, manual dispatches, and release gates retain the full matrix.
- The full Node matrix admits the consistently slow serial tooling and auto-reply command shards first. This keeps the 28-job cap while preventing short alphabetical groups from pushing critical-path work into a later wave.
- Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard.
- `check-additional-*` stripes the supplemental boundary guard list (`scripts/run-additional-boundary-checks.mjs`) into one prompt-heavy shard (`check-additional-boundaries-a`, which includes the Codex prompt snapshot drift check) and one combined shard for the remaining stripes (`check-additional-boundaries-bcd`), each running independent guards concurrently and printing per-check timings. Package-boundary compile/canary work stays together, and runtime topology architecture runs separately from the gateway watch coverage embedded in `build-artifacts`.
- Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.

View File

@@ -35,6 +35,12 @@ const TOOLING_CONFIG = "test/vitest/vitest.tooling.config.ts";
const TOOLING_DOCKER_TEST_FILE = "test/scripts/docker-build-helper.test.ts";
const TOOLING_ISOLATED_CONFIG = "test/vitest/vitest.tooling-isolated.config.ts";
const TOOLING_ISOLATED_TEST_FILE = "test/scripts/openclaw-e2e-instance.test.ts";
// The full matrix is capped at 28 jobs. Admit the consistently slow serial
// shards first so short alphabetical groups cannot leave them on the tail.
const FULL_NODE_TEST_ADMISSION_PRIORITY = new Map([
["core-tooling", 0],
["auto-reply-reply-commands", 1],
]);
// Commands and cron run non-isolated, so keep their split shards as separate
// processes. Combining their include lists can retain test state across groups.
const BUNDLEABLE_NODE_TEST_CONFIGS = new Set(["test/vitest/vitest.infra.config.ts"]);
@@ -1013,6 +1019,15 @@ function bundleNameForConfigs(configs) {
.replace(/[^a-z0-9-]+/giu, "-");
}
function compareFullNodeTestAdmissionOrder(a, b) {
const fallbackPriority = FULL_NODE_TEST_ADMISSION_PRIORITY.size;
return (
(FULL_NODE_TEST_ADMISSION_PRIORITY.get(a.shardName) ?? fallbackPriority) -
(FULL_NODE_TEST_ADMISSION_PRIORITY.get(b.shardName) ?? fallbackPriority) ||
a.checkName.localeCompare(b.checkName)
);
}
function createStripedBatches(values, batchCount) {
const batches = Array.from({ length: batchCount }, () => []);
for (const [index, value] of values.entries()) {
@@ -1144,7 +1159,7 @@ export function createNodeTestShardBundles(options = {}) {
}
}
return [...unbundled, ...bundled].toSorted((a, b) => a.checkName.localeCompare(b.checkName));
return [...unbundled, ...bundled].toSorted(compareFullNodeTestAdmissionOrder);
}
function createCompactNodeTestShardBundles(options = {}) {

View File

@@ -145,6 +145,10 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
).toBe(true);
expect(bundled.every((shard) => shard.runner?.startsWith("blacksmith-"))).toBe(true);
expect(bundled).toEqual(createNodeTestShardBundles({ includeReleaseOnlyPluginShards: false }));
expect(bundled.slice(0, 2).map((shard) => shard.shardName)).toEqual([
"core-tooling",
"auto-reply-reply-commands",
]);
expect(bundled.find((shard) => shard.shardName === "core-unit-fast")?.runner).toBe(
DEFAULT_NODE_TEST_RUNNER,
);