fix(ci): trust frozen plugin prerelease fixtures (#124446)

* fix(ci): trust frozen plugin prerelease fixtures

* fix(ci): apply scoped plugin test omissions
This commit is contained in:
Peter Steinberger
2026-08-15 23:47:25 -07:00
committed by GitHub
parent 7d05f7c5b9
commit 255ca8d96b
4 changed files with 331 additions and 9 deletions
@@ -525,6 +525,7 @@ exit 0
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: "",
PACKAGE_SPEC: "openclaw@beta",
PARENT_WORKFLOW_SHA: parentSha,
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: "[]",
PROVIDER: "openai",
PROVIDER_MODE: "mock-openai",
RELEASE_PACKAGE_SPEC: "",
@@ -1,7 +1,8 @@
// Plugin Prerelease Test Plan tests cover plugin prerelease test plan script behavior.
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { execFileSync, spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { parse } from "yaml";
import { findLaneByName } from "../../scripts/lib/docker-e2e-plan.mts";
import { BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS } from "../../scripts/lib/docker-e2e-scenarios.mts";
@@ -14,9 +15,11 @@ import {
pluginPrereleaseTimeoutComponents,
releaseTimeoutForProfile,
} from "../helpers/release-workflow-timeouts.js";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const CHECKOUT_V6 = "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10";
const UPLOAD_ARTIFACT_V7 = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
type WorkflowStep = {
env?: Record<string, string>;
@@ -63,6 +66,35 @@ function getDockerLane(name: string) {
return lane;
}
function runFrozenTargetNodeExclusionValidation(params: {
fullReleaseValidation: boolean;
patternsJson: string;
}) {
const workflow = readPluginPrereleaseWorkflow();
const validationStep = workflow.jobs.preflight.steps.find(
(step: WorkflowStep) => step.name === "Validate frozen-target Node exclusions",
);
if (!validationStep?.run) {
throw new Error("Missing frozen-target Node exclusion validation step");
}
const root = tempDirs.make("openclaw-plugin-prerelease-excludes-");
const outputPath = join(root, "github-output");
const result = spawnSync("bash", ["-c", validationStep.run], {
encoding: "utf8",
env: {
FULL_RELEASE_VALIDATION: String(params.fullReleaseValidation),
GITHUB_OUTPUT: outputPath,
NODE_TEST_EXCLUDE_PATTERNS_JSON: params.patternsJson,
PATH: process.env.PATH,
},
});
return {
...result,
output: existsSync(outputPath) ? readFileSync(outputPath, "utf8") : "",
};
}
describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
it("covers every pre-release plugin skill surface in the plugin prerelease plan", () => {
const plan = assertPluginPrereleaseTestPlanComplete();
@@ -276,6 +308,166 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
expect(fixtureServer).toContain("/versions/${fixture.version}/artifact");
});
it("validates frozen-target Node omissions as exact full-release-only plugin test paths", () => {
const defaultResult = runFrozenTargetNodeExclusionValidation({
fullReleaseValidation: false,
patternsJson: "[]",
});
expect(defaultResult.status, defaultResult.stderr).toBe(0);
expect(defaultResult.output).toBe("patterns_json=[]\n");
const validPatterns = [
"src/plugins/example.test.ts",
"src/plugins/runtime/example.runtime.test.ts",
];
const fullReleaseResult = runFrozenTargetNodeExclusionValidation({
fullReleaseValidation: true,
patternsJson: JSON.stringify(validPatterns),
});
expect(fullReleaseResult.status, fullReleaseResult.stderr).toBe(0);
expect(fullReleaseResult.output).toBe(`patterns_json=${JSON.stringify(validPatterns)}\n`);
for (const patternsJson of ["", "{}", "null", '"src/plugins/example.test.ts"']) {
const result = runFrozenTargetNodeExclusionValidation({
fullReleaseValidation: true,
patternsJson,
});
expect(result.status, `${patternsJson}\n${result.stderr}`).not.toBe(0);
}
for (const patternsJson of [JSON.stringify(["src/plugins/example.test.ts"]), " [ ] "]) {
const result = runFrozenTargetNodeExclusionValidation({
fullReleaseValidation: false,
patternsJson,
});
expect(result.status, `${patternsJson}\n${result.stderr}`).not.toBe(0);
}
for (const patternsJson of [
JSON.stringify([""]),
JSON.stringify([42]),
JSON.stringify(["/src/plugins/example.test.ts"]),
JSON.stringify(["C:\\src\\plugins\\example.test.ts"]),
JSON.stringify(["../src/plugins/example.test.ts"]),
JSON.stringify(["./src/plugins/example.test.ts"]),
JSON.stringify(["src/plugins/../example.test.ts"]),
JSON.stringify(["src/plugins//example.test.ts"]),
JSON.stringify(["src/agents/example.test.ts"]),
JSON.stringify(["src/plugins/example.ts"]),
JSON.stringify(["src/plugins/*.test.ts"]),
JSON.stringify(["src/plugins/example?.test.ts"]),
JSON.stringify(["src/plugins/[example].test.ts"]),
JSON.stringify(["src/plugins/example.test.ts", "src/plugins/example.test.ts"]),
]) {
const result = runFrozenTargetNodeExclusionValidation({
fullReleaseValidation: true,
patternsJson,
});
expect(result.status, `${patternsJson}\n${result.stderr}`).not.toBe(0);
}
});
it("keeps frozen-target fixture ownership and Node omissions explicit in release evidence", () => {
const pluginWorkflow = readPluginPrereleaseWorkflow();
const pluginSource = readFileSync(".github/workflows/plugin-prerelease.yml", "utf8");
const preflight = pluginWorkflow.jobs.preflight;
const nodeShard = pluginWorkflow.jobs["plugin-prerelease-node-shard"];
const trustedCheckout = nodeShard.steps.find(
(step: WorkflowStep) => step.name === "Checkout trusted npm security inventory",
);
const installInventory = nodeShard.steps.find(
(step: WorkflowStep) => step.name === "Install trusted npm security inventory",
);
const runNodeShard = nodeShard.steps.find(
(step: WorkflowStep) => step.name === "Run release-only plugin Node shard",
);
const releaseWorkflow = readFullReleaseValidationWorkflow();
const releaseSource = readFileSync(".github/workflows/full-release-validation.yml", "utf8");
const targetSummary = releaseWorkflow.jobs.resolve_target.steps.find(
(step: WorkflowStep) => step.name === "Summarize target",
);
const pluginDispatch = releaseWorkflow.jobs.plugin_prerelease.steps.find(
(step: WorkflowStep) => step.name === "Dispatch and monitor plugin prerelease",
);
const evidenceReuse = releaseWorkflow.jobs.evidence_reuse.steps.find(
(step: WorkflowStep) => step.name === "Find reusable validation evidence",
);
const manifest = releaseWorkflow.jobs.summary.steps.find(
(step: WorkflowStep) => step.name === "Write release validation manifest",
);
expect(pluginWorkflow.on.workflow_dispatch.inputs.node_test_exclude_patterns_json).toEqual({
default: "[]",
description:
"Full Release Validation-only exact plugin test paths omitted for a frozen target",
required: false,
type: "string",
});
expect(preflight.outputs.node_test_exclude_patterns_json).toBe(
"${{ steps.node_test_exclusions.outputs.patterns_json }}",
);
expect(trustedCheckout).toMatchObject({
if: "inputs.full_release_validation",
uses: CHECKOUT_V6,
with: {
"persist-credentials": false,
ref: "${{ github.sha }}",
path: ".plugin-prerelease-trusted",
"sparse-checkout": "src/plugins/npm-install-security-scan.release.test.ts",
"sparse-checkout-cone-mode": false,
},
});
expect(installInventory?.run).toContain(
'"$trusted_checkout/src/plugins/npm-install-security-scan.release.test.ts"',
);
expect(installInventory?.run).toContain(
"src/plugins/npm-install-security-scan.release.test.ts",
);
expect(installInventory?.run).toContain('rm -rf -- "$trusted_checkout"');
expect(runNodeShard?.env?.NODE_TEST_EXCLUDE_PATTERNS_JSON).toBe(
"${{ needs.preflight.outputs.node_test_exclude_patterns_json }}",
);
expect(runNodeShard?.run).toContain('process.env.NODE_TEST_EXCLUDE_PATTERNS_JSON ?? "[]"');
expect(runNodeShard?.run).toContain('pattern.slice("src/plugins/".length)');
expect(runNodeShard?.run).toContain("`--exclude=${pattern.slice");
expect(runNodeShard?.run).toContain('["test", "--", ...configs, "--", ...excludeArgs]');
expect(pluginSource).not.toContain("runtime-llm.runtime.test.ts");
expect(
releaseWorkflow.on.workflow_dispatch.inputs.plugin_prerelease_node_exclude_patterns_json,
).toEqual({
default: "[]",
description:
"Exact Plugin Prerelease Node test paths omitted only for frozen-target validation",
required: false,
type: "string",
});
expect(targetSummary?.env?.PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON).toBe(
"${{ inputs.plugin_prerelease_node_exclude_patterns_json }}",
);
expect(targetSummary?.run).toContain("- Plugin prerelease Node exclusions:");
expect(pluginDispatch?.env?.PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON).toBe(
"${{ inputs.plugin_prerelease_node_exclude_patterns_json }}",
);
expect(pluginDispatch?.run).toContain(
'-f node_test_exclude_patterns_json="$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON"',
);
expect(pluginDispatch?.run).toContain("- Frozen-target Node test omissions:");
expect(evidenceReuse?.env?.PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON).toBe(
"${{ inputs.plugin_prerelease_node_exclude_patterns_json }}",
);
expect(evidenceReuse?.run).toContain(
"pluginPrereleaseNodeExcludePatternsJson: $pluginPrereleaseNodeExcludePatternsJson",
);
expect(manifest?.env?.PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON).toBe(
"${{ inputs.plugin_prerelease_node_exclude_patterns_json }}",
);
expect(manifest?.run).toContain(
"pluginPrereleaseNodeExcludePatternsJson: $pluginPrereleaseNodeExcludePatternsJson",
);
expect(releaseSource).not.toContain("runtime-llm.runtime.test.ts");
});
it("wires the full plugin prerelease plan into its release workflow", () => {
const workflow = readCiWorkflow();
const preflight = workflow.jobs.preflight;
@@ -477,7 +669,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
expect(normalCiDispatchCase).toContain('dispatch_and_wait ci.yml "$dispatch_run_name"');
expect(normalCiDispatchCase).not.toContain("full_release_validation=true");
expect(pluginPrereleaseScript).toContain(
'args=(-f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id")',
'args=(-f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id" -f node_test_exclude_patterns_json="$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON")',
);
expect(pluginPrereleaseScript).toContain(
'args+=(-f candidate_artifact_json="$CANDIDATE_ARTIFACT_JSON")',
@@ -500,7 +692,9 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
const pluginNodeShardScript = pluginWorkflow.jobs["plugin-prerelease-node-shard"].steps.find(
(step: WorkflowStep) => step.name === "Run release-only plugin Node shard",
).run;
expect(pluginNodeShardScript).toContain('spawnSync("pnpm", ["test", "--", ...configs]');
expect(pluginNodeShardScript).toContain(
'spawnSync("pnpm", ["test", "--", ...configs, "--", ...excludeArgs]',
);
expect(pluginNodeShardScript).not.toContain("scripts/test-projects.mts");
expect(pluginWorkflow.on.workflow_dispatch.inputs.target_ref).toEqual({
default: "main",
@@ -537,6 +731,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
plugin_prerelease_extension_matrix:
"${{ steps.manifest.outputs.plugin_prerelease_extension_matrix }}",
plugin_prerelease_node_matrix: "${{ steps.manifest.outputs.plugin_prerelease_node_matrix }}",
node_test_exclude_patterns_json: "${{ steps.node_test_exclusions.outputs.patterns_json }}",
plugin_prerelease_static_matrix:
"${{ steps.manifest.outputs.plugin_prerelease_static_matrix }}",
run_plugin_prerelease_docker: "${{ steps.manifest.outputs.run_plugin_prerelease_docker }}",