diff --git a/.agents/skills/openclaw-testing/SKILL.md b/.agents/skills/openclaw-testing/SKILL.md index 60fd4c6660a..95fed1f8a3c 100644 --- a/.agents/skills/openclaw-testing/SKILL.md +++ b/.agents/skills/openclaw-testing/SKILL.md @@ -295,7 +295,11 @@ and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches. The bundled-channel runtime-dependency coverage inside `plugins-integrations` uses the split `bundled-channel-*` and `bundled-channel-update-*` lanes rather than the serial `bundled-channel-deps` lane, so failures produce cheap targeted -reruns for the exact channel/update scenario. +reruns for the exact channel/update scenario. The bundled plugin +install/uninstall sweep is also split into +`bundled-plugin-install-uninstall-0` through +`bundled-plugin-install-uninstall-7`; selecting the legacy +`bundled-plugin-install-uninstall` lane expands to all eight shards. ## Package Acceptance diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index e105f487e73..b880b167b30 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -325,6 +325,9 @@ Release Docker coverage includes: - OpenWebUI coverage inside the `plugins-integrations` chunk when requested - split bundled-channel dependency lanes inside `plugins-integrations` instead of the serial all-in-one bundled-channel lane +- split bundled plugin install/uninstall lanes + `bundled-plugin-install-uninstall-0` through + `bundled-plugin-install-uninstall-7` - live/E2E provider suites and Docker live model coverage when release checks include live suites diff --git a/scripts/lib/docker-e2e-plan.mjs b/scripts/lib/docker-e2e-plan.mjs index cfcbae11a30..89bd9df9171 100644 --- a/scripts/lib/docker-e2e-plan.mjs +++ b/scripts/lib/docker-e2e-plan.mjs @@ -2,6 +2,7 @@ // This module turns the scenario catalog plus env-driven inputs into a concrete // lane plan. It intentionally does not define scenario commands. import { + BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS, DEFAULT_LIVE_RETRIES, allReleasePathLanes, mainLanes, @@ -34,14 +35,23 @@ export function parseLaneSelection(raw) { if (!raw) { return []; } - const laneAliases = new Map([["bundled-channel-deps", "bundled-channel-deps-compat"]]); + const laneAliases = new Map([ + ["bundled-channel-deps", ["bundled-channel-deps-compat"]], + [ + "bundled-plugin-install-uninstall", + Array.from( + { length: BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS }, + (_, index) => `bundled-plugin-install-uninstall-${index}`, + ), + ], + ]); return [ ...new Set( String(raw) .split(/[,\s]+/u) .map((token) => token.trim()) .filter(Boolean) - .map((token) => laneAliases.get(token) ?? token), + .flatMap((token) => laneAliases.get(token) ?? [token]), ), ]; } diff --git a/scripts/lib/docker-e2e-scenarios.mjs b/scripts/lib/docker-e2e-scenarios.mjs index 8487cbb4f1b..30c76656bbc 100644 --- a/scripts/lib/docker-e2e-scenarios.mjs +++ b/scripts/lib/docker-e2e-scenarios.mjs @@ -8,6 +8,7 @@ const LIVE_ACP_TIMEOUT_MS = 20 * 60 * 1000; const LIVE_CLI_TIMEOUT_MS = 20 * 60 * 1000; const LIVE_PROFILE_TIMEOUT_MS = 20 * 60 * 1000; const OPENWEBUI_TIMEOUT_MS = 20 * 60 * 1000; +export const BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS = 8; export const LIVE_RETRY_PATTERNS = [ /529\b/i, @@ -138,6 +139,19 @@ const bundledScenarioLanes = [ ), ]; +const bundledPluginInstallUninstallLanes = Array.from( + { length: BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS }, + (_, index) => + lane( + `bundled-plugin-install-uninstall-${index}`, + `OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL=${BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS} OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX=${index} OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:bundled-plugin-install-uninstall`, + { + resources: ["npm"], + weight: 2, + }, + ), +); + export const mainLanes = [ liveLane("live-models", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-models", { providers: ["claude-cli", "codex-cli", "google-gemini-cli"], @@ -215,14 +229,7 @@ export const mainLanes = [ resources: ["npm", "service"], weight: 6, }), - lane( - "bundled-plugin-install-uninstall", - "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:bundled-plugin-install-uninstall", - { - resources: ["npm"], - weight: 4, - }, - ), + ...bundledPluginInstallUninstallLanes, lane( "plugins-offline", "OPENCLAW_PLUGINS_E2E_CLAWHUB=0 OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugins", @@ -395,14 +402,7 @@ const releasePathChunks = { resources: ["npm", "service"], weight: 6, }), - lane( - "bundled-plugin-install-uninstall", - "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:bundled-plugin-install-uninstall", - { - resources: ["npm"], - weight: 4, - }, - ), + ...bundledPluginInstallUninstallLanes, npmLane("plugin-update", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugin-update"), ...bundledScenarioLanes, serviceLane( diff --git a/test/scripts/docker-e2e-plan.test.ts b/test/scripts/docker-e2e-plan.test.ts index cb4c36ecb5c..fa354027447 100644 --- a/test/scripts/docker-e2e-plan.test.ts +++ b/test/scripts/docker-e2e-plan.test.ts @@ -45,6 +45,9 @@ describe("scripts/lib/docker-e2e-plan", () => { expect(plan.lanes.map((lane) => lane.name)).toContain("mcp-channels"); expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-channel-feishu"); expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-channel-update-acpx"); + expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-plugin-install-uninstall-0"); + expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-plugin-install-uninstall-7"); + expect(plan.lanes.map((lane) => lane.name)).not.toContain("bundled-plugin-install-uninstall"); expect(plan.lanes.map((lane) => lane.name)).not.toContain("bundled-channel-deps"); expect(plan.lanes.map((lane) => lane.name)).not.toContain("openwebui"); }); @@ -111,17 +114,32 @@ describe("scripts/lib/docker-e2e-plan", () => { ]); }); - it("plans bundled plugin install/uninstall as package-backed plugin coverage", () => { - const plan = planFor({ selectedLaneNames: ["bundled-plugin-install-uninstall"] }); + it("maps bundled plugin install/uninstall to package-backed shards", () => { + const selectedLaneNames = parseLaneSelection("bundled-plugin-install-uninstall"); + const plan = planFor({ selectedLaneNames }); - expect(plan.lanes).toEqual([ + expect(selectedLaneNames).toEqual( + Array.from({ length: 8 }, (_, index) => `bundled-plugin-install-uninstall-${index}`), + ); + expect(plan.lanes).toHaveLength(8); + expect(plan.lanes[0]).toEqual( expect.objectContaining({ + command: expect.stringContaining("OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX=0"), imageKind: "functional", live: false, - name: "bundled-plugin-install-uninstall", + name: "bundled-plugin-install-uninstall-0", resources: expect.arrayContaining(["docker", "npm"]), }), - ]); + ); + expect(plan.lanes[7]).toEqual( + expect.objectContaining({ + command: expect.stringContaining("OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX=7"), + imageKind: "functional", + live: false, + name: "bundled-plugin-install-uninstall-7", + resources: expect.arrayContaining(["docker", "npm"]), + }), + ); expect(plan.needs).toMatchObject({ functionalImage: true, package: true,