mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
Live, Docker, channel and release wrappers could repeat failed validation or downgrade failed jobs automatically. Run each test invocation once, remove known-flake redispatch, and require an explicit recorded waiver when the release policy permits one. Wait for the original plugin publisher to settle before reporting failure. Keep bounded artifact-download recovery before tests. Read published v2026.9.6 empty retry metadata without restoring executable allowances or automatic waivers, preserving the sealed plan digest. Use the existing fresh-read owner for release-priority cleanup. Testbox owner/sibling proof and three CI-config replays passed. The two freshness regressions passed 20 repetitions. Published evidence retained its original digest; nonempty retry metadata remained rejected. The stale Testbox assertion now matches its already-landed four-hour lease; this change raises no runtime, test, hook or watchdog budget.
101 lines
4.1 KiB
TypeScript
101 lines
4.1 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import { readFileSync, symlinkSync } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { parse } from "yaml";
|
|
import { resolveDockerE2ePlan } from "../../scripts/lib/docker-e2e-plan.mts";
|
|
import { createPluginPrereleaseTestPlan } from "../../scripts/lib/plugin-prerelease-test-plan.mts";
|
|
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
|
|
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|
const workflow = parse(
|
|
readFileSync(".github/workflows/openclaw-live-and-e2e-checks-reusable.yml", "utf8"),
|
|
) as { jobs: Record<string, { steps: { name?: string; run?: string }[] }> };
|
|
const step = workflow.jobs.prepare_docker_e2e_image?.steps.find(
|
|
(entry) => entry.name === "Plan Docker E2E images",
|
|
);
|
|
if (!step?.run) {
|
|
throw new Error("Missing shared candidate planning step");
|
|
}
|
|
const planningScript = step.run;
|
|
|
|
type Plan = ReturnType<typeof resolveDockerE2ePlan>["plan"];
|
|
|
|
function runPlanningStep(
|
|
releaseProfile: "beta" | "stable" | "full",
|
|
mode: "prepare" | "release" | "targeted",
|
|
upgradeSurvivorScenarios = mode === "targeted" ? "" : "reported-issues",
|
|
) {
|
|
const root = tempDirs.make("openclaw-candidate-plan-");
|
|
const output = join(root, "github-output");
|
|
symlinkSync(resolve("."), join(root, ".release-harness"), "dir");
|
|
execFileSync("bash", ["--noprofile", "--norc", "-c", planningScript], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
timeout: 30_000,
|
|
env: {
|
|
...process.env,
|
|
GITHUB_OUTPUT: output,
|
|
TSX_TSCONFIG_PATH: resolve("tsconfig.json"),
|
|
INCLUDE_OPENWEBUI: "false",
|
|
INCLUDE_RELEASE_PATH_SUITES: String(mode === "release"),
|
|
LANES: mode === "targeted" ? "npm-onboard-channel-agent" : "",
|
|
PREPARE_ONLY: String(mode === "prepare"),
|
|
RELEASE_TEST_PROFILE: releaseProfile,
|
|
OPENCLAW_DOCKER_ALL_TIMINGS: "0",
|
|
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: "",
|
|
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: "",
|
|
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: upgradeSurvivorScenarios,
|
|
},
|
|
});
|
|
return {
|
|
plan: JSON.parse(readFileSync(join(root, ".artifacts/docker-tests/plan.json"), "utf8")) as Plan,
|
|
output: readFileSync(output, "utf8"),
|
|
};
|
|
}
|
|
|
|
describe("shared release candidate preparation", () => {
|
|
it.each(["beta", "stable", "full"] as const)(
|
|
"prepares every plugin child's package under the %s profile",
|
|
(releaseProfile) => {
|
|
const { plan, output } = runPlanningStep(releaseProfile, "prepare");
|
|
const release = runPlanningStep(releaseProfile, "release").plan;
|
|
const child = resolveDockerE2ePlan({
|
|
includeOpenWebUI: false,
|
|
liveMode: "all",
|
|
orderLanes: (lanes) => lanes,
|
|
planReleaseAll: false,
|
|
profile: "all",
|
|
releaseChunk: "",
|
|
releaseProfile,
|
|
selectedLaneNames: createPluginPrereleaseTestPlan().dockerLanes,
|
|
}).plan;
|
|
const missing = [
|
|
...child.requiredPrepublishPluginPackages,
|
|
...release.requiredPrepublishPluginPackages,
|
|
].filter((name) => !plan.requiredPrepublishPluginPackages.includes(name));
|
|
expect(missing).toEqual([]);
|
|
expect(output).toContain(
|
|
`required_prepublish_plugin_packages=${JSON.stringify(plan.requiredPrepublishPluginPackages)}`,
|
|
);
|
|
expect(plan.needs.bareImage).toBe(true);
|
|
expect(plan.needs.functionalImage).toBe(true);
|
|
expect(release.lanes.map((lane) => lane.name)).not.toContain(
|
|
"npm-onboard-slack-candidate-channel-agent",
|
|
);
|
|
},
|
|
);
|
|
|
|
it("keeps targeted execution limited to the selected lane and its packages", () => {
|
|
const { plan } = runPlanningStep("full", "targeted");
|
|
expect(plan.lanes.map((lane) => lane.name)).toEqual(["npm-onboard-channel-agent"]);
|
|
expect(plan.requiredPrepublishPluginPackages).toEqual(["@openclaw/codex"]);
|
|
});
|
|
|
|
it("prepares the default non-soak survivor companion packages", () => {
|
|
const { plan } = runPlanningStep("beta", "prepare", "");
|
|
|
|
expect(plan.requiredPrepublishPluginPackages).toContain("@openclaw/duckduckgo-plugin");
|
|
});
|
|
});
|