From d7fa5059fc8d30664985bb246aee210be8991d13 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 19 Sep 2026 01:48:39 -0700 Subject: [PATCH] fix(tests): use supported Bash for Linux workflow fixtures (#152619) * fix(tests): run QA workflow fixtures with supported Bash * fix(tests): use workflow Bash for release image planning --- test/scripts/qa-profile-run-status.test.ts | 32 +++++++++++++------ .../release-workflow-matrix-plan.test.ts | 6 ++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/test/scripts/qa-profile-run-status.test.ts b/test/scripts/qa-profile-run-status.test.ts index 64c5bbee068c..4a9820236077 100644 --- a/test/scripts/qa-profile-run-status.test.ts +++ b/test/scripts/qa-profile-run-status.test.ts @@ -13,7 +13,11 @@ import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it } from "vitest"; import { parse } from "yaml"; import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; +import { resolveWorkflowBash } from "../helpers/workflow-bash.js"; +let cachedWorkflowBash: string | undefined; +const workflowBash = () => + process.platform === "darwin" ? (cachedWorkflowBash ??= resolveWorkflowBash()) : "bash"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); const repoRoot = fileURLToPath(new URL("../../", import.meta.url)); const collectorPath = path.join(repoRoot, "scripts/qa/qa-profile-run-status.mjs"); @@ -205,12 +209,12 @@ describe("QA profile failure diagnostics", () => { if (failure === "timeout") { f.writeShard(1, { ...f.status(1), exitCode: 124, timedOut: true, timeoutOutcome: "term" }); } - const aggregate = spawnSync("bash", ["-c", aggregateScript], { + const aggregate = spawnSync(workflowBash(), ["-c", aggregateScript], { cwd: f.selected, env: f.env, encoding: "utf8", }); - expect(aggregate.status).toBe(1); + expect(aggregate.status, aggregate.stderr).toBe(1); expect(aggregate.stderr).toContain( failure === "missing" ? "Expected 2 completed status and evidence files" @@ -272,7 +276,7 @@ describe("QA profile failure diagnostics", () => { encoding: "utf8", }).stdout.trim(); for (const expected of ["", "not-a-sha", "f".repeat(40), head]) { - const run = spawnSync("bash", ["-c", script], { + const run = spawnSync(workflowBash(), ["-c", script], { cwd: repoRoot, env: { ...f.env, EXPECTED_WORKFLOW_SHA: expected }, encoding: "utf8", @@ -308,10 +312,14 @@ describe("QA profile failure diagnostics", () => { ], { env, encoding: "utf8" }, ); - const current = spawnSync("bash", ["-c", `status_paths=("$STATUS_PATH")\n${admission}`], { - env, - encoding: "utf8", - }); + const current = spawnSync( + workflowBash(), + ["-c", `status_paths=("$STATUS_PATH")\n${admission}`], + { + env, + encoding: "utf8", + }, + ); expect(current.status).toBe(prior.status); expect(current.status === 0).toBe( code !== "oops" && @@ -353,7 +361,7 @@ describe("QA profile failure diagnostics", () => { f.writeShard(); f.writeShard(1); f.collect({ QA_EXIT_CODE: code }); - const gate = spawnSync("bash", ["-c", finalGateScript], { + const gate = spawnSync(workflowBash(), ["-c", finalGateScript], { env: { ...f.env, QA_EXIT_CODE: code, ALLOW_FAILURES: allowFailures }, encoding: "utf8", }); @@ -472,8 +480,12 @@ describe("QA profile failure diagnostics", () => { const script = consumer.jobs.publish.steps.find( (step: { name: string }) => step.name === "Require one QA evidence file", ).run; - const run = spawnSync("bash", ["-c", script], { cwd: f.root, env: f.env, encoding: "utf8" }); - expect(run.status).toBe(1); + const run = spawnSync(workflowBash(), ["-c", script], { + cwd: f.root, + env: f.env, + encoding: "utf8", + }); + expect(run.status, run.stderr).toBe(1); expect(run.stderr).toContain("Expected exactly one aggregate QA evidence manifest, found 0"); }, ); diff --git a/test/scripts/release-workflow-matrix-plan.test.ts b/test/scripts/release-workflow-matrix-plan.test.ts index 05634dedfef2..ede937edf46a 100644 --- a/test/scripts/release-workflow-matrix-plan.test.ts +++ b/test/scripts/release-workflow-matrix-plan.test.ts @@ -14,6 +14,7 @@ import { createReleaseWorkflowMatrixPlan, } from "../../scripts/plan-release-workflow-matrix.mjs"; import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; +import { resolveWorkflowBash } from "../helpers/workflow-bash.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); @@ -471,6 +472,7 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => { it("builds provider owners used by every direct and Gateway Docker live lane", () => { const definition = workflow(); + const bash = process.platform === "darwin" ? resolveWorkflowBash() : "bash"; const outputDir = tempDirs.make("openclaw-live-image-selection-"); const outputPath = path.join(outputDir, "outputs"); symlinkSync(path.resolve("scripts"), path.join(outputDir, "scripts"), "dir"); @@ -502,7 +504,7 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => { "live image planner step", ); expect(steps.indexOf(setup)).toBeLessThan(steps.indexOf(planner)); - const planned = spawnSync("bash", ["-c", expectDefined(planner.run, "planner command")], { + const planned = spawnSync(bash, ["-c", expectDefined(planner.run, "planner command")], { cwd: outputDir, encoding: "utf8", env, @@ -515,7 +517,7 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => { ), "live image selection step", ); - const result = spawnSync("bash", ["-c", expectDefined(step.run, "selection command")], { + const result = spawnSync(bash, ["-c", expectDefined(step.run, "selection command")], { encoding: "utf8", env: { ...env,