diff --git a/.github/actions/setup-test-bun/action.yml b/.github/actions/setup-test-bun/action.yml index 0ee3d6f21937..09250fe01bb7 100644 --- a/.github/actions/setup-test-bun/action.yml +++ b/.github/actions/setup-test-bun/action.yml @@ -12,10 +12,10 @@ runs: exit 1 fi - # Built from openclaw/bun commit fc90aa4d9c5dac37f7a63a04f8bd2e25df930c60. + # Built from openclaw/bun commit b3684189fe1f3592dfc5f9774060b405a243d9a6. # WebKit commit fb1167ebf2cb9edc1f6771a2c11771b024693ae0. bun_dir="$(mktemp -d "$RUNNER_TEMP/openclaw-test-bun.XXXXXX")" - release_url="https://github.com/openclaw/bun/releases/download/openclaw-v1.4.3-20261001-fc90aa4d9c-webkit-fb1167ebf2" + release_url="https://github.com/openclaw/bun/releases/download/openclaw-v1.4.3-20261002-b3684189fe-webkit-fb1167ebf2" archive="$bun_dir/bun-linux-x64.zip" for asset in bun-linux-x64.zip SHA256SUMS manifest.json; do curl --fail --location --no-progress-meter --show-error \ @@ -32,14 +32,14 @@ runs: done ) printf '%s %s\n' \ - '34bb56d144ea9406fdb0eef41c18eef49d010d29147f15f9b0bd5f7b7a7512a4' \ + 'ba15d6d39be476dd2ec5151a047b51636c67d49281313021d697ccc2494c3987' \ "$archive" | sha256sum --check - unzip -p "$archive" bun-linux-x64/bun > "$bun_dir/bun" printf '%s %s\n' \ "$(jq -er '.assets[] | select(.name == "bun-linux-x64.zip") | .executable.sha256' "$bun_dir/manifest.json")" \ "$bun_dir/bun" | sha256sum --check - printf '%s %s\n' \ - '1b87aed612055f982689925c8b33e9f8b340ea822d9cb82ff3ee14be4c3a216b' \ + '333ee756a2d3fa86089651031bf47be165ed48cf99e024e6597fa1330282d059' \ "$bun_dir/bun" | sha256sum --check - chmod 755 "$bun_dir/bun" "$bun_dir/bun" --version diff --git a/docs/ci/pipeline.md b/docs/ci/pipeline.md index 3c82d09bfea9..d7e8bc770501 100644 --- a/docs/ci/pipeline.md +++ b/docs/ci/pipeline.md @@ -190,11 +190,14 @@ test-project planner to find their owners. The runtime owner admits only qualifi configs, exact files, and partitions; ambiguous selections retain Node. No tests are removed from the selected inventory. -Worktree removal recovery (`src/agents/worktrees/service.removal-recovery.test.ts`) -also supports Bun when it is the entire exact selection in `agents-support`. -Mixed and broad PR selections retain their original Node invocation. Dual-runtime -validation keeps that complete Node selection and adds only the qualified recovery -file when the original include patterns select it. +Worktree removal recovery (`src/agents/worktrees/service.removal-recovery.test.ts`), +plugin CommonJS interoperability (`src/plugins/plugin-module-generation.interop.test.ts`), +oxlint configuration (`test/scripts/oxlint-config.test.ts`), and update timeout +diagnostics (`test/scripts/upgrade-survivor-timeout-diagnostics.test.ts`) also +support Bun when qualified files make up the entire exact selection in their +existing scoped owner. Mixed and broad PR selections retain their original Node +invocation. Dual-runtime validation keeps that complete Node selection and adds +only the qualified files selected by the original include patterns. The gateway-client leaf config also supports Bun. Its existing ordered gateway-core/gateway-client stripes run the core portion on Node and the client @@ -267,12 +270,16 @@ functions remain valid after the original cache buffer is garbage-collected. It also keeps allocator ownership during zero-time event-loop polls, while retaining the idle handoff for polls that can block. -The pinned build pairs Bun `fc90aa4d9c5dac37f7a63a04f8bd2e25df930c60` with WebKit +The pinned build pairs Bun `b3684189fe1f3592dfc5f9774060b405a243d9a6` with WebKit `fb1167ebf2cb9edc1f6771a2c11771b024693ae0` in prerelease -`openclaw-v1.4.3-20261001-fc90aa4d9c-webkit-fb1167ebf2`. -WebKit is unchanged from the previous `17c9ecf9eb` pin. The build adds fixes for -compile-cache idle wakeups, `v8.queryObjects`, idempotent native readable -`ref`/`unref`, the default `module-sync` condition, and `process.once` wrapper identity. +`openclaw-v1.4.3-20261002-b3684189fe-webkit-fb1167ebf2`. +WebKit is unchanged from the previous `fc90aa4d9c` pin. The build fixes post-script +`--` argument separators, hidden CommonJS data exports, and truncated large writes +through native standard I/O pipes. It also switches Darwin file watches to kqueue; +that change does not affect Linux CI. +It retains fixes for compile-cache idle wakeups, `v8.queryObjects`, idempotent +native readable `ref`/`unref`, the default `module-sync` condition, and +`process.once` wrapper identity. It retains the upstream Bun sync through `4b02e1031d` and fixes for thread-safe function ownership, shared-environment deletion, and a module-key crash. The shared provider-catalog retention test is qualified on this build and runs diff --git a/scripts/lib/ci-test-runtime.mts b/scripts/lib/ci-test-runtime.mts index dd730625599a..ddb76953c021 100644 --- a/scripts/lib/ci-test-runtime.mts +++ b/scripts/lib/ci-test-runtime.mts @@ -77,8 +77,33 @@ const bunCompatibleConfigs = new Set([ "test/vitest/vitest.extension-memory.config.ts", gatewayClientConfig, ]); -// Measured whole-file admission; the rest of agents-support retains Node. -const bunCompatibleAgentSupportFiles = ["src/agents/worktrees/service.removal-recovery.test.ts"]; +// Whole-file qualification keeps mixed and broad scoped-owner envelopes on Node. +const bunCompatibleScopedOwners = new Map([ + [ + agentVitestProjectOwners.support.config, + { + dir: agentVitestProjectOwners.support.dir, + files: ["src/agents/worktrees/service.removal-recovery.test.ts"], + }, + ], + [ + "test/vitest/vitest.plugins.config.ts", + { + dir: "src/plugins", + files: ["src/plugins/plugin-module-generation.interop.test.ts"], + }, + ], + [ + "test/vitest/vitest.tooling.config.ts", + { + dir: "", + files: [ + "test/scripts/oxlint-config.test.ts", + "test/scripts/upgrade-survivor-timeout-diagnostics.test.ts", + ], + }, + ], +]); const embeddedRunOwner = agentVitestProjectOwners.embeddedRun; // src/state/openclaw-state-lease.retention.test.ts stays with its default Node owner: // cold fs-safe native initialization roots the caller's ALS through custom_gc. @@ -350,13 +375,15 @@ export function resolveCiTestRuntimeSelections( if (plans.every((plan) => bunCompatibleConfigs.has(plan.config))) { return completeBun(); } + const config = plans[0]!.config; + const scopedOwner = bunCompatibleScopedOwners.get(config); if ( - plans.every((plan) => plan.config === agentVitestProjectOwners.support.config) && - selection.targets.every((file) => bunCompatibleAgentSupportFiles.includes(file)) + scopedOwner && + plans.every((plan) => plan.config === config) && + selection.targets.every((file) => scopedOwner.files.includes(file)) ) { return completeBun(); } - const config = plans[0]!.config; const partition = runtimePartitions.get(config); if ( !partition || @@ -408,13 +435,13 @@ export function resolveCiTestRuntimeSelections( if (bunCompatibleConfigs.has(config)) { return completeBun(); } - if (config === agentVitestProjectOwners.support.config) { - const owner = agentVitestProjectOwners.support; + const scopedOwner = bunCompatibleScopedOwners.get(config); + if (scopedOwner) { const includePatterns = selection.includePatterns?.length ? selection.includePatterns : null; - const qualifiedPatterns = relativizeScopedPatterns(bunCompatibleAgentSupportFiles, owner.dir); + const qualifiedPatterns = relativizeScopedPatterns(scopedOwner.files, scopedOwner.dir); if ( includePatterns && - relativizeScopedPatterns(includePatterns, owner.dir).every((pattern) => + relativizeScopedPatterns(includePatterns, scopedOwner.dir).every((pattern) => qualifiedPatterns.includes(pattern), ) ) { @@ -422,8 +449,15 @@ export function resolveCiTestRuntimeSelections( } const bunFiles = policy === "dual" - ? bunCompatibleAgentSupportFiles.filter((file) => - matchesVitestCliSelection(file, owner.include, [], owner.dir, {}, includePatterns), + ? scopedOwner.files.filter((file) => + matchesVitestCliSelection( + file, + scopedOwner.files, + [], + scopedOwner.dir, + {}, + includePatterns, + ), ) : []; return bunFiles.length ? [...node, { runtime: "bun", includePatterns: bunFiles }] : node; diff --git a/test/scripts/ci-run-node-test-shard.test.ts b/test/scripts/ci-run-node-test-shard.test.ts index 3d7232e93315..429cb9e71868 100644 --- a/test/scripts/ci-run-node-test-shard.test.ts +++ b/test/scripts/ci-run-node-test-shard.test.ts @@ -1092,6 +1092,77 @@ describe("scripts/ci-run-node-test-shard.mts", () => { expect(ciTestShardRequiresBun(selection, "dual")).toBe(row.bun); }); + it.each([ + { + config: "test/vitest/vitest.plugins.config.ts", + dir: "src/plugins", + targets: ["src/plugins/plugin-module-generation.interop.test.ts"], + sibling: "src/plugins/plugin-module-generation.test.ts", + glob: "plugin-module-generation*.test.ts", + }, + { + config: "test/vitest/vitest.tooling.config.ts", + dir: "", + targets: [ + "test/scripts/oxlint-config.test.ts", + "test/scripts/upgrade-survivor-timeout-diagnostics.test.ts", + ], + sibling: "test/scripts/oxlint-report-memory.test.ts", + glob: "test/scripts/*.test.ts", + }, + ])("admits only the qualified complete selections in $config", (row) => { + for (const policy of ["node", "bun-compatible", "dual"] as const) { + const expected = + policy === "node" + ? [{ runtime: "node" }] + : policy === "dual" + ? [{ runtime: "node" }, { runtime: "bun" }] + : [{ runtime: "bun" }]; + for (const targets of [row.targets, ...row.targets.map((target) => [target])]) { + for (const selection of [ + { targets }, + { configs: [row.config], includePatterns: targets }, + { + configs: [row.config], + includePatterns: targets.map((target) => + row.dir ? target.slice(row.dir.length + 1) : target, + ), + }, + ]) { + expect(resolveCiTestRuntimeSelections(selection, policy)).toEqual(expected); + expect(ciTestShardRequiresBun(selection, policy)).toBe(policy !== "node"); + expect( + resolveCiTestRuntimeSelections({ ...selection, vitestArgs: ["--shard=1/2"] }, policy), + ).toEqual([{ runtime: "node" }]); + } + } + for (const includePatterns of [undefined, [], [row.glob]]) { + const selection = { configs: [row.config], includePatterns }; + expect(resolveCiTestRuntimeSelections(selection, policy)).toEqual([ + { runtime: "node" }, + ...(policy === "dual" ? [{ runtime: "bun", includePatterns: row.targets }] : []), + ]); + expect(ciTestShardRequiresBun(selection, policy)).toBe(policy === "dual"); + } + for (const target of row.targets) { + const selection = { configs: [row.config], includePatterns: [target, row.sibling] }; + expect(resolveCiTestRuntimeSelections(selection, policy)).toEqual([ + { runtime: "node" }, + ...(policy === "dual" ? [{ runtime: "bun", includePatterns: [target] }] : []), + ]); + expect(ciTestShardRequiresBun(selection, policy)).toBe(policy === "dual"); + } + for (const selection of [ + { targets: [row.sibling] }, + { targets: [...row.targets, row.sibling] }, + { configs: [row.config], includePatterns: [row.sibling] }, + { configs: [row.config], includePatterns: ["src/infra/worker-task-pool.memory.test.ts"] }, + ]) { + expect(resolveCiTestRuntimeSelections(selection, policy)).toEqual([{ runtime: "node" }]); + } + } + }); + it.each([ { env: { OPENCLAW_NODE_TEST_VITEST_ARGS_JSON: '["--shard=1/2"]' } }, { env: { OPENCLAW_NODE_TEST_VITEST_ARGS_JSON: '["--root=another-root"]' } }, diff --git a/test/scripts/oxlint-config.test.ts b/test/scripts/oxlint-config.test.ts index ab8643685906..a33145772534 100644 --- a/test/scripts/oxlint-config.test.ts +++ b/test/scripts/oxlint-config.test.ts @@ -741,6 +741,8 @@ describe("oxlint config", () => { { cwd: root, encoding: "utf8", + // Capture the complete JSON and owner trace when temporary paths are long. + maxBuffer: 8 * 1024 * 1024, timeout: 30_000, env: { ...process.env,