fix(test): chunk broad script test routing

This commit is contained in:
Vincent Koc
2026-06-20 00:57:21 +02:00
parent 0a1ce14dd1
commit 6fc0a3a9bd
4 changed files with 166 additions and 21 deletions

View File

@@ -561,6 +561,29 @@ function isExplicitTestFileArg(arg) {
return EXPLICIT_TEST_FILE_RE.test(arg) && isExplicitFileTargetArg(arg);
}
function isDelegableBroadProjectRouterTarget(arg, cwd) {
const relative = toRepoRelativeArg(arg, cwd).replace(/\/+$/u, "");
return (
relative === "test/scripts" ||
relative === "test/scripts/*.test.ts" ||
relative === "test/scripts/**/*.test.ts"
);
}
function isExplicitProjectRouterTargetArg(arg, cwd = process.cwd(), fsImpl = fs) {
if (!isPathLikeExplicitFileArg(arg)) {
return false;
}
if (GLOB_PATTERN_CHARS_RE.test(arg)) {
return isDelegableBroadProjectRouterTarget(arg, cwd);
}
if (isExplicitFileTargetArg(arg)) {
return true;
}
const filePath = path.isAbsolute(arg) ? arg : path.resolve(cwd, arg);
return fsImpl.existsSync(filePath) && isDelegableBroadProjectRouterTarget(arg, cwd);
}
function collectExplicitFileTargetArgs(argv, predicate = isExplicitFileTargetArg) {
const files = [];
for (let index = 0; index < argv.length; index += 1) {
@@ -582,6 +605,12 @@ function collectExplicitFileTargetArgs(argv, predicate = isExplicitFileTargetArg
return files;
}
function collectExplicitProjectRouterTargetArgs(argv, cwd = process.cwd(), fsImpl = fs) {
return collectExplicitFileTargetArgs(argv, (arg) =>
isExplicitProjectRouterTargetArg(arg, cwd, fsImpl),
);
}
function collectExplicitTestFileArgs(argv) {
return collectExplicitFileTargetArgs(argv, isExplicitTestFileArg);
}
@@ -701,9 +730,9 @@ function hasNonRunVitestSubcommand(argv) {
}
/**
* Delegates default or explicit-file runs to the repo test-projects runner.
* Delegates explicit path runs to the repo test-projects runner.
*/
export function resolveTestProjectsDelegationArgs(argv) {
export function resolveTestProjectsDelegationArgs(argv, cwd = process.cwd()) {
if (
hasExplicitVitestConfigArg(argv) ||
hasAlternateVitestRootArg(argv) ||
@@ -712,7 +741,7 @@ export function resolveTestProjectsDelegationArgs(argv) {
hasNonRunVitestSubcommand(argv) ||
hasExplicitDisabledRunFlag(argv) ||
hasSeparateVitestOptionValueArg(argv) ||
collectExplicitFileTargetArgs(argv).length === 0
collectExplicitProjectRouterTargetArgs(argv, cwd).length === 0
) {
return null;
}

View File

@@ -287,6 +287,11 @@ const TOOLING_ISOLATED_VITEST_CONFIG = "test/vitest/vitest.tooling-isolated.conf
const TOOLING_VITEST_CONFIG = "test/vitest/vitest.tooling.config.ts";
const TOOLING_DOCKER_TEST_TARGET = "test/scripts/docker-build-helper.test.ts";
const TOOLING_ISOLATED_TEST_TARGET = "test/scripts/openclaw-e2e-instance.test.ts";
const BROAD_TOOLING_SCRIPT_TEST_PATTERNS = new Set([
"test/scripts/**/*.test.ts",
"test/scripts/*.test.ts",
]);
const BROAD_TOOLING_SCRIPT_TEST_TARGET_CHUNK_SIZE = 60;
const TUI_VITEST_CONFIG = "test/vitest/vitest.tui.config.ts";
const TUI_PTY_VITEST_CONFIG = "test/vitest/vitest.tui-pty.config.ts";
const UI_VITEST_CONFIG = "test/vitest/vitest.ui.config.ts";
@@ -1465,6 +1470,64 @@ function splitTargetChunks(targets, chunkCount) {
return chunks;
}
function listBroadScriptTestTargets(pattern, cwd) {
const root = path.join(cwd, "test/scripts");
if (!fs.existsSync(root)) {
return [];
}
return listRepoFilesRecursive(root, cwd)
.filter((file) => file.endsWith(".test.ts") && path.matchesGlob(file, pattern))
.toSorted((left, right) => left.localeCompare(right));
}
function listBroadToolingScriptTestTargets(pattern, cwd) {
return listBroadScriptTestTargets(pattern, cwd).filter(
(file) => classifyTarget(file, cwd) === "tooling",
);
}
function createBroadToolingScriptPlans({ config, forwardedArgs, includePatterns, watchMode, cwd }) {
if (watchMode || config !== TOOLING_VITEST_CONFIG || !includePatterns) {
return null;
}
const [pattern] = includePatterns;
const targets =
includePatterns.length === 1 && BROAD_TOOLING_SCRIPT_TEST_PATTERNS.has(pattern)
? listBroadToolingScriptTestTargets(pattern, cwd)
: includePatterns.every((target) => target.startsWith("test/scripts/"))
? includePatterns
: [];
if (targets.length <= BROAD_TOOLING_SCRIPT_TEST_TARGET_CHUNK_SIZE) {
return null;
}
const chunkCount = Math.ceil(targets.length / BROAD_TOOLING_SCRIPT_TEST_TARGET_CHUNK_SIZE);
const chunks = splitTargetChunks(targets, chunkCount);
return chunks.length > 0
? chunks.map((chunk) => ({
config,
forwardedArgs,
includePatterns: chunk,
watchMode,
}))
: null;
}
function expandBroadToolingScriptTargets(targetArgs, cwd, watchMode) {
if (watchMode) {
return targetArgs;
}
return uniqueOrdered(
targetArgs.flatMap((targetArg) => {
const pattern = toScopedIncludePattern(targetArg, cwd);
if (!BROAD_TOOLING_SCRIPT_TEST_PATTERNS.has(pattern)) {
return [targetArg];
}
const targets = listBroadScriptTestTargets(pattern, cwd);
return targets.length > 0 ? targets : [targetArg];
}),
);
}
function isExistingPathTarget(arg, cwd) {
return fs.existsSync(path.resolve(cwd, arg));
}
@@ -2755,7 +2818,11 @@ export function buildVitestRunPlans(
const changedTargetArgs =
targetArgs.length === 0 ? resolveChangedTargetArgs(args, cwd, listChangedPaths, options) : null;
const requestedTargetArgs = changedTargetArgs ?? targetArgs;
const activeTargetArgs = expandExplicitSourceTestTargets(requestedTargetArgs, cwd);
const activeTargetArgs = expandBroadToolingScriptTargets(
expandExplicitSourceTestTargets(requestedTargetArgs, cwd),
cwd,
watchMode,
);
const activeForwardedArgs =
changedTargetArgs !== null ? stripChangedArgs(forwardedArgs) : forwardedArgs;
if (changedTargetArgs !== null && activeTargetArgs.length === 0) {
@@ -2964,9 +3031,21 @@ export function buildVitestRunPlans(
}),
);
const scopedTargetArgs = useCliTargetArgs ? uniqueOrdered(grouped) : [];
const forwardedPlanArgs = [...nonTargetArgs, ...scopedTargetArgs];
const broadToolingScriptPlans = createBroadToolingScriptPlans({
config,
cwd,
forwardedArgs: forwardedPlanArgs,
includePatterns,
watchMode,
});
if (broadToolingScriptPlans) {
plans.push(...broadToolingScriptPlans);
continue;
}
plans.push({
config,
forwardedArgs: [...nonTargetArgs, ...scopedTargetArgs],
forwardedArgs: forwardedPlanArgs,
includePatterns,
watchMode,
});

View File

@@ -293,6 +293,19 @@ describe("scripts/run-vitest", () => {
]);
});
it("delegates bare explicit directories and globs to the project router", () => {
expect(resolveTestProjectsDelegationArgs(["test/scripts"])).toEqual(["test/scripts"]);
expect(
resolveTestProjectsDelegationArgs(["run", "test/scripts", "--reporter=verbose"]),
).toEqual(["test/scripts", "--reporter=verbose"]);
expect(resolveTestProjectsDelegationArgs(["test/scripts/*.test.ts"])).toEqual([
"test/scripts/*.test.ts",
]);
expect(resolveTestProjectsDelegationArgs(["src/agents/**/*.ts"])).toBeNull();
expect(resolveTestProjectsDelegationArgs(["src/**/*.test.ts"])).toBeNull();
expect(resolveTestProjectsDelegationArgs(["./src"])).toBeNull();
});
it("delegates mixed filters when an explicit file target is present", () => {
expect(
resolveTestProjectsDelegationArgs(["src/agents", "test/scripts/run-vitest.test.ts"]),

View File

@@ -1610,8 +1610,14 @@ describe("scripts/test-projects changed-target routing", () => {
});
});
it("includes the isolated tooling shard for broad shell helper targets", () => {
expect(buildVitestRunPlans(["test/scripts"], process.cwd())).toEqual([
it("chunks the broad shell helper tooling shard after isolated targets", () => {
const plans = buildVitestRunPlans(["test/scripts"], process.cwd());
expect(plans.slice(0, 3)).toEqual([
expect.objectContaining({
config: "test/vitest/vitest.unit-fast.config.ts",
includePatterns: expect.arrayContaining(["test/scripts/arg-utils.test.ts"]),
watchMode: false,
}),
{
config: "test/vitest/vitest.tooling-docker.config.ts",
forwardedArgs: [],
@@ -1624,13 +1630,19 @@ describe("scripts/test-projects changed-target routing", () => {
includePatterns: ["test/scripts/openclaw-e2e-instance.test.ts"],
watchMode: false,
},
{
config: "test/vitest/vitest.tooling.config.ts",
forwardedArgs: [],
includePatterns: ["test/scripts/**/*.test.ts"],
watchMode: false,
},
]);
const toolingPlans = plans.slice(3);
const toolingTargets = toolingPlans.flatMap((plan) => plan.includePatterns ?? []);
expect(toolingPlans.length).toBeGreaterThan(1);
expect(
toolingPlans.every((plan) => plan.config === "test/vitest/vitest.tooling.config.ts"),
).toBe(true);
expect(toolingPlans.every((plan) => (plan.includePatterns?.length ?? 0) <= 60)).toBe(true);
expect(toolingTargets).toContain("test/scripts/run-opengrep.test.ts");
expect(toolingTargets).not.toContain("test/scripts/docker-build-helper.test.ts");
expect(toolingTargets).not.toContain("test/scripts/openclaw-e2e-instance.test.ts");
expect(new Set(toolingTargets).size).toBe(toolingTargets.length);
});
it("routes the src scripts test root to the tooling shard", () => {
@@ -1722,8 +1734,14 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("includes the isolated tooling shard for broad shell helper globs", () => {
expect(buildVitestRunPlans(["test/scripts/*.test.ts"], process.cwd())).toEqual([
it("chunks broad shell helper globs after isolated targets", () => {
const plans = buildVitestRunPlans(["test/scripts/*.test.ts"], process.cwd());
expect(plans.slice(0, 3)).toEqual([
expect.objectContaining({
config: "test/vitest/vitest.unit-fast.config.ts",
includePatterns: expect.arrayContaining(["test/scripts/arg-utils.test.ts"]),
watchMode: false,
}),
{
config: "test/vitest/vitest.tooling-docker.config.ts",
forwardedArgs: [],
@@ -1736,13 +1754,19 @@ describe("scripts/test-projects changed-target routing", () => {
includePatterns: ["test/scripts/openclaw-e2e-instance.test.ts"],
watchMode: false,
},
{
config: "test/vitest/vitest.tooling.config.ts",
forwardedArgs: [],
includePatterns: ["test/scripts/*.test.ts"],
watchMode: false,
},
]);
const toolingPlans = plans.slice(3);
const toolingTargets = toolingPlans.flatMap((plan) => plan.includePatterns ?? []);
expect(toolingPlans.length).toBeGreaterThan(1);
expect(
toolingPlans.every((plan) => plan.config === "test/vitest/vitest.tooling.config.ts"),
).toBe(true);
expect(toolingPlans.every((plan) => (plan.includePatterns?.length ?? 0) <= 60)).toBe(true);
expect(toolingTargets).toContain("test/scripts/run-opengrep.test.ts");
expect(toolingTargets).not.toContain("test/scripts/docker-build-helper.test.ts");
expect(toolingTargets).not.toContain("test/scripts/openclaw-e2e-instance.test.ts");
expect(new Set(toolingTargets).size).toBe(toolingTargets.length);
});
it("keeps broad shell helper watch targets in one tooling shard", () => {