test: simplify changed test routing

This commit is contained in:
Peter Steinberger
2026-04-26 23:58:09 +01:00
parent 199d5f765f
commit 89ab39ca64
12 changed files with 340 additions and 279 deletions

View File

@@ -5,12 +5,11 @@ import { afterEach, describe, expect, it } from "vitest";
import {
detectChangedLanes,
isLiveDockerPackageScriptOnlyChange,
isPackageScriptOnlyChange,
} from "../../scripts/changed-lanes.mjs";
import {
CHANGED_CHECK_VITEST_NO_OUTPUT_TIMEOUT_MS,
createChangedCheckChildEnv,
createChangedCheckPlan,
createChangedCheckVitestEnv,
} from "../../scripts/check-changed.mjs";
import { cleanupTempDirs, makeTempRepoRoot } from "../helpers/temp-repo.js";
@@ -216,8 +215,8 @@ describe("scripts/changed-lanes", () => {
extensionTests: true,
all: false,
});
expect(plan.runExtensionTests).toBe(true);
expect(plan.testTargets).toEqual(["src/plugin-sdk/core.test.ts"]);
expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:extensions");
expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:extensions:test");
});
it("fails safe for root config changes", () => {
@@ -225,8 +224,8 @@ describe("scripts/changed-lanes", () => {
const plan = createChangedCheckPlan(result);
expect(result.lanes.all).toBe(true);
expect(plan.runFullTests).toBe(true);
expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:all");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("routes gitignore changes to tooling instead of all lanes", () => {
@@ -237,10 +236,9 @@ describe("scripts/changed-lanes", () => {
tooling: true,
all: false,
});
expect(plan.runFullTests).toBe(false);
expect(plan.runChangedTestsBroad).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toContain("lint:scripts");
expect(plan.commands.map((command) => command.args[0])).not.toContain("tsgo:all");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("routes live Docker ACP tooling changes through a focused gate", () => {
@@ -258,8 +256,6 @@ describe("scripts/changed-lanes", () => {
all: false,
tooling: false,
});
expect(plan.runFullTests).toBe(false);
expect(plan.runChangedTestsBroad).toBe(false);
expect(plan.commands.map((command) => command.name)).toEqual([
"conflict markers",
"typecheck core tests",
@@ -267,8 +263,6 @@ describe("scripts/changed-lanes", () => {
"lint scripts",
"live Docker shell syntax",
"live Docker scheduler dry run",
"ACP bind unit tests",
"ACPX extension tests",
]);
expect(
plan.commands.find((command) => command.name === "live Docker shell syntax"),
@@ -330,7 +324,6 @@ describe("scripts/changed-lanes", () => {
releaseMetadata: false,
all: false,
});
expect(plan.runFullTests).toBe(false);
expect(plan.commands.map((command) => command.name)).toContain("live Docker scheduler dry run");
});
@@ -400,6 +393,77 @@ describe("scripts/changed-lanes", () => {
});
});
it("classifies normal package script changes from the git diff", () => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-package-scripts-");
git(dir, ["init", "-q", "--initial-branch=main"]);
writeFileSync(
path.join(dir, "package.json"),
`${JSON.stringify(
{
name: "fixture",
scripts: {
test: "node scripts/test-projects.mjs",
},
dependencies: {
leftpad: "1.0.0",
},
},
null,
2,
)}\n`,
"utf8",
);
git(dir, ["add", "package.json"]);
git(dir, [
"-c",
"user.email=test@example.com",
"-c",
"user.name=Test User",
"commit",
"-q",
"-m",
"initial",
]);
writeFileSync(
path.join(dir, "package.json"),
`${JSON.stringify(
{
name: "fixture",
scripts: {
test: "node scripts/test-projects.mjs",
"test:profile": "node scripts/profile-tests.mjs",
},
dependencies: {
leftpad: "1.0.0",
},
},
null,
2,
)}\n`,
"utf8",
);
const output = execFileSync(
process.execPath,
[path.join(repoRoot, "scripts", "changed-lanes.mjs"), "--json", "--base", "HEAD"],
{
cwd: dir,
encoding: "utf8",
env: createNestedGitEnv(),
},
);
expect(JSON.parse(output)).toMatchObject({
paths: ["package.json"],
lanes: {
tooling: true,
all: false,
liveDockerTooling: false,
},
});
});
it("keeps non-script package changes off the live Docker focused gate", () => {
const before = `${JSON.stringify(
{ name: "fixture", scripts: {}, dependencies: { leftpad: "1.0.0" } },
@@ -422,6 +486,41 @@ describe("scripts/changed-lanes", () => {
expect(isLiveDockerPackageScriptOnlyChange(before, after)).toBe(false);
});
it("routes package script-only changes through the tooling gate", () => {
const before = `${JSON.stringify(
{ name: "fixture", scripts: { test: "node test.js" }, dependencies: { leftpad: "1.0.0" } },
null,
2,
)}\n`;
const after = `${JSON.stringify(
{
name: "fixture",
scripts: {
test: "node test.js",
"test:profile": "node scripts/profile-tests.mjs",
},
dependencies: { leftpad: "1.0.0" },
},
null,
2,
)}\n`;
expect(isPackageScriptOnlyChange(before, after)).toBe(true);
const result = detectChangedLanes(["package.json"], {
packageJsonChangeKind: "tooling",
});
const plan = createChangedCheckPlan(result);
expect(result.lanes).toMatchObject({
tooling: true,
all: false,
liveDockerTooling: false,
});
expect(plan.commands.map((command) => command.args[0])).toContain("lint:scripts");
expect(plan.commands.map((command) => command.args[0])).not.toContain("tsgo:all");
});
it("keeps release metadata commits off the full changed gate", () => {
const result = detectChangedLanes([
"CHANGELOG.md",
@@ -443,7 +542,6 @@ describe("scripts/changed-lanes", () => {
core: false,
apps: false,
});
expect(plan.runFullTests).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toEqual([
"check:no-conflict-markers",
"release-metadata:check",
@@ -519,26 +617,24 @@ describe("scripts/changed-lanes", () => {
tooling: true,
all: false,
});
expect(plan.testTargets).toEqual(["test/git-hooks-pre-commit.test.ts"]);
expect(plan.runFullTests).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toContain("lint:scripts");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("keeps shared Vitest wiring changes on the broad changed test path", () => {
it("keeps shared Vitest wiring changes out of check test execution", () => {
const result = detectChangedLanes(["test/vitest/vitest.shared.config.ts"]);
const plan = createChangedCheckPlan(result);
expect(plan.testTargets).toEqual([]);
expect(plan.runChangedTestsBroad).toBe(true);
expect(plan.runFullTests).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toContain("lint:scripts");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("keeps setup changes on the broad changed test path", () => {
it("keeps setup changes out of check test execution", () => {
const result = detectChangedLanes(["test/setup.ts"]);
const plan = createChangedCheckPlan(result);
expect(plan.testTargets).toEqual([]);
expect(plan.runChangedTestsBroad).toBe(true);
expect(plan.runFullTests).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toContain("lint:scripts");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("does not route generated A2UI artifacts as direct Vitest targets", () => {
@@ -548,17 +644,16 @@ describe("scripts/changed-lanes", () => {
]);
const plan = createChangedCheckPlan(result);
expect(plan.testTargets).toEqual(["test/scripts/bundle-a2ui.test.ts"]);
expect(plan.runChangedTestsBroad).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:core");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("routes changed extension Vitest configs to only their owning shard", () => {
const result = detectChangedLanes(["test/vitest/vitest.extension-discord.config.ts"]);
const plan = createChangedCheckPlan(result);
expect(plan.testTargets).toEqual(["test/vitest/vitest.extension-discord.config.ts"]);
expect(plan.runChangedTestsBroad).toBe(false);
expect(plan.runFullTests).toBe(false);
expect(plan.commands.map((command) => command.args[0])).toContain("lint:scripts");
expect(plan.commands.map((command) => command.args[0])).not.toContain("test");
});
it("keeps an empty changed path list as a no-op", () => {
@@ -580,8 +675,6 @@ describe("scripts/changed-lanes", () => {
expect(plan.commands).toEqual([
{ name: "conflict markers", args: ["check:no-conflict-markers"] },
]);
expect(plan.runChangedTestsBroad).toBe(false);
expect(plan.runFullTests).toBe(false);
});
it("keeps docs-only changes cheap", () => {
@@ -592,40 +685,5 @@ describe("scripts/changed-lanes", () => {
expect(plan.commands).toEqual([
{ name: "conflict markers", args: ["check:no-conflict-markers"] },
]);
expect(plan.runChangedTestsBroad).toBe(false);
expect(plan.runFullTests).toBe(false);
});
it("sets a ten-minute Vitest watchdog for changed checks", () => {
expect(CHANGED_CHECK_VITEST_NO_OUTPUT_TIMEOUT_MS).toBe("600000");
expect(createChangedCheckVitestEnv({ PATH: "/usr/bin" })).toMatchObject({
PATH: "/usr/bin",
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: CHANGED_CHECK_VITEST_NO_OUTPUT_TIMEOUT_MS,
OPENCLAW_VITEST_NO_OUTPUT_RETRY: "0",
OPENCLAW_TEST_PROJECTS_SERIAL: "1",
OPENCLAW_VITEST_MAX_WORKERS: "1",
});
expect(
createChangedCheckVitestEnv({
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "45000",
OPENCLAW_VITEST_NO_OUTPUT_RETRY: "1",
}),
).toMatchObject({
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "45000",
OPENCLAW_VITEST_NO_OUTPUT_RETRY: "1",
});
});
it("does not force serial changed-check tests in CI or when workers are explicit", () => {
expect(createChangedCheckVitestEnv({ CI: "true" })).not.toHaveProperty(
"OPENCLAW_VITEST_MAX_WORKERS",
);
expect(createChangedCheckVitestEnv({ OPENCLAW_VITEST_MAX_WORKERS: "4" })).toMatchObject({
OPENCLAW_VITEST_MAX_WORKERS: "4",
});
expect(
createChangedCheckVitestEnv({ OPENCLAW_TEST_PROJECTS_PARALLEL: "4" }),
).not.toHaveProperty("OPENCLAW_TEST_PROJECTS_SERIAL");
});
});

View File

@@ -25,12 +25,23 @@ describe("scripts/test-projects changed-target routing", () => {
).toEqual(["src/shared/string-normalization.test.ts", "src/utils/provider-utils.test.ts"]);
});
it("keeps the broad changed run for Vitest wiring edits", () => {
it("keeps changed mode focused by default for Vitest wiring edits", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"test/vitest/vitest.shared.config.ts",
"src/utils/provider-utils.ts",
]),
).toEqual(["src/utils/provider-utils.test.ts"]);
});
it("keeps the broad changed run available for Vitest wiring edits", () => {
expect(
resolveChangedTargetArgs(
["--changed", "origin/main"],
process.cwd(),
() => ["test/vitest/vitest.shared.config.ts", "src/utils/provider-utils.ts"],
{ env: { OPENCLAW_TEST_CHANGED_BROAD: "1" } },
),
).toBeNull();
});
@@ -129,11 +140,22 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("keeps the broad changed run for shared test helpers", () => {
it("keeps shared test helpers cheap by default when no precise target exists", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"test/helpers/channels/plugin.ts",
]),
).toEqual([]);
});
it("keeps the broad changed run available for shared test helpers", () => {
expect(
resolveChangedTargetArgs(
["--changed", "origin/main"],
process.cwd(),
() => ["test/helpers/channels/plugin.ts"],
{ env: { OPENCLAW_TEST_CHANGED_BROAD: "1" } },
),
).toBeNull();
});
@@ -174,11 +196,22 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("keeps the broad changed run for unknown root surfaces", () => {
it("keeps unknown root surfaces cheap by default", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"unknown/file.txt",
]),
).toEqual([]);
});
it("keeps the broad changed run available for unknown root surfaces", () => {
expect(
resolveChangedTargetArgs(
["--changed", "origin/main"],
process.cwd(),
() => ["unknown/file.txt"],
{ env: { OPENCLAW_TEST_CHANGED_BROAD: "1" } },
),
).toBeNull();
});
@@ -204,11 +237,29 @@ describe("scripts/test-projects changed-target routing", () => {
).toEqual([]);
});
it("adds extension tests for public plugin SDK changes", () => {
it("keeps public plugin SDK changes focused by default", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"src/plugin-sdk/provider-entry.ts",
]);
expect(plans).toEqual([
{
config: "test/vitest/vitest.unit-fast.config.ts",
forwardedArgs: [],
includePatterns: ["src/plugin-sdk/provider-entry.test.ts"],
watchMode: false,
},
]);
});
it("adds extension tests for public plugin SDK changes in broad changed mode", () => {
const plans = buildVitestRunPlans(
["--changed", "origin/main"],
process.cwd(),
() => ["src/plugin-sdk/provider-entry.ts"],
{ env: { OPENCLAW_TEST_CHANGED_BROAD: "1" } },
);
expect(plans).toEqual([
{
config: "test/vitest/vitest.unit-fast.config.ts",
@@ -485,11 +536,29 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("routes plugin-sdk source files with sibling tests narrowly plus extension tests", () => {
it("routes plugin-sdk source files with sibling tests narrowly by default", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"src/plugin-sdk/facade-runtime.ts",
]);
expect(plans).toEqual([
{
config: "test/vitest/vitest.bundled.config.ts",
forwardedArgs: [],
includePatterns: ["src/plugin-sdk/facade-runtime.test.ts"],
watchMode: false,
},
]);
});
it("routes plugin-sdk source files with sibling tests plus extensions in broad changed mode", () => {
const plans = buildVitestRunPlans(
["--changed", "origin/main"],
process.cwd(),
() => ["src/plugin-sdk/facade-runtime.ts"],
{ env: { OPENCLAW_TEST_CHANGED_BROAD: "1" } },
);
expect(plans).toEqual([
{
config: "test/vitest/vitest.bundled.config.ts",
@@ -521,22 +590,27 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("keeps focused changed mode to precise targets only", () => {
expect(
resolveChangedTestTargetPlan(["package.json", "src/commands/channels.add.ts"], {
focused: true,
}),
).toEqual({
it("keeps changed mode to precise targets by default", () => {
expect(resolveChangedTestTargetPlan(["package.json", "src/commands/channels.add.ts"])).toEqual({
mode: "targets",
targets: ["src/commands/channels.add.test.ts"],
});
});
it("uses import-graph targets in focused changed mode", () => {
it("keeps broad changed fallback available through explicit env", () => {
expect(
resolveChangedTestTargetPlan(["test/helpers/plugins/plugin-registration.ts"], {
focused: true,
}).targets,
resolveChangedTestTargetPlan(["package.json", "src/commands/channels.add.ts"], {
env: { OPENCLAW_TEST_CHANGED_BROAD: "1" },
}),
).toEqual({
mode: "broad",
targets: [],
});
});
it("uses import-graph targets in default changed mode", () => {
expect(
resolveChangedTestTargetPlan(["test/helpers/plugins/plugin-registration.ts"]).targets,
).toContain("extensions/openrouter/index.test.ts");
});