openclaw/test/scripts/ci-node-test-plan.test.ts
Peter Steinberger 6d7144de35
fix(gateway): avoid event-loop stalls during device-token storage (#151926)
* fix(gateway): avoid event-loop stalls during device-token storage

* fix: prevent shared-state writes timing out behind native writers

* fix(clickclack): finish SQLite cleanup between persistence tests

* test: bind shared-state admission probes to real instances

* test(gateway): align cron build admission with worker owner
2026-09-18 14:53:26 -07:00

4459 lines
184 KiB
TypeScript

// Ci Node Test Plan tests cover ci node test plan script behavior.
import { existsSync, globSync, readdirSync, writeFileSync } from "node:fs";
import { syncBuiltinESMExports } from "node:module";
import os from "node:os";
import { isAbsolute, join, matchesGlob, relative, resolve } from "node:path";
import { expectDefined } from "@openclaw/normalization-core";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import {
createChangedExtensionFallbackShards,
createChangedNodeTestShards,
} from "../../scripts/lib/ci-changed-node-test-plan.mts";
import {
type CompactNodeTestShard,
createNodeTestShardBundles,
createNodeTestShards,
createSelectedNodeTestShardBundles,
createVitestCacheWarmGroups,
hasCompleteStartupCorpusCoverage,
isExclusiveCompactShardName,
isPolicyTestOwnedPath,
packNodeTestGroups,
resolvePolicyTestTargets,
} from "../../scripts/lib/ci-node-test-plan.mts";
import { isRuntimePlacementIncludePatterns } from "../../scripts/lib/ci-test-timings-schema.mts";
import * as testTimings from "../../scripts/lib/ci-test-timings.mts";
import * as buildPrerequisites from "../../scripts/lib/vitest-build-prerequisites.mts";
import { listVitestRuntimeConsumerFiles } from "../../scripts/lib/vitest-build-prerequisites.mts";
import {
createCompactSplitTimingGeneration,
parseCompactSplitTimingKey,
} from "../../scripts/lib/vitest-shard-metadata.mts";
import { expectNoNodeFsScans } from "../../src/test-utils/fs-scan-assertions.js";
import { listGitTrackedFiles, sortRepoPaths, toRepoPath } from "../../src/test-utils/repo-files.js";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
import { createAgentsCoreVitestConfig } from "../vitest/vitest.agents-core.config.ts";
import {
agentVitestProjectOwners,
embeddedAgentVitestProjectOwners,
} from "../vitest/vitest.agents-paths.mjs";
import { createAgentsSupportVitestConfig } from "../vitest/vitest.agents-support.config.ts";
import { createAgentsVitestConfig } from "../vitest/vitest.agents.config.ts";
import { cliProcessTestFiles } from "../vitest/vitest.cli-process-paths.mjs";
import { createCliProcessVitestConfig } from "../vitest/vitest.cli-process.config.ts";
import { createCommandsVitestConfig } from "../vitest/vitest.commands.config.ts";
import { databaseWorkerCoreTestFiles } from "../vitest/vitest.database-worker-core-paths.mjs";
import { createGatewayClientVitestConfig } from "../vitest/vitest.gateway-client.config.ts";
import { createGatewayCoreVitestConfig } from "../vitest/vitest.gateway-core.config.ts";
import { createGatewayDatabaseWorkersVitestConfig } from "../vitest/vitest.gateway-database-workers.config.ts";
import { createGatewayMethodsIsolatedVitestConfig } from "../vitest/vitest.gateway-methods-isolated.config.ts";
import { createGatewayMethodsVitestConfig } from "../vitest/vitest.gateway-methods.config.ts";
import { createGatewayServerIsolatedVitestConfig } from "../vitest/vitest.gateway-server-isolated.config.ts";
import {
gatewayDatabaseWorkerTestFiles,
isGatewayServerTestFile,
} from "../vitest/vitest.gateway-server-paths.mjs";
import { createGatewayServerVitestConfig } from "../vitest/vitest.gateway-server.config.ts";
import { createInfraVitestConfig } from "../vitest/vitest.infra.config.ts";
import { createMediaUnderstandingVitestConfig } from "../vitest/vitest.media-understanding.config.ts";
import { createMediaVitestConfig } from "../vitest/vitest.media.config.ts";
import { createPluginSdkLightVitestConfig } from "../vitest/vitest.plugin-sdk-light.config.ts";
import { createPluginSdkVitestConfig } from "../vitest/vitest.plugin-sdk.config.ts";
import { createPluginsVitestConfig } from "../vitest/vitest.plugins.config.ts";
import { createRuntimeConfigVitestConfig } from "../vitest/vitest.runtime-config.config.ts";
import { createTasksVitestConfig } from "../vitest/vitest.tasks.config.ts";
import { fullSuiteVitestShards } from "../vitest/vitest.test-shards.mjs";
import { createToolingVitestConfig } from "../vitest/vitest.tooling.config.ts";
import { createTuiVitestConfig } from "../vitest/vitest.tui.config.ts";
import { createUiIsolatedVitestConfig } from "../vitest/vitest.ui-isolated.config.ts";
import { uiTimingTestFiles } from "../vitest/vitest.ui-paths.mjs";
import { createUiTimingVitestConfig } from "../vitest/vitest.ui-timing.config.ts";
import { createUiVitestConfig } from "../vitest/vitest.ui.config.ts";
import { getUnitFastTestFilesForIncludePatterns } from "../vitest/vitest.unit-fast-paths.mjs";
import { createUnitFastVitestConfig } from "../vitest/vitest.unit-fast.config.ts";
import { createUnitVitestConfigWithOptions } from "../vitest/vitest.unit.config.ts";
import { createWizardVitestConfig } from "../vitest/vitest.wizard.config.ts";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
describe("startup corpus coverage", () => {
const files = [
"src/config/config-startup-corpus.test.ts",
"src/config/state-startup-corpus.test.ts",
];
const group = {
shard_name: "core-runtime-config",
configs: ["test/vitest/vitest.runtime-config.config.ts"],
requiresDist: false,
runner: "ubuntu-24.04",
includePatterns: files,
env: { OPENCLAW_VITEST_MAX_WORKERS: "2" },
};
it("retains complete ownership across separate file groups", () => {
const groups = files.map((file) => ({ ...group, includePatterns: [file] }));
expect(hasCompleteStartupCorpusCoverage([{ requiresDist: false, groups }])).toBe(true);
expect(listMatchedTestFiles(createRuntimeConfigVitestConfig({}))).toEqual(
expect.arrayContaining(files),
);
});
it.each<
{ label: string } & Partial<Parameters<typeof hasCompleteStartupCorpusCoverage>[0][number]>
>([
{ label: "partial file list", groups: [{ ...group, includePatterns: files.slice(0, 1) }] },
{ label: "unknown full config", groups: [{ ...group, includePatterns: undefined }] },
{
label: "glob instead of complete files",
groups: [{ ...group, includePatterns: ["src/config/**"] }],
},
{ label: "different config", groups: [{ ...group, configs: ["vitest.config.ts"] }] },
{
label: "native shard",
groups: [{ ...group, env: { OPENCLAW_NODE_TEST_VITEST_ARGS_JSON: '["--shard=1/2"]' } }],
},
{
label: "name filter",
groups: [{ ...group, env: { OPENCLAW_NODE_TEST_VITEST_ARGS_JSON: '["-t","one"]' } }],
},
{ label: "target precedence", groups: [group], targets: files.slice(0, 1) },
{ label: "non-admitted dist row", groups: [group], requiresDist: true },
{ label: "no groups", groups: [] },
])("does not certify $label", ({ label: _label, ...shard }) => {
expect(hasCompleteStartupCorpusCoverage([{ requiresDist: false, ...shard }])).toBe(false);
});
});
type VitestTestConfig = {
dir?: string;
exclude?: string[];
include?: string[];
};
type VitestConfig = {
test?: VitestTestConfig;
};
const PLUGIN_PRERELEASE_NPM_SPEC_TEST = "src/plugins/install.npm-spec.test.ts";
const PRIVATE_QA_TOOLING_TEST = "test/e2e/qa-lab/runtime/gateway-codex-delivery-cache.test.ts";
const DEFAULT_NODE_TEST_RUNNER = "blacksmith-8vcpu-ubuntu-2404";
const BUNDLED_NODE_TEST_RUNNER = "blacksmith-4vcpu-ubuntu-2404";
const EXTRA_LARGE_NODE_TEST_RUNNER = "blacksmith-32vcpu-ubuntu-2404";
const plannerHosts = [
{ label: "hosted-2cpu", logicalCpuCount: 2, totalMemoryBytes: 16 * 1024 ** 3 },
{ label: "large-32cpu", logicalCpuCount: 32, totalMemoryBytes: 512 * 1024 ** 3 },
] as const;
type PlannerHost = (typeof plannerHosts)[number];
function usesTwoWorkerPacking(job: CompactNodeTestShard | undefined) {
return job?.planConcurrency === 2 || job?.env?.OPENCLAW_VITEST_MAX_WORKERS === "2";
}
function isNumberedToolingGroup(group: { shard_name: string }) {
return /^core-tooling-\d+(?:-hosted-\d+)?$/u.test(group.shard_name);
}
function nonToolingPlacement(plan: CompactNodeTestShard[]) {
return plan
.flatMap((job) => {
const groups = job.groups
.filter((group) => !isNumberedToolingGroup(group))
.map((group) => group.shard_name)
.toSorted();
return groups.length === 0
? []
: [
{
groups,
planConcurrency: job.planConcurrency,
pretestBuildMode: job.pretestBuildMode,
requiresDist: job.requiresDist,
runner: job.runner,
},
];
})
.toSorted((a, b) => a.groups.join("\0").localeCompare(b.groups.join("\0")));
}
function isCombinedUnbuiltCliJob(job: CompactNodeTestShard) {
return (
job.groups.length > 1 &&
!job.requiresDist &&
!job.pretestBuildMode &&
job.groups.every((group) =>
group.configs.every((config) =>
["test/vitest/vitest.cli.config.ts", "test/vitest/vitest.cli-process.config.ts"].includes(
config,
),
),
)
);
}
const STORE_ALIAS_CHANGED_PATHS = [
"docs/gateway/secrets.md",
"src/agents/auth-profiles/read-only-availability.test.ts",
"src/agents/auth-profiles/read-only-availability.ts",
"src/agents/model-auth-availability.test.ts",
"src/plugins/manifest-tool-availability.test.ts",
"src/plugins/manifest-tool-availability.ts",
"src/plugins/tools.optional.test.ts",
];
function listTestFiles(rootDir: string): string[] {
const gitFiles = listGitTrackedFiles({ pathspecs: rootDir });
expect(gitFiles).not.toBeNull();
if (gitFiles) {
return gitFiles.filter((line) => line.endsWith(".test.ts"));
}
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(toRepoPath(path));
}
}
};
visit(rootDir);
return sortRepoPaths(files);
}
function listMatchedTestFiles(config: VitestConfig): string[] {
const testConfig = config.test ?? {};
const cwd = testConfig.dir ? resolve(testConfig.dir) : process.cwd();
const exclude = (testConfig.exclude ?? []).map((pattern) =>
isAbsolute(pattern) ? toRepoPath(relative(cwd, pattern)) : toRepoPath(pattern),
);
return globSync(testConfig.include ?? [], {
cwd,
exclude,
})
.map((file) => toRepoPath(relative(process.cwd(), resolve(cwd, file))))
.toSorted((a, b) => a.localeCompare(b));
}
function listAllToolingTestFiles(): string[] {
const originalArgv = process.argv;
try {
process.argv = originalArgv.slice(0, 2);
return listMatchedTestFiles(
createToolingVitestConfig({
...process.env,
OPENCLAW_VITEST_INCLUDE_FILE: undefined,
}),
);
} finally {
process.argv = originalArgv;
}
}
describe("scripts/lib/ci-node-test-plan.mts", () => {
it("keeps Gateway boot plans serial without adding jobs or changing ordinary packing", () => {
vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue({});
vi.spyOn(testTimings, "readRuntimePlacementTimings").mockReturnValue([]);
vi.spyOn(buildPrerequisites, "resolveVitestPretestBuildMode").mockReturnValue(undefined);
const original = fullSuiteVitestShards.slice();
try {
fullSuiteVitestShards.splice(
0,
fullSuiteVitestShards.length,
...[
["agentic-gateway-server-isolated", "gateway-server-isolated"],
["agentic-agents-core-models", "unit-support"],
["ordinary-a", "hooks"],
["ordinary-b", "secrets"],
].map(([name, config]) => ({
name: name!,
config: `fixture-${name}.config.ts`,
projects: [`test/vitest/vitest.${config}.config.ts`],
})),
);
const jobs = createNodeTestShardBundles({
compactMode: "push",
runnerBackend: "blacksmith",
includeReleaseOnlyPluginShards: false,
});
const gateway = expectDefined(
jobs.find((job) =>
job.groups.some((group) =>
group.configs.includes("test/vitest/vitest.gateway-server-isolated.config.ts"),
),
),
"Gateway job",
);
expect(gateway.planConcurrency).toBe(1);
expect(gateway.groups.map((group) => group.shard_name).toSorted()).toEqual([
"agentic-agents-core-models",
"agentic-gateway-server-isolated",
]);
expect(gateway.env).toEqual({ OPENCLAW_VITEST_MAX_WORKERS: "2" });
const ordinary = expectDefined(
jobs.find((job) => job !== gateway),
"ordinary job",
);
expect(ordinary.planConcurrency).toBe(2);
expect(ordinary.groups.map((group) => group.shard_name).toSorted()).toEqual([
"ordinary-a",
"ordinary-b",
]);
expect(jobs).toHaveLength(2);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...original);
}
});
// Read-only cases share this baseline; inventory and timing mutations build fresh plans.
let defaultShards: ReturnType<typeof createNodeTestShards>;
// Only unchanged committed inputs share snapshots; every caller receives its own graph.
const committedCompactPlans = new Map<string, CompactNodeTestShard[]>();
let plannerHostPinned = false;
function pinPlannerHost(host: PlannerHost) {
plannerHostPinned = true;
committedCompactPlans.clear();
vi.spyOn(os, "availableParallelism").mockReturnValue(host.logicalCpuCount);
vi.spyOn(os, "cpus").mockReturnValue(
Array.from({ length: host.logicalCpuCount }, () => ({
model: "fixture",
speed: 0,
times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 },
})),
);
vi.spyOn(os, "totalmem").mockReturnValue(host.totalMemoryBytes);
syncBuiltinESMExports();
vi.stubEnv("CI", "true");
vi.stubEnv("OPENCLAW_CI_TEST_TIMINGS", "1");
expect(Object.keys(testTimings.readCompactGroupTimings("blacksmith")).length).toBeGreaterThan(
0,
);
expect(testTimings.readRuntimePlacementTimings("blacksmith").length).toBeGreaterThan(0);
}
function getCommittedCompactPlan(
compactMode: "push" | "pull-request",
runnerBackend?: string,
): CompactNodeTestShard[] {
const key = JSON.stringify([compactMode, runnerBackend]);
let snapshot = committedCompactPlans.get(key);
if (!snapshot) {
snapshot = structuredClone(
createNodeTestShardBundles({
includeReleaseOnlyPluginShards: false,
compactMode,
...(runnerBackend === undefined ? {} : { runnerBackend }),
}),
);
committedCompactPlans.set(key, snapshot);
}
return structuredClone(snapshot);
}
beforeAll(() => {
defaultShards = createNodeTestShards();
});
it("binds split timing identity to exact complete-file membership", () => {
const common = {
configs: ["test/vitest/vitest.gateway-server.config.ts"],
env: { OPENCLAW_GATEWAY_TEST_WORKERS: "2" },
parentShardName: "agentic-control-plane-agent-chat",
};
const original = createCompactSplitTimingGeneration({
...common,
stripes: [["src/gateway/a.test.ts"], ["src/gateway/b.test.ts"]],
});
expect(
createCompactSplitTimingGeneration({
...common,
stripes: [["src/gateway/a.test.ts"], ["src/gateway/b.test.ts"]],
}),
).toEqual(original);
expect(
createCompactSplitTimingGeneration({
...common,
stripes: [["src/gateway/a.test.ts"], ["src/gateway/c.test.ts"]],
}),
).not.toEqual(original);
expect(
createCompactSplitTimingGeneration({
...common,
stripes: [["src/gateway/b.test.ts"], ["src/gateway/a.test.ts"]],
}),
).not.toEqual(original);
});
it("retains a complete measured generation and ignores complementary partial generations", () => {
const originalShards = fullSuiteVitestShards.slice();
const fixtureShards = originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter(
(config) => config === agentVitestProjectOwners.support.config,
),
}))
.filter((shard) => shard.projects.length > 0);
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...fixtureShards);
try {
let overlays: Record<"blacksmith" | "github", Readonly<Record<string, number>>> = {
blacksmith: { "agentic-agents-support": 165 },
github: { "agentic-agents-support": 253 },
};
vi.spyOn(testTimings, "readCompactGroupTimings").mockImplementation(
(profile) => overlays[profile],
);
const options = {
compactMode: "pull-request" as const,
includeReleaseOnlyPluginShards: false,
runnerBackend: "hybrid",
};
const initialPlan = createNodeTestShardBundles(options);
const supportGroups = (plan: typeof initialPlan) =>
plan
.flatMap((job) => job.groups)
.filter((group) => /^agentic-agents-support-hosted-\d+$/u.test(group.shard_name))
.toSorted((left, right) => left.shard_name.localeCompare(right.shard_name));
const initial = supportGroups(initialPlan);
expect(initial).toHaveLength(2);
overlays.blacksmith = {
...overlays.blacksmith,
...Object.fromEntries(initial.map((group, index) => [group.timing_key!, 247 + index])),
};
const expanded = supportGroups(createNodeTestShardBundles(options));
expect(expanded).toHaveLength(4);
const stripes = expanded.map((group) => group.includePatterns!);
const changedStripesA = stripes.map((patterns) => patterns.slice());
const first = changedStripesA[0]!.shift()!;
const second = changedStripesA[1]!.shift()!;
changedStripesA[0]!.push(second);
changedStripesA[1]!.push(first);
const partialA = createCompactSplitTimingGeneration({
configs: expanded[0]!.configs,
env: expanded[0]!.env,
parentShardName: "agentic-agents-support",
stripes: changedStripesA,
});
const changedStripesB = stripes.map((patterns) => patterns.slice());
const third = changedStripesB[2]!.shift()!;
const fourth = changedStripesB[3]!.shift()!;
changedStripesB[2]!.push(fourth);
changedStripesB[3]!.push(third);
const partialB = createCompactSplitTimingGeneration({
configs: expanded[0]!.configs,
env: expanded[0]!.env,
parentShardName: "agentic-agents-support",
stripes: changedStripesB,
});
overlays = {
github: { "agentic-agents-support": 100 },
blacksmith: {
"agentic-agents-support": 100,
[partialA.timingKeys[0]!]: 1_000,
[partialA.timingKeys[1]!]: 1_000,
[partialB.timingKeys[2]!]: 1_000,
[partialB.timingKeys[3]!]: 1_000,
},
};
const incomplete = createNodeTestShardBundles(options).flatMap((job) => job.groups);
expect(
incomplete.filter((group) => group.shard_name === "agentic-agents-support"),
).toHaveLength(1);
expect(
incomplete.filter((group) => /^agentic-agents-support-hosted-\d+$/u.test(group.shard_name)),
).toHaveLength(0);
overlays.blacksmith = {
...overlays.blacksmith,
...Object.fromEntries(expanded.map((group) => [group.timing_key!, 124])),
};
const stable = supportGroups(createNodeTestShardBundles(options));
expect(stable).toHaveLength(4);
expect(stable.map((group) => group.timing_key)).toEqual(
expanded.map((group) => group.timing_key),
);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
}
});
it("keeps Chromium files in the UI CI owner and Node-driven Playwright files in Node stripes", () => {
const shards = defaultShards;
const uiStripes = shards.filter((shard) =>
shard.shardName.startsWith("core-runtime-media-ui-"),
);
const files = uiStripes.flatMap((shard) => shard.includePatterns ?? []);
expect(files).toContain("ui/src/components/form-controls.browser.test.ts");
expect(files).not.toContain("ui/src/components/markdown-mermaid.runtime.browser.test.ts");
expect(shards.flatMap((shard) => shard.configs)).not.toContain(
"test/vitest/vitest.ui-browser.config.ts",
);
});
it.each(["github", "hybrid"])("keeps oversized sparse groups nonempty on %s", (runnerBackend) => {
const native = createNodeTestShards({ includeReleaseOnlyPluginShards: false });
const targets = [1, 2].map((count) =>
native.find(
(shard) =>
shard.includePatterns?.length === count && !shard.shardName.startsWith("core-tooling"),
)!,
);
expect(targets.every(Boolean)).toBe(true);
const original = testTimings.readCompactGroupTimings;
vi.spyOn(testTimings, "readCompactGroupTimings").mockImplementation((profile) => ({
...original(profile),
...Object.fromEntries(targets.map((target) => [target.shardName, 1_000])),
}));
const plan = createNodeTestShardBundles({
compactMode: "push",
runnerBackend,
includeReleaseOnlyPluginShards: false,
});
for (const target of targets) {
const groups = plan
.flatMap((job) => job.groups)
.filter(
(group) =>
group.shard_name === target.shardName ||
group.shard_name.startsWith(`${target.shardName}-hosted-`),
);
expect(groups.every((group) => (group.includePatterns?.length ?? 0) > 0)).toBe(true);
expect(groups.flatMap((group) => group.includePatterns ?? []).toSorted()).toEqual(
target.includePatterns!.toSorted(),
);
expect(groups).toHaveLength(target.includePatterns!.length);
}
});
it.each([
{ runnerBackend: "github", slowerProfile: "github" },
{ runnerBackend: "hybrid", slowerProfile: "github" },
{ runnerBackend: "hybrid", slowerProfile: "blacksmith" },
])(
"bounds $runnerBackend child groups by the slower $slowerProfile path",
({ runnerBackend, slowerProfile }) => {
const consumer = "test/e2e/qa-lab/runtime/gateway-support-export-runtime.test.ts";
const target = createNodeTestShards().find((shard) =>
shard.includePatterns?.includes(consumer),
)!;
const runtimeConsumers = target.includePatterns!.filter(
(file) => file === consumer || file === PRIVATE_QA_TOOLING_TEST,
);
const buildMode = runtimeConsumers.includes(PRIVATE_QA_TOOLING_TEST)
? "private-qa"
: "runtime";
const originalShards = fullSuiteVitestShards.slice();
// Exercise this owner's split without consuming unrelated suite families' job budget.
const fixtureShards = originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter((config) => target.configs.includes(config)),
}))
.filter((shard) => shard.projects.length > 0);
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...fixtureShards);
try {
vi.spyOn(testTimings, "readCompactGroupTimings").mockImplementation((profile) => ({
[target.shardName]: profile === slowerProfile ? 400 : 100,
}));
const plan = createNodeTestShardBundles({
compactMode: "pull-request",
runnerBackend,
includeReleaseOnlyPluginShards: false,
});
const groups = plan
.flatMap((job) => job.groups)
.filter((group) => group.shard_name.startsWith(`${target.shardName}-hosted-`));
// Runtime consumers share one isolated build child; its fixed build may
// exceed the cap. Remaining work needs three stripes on both profiles.
expect(groups).toHaveLength(4);
if (runnerBackend === "github") {
expect(
plan
.filter((job) => job.predictedSeconds! > 150)
.every((job) => {
if (job.groups.length > 1 && job.pretestBuildMode) {
return (
job.predictedSeconds! <= 210 &&
job.planConcurrency === 1 &&
!job.requiresDist &&
job.groups.every((group) => group.pretestBuildMode)
);
}
return (
job.groups.length === 1 &&
(job.groups[0]!.includePatterns?.length === 1 ||
(job.pretestBuildMode === buildMode &&
job.groups[0]!.includePatterns?.length === runtimeConsumers.length &&
job.groups[0]!.includePatterns?.every((file) =>
runtimeConsumers.some((runtimeConsumer) => runtimeConsumer === file),
)))
);
}),
).toBe(true);
}
expect(
groups
.filter((group) => group.pretestBuildMode !== undefined)
.map(({ pretestBuildMode, includePatterns }) => ({
pretestBuildMode,
includePatterns,
})),
).toEqual([{ pretestBuildMode: buildMode, includePatterns: runtimeConsumers }]);
for (const job of plan) {
if (
job.groups.some(
(group) => groups.includes(group) && group.pretestBuildMode === undefined,
)
) {
expect(job.predictedSeconds, `${runnerBackend}/${slowerProfile}`).toBeLessThanOrEqual(
150,
);
}
}
expect(groups.flatMap((group) => group.includePatterns ?? []).toSorted()).toEqual(
target.includePatterns!.toSorted(),
);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
}
},
);
afterEach(() => {
vi.restoreAllMocks();
if (plannerHostPinned) {
vi.unstubAllEnvs();
syncBuiltinESMExports();
committedCompactPlans.clear();
plannerHostPinned = false;
}
});
it("inventories source-scanning Control UI policy tests", () => {
expect(resolvePolicyTestTargets(["ui/src/pages/chat/view.ts"])).toEqual([
"ui/src/components/web-awesome-migration.node.test.ts",
"ui/src/styles/base-theme-tokens.node.test.ts",
"ui/src/styles/cursor-policy.node.test.ts",
]);
expect(resolvePolicyTestTargets(["docs/web/control-ui.md"])).toEqual([]);
expect(resolvePolicyTestTargets(["extensions/anthropic/openclaw.plugin.json"])).toEqual([
"src/agents/model-ref-shared.test.ts",
]);
});
it("selects the core root budget guard alongside changed messaging test owners", () => {
const guard = "test/scripts/tsgo-core-test-shards.test.ts";
const changed = "src/infra/outbound/outbound-send-service.accepted-outcomes.test.ts";
const shards = expectDefined(createChangedNodeTestShards([changed]), "core test plan");
const targets = shards.flatMap((shard) =>
(shard.targets ?? []).concat(
(shard.groups ?? []).flatMap((group) => group.includePatterns ?? []),
),
);
expect(targets).toContain(changed);
expect(targets.filter((target) => target === guard)).toEqual([guard]);
for (const changedPath of [
"src/auto-reply/reply/new.test.tsx",
"src/infra/outbound/new.test.ts",
"test/tsconfig/tsconfig.core.test.messaging.json",
]) {
expect(resolvePolicyTestTargets([changedPath]), changedPath).toContain(guard);
expect(isPolicyTestOwnedPath(changedPath), changedPath).toBe(false);
}
for (const changedPath of [
"src/infra/outbound/message.ts",
"src/infra/outbound/message.test-support.ts",
"src/agents/new.test.tsx",
"ui/src/pages/new.test.ts",
"packages/example/new.test.tsx",
"extensions/example/new.test.ts",
]) {
expect(resolvePolicyTestTargets([changedPath]), changedPath).not.toContain(guard);
}
});
it("selects provisioning for extracted sources without replacing their test owners", () => {
const provision = "test/scripts/pr-worktree-provision.test.ts";
const manifest = "scripts/pr-lib/wrapper-components.txt";
for (const changedPath of [
"scripts/pr",
"scripts/pr-lib/worktree.sh",
"src/plugins/discovery.ts",
"src/plugins/discovery-availability.ts",
]) {
expect(resolvePolicyTestTargets([changedPath]), changedPath).toContain(provision);
expect(isPolicyTestOwnedPath(changedPath), changedPath).toBe(false);
}
expect(resolvePolicyTestTargets(["src/plugins/unrelated-new-plugin.ts"])).not.toContain(
provision,
);
expect(isPolicyTestOwnedPath(manifest)).toBe(true);
const shards = expectDefined(createChangedNodeTestShards([manifest]), "manifest test plan");
const owners = shards
.flatMap((shard) => shard.groups ?? [])
.filter((group) => group.includePatterns?.includes(provision));
expect(owners).toHaveLength(1);
expect(owners[0]?.configs).toEqual(["test/vitest/vitest.tooling.config.ts"]);
});
it("matches policy owners only for exact changed paths", () => {
const changedPath = "ui/src/styles/base.css";
expect(isPolicyTestOwnedPath(changedPath)).toBe(true);
expect(resolvePolicyTestTargets([changedPath])).not.toEqual([]);
for (const lookalike of [` ${changedPath}`, String.raw`ui\src\pages\chat\view.ts`]) {
expect(isPolicyTestOwnedPath(lookalike), lookalike).toBe(false);
expect(resolvePolicyTestTargets([lookalike]), lookalike).toEqual([]);
}
});
it("projects cache-warm groups from the owned node test plan", () => {
const groups = createVitestCacheWarmGroups();
expect(groups).toHaveLength(12);
expect(groups.every((group) => group.configs.length === 1)).toBe(true);
expect(new Set(groups.flatMap((group) => group.configs))).toHaveProperty("size", 11);
expect(new Set(groups.map((group) => group.shard_name))).toHaveProperty("size", groups.length);
const coreStripeGroups = groups.filter(
(group) => group.configs[0] === "test/vitest/vitest.unit-fast.config.ts",
);
expect(coreStripeGroups).toHaveLength(2);
expect(coreStripeGroups.every((group) => (group.includePatterns?.length ?? 0) > 0)).toBe(true);
const coreStripePatterns = coreStripeGroups.flatMap((group) => group.includePatterns ?? []);
expect(new Set(coreStripePatterns).size).toBe(coreStripePatterns.length);
const isolatedGroups = groups.filter(
(group) =>
group.shard_name.startsWith("cache-warm:core-unit-fast-isolated:") ||
group.shard_name.startsWith("cache-warm:core-unit-fast-fake-timers:"),
);
expect(isolatedGroups).toHaveLength(2);
expect(isolatedGroups.every((group) => group.includePatterns === undefined)).toBe(true);
expect(isolatedGroups.every((group) => group.env === undefined)).toBe(true);
const embeddedGroups = groups.filter((group) =>
group.shard_name.startsWith("cache-warm:agentic-agents-embedded:"),
);
expect(embeddedGroups).toHaveLength(4);
expect(
embeddedGroups.every((group) => group.env?.OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS === "660000"),
).toBe(true);
const gatewayGroups = groups.filter((group) =>
group.shard_name.startsWith("cache-warm:agentic-gateway-methods:"),
);
expect(
gatewayGroups.flatMap((group) => group.configs).toSorted((a, b) => a.localeCompare(b)),
).toEqual([
"test/vitest/vitest.gateway-methods-isolated.config.ts",
"test/vitest/vitest.gateway-methods.config.ts",
]);
expect(gatewayGroups.every((group) => group.includePatterns === undefined)).toBe(true);
expect(gatewayGroups.every((group) => group.env === undefined)).toBe(true);
const autoReplyGroups = groups.filter((group) =>
group.shard_name.startsWith("cache-warm:auto-reply-reply-commands-3:"),
);
expect(autoReplyGroups).toHaveLength(1);
const autoReplyShard = expectDefined(
defaultShards.find((shard) => shard.shardName === "auto-reply-reply-commands-3"),
"auto-reply command shard",
);
expect(autoReplyGroups[0]?.includePatterns).toEqual(autoReplyShard.includePatterns);
expect(autoReplyGroups[0]?.env).toBeUndefined();
expect(groups.find((group) => group.shard_name === "cache-warm:ui-package")).toEqual({
configs: ["ui/vitest.config.ts"],
env: { OPENCLAW_VITEST_MAX_WORKERS: "1" },
includePatterns: [
"ui/src/components/app-sidebar.test.ts",
"ui/src/pages/chat/chat-view.test.ts",
"ui/src/pages/chat/chat-pane-lifecycle.test.ts",
"ui/src/pages/usage/metrics.node.test.ts",
],
shard_name: "cache-warm:ui-package",
});
});
it("creates split shards without walking test roots", () => {
const payload = expectNoNodeFsScans<{
includePatterns: number;
shards: number;
}>(`
const { createNodeTestShards } = await import("./scripts/lib/ci-node-test-plan.mts");
const shards = createNodeTestShards();
return {
includePatterns: shards.reduce(
(total, shard) => total + (shard.includePatterns?.length ?? 0),
0,
),
shards: shards.length,
};
`);
expect(payload.shards).toBeGreaterThan(0);
expect(payload.includePatterns).toBeGreaterThan(0);
});
it("bundles split shards with deterministic unique identities and unchanged coverage", () => {
const base = createNodeTestShards({ includeReleaseOnlyPluginShards: false });
const bundled = createNodeTestShardBundles({ includeReleaseOnlyPluginShards: false });
const basePatterns = base
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
const bundledPatterns = bundled
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
expect(bundled.length).toBeLessThan(base.length);
expect(new Set(bundled.map((shard) => shard.checkName)).size).toBe(bundled.length);
expect(bundledPatterns).toEqual(basePatterns);
expect(
bundled
.filter((shard) => shard.shardName.startsWith("bundle-"))
.every((shard) => (shard.includePatterns?.length ?? 0) <= 64),
).toBe(true);
expect(bundled.every((shard) => shard.runner?.startsWith("blacksmith-"))).toBe(true);
expect(bundled).toEqual(createNodeTestShardBundles({ includeReleaseOnlyPluginShards: false }));
expect(bundled.slice(0, 7).map((shard) => shard.shardName)).toEqual([
"core-unit-fast-1",
"core-unit-fast-2",
"core-tooling-1",
"core-tooling-10",
"core-tooling-11",
"core-tooling-12",
"core-tooling-13",
]);
expect(bundled.find((shard) => shard.shardName === "core-unit-fast-1")?.runner).toBe(
DEFAULT_NODE_TEST_RUNNER,
);
expect(bundled.find((shard) => shard.shardName === "core-unit-fast-2")?.runner).toBe(
DEFAULT_NODE_TEST_RUNNER,
);
expect(
bundled.find((shard) => shard.shardName === "agentic-control-plane-startup-health-runtime")
?.env,
).toEqual({ OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "60000" });
expect(
bundled.find((shard) => shard.shardName === "agentic-control-plane-startup-core")?.runner,
).toBe(DEFAULT_NODE_TEST_RUNNER);
expect(bundled.find((shard) => shard.shardName === "bundle-infra-small-1")?.runner).toBe(
"blacksmith-4vcpu-ubuntu-2404",
);
expect(
new Set(
bundled
.filter((shard) => shard.shardName.startsWith("bundle-"))
.flatMap((shard) => shard.configs),
),
).toEqual(new Set(["test/vitest/vitest.infra.config.ts"]));
expect(bundled.some((shard) => shard.shardName.startsWith("bundle-commands-"))).toBe(false);
expect(bundled.some((shard) => shard.shardName.startsWith("bundle-cron-"))).toBe(false);
expect(bundled.some((shard) => shard.shardName.startsWith("bundle-agents-core-"))).toBe(false);
expect(bundled.some((shard) => shard.shardName.startsWith("bundle-gateway-server-"))).toBe(
false,
);
});
it.each([
{ profile: "blacksmith", legacy: 116, measured: 310, defaultSeconds: 25 },
{ profile: "github", legacy: 186, measured: 370, defaultSeconds: 40 },
{ profile: "hybrid", legacy: 101, measured: 270, defaultSeconds: 22 },
])(
"prefers $profile measurements while retaining unmeasured hints and defaults",
({ profile, legacy, measured, defaultSeconds }) => {
const timings = vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue({});
const options = {
includeReleaseOnlyPluginShards: false,
compactMode: "pull-request" as const,
runnerBackend: profile,
};
const fallback = createNodeTestShardBundles(options);
const tuiJob = (plan: typeof fallback) =>
plan.find((shard) =>
shard.groups.some((group) => group.shard_name === "core-runtime-tui-pty"),
);
expect(tuiJob(fallback)?.predictedSeconds).toBe(legacy);
timings.mockImplementation((runner) => ({
"core-runtime-tui-pty": runner === "blacksmith" ? 310 : 370,
"removed-test-group": 999,
}));
const updated = createNodeTestShardBundles(options);
expect(tuiJob(updated)?.groups.map((group) => group.shard_name)).toEqual([
"core-runtime-tui-pty",
]);
expect(tuiJob(updated)?.predictedSeconds).toBe(measured);
expect(
updated.find((shard) =>
shard.groups.some((group) => group.shard_name === "core-support-boundary"),
)?.predictedSeconds,
).toBe(defaultSeconds);
const groupNames = (plan: typeof fallback) =>
plan.flatMap((shard) => shard.groups.map((group) => group.shard_name)).toSorted();
expect(groupNames(updated)).toEqual(groupNames(fallback));
// Two complete, compatible configs share setup without changing either
// process envelope. Blacksmith placements request capacity for overlapping plans.
const fixtureConfigs = new Set([
"test/vitest/vitest.hooks.config.ts",
"test/vitest/vitest.secrets.config.ts",
]);
const originalShards = fullSuiteVitestShards.slice();
try {
const fixtureShards = originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter((config) => fixtureConfigs.has(config)),
}))
.filter((shard) => shard.projects.length > 0);
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...fixtureShards);
const base = createNodeTestShards(options);
expect(base).toHaveLength(2);
const groupSeconds = profile === "github" ? 70 : 170;
timings.mockReturnValue(
Object.fromEntries(base.map((shard) => [shard.shardName, groupSeconds])),
);
const packed = createNodeTestShardBundles(options);
expect(packed).toHaveLength(1);
expect(packed[0]?.groups).toEqual(
base.map(({ checkName: _checkName, shardName, ...group }) => ({
...group,
shard_name: shardName,
})),
);
expect(packed[0]?.planConcurrency).toBe(profile === "github" ? 1 : 2);
expect(packed[0]?.runner).toBe(
profile === "github" ? base[0]?.runner : EXTRA_LARGE_NODE_TEST_RUNNER,
);
expect(packed[0]?.predictedSeconds).toBe(profile === "hybrid" ? 296 : groupSeconds * 2);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
}
// Cheap envelopes use the time budget instead of creating extra
// runners at ten groups; serial placements keep their existing count limit.
timings.mockReturnValue(
Object.fromEntries(createNodeTestShards(options).map((shard) => [shard.shardName, 1])),
);
const dense = createNodeTestShardBundles(options);
expect(dense.some((shard) => shard.groups.length > 10)).toBe(profile !== "github");
for (const shard of dense.filter((entry) => entry.groups.length > 10)) {
expect(shard).toMatchObject({
planConcurrency: shard.env?.OPENCLAW_VITEST_MAX_WORKERS === "2" ? 1 : 2,
requiresDist: false,
runner: EXTRA_LARGE_NODE_TEST_RUNNER,
});
expect(shard.pretestBuildMode).toBeUndefined();
expect(shard.predictedSeconds).toBeLessThanOrEqual(360);
}
},
);
it("keeps hybrid fallback bounds and unmeasured stripes when other measurements change", () => {
vi.spyOn(testTimings, "readRuntimePlacementTimings").mockReturnValue([]);
const timings = vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue({});
const options = {
includeReleaseOnlyPluginShards: false,
compactMode: "push" as const,
runnerBackend: "hybrid",
};
const fallback = createNodeTestShardBundles(options);
expect(
fallback
.filter((shard) => !shard.requiresDist)
.every(
(shard) =>
(shard.predictedSeconds ?? Infinity) <=
(usesTwoWorkerPacking(shard) ? 360 : isCombinedUnbuiltCliJob(shard) ? 250 : 210),
),
).toBe(true);
// Slow process files retain singleton envelopes without inheriting the
// separate runtime-consumer group's build.
expect(
fallback
.filter((shard) => !shard.requiresDist)
.filter((shard) =>
shard.groups.some((group) => isExclusiveCompactShardName(group.shard_name)),
)
.filter((shard) => !((shard.predictedSeconds ?? Infinity) <= 150))
.filter((shard) => !isCombinedUnbuiltCliJob(shard))
.map((shard) => ({
groups: shard.groups.map((group) => ({
configs: group.configs,
includePatterns: group.includePatterns,
pretestBuildMode: group.pretestBuildMode,
})),
planConcurrency: shard.planConcurrency,
pretestBuildMode: shard.pretestBuildMode,
predictedSeconds: shard.predictedSeconds,
}))
.toSorted((a, b) =>
(a.groups[0]!.includePatterns ?? a.groups[0]!.configs)
.join(",")
.localeCompare((b.groups[0]!.includePatterns ?? b.groups[0]!.configs).join(",")),
),
).toEqual([
{
groups: [
{
configs: ["test/vitest/vitest.cli-process.config.ts"],
includePatterns: ["src/cli/gateway-backed-exit-health.process.test.ts"],
pretestBuildMode: undefined,
},
],
planConcurrency: 1,
pretestBuildMode: undefined,
predictedSeconds: 200,
},
{
groups: [
{
configs: ["test/vitest/vitest.cli-process.config.ts"],
includePatterns: ["src/cli/gateway-backed-exit.process.test.ts"],
pretestBuildMode: undefined,
},
],
planConcurrency: 1,
pretestBuildMode: undefined,
predictedSeconds: 200,
},
]);
const agentChatStripes = fallback
.flatMap((shard) => shard.groups)
.filter((group) => group.shard_name.startsWith("agentic-control-plane-agent-chat-hosted-"));
expect(agentChatStripes.length).toBeGreaterThan(1);
expect(
agentChatStripes.every(
(group) =>
!group.includePatterns?.includes("src/gateway/server.chat.gateway-server-chat.test.ts") ||
!group.includePatterns.includes("src/gateway/server.sessions.create.test.ts"),
),
).toBe(true);
timings.mockImplementation((runner): Readonly<Record<string, number>> =>
runner === "blacksmith"
? {
"agentic-agents-core-models": 123,
"core-unit-fast-1": 100,
"core-runtime-hooks": 80,
}
: {},
);
const updated = createNodeTestShardBundles(options);
const tail = updated.find((shard) =>
shard.groups.some((group) => group.shard_name === "agentic-gateway-core-3"),
);
expect(tail?.predictedSeconds).toBeGreaterThanOrEqual(140);
expect(tail?.predictedSeconds).toBeLessThanOrEqual(usesTwoWorkerPacking(tail) ? 360 : 210);
});
it.each([
{ profile: "github", timingProfile: "github", addedSeconds: 40 },
{ profile: "hybrid", timingProfile: "blacksmith", addedSeconds: 35 },
] as const)(
"retains parent floors and uses higher $profile child timings without changing test partitions",
({ profile, timingProfile, addedSeconds }) => {
const originalShards = fullSuiteVitestShards.slice();
const fixtureShards = originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter(
(config) => config === agentVitestProjectOwners.support.config,
),
}))
.filter((shard) => shard.projects.length > 0);
// Other families can change packing and job-level rounding without changing these timings.
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...fixtureShards);
try {
const shardName = "agentic-agents-support-hosted-2";
let directTimings: Readonly<Record<string, number>> = {};
vi.spyOn(testTimings, "readCompactGroupTimings").mockImplementation(
(runner): Readonly<Record<string, number>> => ({
"agentic-agents-support": runner === "blacksmith" ? 165 : 253,
...(runner === timingProfile ? directTimings : {}),
}),
);
const options = {
includeReleaseOnlyPluginShards: false,
compactMode: "push" as const,
runnerBackend: profile,
};
const unmeasured = createNodeTestShardBundles(options);
const initialGroup = unmeasured
.flatMap((shard) => shard.groups)
.find((group) => group.shard_name === shardName);
expect(initialGroup?.timing_key).toMatch(
/^agentic-agents-support#selector-.+#generation-.+#part-2-of-2#include-.+$/u,
);
const timingKey = initialGroup!.timing_key!;
directTimings = { [timingKey]: 1 };
const belowFloor = createNodeTestShardBundles(options);
// Both larger samples exceed the parent share, so their delta measures
// direct timing precedence independently of the retained floor.
directTimings = { [timingKey]: 200 };
const baseline = createNodeTestShardBundles(options);
directTimings = { [timingKey]: 240 };
const updated = createNodeTestShardBundles(options);
const totalSeconds = (plan: typeof baseline) =>
plan.reduce((sum, shard) => sum + (shard.predictedSeconds ?? 0), 0);
const testPartition = (plan: typeof baseline) =>
plan
.flatMap((shard) => shard.groups)
.map((group) => ({
name: group.shard_name,
configs: group.configs,
includePatterns: group.includePatterns,
runner: group.runner,
}))
.toSorted((a, b) => a.name.localeCompare(b.name));
expect(totalSeconds(belowFloor)).toBe(totalSeconds(unmeasured));
expect(totalSeconds(updated) - totalSeconds(baseline)).toBe(addedSeconds);
expect(testPartition(baseline)).toEqual(testPartition(unmeasured));
expect(testPartition(updated)).toEqual(testPartition(baseline));
expect(
updated.flatMap((shard) => shard.groups).find((group) => group.shard_name === shardName)
?.timing_key,
).toBe(timingKey);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
}
},
);
it("preserves each plugin project inventory when runtime consumers are partitioned", () => {
const originalArgv = process.argv;
process.argv = originalArgv.slice(0, 2);
try {
const env = { ...process.env, OPENCLAW_VITEST_INCLUDE_FILE: undefined };
const includeFile = join(tempDirs.make("openclaw-plugin-plan-"), "include.json");
const plan = createNodeTestShardBundles({
compactMode: "push",
runnerBackend: "github",
includeReleaseOnlyPluginShards: true,
});
const capacities = new Map([
[BUNDLED_NODE_TEST_RUNNER, 4],
[DEFAULT_NODE_TEST_RUNNER, 8],
[EXTRA_LARGE_NODE_TEST_RUNNER, 32],
]);
for (const job of plan) {
expect(job.planConcurrency).toBe(1);
expect(job.runner).toBe(job.groups[0]?.runner);
for (const group of job.groups) {
expect(expectDefined(capacities.get(job.runner), "job capacity")).toBeGreaterThanOrEqual(
expectDefined(capacities.get(group.runner), "group capacity"),
);
}
}
for (const { config, create } of [
{ config: "test/vitest/vitest.plugins.config.ts", create: createPluginsVitestConfig },
{ config: "test/vitest/vitest.plugin-sdk.config.ts", create: createPluginSdkVitestConfig },
{
config: "test/vitest/vitest.plugin-sdk-light.config.ts",
create: createPluginSdkLightVitestConfig,
},
]) {
const expected = listMatchedTestFiles(create(env));
const actual = plan
.flatMap((job) => job.groups)
.filter((group) => group.configs.includes(config))
.flatMap((group) => {
if (!group.includePatterns) {
return listMatchedTestFiles(create(env));
}
writeFileSync(includeFile, JSON.stringify(group.includePatterns));
return listMatchedTestFiles(
create({ ...env, OPENCLAW_VITEST_INCLUDE_FILE: includeFile }),
);
});
expect(actual.toSorted(), config).toEqual(expected.toSorted());
expect(new Set(actual).size, config).toBe(actual.length);
}
} finally {
process.argv = originalArgv;
}
});
it("partitions whole-config runtime consumers from ordinary serial CLI work", () => {
const originalShards = fullSuiteVitestShards.slice();
const config = "test/vitest/vitest.cli-process.config.ts";
const selected = originalShards
.map((shard) => ({ ...shard, projects: shard.projects.filter((entry) => entry === config) }))
.filter((shard) => shard.projects.length > 0);
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...selected);
const timings = vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue({});
const options = {
includeReleaseOnlyPluginShards: false,
compactMode: "push" as const,
runnerBackend: "hybrid",
};
try {
const plan = createNodeTestShardBundles(options);
const runtimeJobs = plan.filter((job) => job.pretestBuildMode);
expect(runtimeJobs).toHaveLength(1);
const [runtimeJob] = runtimeJobs;
expect(runtimeJob).toMatchObject({ planConcurrency: 1, pretestBuildMode: "runtime" });
expect(runtimeJob!.predictedSeconds).toBeLessThanOrEqual(150);
expect(runtimeJob!.groups).toHaveLength(1);
const runtimeFiles = listVitestRuntimeConsumerFiles([config]).toSorted();
expect(runtimeJob!.groups[0]!.includePatterns?.toSorted()).toEqual(runtimeFiles);
const catalogFiles = listMatchedTestFiles(createCliProcessVitestConfig({})).toSorted();
const ordinaryJobs = plan.filter((job) => !job.pretestBuildMode);
expect(
ordinaryJobs
.flatMap((job) => job.groups.flatMap((group) => group.includePatterns ?? []))
.toSorted(),
).toEqual(catalogFiles.filter((file) => !runtimeFiles.includes(file)));
for (const job of plan) {
expect(job).toMatchObject({ planConcurrency: 1, requiresDist: false });
for (const group of job.groups) {
expect(group.configs).toEqual([config]);
expect(group.env?.OPENCLAW_VITEST_MAX_WORKERS).toBe("2");
expect(group.pretestBuildMode).toBe(job.pretestBuildMode);
expect(group.requiresDist).toBe(false);
}
}
expect(
plan
.flatMap((job) => job.groups.flatMap((group) => group.includePatterns ?? []))
.toSorted(),
).toEqual(catalogFiles);
// An oversized measured runtime child remains truthful and alone; ordinary
// files must not inherit its prerequisite through a sibling exemption.
timings.mockReturnValue(
Object.fromEntries(runtimeJob!.groups.map((group) => [group.timing_key!, 100])),
);
const expensive = createNodeTestShardBundles(options).filter((job) => job.pretestBuildMode);
expect(expensive).toHaveLength(1);
// Hybrid scales the 100s sample to 87s, then charges one 100s runtime build.
expect(expensive[0]).toMatchObject({ predictedSeconds: 187, planConcurrency: 1 });
expect(expensive[0]!.groups).toHaveLength(1);
expect(expensive[0]!.groups[0]!.includePatterns?.toSorted()).toEqual(runtimeFiles);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
}
});
it("spends the hybrid CLI budget only on complete affordable non-build bins", () => {
const originalShards = fullSuiteVitestShards.slice();
const originalProcessFiles = cliProcessTestFiles.slice();
const configs = new Set([
"test/vitest/vitest.cli.config.ts",
"test/vitest/vitest.cli-process.config.ts",
]);
const selected = originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter((entry) => configs.has(entry)),
}))
.filter((shard) => shard.projects.length > 0);
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...selected);
const timings = vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue({
"agentic-cli": 136,
});
const options = {
includeReleaseOnlyPluginShards: false,
compactMode: "pull-request" as const,
runnerBackend: "hybrid",
};
try {
const plan = createNodeTestShardBundles(options);
const combined = plan.filter(isCombinedUnbuiltCliJob);
expect(combined).toHaveLength(2);
const cliJobs = plan.filter((job) =>
job.groups.some((group) => group.shard_name === "agentic-cli"),
);
expect(cliJobs).toHaveLength(1);
expect(cliJobs[0]).toMatchObject({
planConcurrency: 1,
runner: "blacksmith-16vcpu-ubuntu-2404",
});
// The combined bin uses the larger CLI budget, beyond the 150s child limit.
expect(cliJobs[0]!.predictedSeconds).toBeGreaterThan(150);
expect(cliJobs[0]!.pretestBuildMode).toBeUndefined();
expect(cliJobs[0]!.groups).toHaveLength(2);
expect(cliJobs[0]!.groups[0]!.includePatterns).toBeUndefined();
const processGroups = plan.flatMap((job) =>
job.groups.filter((group) =>
group.configs.includes("test/vitest/vitest.cli-process.config.ts"),
),
);
expect(processGroups.flatMap((group) => group.includePatterns ?? []).toSorted()).toEqual(
listMatchedTestFiles(createCliProcessVitestConfig({})).toSorted(),
);
expect(
combined.every((job) => job.predictedSeconds! <= 250 && job.planConcurrency === 1),
).toBe(true);
for (const job of plan.filter((candidate) => candidate.pretestBuildMode)) {
expect(job.predictedSeconds).toBeLessThanOrEqual(150);
expect(job.groups.every((group) => group.pretestBuildMode === "runtime")).toBe(true);
}
const combinedProcessGroups = combined
.flatMap((job) => job.groups)
.filter((group) => group.configs.includes("test/vitest/vitest.cli-process.config.ts"));
const processKeys = combinedProcessGroups.flatMap((group) =>
group.timing_key ? [group.timing_key] : [],
);
expect(processKeys).toHaveLength(combinedProcessGroups.length);
expect(new Set(processKeys).size).toBe(processKeys.length);
// Each child still fits its 150s limit, but two no longer fit a 250s bin.
timings.mockReturnValue(Object.fromEntries(processKeys.map((key) => [key, 160])));
const overBudget = createNodeTestShardBundles(options);
expect(
overBudget.every(
(job) =>
job.groups.filter((group) => processKeys.includes(group.timing_key ?? "")).length <= 1,
),
).toBe(true);
// A truthful oversized child must remain alone even beside a tiny CLI.
timings.mockReturnValue({
"agentic-cli": 3,
...Object.fromEntries(processKeys.map((key) => [key, 200])),
});
const oversized = createNodeTestShardBundles(options).filter((job) =>
job.groups.some((group) => processKeys.includes(group.timing_key ?? "")),
);
expect(oversized).toHaveLength(processKeys.length);
expect(oversized.every((job) => job.groups.length === 1)).toBe(true);
// Cheaper complete CLI children can share below 150s. A later unrelated
// non-dist owner must not invalidate that earlier sibling exemption.
const processFiles = ["src/cli/help-exit.process.test.ts", "src/cli/one-shot-exit.test.ts"];
cliProcessTestFiles.splice(0, cliProcessTestFiles.length, ...processFiles);
const smallerConfigs = new Set([
"test/vitest/vitest.cli-process.config.ts",
"test/vitest/vitest.tooling-isolated.config.ts",
]);
fullSuiteVitestShards.splice(
0,
fullSuiteVitestShards.length,
...originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter((config) => smallerConfigs.has(config)),
}))
.filter((shard) => shard.projects.length > 0),
);
timings.mockImplementation((profile) => ({
"agentic-cli-process": profile === "github" ? 200 : 120,
"core-tooling-isolated": 20,
}));
const cheaper = createNodeTestShardBundles(options);
const cliJob = cheaper.find((job) =>
job.groups.some((group) =>
group.configs.includes("test/vitest/vitest.cli-process.config.ts"),
),
)!;
expect(cliJob.groups).toHaveLength(2);
expect(cliJob.groups.flatMap((group) => group.includePatterns ?? []).toSorted()).toEqual(
processFiles.toSorted(),
);
const unrelated = cheaper.find((job) =>
job.groups.some((group) => group.shard_name === "core-tooling-isolated"),
)!;
expect(unrelated).toMatchObject({ runner: cliJob.runner, requiresDist: false });
expect(unrelated.groups).toHaveLength(1);
expect(unrelated.pretestBuildMode).toBeUndefined();
expect(cliJob.predictedSeconds! + unrelated.predictedSeconds!).toBeLessThanOrEqual(150);
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
cliProcessTestFiles.splice(0, cliProcessTestFiles.length, ...originalProcessFiles);
}
});
function checkCommittedCompactPolicies(host: PlannerHost) {
pinPlannerHost(host);
const base = createNodeTestShards({ includeReleaseOnlyPluginShards: false });
const compact = getCommittedCompactPlan("push");
const pullRequestCompact = getCommittedCompactPlan("pull-request");
const githubCompact = getCommittedCompactPlan("push", "github");
const githubPullRequestCompact = getCommittedCompactPlan("pull-request", "github");
const hybridCompact = getCommittedCompactPlan("push", "hybrid");
const hybridPullRequestCompact = getCommittedCompactPlan("pull-request", "hybrid");
const placementTimings = vi
.spyOn(testTimings, "readRuntimePlacementTimings")
.mockReturnValue([]);
const hybridBeforePlacement = createNodeTestShardBundles({
includeReleaseOnlyPluginShards: false,
compactMode: "pull-request",
runnerBackend: "hybrid",
});
placementTimings.mockRestore();
const expectedToolingOwnerNames = Array.from(
{ length: 16 },
(_, index) => `core-tooling-${index + 1}`,
);
const pushExcludedShardNames = new Set([
"core-runtime-tui-pty",
...expectedToolingOwnerNames,
"core-tooling-isolated",
]);
const gatewayFiles = [
"src/cli/gateway-backed-exit.process.test.ts",
"src/cli/gateway-backed-exit-health.process.test.ts",
];
for (const profile of [
{
name: "Blacksmith",
pullRequest: pullRequestCompact,
push: compact,
largeOwners: [
"agentic-cli",
"agentic-control-plane-http-plugin-ws",
"agentic-commands-doctor-platform",
"agentic-commands-doctor-sessions-cron",
"agentic-commands-doctor-sessions-cron-memory",
"agentic-commands-doctor-sessions-cron-sqlite",
"agentic-control-plane-auth-node",
"core-runtime-infra-storage-state",
"agentic-control-plane-runtime-ui-tools",
"core-runtime-infra-heartbeat-runner",
"core-runtime-infra-system-runtime",
"auto-reply-reply-agent-runner",
"agentic-commands-status-tools",
"agentic-commands-doctor",
"agentic-agents-core-isolated",
],
largeFiles: ["src/cli/update-dry-run-state.process.test.ts"],
},
{
name: "GitHub-hosted",
pullRequest: githubPullRequestCompact,
push: githubCompact,
largeOwners: [
"core-runtime-cron-service",
"agentic-agents-tools",
"core-runtime-infra-storage-state",
],
largeFiles: [],
},
{
name: "hybrid",
pullRequest: hybridPullRequestCompact,
push: hybridCompact,
largeOwners: [
"agentic-commands-doctor-config-state",
"core-runtime-cron-service",
"agentic-control-plane-runtime-shared-token",
"agentic-commands-doctor-platform",
],
largeFiles: gatewayFiles,
},
]) {
expect(profile.push.length, `${profile.name} excludes PR-only work`).toBeLessThan(
profile.pullRequest.length,
);
for (const plan of [profile.push, profile.pullRequest]) {
// Capacity belongs to the workload even when timing changes reorder rows.
const groups = plan.flatMap((shard) => shard.groups);
for (const owner of profile.largeOwners) {
const selected = groups.filter(
(group) => group.shard_name.replace(/-hosted-\d+$/u, "") === owner,
);
expect(selected.length, `${profile.name}: ${owner}`).toBeGreaterThan(0);
expect(
selected.every((group) => group.runner === DEFAULT_NODE_TEST_RUNNER),
`${profile.name}: ${owner}`,
).toBe(true);
}
for (const largeFile of profile.largeFiles) {
const selected = groups.filter((group) => group.includePatterns?.includes(largeFile));
expect(selected, largeFile).toHaveLength(1);
expect(selected[0]?.runner, largeFile).toBe(DEFAULT_NODE_TEST_RUNNER);
}
const supportGroups = groups.filter((group) =>
group.configs.includes(agentVitestProjectOwners.support.config),
);
expect(supportGroups.length, profile.name).toBeGreaterThan(0);
if (profile.name === "Blacksmith") {
for (const owner of [
"agentic-control-plane-agent-chat",
"agentic-gateway-core-3",
"core-runtime-infra-storage-state",
]) {
expect(
groups.filter((group) => group.shard_name.startsWith(`${owner}-hosted-`)).length,
owner,
).toBeGreaterThan(1);
}
expect(supportGroups).toEqual([
{
shard_name: "agentic-agents-support",
configs: [agentVitestProjectOwners.support.config],
requiresDist: false,
runner: EXTRA_LARGE_NODE_TEST_RUNNER,
},
]);
expect(plan.find((shard) => shard.groups.includes(supportGroups[0]!))).toMatchObject({
checkName: "checks-node-compact-large32-1",
shardName: "compact-large32-1",
groups: supportGroups,
runner: EXTRA_LARGE_NODE_TEST_RUNNER,
planConcurrency: 1,
timeoutMinutes: 120,
});
} else {
expect(supportGroups.every((group) => group.runner === DEFAULT_NODE_TEST_RUNNER)).toBe(
true,
);
if (profile.name === "GitHub-hosted") {
expect(plan.some((shard) => shard.runner === EXTRA_LARGE_NODE_TEST_RUNNER)).toBe(false);
expect(plan.every((shard) => shard.planConcurrency === 1)).toBe(true);
}
}
const cliProcessJobs = plan.filter((shard) =>
shard.groups.some((group) =>
group.configs.includes("test/vitest/vitest.cli-process.config.ts"),
),
);
const gatewayJobs = new Set(
gatewayFiles.map((file) => {
const gatewayJob = cliProcessJobs.find((shard) =>
shard.groups.some((group) => group.includePatterns?.includes(file)),
);
expect(gatewayJob?.groups, `${profile.name}: ${file}`).toEqual([
expect.objectContaining({ includePatterns: [file] }),
]);
return gatewayJob;
}),
);
const runtimeCliJobs = cliProcessJobs.filter((shard) => shard.pretestBuildMode);
for (const file of [
"src/cli/acp-cli-exit.process.test.ts",
"src/cli/update-dry-run-state.process.test.ts",
]) {
const job = expectDefined(
runtimeCliJobs.find((shard) =>
shard.groups.some((group) => group.includePatterns?.includes(file)),
),
`runtime CLI owner for ${file}`,
);
expect(job.pretestBuildMode).toBe(
job.groups.some((group) => group.includePatterns?.includes(PRIVATE_QA_TOOLING_TEST))
? "private-qa"
: "runtime",
);
expect(job.groups).toContainEqual(
expect.objectContaining({
pretestBuildMode: "runtime",
includePatterns: expect.arrayContaining([file]),
}),
);
}
for (const shard of cliProcessJobs) {
// Hosted runtime groups share one preparation within 210s; no-build
// gateway files retain 200s and complete hybrid CLI bins retain 250s.
const budget = gatewayJobs.has(shard)
? 200
: profile.name === "GitHub-hosted" && shard.pretestBuildMode
? 210
: profile.name === "hybrid" && isCombinedUnbuiltCliJob(shard)
? 250
: 150;
if (profile.name === "GitHub-hosted" && shard.pretestBuildMode) {
expect(shard.groups.every((group) => group.pretestBuildMode)).toBe(true);
}
expect(shard.predictedSeconds, profile.name).toBeLessThanOrEqual(budget);
}
expect(
plan.every(
(shard) =>
shard.groups.length > 0 && (usesTwoWorkerPacking(shard) || shard.groups.length <= 10),
),
).toBe(true);
expect(plan.every((shard) => Number.isFinite(shard.predictedSeconds))).toBe(true);
const names = plan.flatMap((shard) => shard.groups.map((group) => group.shard_name));
expect(new Set(names).size).toBe(names.length);
expect(new Set(plan.map((shard) => shard.checkName)).size).toBe(plan.length);
expect(new Set(plan.map((shard) => shard.shardName)).size).toBe(plan.length);
expect(plan.length, `${profile.name} row budget`).toBeLessThanOrEqual(80);
}
}
expect(compact.every((shard) => Array.isArray(shard.groups))).toBe(true);
expect(compact.every((shard) => usesTwoWorkerPacking(shard) || shard.groups.length <= 10)).toBe(
true,
);
expect(compact.some((shard) => shard.requiresDist)).toBe(true);
expect(
compact.every((shard) =>
shard.groups.every(
(group) =>
group.requiresDist === shard.requiresDist && group.runner === shard.groups[0]?.runner,
),
),
).toBe(true);
const jobOf = (name: string) =>
compact.findIndex((shard) => shard.groups.some((group) => group.shard_name === name));
expect(jobOf("agentic-agents-core-runner-embedded")).toBeGreaterThanOrEqual(0);
for (const prefix of [
"agentic-agents-embedded-base",
"agentic-gateway-core",
"core-runtime-media-ui",
"core-unit-src-security",
]) {
const jobs = [1, 2, 3].flatMap((stripe) => {
const owner = `${prefix}-${stripe}`;
const placements = compact.flatMap((shard, index) =>
shard.groups
.filter((group) => group.shard_name.replace(/-hosted-\d+$/u, "") === owner)
.map(() => index),
);
expect(placements.length, owner).toBeGreaterThan(0);
return placements;
});
expect(new Set(jobs).size).toBe(jobs.length);
}
// Cheap stripes may legally co-locate in one bin; only existence matters.
for (const owner of ["core-unit-fast-1", "core-unit-fast-2"]) {
expect(
compact.some((job) =>
job.groups.some((group) => group.shard_name.replace(/-hosted-\d+$/u, "") === owner),
),
).toBe(true);
}
// Timing-sensitive and runtime-building jobs stay serial. Ordinary Blacksmith
// placements may overlap only with the larger request; logical groups stay intact.
for (const shard of [
...pullRequestCompact,
...githubPullRequestCompact,
...hybridPullRequestCompact,
]) {
const exclusiveCount = shard.groups.filter((group) =>
isExclusiveCompactShardName(group.shard_name),
).length;
if (exclusiveCount > 0) {
expect(exclusiveCount).toBe(shard.groups.length);
expect(shard.planConcurrency).toBe(1);
}
const originalHybridJob = hybridPullRequestCompact.includes(shard)
? expectDefined(
hybridBeforePlacement.find((job) => job.checkName === shard.checkName),
"original hybrid runner anchor",
)
: undefined;
if (
originalHybridJob?.planConcurrency === 2 &&
shard.planConcurrency === 1 &&
!usesTwoWorkerPacking(shard)
) {
expect(shard.pretestBuildMode).toBe("runtime");
}
const promoted =
originalHybridJob !== undefined &&
originalHybridJob.pretestBuildMode === undefined &&
shard.pretestBuildMode === "runtime";
if (promoted) {
expect(shard.pretestBuildMode).toBe("runtime");
expect(shard.planConcurrency).toBe(1);
expect(exclusiveCount).toBe(0);
expect(shard.requiresDist).toBe(false);
expect(shard.env).toStrictEqual(originalHybridJob.env);
for (const original of originalHybridJob.groups) {
const retained = expectDefined(
shard.groups.find((group) => group.shard_name === original.shard_name),
"retained ordinary group",
);
// Already-serial recipients keep their job cap; only parallel recipients need group pins.
if (originalHybridJob.planConcurrency === 2) {
expect(retained).toEqual({
...original,
env: { OPENCLAW_VITEST_MAX_WORKERS: "2", ...original.env },
});
} else {
expect(retained).toStrictEqual(original);
}
}
}
if (
!githubPullRequestCompact.includes(shard) &&
!exclusiveCount &&
!shard.requiresDist &&
!promoted
) {
expect(
shard.groups.every(
(group) => Boolean(group.pretestBuildMode) === Boolean(shard.pretestBuildMode),
),
).toBe(true);
}
if (shard.planConcurrency === 2) {
expect(githubPullRequestCompact).not.toContain(shard);
expect(shard.runner).toBe(EXTRA_LARGE_NODE_TEST_RUNNER);
expect(shard.groups.length).toBeGreaterThan(1);
expect(shard.pretestBuildMode).toBeUndefined();
expect(shard.requiresDist).toBe(false);
} else {
expect(shard.planConcurrency).toBe(1);
const blacksmithTooling =
pullRequestCompact.includes(shard) &&
shard.groups.some((group) =>
group.configs.includes("test/vitest/vitest.tooling.config.ts"),
);
const nativeFullCli =
!githubPullRequestCompact.includes(shard) &&
shard.groups.some((group) => group.shard_name === "agentic-cli");
expect(shard.runner).toBe(
originalHybridJob
? originalHybridJob.runner
: blacksmithTooling ||
usesTwoWorkerPacking(shard) ||
shard.groups[0]?.runner === EXTRA_LARGE_NODE_TEST_RUNNER
? EXTRA_LARGE_NODE_TEST_RUNNER
: nativeFullCli
? "blacksmith-16vcpu-ubuntu-2404"
: shard.groups[0]?.runner,
);
}
}
expect(
pullRequestCompact.filter((shard) =>
shard.groups.some((group) => isExclusiveCompactShardName(group.shard_name)),
).length,
).toBeGreaterThan(0);
const hybridJobFor = (name: string) =>
hybridPullRequestCompact.find((shard) =>
shard.groups.some((group) => group.shard_name === name),
);
const hybridCliJob = hybridJobFor("agentic-cli");
const hybridToolingIsolatedJob = hybridJobFor("core-tooling-isolated");
expect(hybridCliJob).toBeDefined();
expect(hybridToolingIsolatedJob).toBeDefined();
expect(hybridToolingIsolatedJob?.checkName).not.toBe(hybridCliJob?.checkName);
const expectedEmbeddedAgentGroupNames = [
"agentic-agents-embedded-base-1",
"agentic-agents-embedded-base-2",
"agentic-agents-embedded-base-3",
"agentic-agents-embedded-incomplete-turn",
"agentic-agents-embedded-overflow-compaction",
"agentic-agents-embedded-run",
];
// Scoped configs drop unit-fast files and the shared live/e2e suffixes, so
// a striped owner covers only the files its config runs; the rest are inert.
const ownerScopedTestFiles = (owner: { dir: string; include: string[]; exclude: string[] }) => {
const unitFastFiles = new Set(
getUnitFastTestFilesForIncludePatterns(owner.include, { dir: owner.dir }),
);
return globSync(owner.include)
.map(toRepoPath)
.filter(
(file) =>
!unitFastFiles.has(file) &&
!file.endsWith(".live.test.ts") &&
!file.endsWith(".e2e.test.ts") &&
!owner.exclude.some((pattern) => matchesGlob(file, pattern)),
);
};
// The embedded composite expands into per-config groups and stripes its
// serial base config; whole-config runtime consumers may also be striped.
const embeddedBaseOwnerFiles = ownerScopedTestFiles(agentVitestProjectOwners.embedded);
const gatewayMethodsOwnerFiles = [
...listMatchedTestFiles(createGatewayMethodsVitestConfig({})),
...listMatchedTestFiles(createGatewayMethodsIsolatedVitestConfig({})),
];
const gatewayServerIsolatedOwnerFiles = [
...listMatchedTestFiles(createGatewayServerIsolatedVitestConfig({})),
...listMatchedTestFiles(createGatewayDatabaseWorkersVitestConfig({})),
];
const compactGroups = compact.flatMap((shard) => shard.groups);
const pullRequestCompactGroups = pullRequestCompact.flatMap((shard) => shard.groups);
const expectedGroupNames = base.flatMap((shard) =>
shard.shardName === "agentic-agents-embedded"
? expectedEmbeddedAgentGroupNames
: [shard.shardName],
);
const compactOwnerNames = (plan: typeof githubCompact) =>
new Set(
plan.flatMap((shard) =>
shard.groups.map((group) => group.shard_name.replace(/-hosted-\d+$/u, "")),
),
);
expect(compactOwnerNames(compact)).toEqual(
new Set(expectedGroupNames.filter((name) => !pushExcludedShardNames.has(name))),
);
expect(compactOwnerNames(pullRequestCompact)).toEqual(new Set(expectedGroupNames));
expect(compactOwnerNames(githubCompact)).toEqual(compactOwnerNames(compact));
expect(compactOwnerNames(githubPullRequestCompact)).toEqual(
compactOwnerNames(pullRequestCompact),
);
expect(compactOwnerNames(hybridCompact)).toEqual(compactOwnerNames(githubCompact));
expect(compactOwnerNames(hybridPullRequestCompact)).toEqual(
compactOwnerNames(githubPullRequestCompact),
);
for (const plan of [
compact,
pullRequestCompact,
githubCompact,
githubPullRequestCompact,
hybridCompact,
hybridPullRequestCompact,
]) {
for (const owner of base) {
const groups = plan
.flatMap((shard) => shard.groups)
.filter((group) => group.shard_name.startsWith(`${owner.shardName}-hosted-`));
if (groups.length === 0) {
continue;
}
const jobs = groups.map((group) => plan.findIndex((shard) => shard.groups.includes(group)));
for (const jobIndex of new Set(jobs)) {
const job = plan[jobIndex]!;
const siblings = groups.filter((group) => job.groups.includes(group));
if (siblings.length === 1) {
continue;
}
expect(job).toMatchObject({ planConcurrency: 1, requiresDist: false });
expect(job.predictedSeconds).toBeLessThanOrEqual(job.pretestBuildMode ? 150 : 250);
expect(job.groups.every((group) => isExclusiveCompactShardName(group.shard_name))).toBe(
true,
);
if (job.pretestBuildMode === undefined) {
expect([hybridCompact, hybridPullRequestCompact]).toContain(plan);
expect(isCombinedUnbuiltCliJob(job)).toBe(true);
} else {
expect(job.pretestBuildMode).toBe("runtime");
}
expect(
siblings.every((group) => /^agentic-cli-process-hosted-\d+$/u.test(group.shard_name)),
).toBe(true);
expect(siblings.every((group) => group.pretestBuildMode === job.pretestBuildMode)).toBe(
true,
);
}
const actual = groups.flatMap((group) => group.includePatterns ?? []);
expect(new Set(actual).size, owner.shardName).toBe(actual.length);
if (owner.includePatterns) {
expect(actual.toSorted(), owner.shardName).toEqual(owner.includePatterns.toSorted());
} else if (owner.shardName === "agentic-agents-support") {
const expected = ownerScopedTestFiles(agentVitestProjectOwners.support);
expect(actual.toSorted()).toEqual(expected.toSorted());
} else if (owner.shardName === "agentic-cli-process") {
expect(actual.toSorted()).toEqual(
listMatchedTestFiles(createCliProcessVitestConfig({})).toSorted(),
);
} else if (owner.shardName === "core-runtime-config") {
expect(actual.toSorted()).toEqual(
listMatchedTestFiles(createRuntimeConfigVitestConfig({})).toSorted(),
);
} else if (owner.shardName === "agentic-gateway-methods") {
expect(actual.toSorted()).toEqual(gatewayMethodsOwnerFiles.toSorted());
} else if (owner.shardName === "agentic-gateway-server-isolated") {
expect(actual.toSorted()).toEqual(gatewayServerIsolatedOwnerFiles.toSorted());
}
}
}
// Pushes omit only the explicit low-signal families; PR fallback retains
// their include-pattern coverage when special setup prevents targeting.
expect(
compactGroups
.flatMap((group) => group.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b)),
).toEqual(
base
.filter((shard) => !pushExcludedShardNames.has(shard.shardName))
.flatMap((shard) => shard.includePatterns ?? [])
.concat(
embeddedBaseOwnerFiles,
gatewayMethodsOwnerFiles,
gatewayServerIsolatedOwnerFiles,
listMatchedTestFiles(createCliProcessVitestConfig({})),
listMatchedTestFiles(createPluginSdkVitestConfig({})),
listMatchedTestFiles(createPluginSdkLightVitestConfig({})),
listMatchedTestFiles(createRuntimeConfigVitestConfig({})),
)
.toSorted((a, b) => a.localeCompare(b)),
);
expect(
pullRequestCompactGroups
.flatMap((group) => group.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b)),
).toEqual(
base
.flatMap((shard) => shard.includePatterns ?? [])
.concat(
embeddedBaseOwnerFiles,
gatewayMethodsOwnerFiles,
gatewayServerIsolatedOwnerFiles,
listMatchedTestFiles(createCliProcessVitestConfig({})),
listMatchedTestFiles(createPluginSdkVitestConfig({})),
listMatchedTestFiles(createPluginSdkLightVitestConfig({})),
listMatchedTestFiles(createRuntimeConfigVitestConfig({})),
)
.toSorted((a, b) => a.localeCompare(b)),
);
expect(compact.every((shard) => shard.groups.every((group) => group.configs.length > 0))).toBe(
true,
);
expect(
pullRequestCompact
.flatMap((shard) => shard.groups)
.find((group) => group.shard_name === "core-runtime-tui-pty")?.env,
).toEqual({
OPENCLAW_TUI_PTY_INCLUDE_LOCAL: "1",
OPENCLAW_TUI_PTY_USE_BUILT_CLI: "1",
// Timing-sensitive groups pin the worker budget while the job-level
// default scales with the runner class.
OPENCLAW_VITEST_MAX_WORKERS: "2",
});
expect(
compact.flatMap((shard) => shard.groups).find((group) => group.shard_name === "agentic-cli")
?.env,
).toEqual({
OPENCLAW_VITEST_MAX_WORKERS: "2",
});
for (const prefix of ["agentic-gateway-core", "core-runtime-media-ui"]) {
for (const suffix of ["1", "2", "3"]) {
const groups = compact
.flatMap((shard) => shard.groups)
.filter(
(group) => group.shard_name.replace(/-hosted-\d+$/u, "") === `${prefix}-${suffix}`,
);
expect(groups.length).toBeGreaterThan(0);
for (const group of groups) {
expect(group.env).toEqual({ OPENCLAW_VITEST_MAX_WORKERS: "2" });
}
}
}
expect(
compact
.flatMap((shard) => shard.groups)
.find((group) => group.shard_name === "core-runtime-media-ui-support")?.env,
).toEqual({ OPENCLAW_VITEST_MAX_WORKERS: "2" });
const startupCoreJob = compact.find((shard) =>
shard.groups.some((group) => group.shard_name === "agentic-control-plane-startup-core"),
);
expect(startupCoreJob?.runner).toBe(
usesTwoWorkerPacking(startupCoreJob)
? EXTRA_LARGE_NODE_TEST_RUNNER
: DEFAULT_NODE_TEST_RUNNER,
);
expect(
startupCoreJob?.groups.find(
(group) => group.shard_name === "agentic-control-plane-startup-core",
)?.runner,
).toBe(DEFAULT_NODE_TEST_RUNNER);
expect(
compact
.flatMap((shard) => shard.groups)
.find((group) => group.shard_name === "agentic-control-plane-startup-health-runtime")?.env,
).toEqual({ OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "60000" });
const largeJobs = compact.filter(
(shard) => !shard.requiresDist && shard.checkName.startsWith("checks-node-compact-large-"),
);
const smallJobs = compact.filter(
(shard) => !shard.requiresDist && shard.checkName.startsWith("checks-node-compact-small-"),
);
const extraLargeJobs = compact.filter(
(shard) => !shard.requiresDist && shard.checkName.startsWith("checks-node-compact-large32-"),
);
const distJobs = compact.filter((shard) => shard.requiresDist);
expect(largeJobs.length).toBeGreaterThan(0);
expect(smallJobs.length).toBeGreaterThan(0);
expect(extraLargeJobs).toHaveLength(1);
expect(distJobs).toHaveLength(1);
expect(largeJobs.length + smallJobs.length + extraLargeJobs.length + distJobs.length).toBe(
compact.length,
);
expect(
largeJobs.every((shard) =>
shard.groups.every((group) => group.runner === DEFAULT_NODE_TEST_RUNNER),
),
).toBe(true);
expect(
smallJobs.every((shard) =>
shard.groups.every((group) => group.runner === BUNDLED_NODE_TEST_RUNNER),
),
).toBe(true);
expect(extraLargeJobs[0]?.runner).toBe(EXTRA_LARGE_NODE_TEST_RUNNER);
for (const shard of [
...compact,
...pullRequestCompact,
...githubCompact,
...githubPullRequestCompact,
...hybridCompact,
...hybridPullRequestCompact,
]) {
for (const group of shard.groups) {
if (
!group.configs.some((config) => /vitest\.cli(?:-process)?\.config\.ts$/u.test(config))
) {
continue;
}
expect(isExclusiveCompactShardName(group.shard_name)).toBe(true);
expect(group.env?.OPENCLAW_VITEST_MAX_WORKERS).toBe("2");
if ((shard.predictedSeconds ?? 0) > 150) {
if (isCombinedUnbuiltCliJob(shard)) {
expect([...hybridCompact, ...hybridPullRequestCompact]).toContain(shard);
expect(shard.predictedSeconds).toBeLessThanOrEqual(250);
} else if (shard.pretestBuildMode && shard.groups.length > 1) {
expect([...githubCompact, ...githubPullRequestCompact]).toContain(shard);
expect(shard).toMatchObject({ planConcurrency: 1, requiresDist: false });
expect(shard.groups.every((entry) => entry.pretestBuildMode)).toBe(true);
expect(shard.predictedSeconds).toBeLessThanOrEqual(210);
} else {
expect(shard.groups).toHaveLength(1);
}
}
}
}
expect(compact).toEqual(
createNodeTestShardBundles({
includeReleaseOnlyPluginShards: false,
compactMode: "push",
}),
);
const embeddedAgentGroups = compact
.flatMap((shard) => shard.groups)
.filter((group) => group.shard_name.startsWith("agentic-agents-embedded-"));
expect(embeddedAgentGroups.map((group) => group.shard_name).toSorted()).toEqual(
expectedEmbeddedAgentGroupNames,
);
expect(
compact.some((shard) =>
shard.groups.some((group) => group.shard_name === "agentic-agents-embedded"),
),
).toBe(false);
// The base config repeats once per stripe; its files stay partitioned below.
expect(new Set(embeddedAgentGroups.flatMap((group) => group.configs))).toEqual(
new Set(embeddedAgentVitestProjectOwners.map((owner) => owner.config)),
);
expect(
embeddedAgentGroups.every(
(group) => group.env?.OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS === "660000",
),
).toBe(true);
// The base config runs serially with a shared module graph, so its stripes
// must partition the owner's files: a repeat re-runs a suite, a miss drops it.
const embeddedBaseGroups = embeddedAgentGroups.filter((group) =>
/^agentic-agents-embedded-base-\d+$/u.test(group.shard_name),
);
const embeddedBaseFiles = embeddedBaseGroups.flatMap((group) => group.includePatterns ?? []);
expect(embeddedBaseGroups).toHaveLength(3);
expect(
embeddedBaseGroups.every(
(group) => group.configs[0] === agentVitestProjectOwners.embedded.config,
),
).toBe(true);
expect(new Set(embeddedBaseFiles).size).toBe(embeddedBaseFiles.length);
expect(embeddedBaseFiles.toSorted((a, b) => a.localeCompare(b))).toEqual(
embeddedBaseOwnerFiles.toSorted((a, b) => a.localeCompare(b)),
);
expect(
compact
.filter((shard) => shard.groups.some((group) => !group.includePatterns))
.every((shard) => shard.timeoutMinutes === 120),
).toBe(true);
// Whole-config groups now pack into the same runtime-balanced bins as
// include-pattern groups; the separate "-whole-" job class is gone.
expect(compact.some((shard) => shard.checkName.includes("-whole-"))).toBe(false);
expect(
compact.some((shard) => shard.groups.some((group) => group.shard_name === "core-tooling")),
).toBe(false);
expect(
pullRequestCompact
.flatMap((shard) => shard.groups)
.find((group) => group.shard_name === "core-tooling-isolated"),
).toEqual(
expect.objectContaining({
configs: [
"test/vitest/vitest.tooling-docker.config.ts",
"test/vitest/vitest.tooling-isolated.config.ts",
],
}),
);
// The docker helper config rides with the isolated shard on both plans;
// no standalone core-tooling-docker group remains.
expect(
pullRequestCompact
.flatMap((shard) => shard.groups)
.some((group) => group.shard_name === "core-tooling-docker"),
).toBe(false);
const toolingGroups = pullRequestCompactGroups.filter((group) =>
/^core-tooling-\d+(?:-hosted-\d+)?$/u.test(group.shard_name),
);
const toolingFiles = toolingGroups.flatMap((group) => group.includePatterns ?? []);
expect(
new Set(
[...compactOwnerNames(pullRequestCompact)].filter((name) =>
/^core-tooling-\d+$/u.test(name),
),
),
).toEqual(new Set(expectedToolingOwnerNames));
expect(
toolingGroups.every((group) => group.configs[0] === "test/vitest/vitest.tooling.config.ts"),
).toBe(true);
expect(new Set(toolingFiles).size).toBe(toolingFiles.length);
expect(toolingFiles.toSorted((a, b) => a.localeCompare(b))).toEqual(listAllToolingTestFiles());
}
it.each(plannerHosts)(
"preserves coverage and execution policies with committed compact measurements ($label)",
checkCommittedCompactPolicies,
);
it("splits the slow core unit shards while keeping paired source/security coverage", () => {
const coreUnitShards = defaultShards
.filter((shard) => shard.shardName.startsWith("core-unit-"))
.map((shard) => ({
configs: shard.configs,
requiresDist: shard.requiresDist,
shardName: shard.shardName,
}));
expect(coreUnitShards).toEqual([
{
configs: ["test/vitest/vitest.unit-fast.config.ts"],
requiresDist: false,
shardName: "core-unit-fast-1",
},
{
configs: ["test/vitest/vitest.unit-fast.config.ts"],
requiresDist: false,
shardName: "core-unit-fast-2",
},
{
configs: ["test/vitest/vitest.unit-fast-isolated.config.ts"],
requiresDist: false,
shardName: "core-unit-fast-isolated",
},
{
configs: ["test/vitest/vitest.unit-fast-fake-timers.config.ts"],
requiresDist: false,
shardName: "core-unit-fast-fake-timers",
},
{
configs: ["test/vitest/vitest.unit-src.config.ts"],
requiresDist: false,
shardName: "core-unit-src-security-1",
},
{
configs: ["test/vitest/vitest.unit-src.config.ts"],
requiresDist: false,
shardName: "core-unit-src-security-2",
},
{
configs: ["test/vitest/vitest.unit-src.config.ts"],
requiresDist: false,
shardName: "core-unit-src-security-3",
},
{
configs: ["test/vitest/vitest.unit-security.config.ts"],
requiresDist: false,
shardName: "core-unit-src-security-support",
},
{
configs: ["test/vitest/vitest.unit-support.config.ts"],
requiresDist: false,
shardName: "core-unit-support",
},
]);
});
it("keeps Doctor session SQLite owners complete and isolated", () => {
const ownerNames = [
"agentic-commands-doctor-sessions-cron",
"agentic-commands-doctor-sessions-cron-memory",
"agentic-commands-doctor-sessions-cron-sqlite",
];
const base = createNodeTestShards({ includeReleaseOnlyPluginShards: false });
const commandShards = base.filter((shard) =>
shard.configs.includes("test/vitest/vitest.commands.config.ts"),
);
const owners = new Map(
commandShards
.filter((shard) => ownerNames.includes(shard.shardName))
.map((shard) => [shard.shardName, shard.includePatterns]),
);
expect(owners.get("agentic-commands-doctor-sessions-cron-memory")).toEqual([
"src/commands/doctor-session-sqlite.memory.test.ts",
]);
expect(owners.get("agentic-commands-doctor-sessions-cron-sqlite")).toEqual([
"src/commands/doctor-session-sqlite.test.ts",
]);
expect(owners.get("agentic-commands-doctor-sessions-cron")).toEqual([
"src/commands/doctor-heartbeat-cadence-migration.test.ts",
"src/commands/doctor-heartbeat-scratch-migration.test.ts",
"src/commands/doctor-heartbeat-session-target.test.ts",
"src/commands/doctor-heartbeat-source-archive.test.ts",
"src/commands/doctor-heartbeat-task-migration.test.ts",
"src/commands/doctor-session-canonical-keys.memory.test.ts",
"src/commands/doctor-session-canonical-keys.retention.test.ts",
"src/commands/doctor-session-delivery-state.test.ts",
"src/commands/doctor-session-exec-policy.test.ts",
"src/commands/doctor-session-incognito-key-repair.test.ts",
"src/commands/doctor-session-snapshots.test.ts",
"src/commands/doctor-session-sqlite-readers.test.ts",
"src/commands/doctor-session-sqlite.codex-binding.test.ts",
"src/commands/doctor-session-sqlite.deferred-plugin.test.ts",
"src/commands/doctor-session-sqlite.discovery.test.ts",
"src/commands/doctor-session-sqlite.retained-source-verification.test.ts",
"src/commands/doctor-session-sqlite.shared-orphan.test.ts",
"src/commands/doctor-session-sqlite.shared-store.test.ts",
"src/commands/doctor-session-state-providers.test.ts",
"src/commands/doctor-session-title-repair.test.ts",
"src/commands/doctor-session-transcript-headers.test.ts",
"src/commands/doctor-session-transcript-labels.test.ts",
"src/commands/doctor-session-transcripts.incident.test.ts",
"src/commands/doctor-session-transcripts.sqlite.test.ts",
"src/commands/doctor-session-transcripts.test.ts",
"src/commands/doctor-session-worktree-workspace.test.ts",
]);
const commandFiles = commandShards.flatMap((shard) => shard.includePatterns ?? []).toSorted();
expect(commandFiles).toEqual(listMatchedTestFiles(createCommandsVitestConfig({})));
expect(new Set(commandFiles).size).toBe(commandFiles.length);
for (const compactMode of ["push", "pull-request"] as const) {
const plan = getCommittedCompactPlan(compactMode, "blacksmith");
const jobs = ownerNames.map((name) =>
plan.findIndex((shard) => shard.groups.some((group) => group.shard_name === name)),
);
expect(jobs.every((job) => job >= 0)).toBe(true);
expect(new Set(jobs).size).toBe(jobs.length);
expect(
plan
.flatMap((shard) => shard.groups)
.filter((group) => ownerNames.includes(group.shard_name))
.every((group) => group.runner === DEFAULT_NODE_TEST_RUNNER),
).toBe(true);
}
const families = [ownerNames, [1, 2, 3].map((part) => `agentic-gateway-core-${part}`)];
const fixtureConfigs = new Set(
base
.filter((shard) => families.some((family) => family.includes(shard.shardName)))
.flatMap((shard) => shard.configs),
);
const originalShards = fullSuiteVitestShards.slice();
const fixtureShards = originalShards
.map((shard) => ({
...shard,
projects: shard.projects.filter((config) => fixtureConfigs.has(config)),
}))
.filter((shard) => shard.projects.length > 0);
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...fixtureShards);
try {
// Affordable descendants must also stay apart from their unsplit ancestors'
// siblings: an immediate-selector-only rule loses the Doctor/giant boundary.
const fixtureTimings = Object.fromEntries(
base
.filter((shard) => shard.configs.some((config) => fixtureConfigs.has(config)))
.map((shard) => [shard.shardName, 1]),
);
vi.spyOn(testTimings, "readCompactGroupTimings").mockImplementation((profile) => ({
...fixtureTimings,
...Object.fromEntries(
families.flatMap((family) =>
family.map((name, index) => [
name,
index === 0 ? (profile === "github" ? 400 : 100) : 10,
]),
),
),
}));
const nestedPlan = createNodeTestShardBundles({
compactMode: "pull-request",
includeReleaseOnlyPluginShards: false,
runnerBackend: "hybrid",
});
for (const family of families) {
const placements = nestedPlan.flatMap((job, jobIndex) =>
job.groups
.filter((group) => family.includes(group.shard_name.replace(/-hosted-\d+$/u, "")))
.map((group) => ({ group, jobIndex })),
);
expect(
placements.filter(({ group }) => group.shard_name.startsWith(`${family[0]}-hosted-`)),
).toHaveLength(3);
expect(new Set(placements.map(({ jobIndex }) => jobIndex)).size, family[0]).toBe(
placements.length,
);
for (const name of family) {
const actual = placements
.filter(({ group }) => group.shard_name.replace(/-hosted-\d+$/u, "") === name)
.flatMap(({ group }) => group.includePatterns ?? []);
expect(actual.toSorted(), name).toEqual(
base.find((shard) => shard.shardName === name)?.includePatterns?.toSorted(),
);
}
}
} finally {
fullSuiteVitestShards.splice(0, fullSuiteVitestShards.length, ...originalShards);
}
});
it("partitions each giant compact group across three deterministic stripes", () => {
const env = { ...process.env, OPENCLAW_VITEST_INCLUDE_FILE: undefined };
const cases = [
{
stripeConfigs: [createUiVitestConfig(env)],
supportConfigs: [
createMediaVitestConfig(env),
createMediaUnderstandingVitestConfig(env),
createTuiVitestConfig(env),
createUiIsolatedVitestConfig(env),
createUiTimingVitestConfig(env),
createWizardVitestConfig(env),
],
prefix: "core-runtime-media-ui",
},
{
stripeConfigs: [createGatewayCoreVitestConfig(env), createGatewayClientVitestConfig(env)],
supportConfigs: [],
prefix: "agentic-gateway-core",
},
{
stripeConfigs: [
createUnitVitestConfigWithOptions(env, {
name: "unit-src",
includePatterns: ["src/**/*.test.ts"],
extraExcludePatterns: ["src/acp/**", "src/security/**"],
}),
],
supportConfigs: [
createUnitVitestConfigWithOptions(env, {
name: "unit-security",
includePatterns: ["src/security/**/*.test.ts"],
passWithNoTests: true,
}),
],
prefix: "core-unit-src-security",
},
];
const shards = defaultShards;
for (const { prefix, stripeConfigs, supportConfigs } of cases) {
const stripes = shards.filter(
(shard) => /^.+-\d+$/u.test(shard.shardName) && shard.shardName.startsWith(`${prefix}-`),
);
// Files needing a pretest build are pulled into a sibling `-runtime`
// shard so only one job builds; coverage must still be complete across
// the stripes plus that shard.
const coverageShards = shards.filter((shard) => shard.shardName.startsWith(`${prefix}-`));
const actual = coverageShards
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
const expected = stripeConfigs
.flatMap((config) => listMatchedTestFiles(config))
.toSorted((a, b) => a.localeCompare(b));
expect(stripes.map((stripe) => stripe.shardName)).toEqual([
`${prefix}-1`,
`${prefix}-2`,
`${prefix}-3`,
]);
expect(stripes.every((stripe) => (stripe.includePatterns?.length ?? 0) > 0)).toBe(true);
expect(new Set(actual).size).toBe(actual.length);
expect(actual).toEqual(expected);
const support = shards.find((shard) => shard.shardName === `${prefix}-support`);
if (supportConfigs.length === 0) {
expect(support).toBeUndefined();
} else {
expect(support?.includePatterns).toBeUndefined();
expect(support?.configs).toHaveLength(supportConfigs.length);
}
}
});
it.each(["blacksmith", "github", "hybrid"])(
"runs timing budgets once through their support owner in %s hosted plans",
(runnerBackend) => {
const groups = getCommittedCompactPlan("pull-request", runnerBackend).flatMap(
(job) => job.groups,
);
const owners = groups.filter((group) =>
group.configs.includes("test/vitest/vitest.ui-timing.config.ts"),
);
expect(owners).toHaveLength(1);
expect(owners[0]?.shard_name).toBe("core-runtime-media-ui-support");
expect(owners[0]?.includePatterns).toBeUndefined();
const stripedFiles = groups.flatMap((group) => group.includePatterns ?? []);
for (const file of uiTimingTestFiles) {
// Shared UI excludes these files; leaving them in its stripes silently skips them.
expect(stripedFiles).not.toContain(file);
}
expect(
listMatchedTestFiles(
createUiTimingVitestConfig({ OPENCLAW_VITEST_INCLUDE_FILE: undefined }),
),
).toEqual(uiTimingTestFiles);
},
);
it("names the node shard checks as core test lanes", () => {
const shards = defaultShards;
expect(shards).not.toHaveLength(0);
expect(shards.map((shard) => shard.checkName)).toEqual(
shards.map((shard) =>
shard.shardName.startsWith("core-unit-")
? `checks-node-core-${shard.shardName.slice("core-unit-".length)}`
: `checks-node-${shard.shardName}`,
),
);
});
it("keeps extension, bundled, contracts, and channels configs out of the core node lane", () => {
const configs = defaultShards.flatMap((shard) => shard.configs);
expect(configs).not.toContain("test/vitest/vitest.channels.config.ts");
expect(configs).not.toContain("test/vitest/vitest.contracts.config.ts");
expect(configs).not.toContain("test/vitest/vitest.bundled.config.ts");
expect(configs).not.toContain("test/vitest/vitest.full-extensions.config.ts");
expect(configs).not.toContain("test/vitest/vitest.extension-telegram.config.ts");
});
it("keeps compact agentic config ownership aligned with the full agentic project set", () => {
const fullAgenticShard = fullSuiteVitestShards.find((shard) => shard.name === "agentic");
const intentionallyExcludedConfigs = new Set(["test/vitest/vitest.channels.config.ts"]);
const expectedConfigs = (fullAgenticShard?.projects ?? [])
.filter((config) => !intentionallyExcludedConfigs.has(config))
.toSorted((left, right) => left.localeCompare(right));
const actualConfigs = [
...new Set(
defaultShards
.filter((shard) => shard.shardName.startsWith("agentic-"))
.flatMap((shard) => shard.configs),
),
].toSorted((left, right) => left.localeCompare(right));
expect(fullAgenticShard).toBeDefined();
expect(actualConfigs).toEqual(expectedConfigs);
});
it("marks only dist-dependent shards for built artifact restore", () => {
const requiresDistShardNames = defaultShards
.filter((shard) => shard.requiresDist)
.map((shard) => shard.shardName);
expect(requiresDistShardNames).toEqual(["core-support-boundary", "core-runtime-tui-pty"]);
});
it("preserves runtime preparation and core-only ownership in full and compact plans", () => {
const qaConfig = "test/vitest/vitest.extension-qa.config.ts";
const doctorRuntimeTargets = [
"src/commands/doctor-config-flow.legacy-composition.test.ts",
"src/commands/doctor-config-preflight.process.test.ts",
"src/commands/doctor-config-preflight.refusal.process.test.ts",
"src/commands/doctor-config-preflight.v17-atomicity.process.test.ts",
];
const runtimeTargets = [
"test/e2e/qa-lab/runtime/gateway-support-export-runtime.test.ts",
"src/infra/update-managed-service-handoff-lifecycle.test.ts",
...doctorRuntimeTargets,
"src/commands/doctor-plugin-install-config.process.test.ts",
"src/gateway/gateway-active-memory.test.ts",
"src/gateway/gateway-auth-recovery.test.ts",
"src/gateway/gateway-concurrent-streams.test.ts",
"src/gateway/gateway-cron-process-identity.windows.test.ts",
"src/gateway/gateway-route-model-reuse.test.ts",
"src/gateway/gateway-ssh-upload-signal.test.ts",
"src/gateway/server.config-patch.test.ts",
];
const databaseWorkerFiles = new Set(
listMatchedTestFiles(createGatewayDatabaseWorkersVitestConfig({})),
);
const ownsRuntimeTarget = (
group: { configs: string[]; includePatterns?: string[] },
file: string,
) =>
group.includePatterns
? group.includePatterns.includes(file)
: group.configs.includes("test/vitest/vitest.gateway-database-workers.config.ts") &&
databaseWorkerFiles.has(file);
const full = defaultShards;
const compact = createNodeTestShardBundles({ compact: true, compactMode: "pull-request" });
for (const shards of [full, compact]) {
expect(
shards.flatMap((shard) =>
"configs" in shard ? shard.configs : shard.groups.flatMap((group) => group.configs),
),
).not.toContain(qaConfig);
for (const runtimeTarget of [...runtimeTargets, PRIVATE_QA_TOOLING_TEST]) {
const owner = expectDefined(
shards.find((shard) =>
("configs" in shard ? [shard] : shard.groups).some((group) =>
ownsRuntimeTarget(group, runtimeTarget),
),
),
`runtime owner for ${runtimeTarget}`,
);
const groups = "configs" in owner ? [owner] : owner.groups;
// A shared build takes the strongest requirement of its complete selection.
const containsPrivateQa = groups.some((group) =>
group.includePatterns?.includes(PRIVATE_QA_TOOLING_TEST),
);
expect(owner.pretestBuildMode, runtimeTarget).toBe(
containsPrivateQa ? "private-qa" : "runtime",
);
const group = groups.find((entry) => ownsRuntimeTarget(entry, runtimeTarget));
expect(group?.pretestBuildMode, runtimeTarget).toBe(
group?.includePatterns?.includes(PRIVATE_QA_TOOLING_TEST) ? "private-qa" : "runtime",
);
}
}
const doctorName = "agentic-commands-doctor-config-state";
const doctor = full.find((shard) => shard.shardName === doctorName)!;
const placements = compact.flatMap((job, jobIndex) =>
job.groups
.filter((group) => group.shard_name.replace(/-hosted-\d+$/u, "") === doctorName)
.map((group) => ({ group, job, jobIndex })),
);
const runtimeParts = placements.filter(({ group }) => group.pretestBuildMode === "runtime");
expect(runtimeParts).toHaveLength(1);
expect(runtimeParts[0]?.job.planConcurrency).toBe(1);
expect(runtimeParts[0]?.group.includePatterns).toEqual(doctorRuntimeTargets);
expect(placements.some(({ group }) => group.pretestBuildMode === undefined)).toBe(true);
expect(placements.flatMap(({ group }) => group.includePatterns ?? []).toSorted()).toEqual(
doctor.includePatterns?.toSorted(),
);
expect(new Set(placements.map(({ jobIndex }) => jobIndex)).size).toBe(placements.length);
for (const { group } of placements) {
expect(group.configs).toEqual(doctor.configs);
expect(group.env).toEqual(doctor.env);
expect(group.requiresDist).toBe(doctor.requiresDist);
expect(group.runner).toBe(BUNDLED_NODE_TEST_RUNNER);
}
});
it("splits tooling checks independently from built artifacts", () => {
const compilerFixture = "test/scripts/write-unified-entry-dts.test.ts";
const toolingShards = defaultShards.filter((shard) =>
shard.shardName.startsWith("core-tooling"),
);
const compilerParent = toolingShards.find((shard) =>
shard.includePatterns?.includes(compilerFixture),
)!;
for (const runnerBackend of ["blacksmith", "hybrid", "github"]) {
const jobs = getCommittedCompactPlan("pull-request", runnerBackend);
const owner = jobs.find((job) =>
job.groups.some((group) => group.includePatterns?.includes(compilerFixture)),
);
// This fixture runs the real full-build guard, which needs more than the
// available heap observed inside a small runner's retained tooling graph.
expect(owner?.runner, runnerBackend).toBe(
runnerBackend === "github" ? DEFAULT_NODE_TEST_RUNNER : EXTRA_LARGE_NODE_TEST_RUNNER,
);
const precise = createSelectedNodeTestShardBundles([compilerFixture], { runnerBackend });
const preciseOwner = precise?.find((job) =>
job.groups.some((group) => group.includePatterns?.includes(compilerFixture)),
);
expect(preciseOwner?.runner, runnerBackend).toBe(owner?.runner);
expect(preciseOwner?.planConcurrency).toBe(1);
expect(preciseOwner?.groups).toEqual([
expect.objectContaining({
includePatterns: [compilerFixture],
env: expect.objectContaining({ OPENCLAW_VITEST_MAX_WORKERS: "2" }),
}),
]);
expect(
jobs
.flatMap((job) => job.groups)
.filter((group) => group.includePatterns?.includes(compilerFixture)),
).toHaveLength(1);
if (runnerBackend !== "blacksmith") {
const siblings = jobs
.flatMap((job) => job.groups)
.filter(
(group) =>
group.shard_name.startsWith(`${compilerParent.shardName}-hosted-`) &&
!group.includePatterns?.includes(compilerFixture),
);
expect(siblings.length).toBeGreaterThan(0);
expect(siblings.every((group) => group.runner === BUNDLED_NODE_TEST_RUNNER)).toBe(true);
}
}
const sdkFixture = "test/scripts/write-plugin-sdk-entry-dts.test.ts";
for (const runnerBackend of ["blacksmith", "hybrid", "github"]) {
const sdkJobs = createSelectedNodeTestShardBundles([sdkFixture], { runnerBackend });
const fullOwner = getCommittedCompactPlan("pull-request", runnerBackend).find((job) =>
job.groups.some((group) => group.includePatterns?.includes(sdkFixture)),
);
const selectedOwner = sdkJobs?.find((job) =>
job.groups.some((group) => group.includePatterns?.includes(sdkFixture)),
);
for (const owner of [fullOwner, selectedOwner]) {
expect(owner?.runner, runnerBackend).toBe(
runnerBackend === "github" ? BUNDLED_NODE_TEST_RUNNER : EXTRA_LARGE_NODE_TEST_RUNNER,
);
expect(owner?.planConcurrency).toBe(1);
}
expect(selectedOwner?.groups).toEqual([
expect.objectContaining({
includePatterns: [sdkFixture],
env: expect.objectContaining({ OPENCLAW_VITEST_MAX_WORKERS: "2" }),
}),
]);
}
const stripes = toolingShards.filter((shard) => /^core-tooling-\d+$/u.test(shard.shardName));
expect(stripes).toHaveLength(16);
for (const stripe of stripes) {
expect(stripe.configs).toEqual(["test/vitest/vitest.tooling.config.ts"]);
expect(stripe.requiresDist).toBe(false);
expect(stripe.includePatterns?.length ?? 0).toBeGreaterThan(0);
}
// Stripes partition the tooling files: no overlap, nothing dropped.
const stripeFiles = stripes.flatMap((stripe) => stripe.includePatterns ?? []);
expect(new Set(stripeFiles).size).toBe(stripeFiles.length);
const processProofFiles = [
"test/scripts/ci-git-owner.test.ts",
"test/scripts/managed-child-process.test.ts",
"test/scripts/vitest-worker-artifacts.test.ts",
"test/scripts/vitest-worker-artifacts.transforms.test.ts",
"test/scripts/openclaw-performance-git-lifecycle.test.ts",
"test/scripts/ci-linux-git.test.ts",
"test/scripts/pr-merge-outcome.test.ts",
];
const processProofStripes = processProofFiles.map(
(file) => stripes.find((stripe) => stripe.includePatterns?.includes(file))?.shardName,
);
expect(processProofStripes).not.toContain(undefined);
expect(new Set(processProofStripes).size).toBe(processProofFiles.length);
const runtimeStripe = stripes.find((stripe) =>
stripe.includePatterns?.includes(
"test/e2e/qa-lab/runtime/gateway-support-export-runtime.test.ts",
),
);
expect(runtimeStripe?.pretestBuildMode).toBe(
runtimeStripe?.includePatterns?.includes(PRIVATE_QA_TOOLING_TEST) ? "private-qa" : "runtime",
);
expect(
toolingShards.find((shard) => shard.shardName === "core-tooling-isolated"),
).toMatchObject({
configs: [
"test/vitest/vitest.tooling-docker.config.ts",
"test/vitest/vitest.tooling-isolated.config.ts",
],
requiresDist: false,
});
});
it("keeps non-tooling placement checks independent of singleton tooling split names", () => {
const anchor: CompactNodeTestShard = {
checkName: "checks-node-compact-small-1",
shardName: "compact-small-1",
runner: BUNDLED_NODE_TEST_RUNNER,
requiresDist: false,
planConcurrency: 1,
groups: [
{
shard_name: "core-unit-fast",
configs: ["test/vitest/vitest.unit-fast.config.ts"],
requiresDist: false,
runner: BUNDLED_NODE_TEST_RUNNER,
},
],
};
// Projection fixture from the real six-file inventory proof: the 330-second
// file stays alone with the same execution policy when its stripe is named.
const unsplit: CompactNodeTestShard = {
...anchor,
checkName: "checks-node-compact-small-22",
shardName: "compact-small-22",
predictedSeconds: 330,
groups: [
{
shard_name: "core-tooling-1",
configs: ["test/vitest/vitest.tooling.config.ts"],
includePatterns: ["test/scripts/pr-merge-outcome.test.ts"],
requiresDist: false,
runner: BUNDLED_NODE_TEST_RUNNER,
env: { OPENCLAW_VITEST_MAX_WORKERS: "2" },
},
],
};
const split: CompactNodeTestShard = {
...unsplit,
checkName: "checks-node-compact-small-33",
shardName: "compact-small-33",
groups: [{ ...unsplit.groups[0]!, shard_name: "core-tooling-1-hosted-1" }],
};
const original = structuredClone({ anchor, unsplit, split });
const expected = [
{
groups: ["core-unit-fast"],
planConcurrency: 1,
pretestBuildMode: undefined,
requiresDist: false,
runner: BUNDLED_NODE_TEST_RUNNER,
},
];
expect(nonToolingPlacement([anchor, unsplit])).toEqual(expected);
expect(nonToolingPlacement([anchor, split])).toEqual(expected);
expect(nonToolingPlacement([split])).not.toEqual(expected);
for (const changed of [
{ ...anchor, runner: DEFAULT_NODE_TEST_RUNNER },
{ ...anchor, planConcurrency: 2 },
{ ...anchor, requiresDist: true },
{ ...anchor, pretestBuildMode: "runtime" as const },
]) {
expect(nonToolingPlacement([changed, split])).not.toEqual(expected);
}
expect({ anchor, unsplit, split }).toEqual(original);
});
it("keeps precise tooling selection through hosted overflow refusal", () => {
const tooling = defaultShards.filter((shard) => /^core-tooling-\d+$/u.test(shard.shardName));
const selected = tooling.flatMap((shard) => shard.includePatterns ?? []).slice(0, 96);
expect(selected).toHaveLength(96);
vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue(
Object.fromEntries(tooling.map((shard) => [shard.shardName, 20_000])),
);
// Every selected file is now indivisible above the admission cap. Overflow
// must retain these 96 files plus two dist owners, never resurrect the full suite.
expect(() => createSelectedNodeTestShardBundles(selected, { runnerBackend: "github" })).toThrow(
"exceeds 80 jobs (98 planned)",
);
});
it("keeps the private runtime prerequisite on precise tooling readers", () => {
const shards = createSelectedNodeTestShardBundles([PRIVATE_QA_TOOLING_TEST]);
expect(shards).not.toBeNull();
const readers = shards?.filter((shard) => !shard.requiresDist) ?? [];
expect(readers).toHaveLength(1);
expect(readers[0]?.pretestBuildMode).toBe("private-qa");
expect(readers[0]?.planConcurrency).toBe(1);
expect(readers[0]?.groups.flatMap((group) => group.includePatterns ?? [])).toEqual([
PRIVATE_QA_TOOLING_TEST,
]);
});
it.each(["blacksmith", "github", "hybrid"])(
"shares one prepared runtime across affordable %s tooling groups",
(runnerBackend) => {
const targets = [
PRIVATE_QA_TOOLING_TEST,
"test/e2e/qa-lab/runtime/gateway-support-export-runtime.test.ts",
];
vi.spyOn(testTimings, "readCompactGroupTimings").mockReturnValue(
Object.fromEntries(defaultShards.map((shard) => [shard.shardName, 1])),
);
const plan = createSelectedNodeTestShardBundles(targets, { runnerBackend });
expect(plan).not.toBeNull();
const readers = plan!.filter((shard) => !shard.requiresDist);
expect(readers).toHaveLength(1);
expect(readers[0]).toMatchObject({ pretestBuildMode: "private-qa", planConcurrency: 1 });
expect(readers[0]!.predictedSeconds).toBeLessThanOrEqual(
runnerBackend === "github" ? 210 : 150,
);
expect(readers[0]!.groups.every((group) => group.pretestBuildMode)).toBe(true);
expect(readers[0]!.groups.flatMap((group) => group.includePatterns ?? []).toSorted()).toEqual(
targets.toSorted(),
);
},
);
it("packs serial groups within both the time and group-count budgets", () => {
const groups = [34, 33, 33, 32, 32, 32, 10, 21, 17, 17, 15, 10, 8, 7, 7, 7, 6, 5, 5];
const bins = packNodeTestGroups(
groups,
(bin, group) => bin.length < 10 && bin.reduce((sum, value) => sum + value, group) <= 210,
true,
);
expect(bins).toHaveLength(2);
expect(bins.flat().toSorted((a, b) => a - b)).toEqual(groups.toSorted((a, b) => a - b));
for (const bin of bins) {
expect(bin.length).toBeLessThanOrEqual(10);
expect(bin.reduce((sum, value) => sum + value, 0)).toBeLessThanOrEqual(210);
}
});
it.each(["runner", "dist", "preparation"] as const)(
"keeps group exchanges within the owner's %s boundary",
(boundary) => {
const groups = [144, 120, 72, 48, 48, 48].map((seconds, index) => ({
index,
seconds,
runner: boundary === "runner" && index === 2 ? "other" : "shared",
requiresDist: boundary === "dist" && index === 2,
preparation: boundary === "preparation" && index === 2,
}));
const bins = packNodeTestGroups(
groups,
(bin, group) =>
!group.preparation &&
bin.every(
(entry) =>
!entry.preparation &&
entry.runner === group.runner &&
entry.requiresDist === group.requiresDist,
) &&
bin.reduce((sum, entry) => sum + entry.seconds, group.seconds) <= 240,
true,
);
expect(bins).toHaveLength(3);
expect(bins.flat().toSorted((a, b) => a.index - b.index)).toEqual(groups);
expect(bins.find((bin) => bin.some((group) => group.index === 2))).toEqual([groups[2]]);
for (const bin of bins) {
expect(bin.reduce((sum, group) => sum + group.seconds, 0)).toBeLessThanOrEqual(240);
}
},
);
it("keeps hosted tooling within the GitHub job cap when its inventory grows", async () => {
const unitFastPaths = await vi.importActual<
typeof import("../vitest/vitest.unit-fast-paths.mjs")
>("../vitest/vitest.unit-fast-paths.mjs");
// Fifty-six full-budget anchors leave 24 of the 80 jobs for tooling.
const anchors = Array.from({ length: 56 }, (_, index) => ({
config: `test/vitest/vitest.capacity-anchor-${index}.config.ts`,
name: `capacity-anchor-${index}`,
projects: [`test/vitest/vitest.capacity-anchor-${index}.config.ts`],
}));
const fixtureShards = [
...anchors,
{
config: "test/vitest/vitest.full-core-tooling.config.ts",
name: "core-tooling",
projects: ["test/vitest/vitest.tooling.config.ts"],
},
];
// Ten files per tooling family exercise full stripes and packable tails; the compiler
// fixture puts a stronger runner in a tail that can absorb smaller families.
const fixtureFiles = [
...Array.from(
{ length: 160 },
(_, index) => `test/scripts/fixture-${String(index).padStart(3, "0")}.test.ts`,
),
"test/scripts/write-unified-entry-dts.test.ts",
];
const fixtureTimings = Object.fromEntries([
...anchors.map(({ name }): [string, number] => [name, 210]),
...Array.from({ length: 16 }, (_, index): [string, number] => [
`core-tooling-${index + 1}`,
200,
]),
]);
const options = {
compactMode: "pull-request" as const,
runnerBackend: "github",
includeReleaseOnlyPluginShards: false,
};
const inventoryGrowthFile = "test/scripts/resolve-fs-safe-native-contract.test.ts";
const extraInventories = [
["test/scripts/npm-package-locks-report.test.ts"],
Array.from({ length: 10 }, (_, index) => `test/scripts/zz-growth-probe-${index}.test.ts`),
["test/scripts/openclaw-performance-crabbox.test.ts"],
["test/scripts/install-smoke-ref-admission.test.ts"],
[
"test/scripts/npm-package-locks-report.test.ts",
"test/scripts/openclaw-performance-crabbox.test.ts",
"test/scripts/install-smoke-ref-admission.test.ts",
],
];
const isHostedToolingGroup = (group: { shard_name: string }) =>
/^core-tooling-\d+-hosted-\d+$/u.test(group.shard_name);
const runnerRanks = new Map([
[BUNDLED_NODE_TEST_RUNNER, 0],
[DEFAULT_NODE_TEST_RUNNER, 1],
[EXTRA_LARGE_NODE_TEST_RUNNER, 2],
]);
const createPlanWithInventory = async (
includeGrowthFile: boolean,
extraFiles: string[] = [],
) => {
vi.resetModules();
vi.doMock("../vitest/vitest.unit-fast-paths.mjs", () => ({
...unitFastPaths,
getUnitFastTestFiles: () => [],
getUnitFastIsolatedTestFiles: () => [],
getUnitFastTimerTestFiles: () => [],
getUnitFastTestFilesForIncludePatterns: () => [],
}));
vi.doMock("../vitest/vitest.test-shards.mjs", async (importOriginal) => ({
...(await importOriginal<typeof import("../vitest/vitest.test-shards.mjs")>()),
fullSuiteVitestShards: fixtureShards,
}));
vi.doMock("../../scripts/lib/ci-test-timings.mts", () => ({
...testTimings,
readCompactGroupTimings: () => fixtureTimings,
}));
vi.doMock("../../scripts/lib/list-test-files.mts", () => ({
listTrackedTestFiles(rootDir: string) {
return rootDir === "test"
? [
...fixtureFiles,
...extraFiles,
...(includeGrowthFile ? [inventoryGrowthFile] : []),
].toSorted()
: [];
},
}));
try {
const { createNodeTestShardBundles: createPlan } =
await import("../../scripts/lib/ci-node-test-plan.mts");
return createPlan(options);
} finally {
vi.doUnmock("../../scripts/lib/list-test-files.mts");
vi.doUnmock("../../scripts/lib/ci-test-timings.mts");
vi.doUnmock("../vitest/vitest.test-shards.mjs");
vi.doUnmock("../vitest/vitest.unit-fast-paths.mjs");
vi.resetModules();
}
};
const baseline = await createPlanWithInventory(false);
const baselineToolingFiles = baseline
.flatMap((job) => job.groups)
.filter(isNumberedToolingGroup)
.flatMap((group) => group.includePatterns ?? []);
expect(baseline).toHaveLength(80);
expect(baselineToolingFiles.toSorted()).toEqual(fixtureFiles.toSorted());
const grown = await createPlanWithInventory(true);
const toolingGroups = grown.flatMap((job) => job.groups).filter(isNumberedToolingGroup);
const toolingFiles = toolingGroups.flatMap((group) => group.includePatterns ?? []);
const crossRunnerHostedJobs: CompactNodeTestShard[] = [];
expect(grown.length).toBeLessThanOrEqual(80);
expect(new Set(toolingFiles).size).toBe(toolingFiles.length);
expect(toolingFiles.toSorted()).toEqual(
[...baselineToolingFiles, inventoryGrowthFile].toSorted(),
);
expect(nonToolingPlacement(grown)).toEqual(nonToolingPlacement(baseline));
for (const job of grown) {
const hostedToolingGroups = job.groups.filter(isHostedToolingGroup);
if (hostedToolingGroups.length === 0) {
continue;
}
const families = hostedToolingGroups.map((group) =>
group.shard_name.replace(/-hosted-\d+$/u, ""),
);
expect(new Set(families).size).toBe(families.length);
expect(job.requiresDist).toBe(false);
expect(job.planConcurrency).toBe(1);
expect(job.groups.length).toBeLessThanOrEqual(10);
if (job.groups.length > 1) {
expect(job.predictedSeconds).toBeLessThanOrEqual(job.pretestBuildMode ? 210 : 150);
if (job.pretestBuildMode) {
expect(job.groups.every((group) => group.pretestBuildMode)).toBe(true);
}
}
expect(job.runner).toBe(job.groups[0]?.runner);
expect(
hostedToolingGroups.every(
(group) => (runnerRanks.get(job.runner) ?? -1) >= (runnerRanks.get(group.runner) ?? 0),
),
).toBe(true);
if (hostedToolingGroups.some((group) => group.runner !== job.runner)) {
crossRunnerHostedJobs.push(job);
expect(job.groups.every(isHostedToolingGroup)).toBe(true);
expect(job.pretestBuildMode).toBeUndefined();
expect(job.groups.every((group) => group.pretestBuildMode === undefined)).toBe(true);
}
}
// Inventory growth can move numbered stripes; validate every mixed-capacity
// job above instead of pinning one incidental packing layout.
expect(crossRunnerHostedJobs.length).toBeGreaterThan(0);
for (const extraFiles of extraInventories) {
const extraBaseline = await createPlanWithInventory(false, extraFiles);
const expanded = await createPlanWithInventory(true, extraFiles);
const expandedToolingFiles = expanded
.flatMap((job) => job.groups)
.filter(isNumberedToolingGroup)
.flatMap((group) => group.includePatterns ?? []);
expect(expanded.length).toBeLessThanOrEqual(80);
expect(new Set(expandedToolingFiles).size).toBe(expandedToolingFiles.length);
expect(expandedToolingFiles.toSorted()).toEqual(
[...new Set([...baselineToolingFiles, inventoryGrowthFile, ...extraFiles])].toSorted(),
);
expect(nonToolingPlacement(extraBaseline)).toEqual(nonToolingPlacement(baseline));
expect(nonToolingPlacement(expanded)).toEqual(nonToolingPlacement(extraBaseline));
for (const job of expanded.filter((candidate) =>
candidate.groups.some(isHostedToolingGroup),
)) {
const families = job.groups
.filter(isHostedToolingGroup)
.map((group) => group.shard_name.replace(/-hosted-\d+$/u, ""));
expect(new Set(families).size).toBe(families.length);
expect(job.requiresDist).toBe(false);
expect(job.planConcurrency).toBe(1);
expect(job.groups.length).toBeLessThanOrEqual(10);
if (job.groups.length > 1) {
expect(job.predictedSeconds).toBeLessThanOrEqual(job.pretestBuildMode ? 210 : 150);
if (job.pretestBuildMode) {
expect(job.groups.every((group) => group.pretestBuildMode)).toBe(true);
}
}
expect(job.runner).toBe(job.groups[0]?.runner);
expect(
job.groups.every(
(group) => (runnerRanks.get(job.runner) ?? -1) >= (runnerRanks.get(group.runner) ?? 0),
),
).toBe(true);
}
}
});
it("assigns Blacksmith runners to every core node shard", () => {
const shards = defaultShards;
expect(shards).not.toHaveLength(0);
expect(shards.every((shard) => shard.runner?.startsWith("blacksmith-"))).toBe(true);
});
it("splits core runtime configs into smaller source-only shards", () => {
const runtimeShards = defaultShards
.filter((shard) => shard.shardName.startsWith("core-runtime-"))
.map((shard) => ({
configs: shard.configs,
requiresDist: shard.requiresDist,
runner: shard.runner,
shardName: shard.shardName,
}));
expect(runtimeShards).toEqual([
{
configs: ["test/vitest/vitest.hooks.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-hooks",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-approval-exec",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-channel-plugin",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-cli-ui",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-device",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-diagnostics-state",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-core-utils",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-env-auth",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-events-runtime",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-file-safety",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-files-commands",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-gateway-lock-argv",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-gateway-processes",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-gateway-watch",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-heartbeat-core",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-heartbeat-runner",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-misc",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-misc-dedupe-disk",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-misc-os",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-misc-values",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-net-install",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-network-node",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-network-platform",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-outbound-actions",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-outbound-core",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-provider-push",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-repo-tooling",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-storage-state",
},
{
configs: ["test/vitest/vitest.infra.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-system-runtime",
},
{
configs: ["test/vitest/vitest.secrets.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-secrets",
},
{
configs: ["test/vitest/vitest.logging.config.ts", "test/vitest/vitest.process.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-infra-process",
},
{
configs: ["test/vitest/vitest.runtime-config.config.ts"],
requiresDist: false,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-config",
},
{
configs: ["test/vitest/vitest.tui-pty.config.ts"],
requiresDist: true,
runner: "blacksmith-4vcpu-ubuntu-2404",
shardName: "core-runtime-tui-pty",
},
{
configs: ["test/vitest/vitest.ui.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-media-ui-1",
},
{
configs: ["test/vitest/vitest.ui.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-media-ui-2",
},
{
configs: ["test/vitest/vitest.ui.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-media-ui-3",
},
{
configs: [
"test/vitest/vitest.media.config.ts",
"test/vitest/vitest.media-understanding.config.ts",
"test/vitest/vitest.tui.config.ts",
"test/vitest/vitest.ui-isolated.config.ts",
"test/vitest/vitest.ui-timing.config.ts",
"test/vitest/vitest.wizard.config.ts",
],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-media-ui-support",
},
{
configs: [
"test/vitest/vitest.acp.config.ts",
"test/vitest/vitest.shared-core.config.ts",
"test/vitest/vitest.tasks.config.ts",
"test/vitest/vitest.utils.config.ts",
],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-shared",
},
{
configs: ["test/vitest/vitest.cron.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-cron-core",
},
{
configs: ["test/vitest/vitest.cron.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-cron-isolated-agent",
},
{
configs: ["test/vitest/vitest.cron.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "core-runtime-cron-service",
},
]);
});
it("keeps the full TUI PTY suite in its dedicated built-CLI shard", () => {
const tuiPtyShard = defaultShards.find((shard) => shard.shardName === "core-runtime-tui-pty");
expect(tuiPtyShard).toMatchObject({
checkName: "checks-node-core-runtime-tui-pty",
configs: ["test/vitest/vitest.tui-pty.config.ts"],
env: {
OPENCLAW_TUI_PTY_INCLUDE_LOCAL: "1",
OPENCLAW_TUI_PTY_USE_BUILT_CLI: "1",
},
requiresDist: true,
});
expect(tuiPtyShard?.includePatterns).toBeUndefined();
});
it("covers every infra test exactly once across core runtime infra shards", () => {
const infraShards = defaultShards.filter((shard) =>
shard.shardName.startsWith("core-runtime-infra-"),
);
const actual = infraShards
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
expect(infraShards.map((shard) => shard.shardName)).toEqual([
"core-runtime-infra-approval-exec",
"core-runtime-infra-channel-plugin",
"core-runtime-infra-cli-ui",
"core-runtime-infra-device",
"core-runtime-infra-diagnostics-state",
"core-runtime-infra-core-utils",
"core-runtime-infra-env-auth",
"core-runtime-infra-events-runtime",
"core-runtime-infra-file-safety",
"core-runtime-infra-files-commands",
"core-runtime-infra-gateway-lock-argv",
"core-runtime-infra-gateway-processes",
"core-runtime-infra-gateway-watch",
"core-runtime-infra-heartbeat-core",
"core-runtime-infra-heartbeat-runner",
"core-runtime-infra-misc",
"core-runtime-infra-misc-dedupe-disk",
"core-runtime-infra-misc-os",
"core-runtime-infra-misc-values",
"core-runtime-infra-net-install",
"core-runtime-infra-network-node",
"core-runtime-infra-network-platform",
"core-runtime-infra-outbound-actions",
"core-runtime-infra-outbound-core",
"core-runtime-infra-provider-push",
"core-runtime-infra-repo-tooling",
"core-runtime-infra-storage-state",
"core-runtime-infra-system-runtime",
"core-runtime-infra-process",
]);
expect(actual).toEqual(
[...new Set([...listTestFiles("src/infra"), ...databaseWorkerCoreTestFiles])].toSorted(
(a, b) => a.localeCompare(b),
),
);
expect(new Set(actual).size).toBe(actual.length);
});
it("preserves Gateway runner hooks while assigning database consumers to forks", () => {
const worker = createGatewayDatabaseWorkersVitestConfig({});
const core = createGatewayCoreVitestConfig({});
const server = createGatewayServerVitestConfig({});
const methods = createGatewayMethodsVitestConfig({});
expect(methods.test?.pool).toBe("forks");
expect(worker.test?.pool).toBe("forks");
expect(core.test?.isolate).toBe(true);
for (const shared of [worker, server, methods]) {
expect(shared.test?.isolate).toBe(false);
}
for (const previous of [core, server, methods]) {
expect(worker.test?.runner).toBe(previous.test?.runner);
expect(worker.test?.setupFiles).toEqual(previous.test?.setupFiles);
}
expect(listMatchedTestFiles(worker)).toEqual(gatewayDatabaseWorkerTestFiles);
const former = new Set([core, server, methods].flatMap(listMatchedTestFiles));
for (const file of gatewayDatabaseWorkerTestFiles) {
expect(former.has(file), file).toBe(false);
expect(isGatewayServerTestFile(file), file).toBe(false);
}
expect(
defaultShards.filter((shard) =>
shard.configs.includes("test/vitest/vitest.gateway-database-workers.config.ts"),
),
).toHaveLength(1);
const includeFile = join(tempDirs.make("gateway-database-routing-"), "include.json");
writeFileSync(includeFile, JSON.stringify([gatewayDatabaseWorkerTestFiles[0]]));
expect(
listMatchedTestFiles(
createGatewayDatabaseWorkersVitestConfig({ OPENCLAW_VITEST_INCLUDE_FILE: includeFile }),
),
).toEqual([gatewayDatabaseWorkerTestFiles[0]]);
});
it("keeps host-owned database consumers in forks and out of their former projects", () => {
const infra = createInfraVitestConfig({});
const support = createAgentsSupportVitestConfig({});
expect(infra.test?.pool).toBe("forks");
expect(infra.test?.setupFiles).toEqual(support.test?.setupFiles);
const admitted = new Set(listMatchedTestFiles(infra));
expect(admitted.has("src/agents/sessions/sdk.auth-migration.test.ts")).toBe(true);
const former = new Set(
[
createUnitVitestConfigWithOptions({}),
createUnitFastVitestConfig(),
createAgentsCoreVitestConfig({}),
support,
createAgentsVitestConfig({}),
createPluginSdkLightVitestConfig({}),
createPluginSdkVitestConfig({}),
createPluginsVitestConfig({}),
createTasksVitestConfig({}),
createToolingVitestConfig({}),
createWizardVitestConfig({}),
createCommandsVitestConfig({}),
].flatMap(listMatchedTestFiles),
);
for (const file of databaseWorkerCoreTestFiles) {
expect(admitted.has(file), file).toBe(true);
expect(former.has(file), file).toBe(false);
}
const recoveryTest = "src/wizard/setup.inference-recovery.integration.test.ts";
expect(admitted.has(recoveryTest), recoveryTest).toBe(true);
expect(former.has(recoveryTest), recoveryTest).toBe(false);
const selected = [
"src/plugin-state/plugin-state-store.test.ts",
"test/plugins/beam-http-identity.test.ts",
];
const includeFile = join(tempDirs.make("database-worker-routing-"), "include.json");
writeFileSync(includeFile, JSON.stringify(selected));
expect(
listMatchedTestFiles(
createInfraVitestConfig({ OPENCLAW_VITEST_INCLUDE_FILE: includeFile }),
).toSorted(),
).toEqual(selected.toSorted());
});
it("covers every cron test exactly once across core runtime cron shards", () => {
const cronShards = defaultShards.filter((shard) =>
shard.shardName.startsWith("core-runtime-cron-"),
);
const actual = cronShards
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
expect(cronShards.map((shard) => shard.shardName)).toEqual([
"core-runtime-cron-core",
"core-runtime-cron-isolated-agent",
"core-runtime-cron-service",
]);
expect(actual).toEqual(listTestFiles("src/cron"));
expect(new Set(actual).size).toBe(actual.length);
});
it("splits the agentic lane into control-plane, command, agent, gateway, SDK, and plugin shards", () => {
const shards = defaultShards;
const controlPlaneShards = shards.filter((shard) =>
shard.shardName.startsWith("agentic-control-plane-"),
);
const cliShard = shards.find((shard) => shard.shardName === "agentic-cli");
const cliProcessShard = shards.find((shard) => shard.shardName === "agentic-cli-process");
const commandSupportShard = shards.find(
(shard) => shard.shardName === "agentic-command-support",
);
const commandShards = shards.filter((shard) => shard.shardName.startsWith("agentic-commands-"));
const agentShards = shards.filter((shard) => shard.shardName.startsWith("agentic-agents-"));
const gatewayCoreShards = shards.filter((shard) =>
shard.shardName.startsWith("agentic-gateway-core-"),
);
const gatewayMethodsShard = shards.find(
(shard) => shard.shardName === "agentic-gateway-methods",
);
const pluginSdkShard = shards.find((shard) => shard.shardName === "agentic-plugin-sdk");
const pluginsShard = shards.find((shard) => shard.shardName === "agentic-plugins");
expect(controlPlaneShards.map((shard) => shard.shardName)).toEqual([
"agentic-control-plane-agent-chat",
"agentic-control-plane-auth-node",
"agentic-control-plane-http-models",
"agentic-control-plane-http-plugin-ws",
"agentic-control-plane-runtime",
"agentic-control-plane-runtime-config",
"agentic-control-plane-runtime-cron",
"agentic-control-plane-runtime-server",
"agentic-control-plane-runtime-shared-token",
"agentic-control-plane-runtime-state",
"agentic-control-plane-runtime-ui-tools",
"agentic-control-plane-startup-config",
"agentic-control-plane-startup-core",
"agentic-control-plane-startup-health-runtime",
"agentic-control-plane-startup-restart-close",
]);
expect(controlPlaneShards).toEqual(
controlPlaneShards.map((shard) => ({
checkName: `checks-node-${shard.shardName}`,
configs: ["test/vitest/vitest.gateway-server.config.ts"],
...(shard.shardName === "agentic-control-plane-runtime-config"
? { pretestBuildMode: "runtime" }
: {}),
...(shard.shardName === "agentic-control-plane-startup-health-runtime"
? { env: { OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "60000" } }
: {}),
...(shard.includePatterns?.includes("src/gateway/server-sidecar-retention.test.ts")
? { pretestBuildMode: "runtime" }
: {}),
includePatterns: shard.includePatterns,
requiresDist: false,
runner:
shard.shardName === "agentic-control-plane-startup-core"
? DEFAULT_NODE_TEST_RUNNER
: "blacksmith-4vcpu-ubuntu-2404",
shardName: shard.shardName,
})),
);
const controlPlaneShardFiles = controlPlaneShards
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
const expectedControlPlaneFiles = listMatchedTestFiles(
createGatewayServerVitestConfig({
...process.env,
OPENCLAW_VITEST_INCLUDE_FILE: undefined,
}),
);
expect(
listTestFiles("src/gateway")
.filter(isGatewayServerTestFile)
.toSorted((a, b) => a.localeCompare(b)),
).toEqual(expectedControlPlaneFiles);
expect(controlPlaneShardFiles).toEqual(expectedControlPlaneFiles);
expect(new Set(controlPlaneShardFiles).size).toBe(controlPlaneShardFiles.length);
expect(cliShard).toEqual({
checkName: "checks-node-agentic-cli",
shardName: "agentic-cli",
configs: ["test/vitest/vitest.cli.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
});
expect(cliProcessShard).toEqual({
checkName: "checks-node-agentic-cli-process",
shardName: "agentic-cli-process",
configs: ["test/vitest/vitest.cli-process.config.ts"],
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
});
expect(commandSupportShard).toEqual({
checkName: "checks-node-agentic-command-support",
shardName: "agentic-command-support",
configs: [
"test/vitest/vitest.commands-light.config.ts",
"test/vitest/vitest.daemon.config.ts",
],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
});
expect(commandShards.map((shard) => shard.shardName)).toEqual([
"agentic-commands-agent-channel",
"agentic-commands-doctor",
"agentic-commands-doctor-auth",
"agentic-commands-doctor-config-state",
"agentic-commands-doctor-gateway",
"agentic-commands-doctor-platform",
"agentic-commands-doctor-plugins-tools",
"agentic-commands-doctor-sessions-cron",
"agentic-commands-doctor-sessions-cron-memory",
"agentic-commands-doctor-sessions-cron-sqlite",
"agentic-commands-doctor-shared",
"agentic-commands-doctor-whatsapp",
"agentic-commands-doctor-workspace",
"agentic-commands-models",
"agentic-commands-onboard-config",
"agentic-commands-status-tools",
]);
expect(commandShards).toEqual(
commandShards.map((shard) => ({
checkName: `checks-node-${shard.shardName}`,
configs: ["test/vitest/vitest.commands.config.ts"],
includePatterns: shard.includePatterns,
...(shard.shardName === "agentic-commands-doctor-config-state" ||
shard.shardName === "agentic-commands-doctor-plugins-tools"
? { pretestBuildMode: "runtime" }
: {}),
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: shard.shardName,
})),
);
expect(
commandShards.find((shard) => shard.shardName === "agentic-commands-doctor-auth")
?.includePatterns,
).toContain("src/commands/oauth-tls-preflight.doctor.test.ts");
const commandShardFiles = commandShards
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
const expectedCommandFiles = listMatchedTestFiles(createCommandsVitestConfig({}));
expect(commandShardFiles).toEqual(expectedCommandFiles);
expect(new Set(commandShardFiles).size).toBe(commandShardFiles.length);
expect(agentShards).toEqual([
{
checkName: "checks-node-agentic-agents-core-auth",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[0]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-auth",
},
{
checkName: "checks-node-agentic-agents-core-models",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[1]?.includePatterns,
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-models",
},
{
checkName: "checks-node-agentic-agents-core-tools",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[2]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-tools",
},
{
checkName: "checks-node-agentic-agents-core-subagents",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[3]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-subagents",
},
// cli-runner stripes: agents-core runs files serially, so the
// import-heavy suite splits across jobs to parallelize at bin level.
{
checkName: "checks-node-agentic-agents-core-runner-cli-1",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[4]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runner-cli-1",
},
{
checkName: "checks-node-agentic-agents-core-runner-cli-2",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[5]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runner-cli-2",
},
{
checkName: "checks-node-agentic-agents-core-runner-cli-3",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[6]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runner-cli-3",
},
{
checkName: "checks-node-agentic-agents-core-runner-commands",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[7]?.includePatterns,
requiresDist: false,
pretestBuildMode: "runtime",
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runner-commands",
},
{
checkName: "checks-node-agentic-agents-core-runner-embedded",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[8]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runner-embedded",
},
{
checkName: "checks-node-agentic-agents-core-runner-sessions",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[9]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runner-sessions",
},
{
checkName: "checks-node-agentic-agents-core-runtime",
configs: ["test/vitest/vitest.agents-core.config.ts"],
includePatterns: agentShards[10]?.includePatterns,
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-runtime",
},
{
checkName: "checks-node-agentic-agents-core-spawn-production-boundary",
configs: ["test/vitest/vitest.agents-spawn-production-boundary.config.ts"],
includePatterns: agentShards[11]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-spawn-production-boundary",
},
{
checkName: "checks-node-agentic-agents-core-isolated",
configs: ["test/vitest/vitest.agents-core-isolated.config.ts"],
includePatterns: agentShards[12]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-core-isolated",
},
{
checkName: "checks-node-agentic-agents-embedded",
configs: [
"test/vitest/vitest.agents-embedded-agent.config.ts",
"test/vitest/vitest.agents-embedded-agent-incomplete-turn.config.ts",
"test/vitest/vitest.agents-embedded-agent-overflow-compaction.config.ts",
"test/vitest/vitest.agents-embedded-agent-run.config.ts",
],
env: { OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "660000" },
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-embedded",
},
{
checkName: "checks-node-agentic-agents-support",
configs: ["test/vitest/vitest.agents-support.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-support",
},
{
checkName: "checks-node-agentic-agents-tools",
configs: ["test/vitest/vitest.agents-tools.config.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-agents-tools",
},
]);
expect(pluginSdkShard).toEqual({
checkName: "checks-node-agentic-plugin-sdk",
shardName: "agentic-plugin-sdk",
configs: [
"test/vitest/vitest.plugin-sdk-light.config.ts",
"test/vitest/vitest.plugin-sdk.config.ts",
],
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
});
const gatewayCoreConfigs = [
"test/vitest/vitest.gateway-core.config.ts",
"test/vitest/vitest.gateway-client.config.ts",
];
expect(gatewayCoreShards.slice(0, 3)).toEqual(
[1, 2, 3].map((stripe) => ({
checkName: `checks-node-agentic-gateway-core-${stripe}`,
shardName: `agentic-gateway-core-${stripe}`,
configs: gatewayCoreConfigs,
includePatterns: gatewayCoreShards[stripe - 1]?.includePatterns,
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
})),
);
// The pretest runtime build is charged per job, so the files that need it
// stay in one shard; the ordinary stripes must never carry a build mode.
expect(gatewayCoreShards.filter((shard) => shard.pretestBuildMode != null)).toEqual([
{
checkName: "checks-node-agentic-gateway-core-runtime",
shardName: "agentic-gateway-core-runtime",
configs: gatewayCoreConfigs,
includePatterns: [
"src/gateway/gateway-active-memory.test.ts",
"src/gateway/gateway-concurrent-streams.test.ts",
],
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
},
]);
expect(gatewayMethodsShard).toEqual({
checkName: "checks-node-agentic-gateway-methods",
shardName: "agentic-gateway-methods",
configs: [
"test/vitest/vitest.gateway-methods.config.ts",
"test/vitest/vitest.gateway-methods-isolated.config.ts",
],
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
});
expect(pluginsShard).toEqual({
checkName: "checks-node-agentic-plugins",
shardName: "agentic-plugins",
configs: ["test/vitest/vitest.plugins.config.ts"],
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
});
});
it("keeps plugin prerelease npm install behavior on the release-only agentic plugin shard", () => {
const pluginsShard = defaultShards.find((shard) => shard.shardName === "agentic-plugins");
expect(pluginsShard).toEqual({
checkName: "checks-node-agentic-plugins",
configs: ["test/vitest/vitest.plugins.config.ts"],
pretestBuildMode: "runtime",
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
shardName: "agentic-plugins",
});
expect(listMatchedTestFiles(createPluginsVitestConfig({}))).toContain(
PLUGIN_PRERELEASE_NPM_SPEC_TEST,
);
});
it("covers flat agents-core and explicitly nested isolated tests exactly once", () => {
const actual = defaultShards
.filter((shard) => shard.shardName.startsWith("agentic-agents-core-"))
.flatMap((shard) => shard.includePatterns ?? [])
.toSorted((a, b) => a.localeCompare(b));
const expected = [
...listTestFiles("src/agents").filter(
(file) => !relative("src/agents", file).replaceAll("\\", "/").includes("/"),
),
...agentVitestProjectOwners.coreIsolated.include.filter((file) =>
relative("src/agents", file).replaceAll("\\", "/").includes("/"),
),
...agentVitestProjectOwners.spawnProductionBoundary.include,
]
.filter((file) => !databaseWorkerCoreTestFiles.includes(file))
.toSorted((a, b) => a.localeCompare(b));
expect(actual).toEqual(expected);
expect(new Set(actual).size).toBe(actual.length);
});
it("keeps embedded-agent tests in four bounded config surfaces", () => {
const shard = defaultShards.find(
(candidate) => candidate.shardName === "agentic-agents-embedded",
);
const incompleteTurnFiles = new Set(agentVitestProjectOwners.embeddedIncompleteTurn.include);
const overflowCompactionFiles = new Set(
agentVitestProjectOwners.embeddedOverflowCompaction.include,
);
const actual = [
...globSync(agentVitestProjectOwners.embedded.include)
.map(toRepoPath)
.filter((file) => !incompleteTurnFiles.has(file) && !overflowCompactionFiles.has(file)),
...agentVitestProjectOwners.embeddedIncompleteTurn.include,
...agentVitestProjectOwners.embeddedOverflowCompaction.include,
...globSync(agentVitestProjectOwners.embeddedRun.include).map(toRepoPath),
].toSorted((left, right) => left.localeCompare(right));
const expected = listTestFiles("src/agents/embedded-agent-runner").toSorted((left, right) =>
left.localeCompare(right),
);
expect(shard?.configs).toEqual(embeddedAgentVitestProjectOwners.map((owner) => owner.config));
expect(actual).toEqual(expected);
expect(new Set(actual).size).toBe(actual.length);
});
it("keeps expensive plugin shards release-only when normal CI asks for the cheaper plan", () => {
const shards = createNodeTestShards({ includeReleaseOnlyPluginShards: false });
const shardNames = shards.map((shard) => shard.shardName);
expect(shardNames).not.toContain("agentic-plugins");
expect(shardNames).toContain("agentic-gateway-core-1");
expect(shardNames).toContain("agentic-gateway-core-2");
expect(shardNames).toContain("agentic-gateway-core-3");
expect(shardNames).toContain("agentic-gateway-methods");
expect(shardNames).toContain("agentic-plugin-sdk");
});
it("keeps changed native browser tests in UI jobs and out of extension fallback", () => {
const target = "extensions/workboard/browser/catalog.test.ts";
const shards = createChangedNodeTestShards([target]);
expect(shards).not.toBeNull();
expect(shards?.flatMap((shard) => shard.targets ?? shard.includePatterns ?? [])).toContain(
target,
);
expect(createChangedExtensionFallbackShards([target])).toEqual([]);
});
it("prepares the sticker provider runtime in extension fallback", () => {
const target = "extensions/telegram/src/sticker-cache.selection.test.ts";
const owners = createChangedExtensionFallbackShards([target]).filter((shard) =>
(shard.groups ?? [shard]).some((group) => group.includePatterns?.includes(target)),
);
expect(owners).toHaveLength(1);
expect(owners[0]?.pretestBuildMode).toBe("runtime");
});
it("retains the changed host plugin test when the store-alias diff forces fallback", () => {
expect(createChangedNodeTestShards(STORE_ALIAS_CHANGED_PATHS)).toBeNull();
const options = {
changedPaths: STORE_ALIAS_CHANGED_PATHS,
includeReleaseOnlyPluginShards: false,
};
const shards = [
...createNodeTestShards(options),
...createChangedExtensionFallbackShards(STORE_ALIAS_CHANGED_PATHS),
];
expect(shards.filter((shard) => shard.shardName === "agentic-plugins")).toEqual([
{
checkName: "checks-node-agentic-plugins",
shardName: "agentic-plugins",
configs: ["test/vitest/vitest.plugins.config.ts"],
includePatterns: ["src/plugins/tools.optional.test.ts"],
requiresDist: false,
runner: DEFAULT_NODE_TEST_RUNNER,
},
]);
});
it("retains only exact changed plugin-owner tests in deterministic order", () => {
const options = {
includeReleaseOnlyPluginShards: false,
changedPaths: [
...STORE_ALIAS_CHANGED_PATHS.toReversed(),
" src/plugins/tools.optional.test.ts",
String.raw`src\plugins\tools.optional.test.ts`,
"src/plugins/tools.optional.test.ts",
PLUGIN_PRERELEASE_NPM_SPEC_TEST,
"src/plugins/contracts/plugin-sdk-subpaths.test.ts",
"src/plugins/loader.test.ts",
"src/plugins/install.npm-spec.e2e.test.ts",
],
};
const shards = createNodeTestShards(options);
expect(shards.find((shard) => shard.shardName === "agentic-plugins")?.includePatterns).toEqual([
PLUGIN_PRERELEASE_NPM_SPEC_TEST,
"src/plugins/tools.optional.test.ts",
]);
expect(shards.filter((shard) => shard.shardName !== "agentic-plugins")).toEqual(
createNodeTestShards({ includeReleaseOnlyPluginShards: false }),
);
expect(createNodeTestShards({ ...options, includeReleaseOnlyPluginShards: true })).toEqual(
defaultShards,
);
});
it("does not widen plugin coverage for deleted tests, sources, docs, or directories", () => {
const deletedTest = "src/plugins/deleted-ci-routing.test.ts";
expect(existsSync(deletedTest)).toBe(false);
const options = {
includeReleaseOnlyPluginShards: false,
changedPaths: [deletedTest, "src/plugins/tools.ts", "src/plugins", "docs/ci.md"],
};
expect(createNodeTestShards(options)).toEqual(
createNodeTestShards({ includeReleaseOnlyPluginShards: false }),
);
});
it.each(
plannerHosts.flatMap((host) =>
["blacksmith", "github", "hybrid"].map((runnerBackend) =>
Object.assign({}, host, { runnerBackend }),
),
),
)(
"retains changed plugin tests once in $runnerBackend compact fallback without changing group policies ($label)",
({ runnerBackend, ...host }) => {
pinPlannerHost(host);
const options = {
compactMode: "pull-request" as const,
includeReleaseOnlyPluginShards: false,
runnerBackend,
};
const changedOptions = { ...options, changedPaths: STORE_ALIAS_CHANGED_PATHS };
// Each input has its own admission before runtime placement materializes caps.
const observations = vi.spyOn(testTimings, "readRuntimePlacementTimings").mockReturnValue([]);
const beforeAdmission = createNodeTestShardBundles(options);
const afterAdmission = createNodeTestShardBundles(changedOptions);
observations.mockRestore();
const before = getCommittedCompactPlan(options.compactMode, runnerBackend);
const after = createNodeTestShardBundles(changedOptions);
const groups = after.flatMap((shard) => shard.groups);
type Group = (typeof groups)[number];
const isRepartitionableTooling = (group: Group) =>
runnerBackend === "github" &&
/^core-tooling-\d+-hosted-\d+$/u.test(group.shard_name) &&
group.configs.includes("test/vitest/vitest.tooling.config.ts") &&
group.includePatterns !== undefined &&
group.includePatterns.length > 0 &&
!group.requiresDist &&
group.pretestBuildMode === undefined &&
[BUNDLED_NODE_TEST_RUNNER, DEFAULT_NODE_TEST_RUNNER, EXTRA_LARGE_NODE_TEST_RUNNER].includes(
group.runner,
);
const toolingParent = (group: Group) => group.shard_name.replace(/-hosted-\d+$/u, "");
const timingFamilies = (plan: typeof before) => {
const families = new Map<string, Array<{ group: Group; part: number }>>();
for (const group of plan.flatMap((shard) => shard.groups)) {
if (/^core-tooling-\d+-hosted-\d+$/u.test(group.shard_name)) {
expect(group.timing_key).toBeDefined();
}
if (group.timing_key === undefined) {
continue;
}
const key = expectDefined(
parseCompactSplitTimingKey(group.timing_key),
"parsed compact split timing key",
);
const parent = key.parentShardName;
const part = key.part;
const hostedName = /^(.+)-hosted-([1-9]\d*)$/u.exec(group.shard_name);
expect(parent, "timing key parent must match hosted group name").toBe(
hostedName?.[1] ?? group.shard_name,
);
expect(part, "timing key part must match hosted group ordinal").toBe(
hostedName ? Number(hostedName[2]) : 1,
);
const family = families.get(parent) ?? [];
family.push({ group, part });
families.set(parent, family);
}
for (const family of families.values()) {
family.sort((a, b) => a.part - b.part);
}
return families;
};
const inheritedGroupsFor = (admission: typeof before) =>
new Map(
admission.flatMap((job) =>
job.planConcurrency === 2
? job.groups
.filter((group) => group.env?.OPENCLAW_VITEST_MAX_WORKERS === undefined)
.map((group): [string, Group] => [group.shard_name, group])
: [],
),
);
const beforeInherited = inheritedGroupsFor(beforeAdmission);
const afterInherited = inheritedGroupsFor(afterAdmission);
const declarationEnv = (
group: Pick<Group, "shard_name" | "env">,
plan: typeof before,
originals: Map<string, Group>,
) => {
const original = originals.get(group.shard_name);
const job = plan.find((entry) =>
entry.groups.some((candidate) => candidate.shard_name === group.shard_name),
);
if (original && job?.planConcurrency === 1) {
expect(
group.env?.OPENCLAW_VITEST_MAX_WORKERS ?? job.env?.OPENCLAW_VITEST_MAX_WORKERS,
).toBe("2");
const { OPENCLAW_VITEST_MAX_WORKERS: _workers, ...otherEnv } = group.env ?? {};
expect(otherEnv).toEqual(original.env ?? {});
return original.env;
}
return group.env;
};
const expectPluginPolicy = (plan: typeof after, originals: Map<string, Group>) => {
expect(
plan
.flatMap((job) => job.groups)
.filter((group) => group.shard_name === "agentic-plugins")
.map((group) =>
Object.assign({}, group, { env: declarationEnv(group, plan, originals) }),
),
).toEqual([
{
shard_name: "agentic-plugins",
configs: ["test/vitest/vitest.plugins.config.ts"],
includePatterns: ["src/plugins/tools.optional.test.ts"],
requiresDist: false,
runner: expect.stringMatching(/^blacksmith-(?:4|8)vcpu-ubuntu-2404$/u),
},
]);
};
expectPluginPolicy(after, afterInherited);
// Keep the transition controls independent of current inventory placement.
const pluginDeclaration = expectDefined(
afterAdmission
.flatMap((job) => job.groups)
.find((group) => group.shard_name === "agentic-plugins"),
"declared plugin group",
);
const pluginAdmission: CompactNodeTestShard = {
checkName: "plugin-policy-control",
shardName: "plugin-policy-control",
groups: [pluginDeclaration],
requiresDist: false,
runner: pluginDeclaration.runner,
planConcurrency: 2,
};
const pinnedPlugin: Group = {
...pluginDeclaration,
env: { OPENCLAW_VITEST_MAX_WORKERS: "2" },
};
const promotedPlugin = [{ ...pluginAdmission, groups: [pinnedPlugin], planConcurrency: 1 }];
const pluginInherited = inheritedGroupsFor([pluginAdmission]);
expectPluginPolicy(promotedPlugin, pluginInherited);
const invalidPluginEnvs: Array<Group["env"]> = [
undefined,
{ OPENCLAW_VITEST_MAX_WORKERS: "1" },
{ OPENCLAW_VITEST_MAX_WORKERS: "3" },
{ OPENCLAW_VITEST_MAX_WORKERS: "2", UNDECLARED_POLICY: "1" },
];
for (const env of invalidPluginEnvs) {
pinnedPlugin.env = env;
expect(() => expectPluginPolicy(promotedPlugin, pluginInherited)).toThrow();
}
pinnedPlugin.env = { OPENCLAW_VITEST_MAX_WORKERS: "2" };
expect(() =>
expectPluginPolicy(
promotedPlugin,
inheritedGroupsFor([{ ...pluginAdmission, planConcurrency: 1 }]),
),
).toThrow();
const expectedTimingKeys = (
parent: string,
family: Array<{ group: Group; part: number }>,
plan: typeof before,
originals: Map<string, Group>,
) => {
const first = expectDefined(family[0], "first timing family entry").group;
const env = declarationEnv(first, plan, originals);
return createCompactSplitTimingGeneration({
parentShardName: parent,
configs: first.configs,
env,
stripes: family.map(({ group }) => {
expect(group.configs).toEqual(first.configs);
expect(declarationEnv(group, plan, originals)).toEqual(env);
const files = expectDefined(group.includePatterns, "timing family group membership");
expect(files.length).toBeGreaterThan(0);
return files;
}),
}).timingKeys;
};
const expectTimingFamilies = (plan: typeof before, originals: Map<string, Group>) => {
for (const [parent, family] of timingFamilies(plan)) {
expect(family.map(({ part }) => part)).toEqual(
Array.from({ length: family.length }, (_, index) => index + 1),
);
expect(family.map(({ group }) => group.timing_key)).toEqual(
expectedTimingKeys(parent, family, plan, originals),
);
}
};
const policies = (plan: typeof before, originals: Map<string, Group>) => {
const nonPlugin = plan
.flatMap((shard) => shard.groups)
.filter((group) => group.shard_name !== "agentic-plugins");
return {
descriptors: nonPlugin
.filter((group) => !isRepartitionableTooling(group))
.map(({ runner: _runner, ...group }) => {
const env = declarationEnv(group, plan, originals);
if (env === undefined) {
delete group.env;
} else {
group.env = env;
}
return group;
})
.toSorted((a, b) => a.shard_name.localeCompare(b.shard_name)),
// Allocation may change, but every file must retain its complete execution policy.
tooling: nonPlugin
.filter(isRepartitionableTooling)
.flatMap((group) => {
const files = expectDefined(
group.includePatterns,
"repartitionable tooling membership",
);
// A split can move tests out of the compiler's larger runner group.
expect(group.runner).toBe(
files.includes("test/scripts/write-unified-entry-dts.test.ts")
? DEFAULT_NODE_TEST_RUNNER
: BUNDLED_NODE_TEST_RUNNER,
);
return files.map((file) => ({
parent: toolingParent(group),
file,
configs: group.configs,
env: group.env,
pretestBuildMode: group.pretestBuildMode,
requiresDist: group.requiresDist,
exclusive: isExclusiveCompactShardName(group.shard_name),
}));
})
.toSorted((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),
};
};
expectTimingFamilies(before, beforeInherited);
expectTimingFamilies(after, afterInherited);
expect(policies(after, afterInherited)).toEqual(policies(before, beforeInherited));
if (runnerBackend === "hybrid") {
const serial = structuredClone(before);
const serialGroup = expectDefined(
serial
.filter(
(job) => job.planConcurrency === 1 && job.env?.OPENCLAW_VITEST_MAX_WORKERS === "2",
)
.flatMap((job) => job.groups)
.find((group) => group.env?.OPENCLAW_VITEST_MAX_WORKERS === undefined),
"already-serial group using its job worker cap",
);
serialGroup.env = { ...serialGroup.env, OPENCLAW_VITEST_MAX_WORKERS: "2" };
expect(() =>
expect(policies(serial, beforeInherited)).toEqual(policies(before, beforeInherited)),
).toThrow();
const promoted = structuredClone(before);
const recipient = expectDefined(
promoted.find(
(job) =>
job.planConcurrency === 2 &&
job.groups.some(
(group) => group.timing_key && group.env?.OPENCLAW_VITEST_MAX_WORKERS === undefined,
) &&
job.groups.every(
(group) =>
group.env?.OPENCLAW_VITEST_MAX_WORKERS !== undefined ||
isRuntimePlacementIncludePatterns(group.includePatterns),
),
),
"existing parallel recipient with hosted timing parts",
);
recipient.planConcurrency = 1;
recipient.pretestBuildMode = "runtime";
const keys = recipient.groups.map((group) => group.timing_key);
for (const group of recipient.groups) {
group.env = { OPENCLAW_VITEST_MAX_WORKERS: "2", ...group.env };
}
expectTimingFamilies(promoted, beforeInherited);
expect(policies(promoted, beforeInherited)).toEqual(policies(before, beforeInherited));
expect(recipient.groups.map((group) => group.timing_key)).toEqual(keys);
recipient.env = { ...recipient.env, OPENCLAW_VITEST_MAX_WORKERS: "2" };
for (const group of recipient.groups) {
const original = beforeInherited.get(group.shard_name);
if (original) {
group.env = original.env;
}
}
expectTimingFamilies(promoted, beforeInherited);
expect(policies(promoted, beforeInherited)).toEqual(policies(before, beforeInherited));
delete recipient.env.OPENCLAW_VITEST_MAX_WORKERS;
for (const group of recipient.groups) {
group.env = { OPENCLAW_VITEST_MAX_WORKERS: "2", ...group.env };
}
const hosted = expectDefined(
recipient.groups.find(
(group) => group.timing_key && beforeInherited.has(group.shard_name),
),
"hosted recipient group",
);
const original = expectDefined(
beforeInherited.get(hosted.shard_name),
"original parallel group",
);
for (const env of [original.env, { ...original.env, OPENCLAW_VITEST_MAX_WORKERS: "3" }]) {
if (env === undefined) {
delete hosted.env;
} else {
hosted.env = env;
}
expect(() => expectTimingFamilies(promoted, beforeInherited)).toThrow();
expect(() =>
expect(policies(promoted, beforeInherited)).toEqual(policies(before, beforeInherited)),
).toThrow();
}
}
if (runnerBackend === "github") {
const regenerateTimingKeys = (plan: typeof before) => {
for (const [parent, family] of timingFamilies(plan)) {
const keys = expectedTimingKeys(parent, family, plan, afterInherited);
family.forEach(({ group }, index) => {
group.timing_key = expectDefined(keys[index], "regenerated family timing key");
});
}
};
for (const mutation of ["missing", "duplicated", "env", "runner"] as const) {
const mutated = structuredClone(after);
const tooling = mutated.flatMap((shard) => shard.groups).filter(isRepartitionableTooling);
const target = expectDefined(
tooling.find(
(group) =>
expectDefined(group.includePatterns, "tooling mutation membership").length > 1,
),
"multi-file tooling mutation target",
);
const files = expectDefined(target.includePatterns, "tooling mutation target membership");
if (mutation === "missing") {
files.pop();
} else if (mutation === "duplicated") {
const otherFamily = expectDefined(
tooling.find((group) => toolingParent(group) !== toolingParent(target)),
"tooling group from another family",
);
files.push(
expectDefined(
expectDefined(otherFamily.includePatterns, "other tooling family membership")[0],
"file from another tooling family",
),
);
} else if (mutation === "env") {
for (const { group } of expectDefined(
timingFamilies(mutated).get(toolingParent(target)),
"timing family for environment mutation",
)) {
group.env = { ...group.env, OPENCLAW_VITEST_MAX_WORKERS: "3" };
}
} else {
target.runner =
target.runner === BUNDLED_NODE_TEST_RUNNER
? DEFAULT_NODE_TEST_RUNNER
: BUNDLED_NODE_TEST_RUNNER;
}
regenerateTimingKeys(mutated);
expectTimingFamilies(mutated, afterInherited);
expect(
() =>
expect(policies(mutated, afterInherited)).toEqual(policies(before, beforeInherited)),
`${mutation} must fail policy validation even with valid timing keys`,
).toThrow();
}
for (const identity of ["parent", "part"] as const) {
const forged = structuredClone(after);
const families = timingFamilies(forged);
const [parent, family] = expectDefined(
[...families].find(([, entries]) => entries.length >= 2),
"multi-part timing family for identity control",
);
const wrongParent = `${parent}-forged`;
expect(families.has(wrongParent)).toBe(false);
const ordered = identity === "part" ? family.toReversed() : family;
const keys = expectedTimingKeys(
identity === "parent" ? wrongParent : parent,
ordered,
forged,
afterInherited,
);
ordered.forEach(({ group }, index) => {
group.timing_key = expectDefined(keys[index], "forged family timing key");
});
expect(() => expectTimingFamilies(forged, afterInherited)).toThrow(
identity === "parent"
? "timing key parent must match hosted group name"
: "timing key part must match hosted group ordinal",
);
}
const stale = structuredClone(after);
const staleGroup = expectDefined(
stale
.flatMap((shard) => shard.groups)
.find(
(group) =>
isRepartitionableTooling(group) &&
expectDefined(group.includePatterns, "stale-key control membership").length > 1,
),
"multi-file tooling group for stale-key control",
);
const savedKey = expectDefined(staleGroup.timing_key, "original stale-control timing key");
expectDefined(staleGroup.includePatterns, "stale-control group membership").pop();
regenerateTimingKeys(stale);
expectTimingFamilies(stale, afterInherited);
staleGroup.timing_key = savedKey;
expect(
() => expectTimingFamilies(stale, afterInherited),
"stale generation key must fail identity",
).toThrow();
}
expect(
after.every(
(shard) =>
(usesTwoWorkerPacking(shard) || shard.groups.length <= 10) &&
(shard.planConcurrency === 1 ||
(runnerBackend !== "github" &&
shard.planConcurrency === 2 &&
shard.runner === EXTRA_LARGE_NODE_TEST_RUNNER)),
),
).toBe(true);
expect(after.length).toBeLessThanOrEqual(80);
},
);
it("splits auto-reply into balanced core/top-level and reply subtree shards", () => {
const shards = defaultShards;
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-top-level",
configs: [
"test/vitest/vitest.auto-reply-core.config.ts",
"test/vitest/vitest.auto-reply-top-level.config.ts",
],
requiresDist: false,
shardName: "auto-reply-core-top-level",
},
{
checkName: "checks-node-auto-reply-reply-agent-runner",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-agent-runner",
},
{
checkName: "checks-node-auto-reply-reply-commands-1",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-commands-1",
},
{
checkName: "checks-node-auto-reply-reply-commands-2",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-commands-2",
},
{
checkName: "checks-node-auto-reply-reply-commands-3",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-commands-3",
},
{
checkName: "checks-node-auto-reply-reply-dispatch",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-dispatch",
},
{
checkName: "checks-node-auto-reply-reply-dispatch-core",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-dispatch-core",
},
{
checkName: "checks-node-auto-reply-reply-dispatch-delivery",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-dispatch-delivery",
},
{
checkName: "checks-node-auto-reply-reply-dispatch-lifecycle",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-dispatch-lifecycle",
},
{
checkName: "checks-node-auto-reply-reply-session",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-session",
},
{
checkName: "checks-node-auto-reply-reply-state-routing",
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
requiresDist: false,
shardName: "auto-reply-reply-state-routing",
},
]);
});
it("covers every auto-reply reply test exactly once across split shards", () => {
const actual = defaultShards
.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);
});
it("keeps each dispatch entrypoint in its own dedicated shard", () => {
const dispatchEntrypoints = new Map([
["auto-reply-reply-dispatch-core", "src/auto-reply/reply/dispatch-from-config.test.ts"],
[
"auto-reply-reply-dispatch-delivery",
"src/auto-reply/reply/dispatch-from-config.delivery.test.ts",
],
[
"auto-reply-reply-dispatch-lifecycle",
"src/auto-reply/reply/dispatch-from-config.lifecycle.test.ts",
],
]);
const shards = defaultShards;
for (const [shardName, entrypoint] of dispatchEntrypoints) {
expect(shards.find((shard) => shard.shardName === shardName)?.includePatterns).toEqual([
entrypoint,
]);
}
});
});