ci: shard bundled plugin release sweep

This commit is contained in:
Peter Steinberger
2026-04-27 13:05:11 +01:00
parent 0dfea099d6
commit f68ef1ae7c
5 changed files with 59 additions and 24 deletions

View File

@@ -295,7 +295,11 @@ and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches.
The bundled-channel runtime-dependency coverage inside `plugins-integrations` The bundled-channel runtime-dependency coverage inside `plugins-integrations`
uses the split `bundled-channel-*` and `bundled-channel-update-*` lanes rather uses the split `bundled-channel-*` and `bundled-channel-update-*` lanes rather
than the serial `bundled-channel-deps` lane, so failures produce cheap targeted 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 ## Package Acceptance

View File

@@ -325,6 +325,9 @@ Release Docker coverage includes:
- OpenWebUI coverage inside the `plugins-integrations` chunk when requested - OpenWebUI coverage inside the `plugins-integrations` chunk when requested
- split bundled-channel dependency lanes inside `plugins-integrations` instead - split bundled-channel dependency lanes inside `plugins-integrations` instead
of the serial all-in-one bundled-channel lane 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 - live/E2E provider suites and Docker live model coverage when release checks
include live suites include live suites

View File

@@ -2,6 +2,7 @@
// This module turns the scenario catalog plus env-driven inputs into a concrete // This module turns the scenario catalog plus env-driven inputs into a concrete
// lane plan. It intentionally does not define scenario commands. // lane plan. It intentionally does not define scenario commands.
import { import {
BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS,
DEFAULT_LIVE_RETRIES, DEFAULT_LIVE_RETRIES,
allReleasePathLanes, allReleasePathLanes,
mainLanes, mainLanes,
@@ -34,14 +35,23 @@ export function parseLaneSelection(raw) {
if (!raw) { if (!raw) {
return []; 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 [ return [
...new Set( ...new Set(
String(raw) String(raw)
.split(/[,\s]+/u) .split(/[,\s]+/u)
.map((token) => token.trim()) .map((token) => token.trim())
.filter(Boolean) .filter(Boolean)
.map((token) => laneAliases.get(token) ?? token), .flatMap((token) => laneAliases.get(token) ?? [token]),
), ),
]; ];
} }

View File

@@ -8,6 +8,7 @@ const LIVE_ACP_TIMEOUT_MS = 20 * 60 * 1000;
const LIVE_CLI_TIMEOUT_MS = 20 * 60 * 1000; const LIVE_CLI_TIMEOUT_MS = 20 * 60 * 1000;
const LIVE_PROFILE_TIMEOUT_MS = 20 * 60 * 1000; const LIVE_PROFILE_TIMEOUT_MS = 20 * 60 * 1000;
const OPENWEBUI_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 = [ export const LIVE_RETRY_PATTERNS = [
/529\b/i, /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 = [ export const mainLanes = [
liveLane("live-models", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-models", { liveLane("live-models", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-models", {
providers: ["claude-cli", "codex-cli", "google-gemini-cli"], providers: ["claude-cli", "codex-cli", "google-gemini-cli"],
@@ -215,14 +229,7 @@ export const mainLanes = [
resources: ["npm", "service"], resources: ["npm", "service"],
weight: 6, weight: 6,
}), }),
lane( ...bundledPluginInstallUninstallLanes,
"bundled-plugin-install-uninstall",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:bundled-plugin-install-uninstall",
{
resources: ["npm"],
weight: 4,
},
),
lane( lane(
"plugins-offline", "plugins-offline",
"OPENCLAW_PLUGINS_E2E_CLAWHUB=0 OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugins", "OPENCLAW_PLUGINS_E2E_CLAWHUB=0 OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugins",
@@ -395,14 +402,7 @@ const releasePathChunks = {
resources: ["npm", "service"], resources: ["npm", "service"],
weight: 6, weight: 6,
}), }),
lane( ...bundledPluginInstallUninstallLanes,
"bundled-plugin-install-uninstall",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:bundled-plugin-install-uninstall",
{
resources: ["npm"],
weight: 4,
},
),
npmLane("plugin-update", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugin-update"), npmLane("plugin-update", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugin-update"),
...bundledScenarioLanes, ...bundledScenarioLanes,
serviceLane( serviceLane(

View File

@@ -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("mcp-channels");
expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-channel-feishu"); 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-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("bundled-channel-deps");
expect(plan.lanes.map((lane) => lane.name)).not.toContain("openwebui"); 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", () => { it("maps bundled plugin install/uninstall to package-backed shards", () => {
const plan = planFor({ selectedLaneNames: ["bundled-plugin-install-uninstall"] }); 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({ expect.objectContaining({
command: expect.stringContaining("OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX=0"),
imageKind: "functional", imageKind: "functional",
live: false, live: false,
name: "bundled-plugin-install-uninstall", name: "bundled-plugin-install-uninstall-0",
resources: expect.arrayContaining(["docker", "npm"]), 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({ expect(plan.needs).toMatchObject({
functionalImage: true, functionalImage: true,
package: true, package: true,