From afff0716f7d720272fb68d31b5a877dffc241bda Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:56:15 -0500 Subject: [PATCH] ci: shard checks-node-test by vitest suite --- .github/workflows/ci.yml | 112 +++++++++++++++++++++++++++++- scripts/lib/ci-node-test-plan.mjs | 30 ++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 scripts/lib/ci-node-test-plan.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6a6c2acc86..55082d8c8d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: checks_fast_extensions_matrix: ${{ steps.manifest.outputs.checks_fast_extensions_matrix }} run_checks: ${{ steps.manifest.outputs.run_checks }} checks_matrix: ${{ steps.manifest.outputs.checks_matrix }} + checks_node_test_matrix: ${{ steps.manifest.outputs.checks_node_test_matrix }} run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }} extension_fast_matrix: ${{ steps.manifest.outputs.extension_fast_matrix }} run_check: ${{ steps.manifest.outputs.run_check }} @@ -135,6 +136,9 @@ jobs: run: | node --input-type=module <<'EOF' import { appendFileSync } from "node:fs"; + import { + createNodeTestShards, + } from "./scripts/lib/ci-node-test-plan.mjs"; import { createExtensionTestShards, DEFAULT_EXTENSION_TEST_SHARD_COUNT, @@ -216,7 +220,6 @@ jobs: checks_matrix: createMatrix( runNode ? [ - { check_name: "checks-node-test", runtime: "node", task: "test" }, { check_name: "checks-node-channels", runtime: "node", task: "channels" }, ...(isPush ? [ @@ -232,6 +235,17 @@ jobs: ] : [], ), + checks_node_test_matrix: createMatrix( + runNode + ? createNodeTestShards().map((shard) => ({ + check_name: shard.checkName, + runtime: "node", + task: "test-shard", + shard_name: shard.shardName, + configs: shard.configs, + })) + : [], + ), run_extension_fast: hasChangedExtensions, extension_fast_matrix: createMatrix( hasChangedExtensions @@ -599,6 +613,102 @@ jobs: ;; esac + checks-node-test-shard: + name: ${{ matrix.check_name }} + needs: [preflight, build-artifacts] + if: always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' + runs-on: blacksmith-16vcpu-ubuntu-2404 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.preflight.outputs.checks_node_test_matrix) }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: "${{ matrix.node_version || '24.x' }}" + cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24' }}" + install-bun: "false" + use-sticky-disk: "false" + + - name: Configure Node test resources + run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV" + + - name: Download dist artifact + uses: actions/download-artifact@v8 + with: + name: dist-build + path: dist/ + + - name: Download A2UI bundle artifact + uses: actions/download-artifact@v8 + with: + name: canvas-a2ui-bundle + path: src/canvas-host/a2ui/ + + - name: Run Node test shard + env: + NODE_OPTIONS: --max-old-space-size=6144 + OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }} + shell: bash + run: | + set -euo pipefail + node --input-type=module <<'EOF' + import { spawnSync } from "node:child_process"; + import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./scripts/run-vitest.mjs"; + + const configs = JSON.parse(process.env.OPENCLAW_NODE_TEST_CONFIGS_JSON ?? "[]"); + if (!Array.isArray(configs) || configs.length === 0) { + console.error("Missing node test shard configs"); + process.exit(1); + } + + for (const config of configs) { + console.error(`[test] starting ${config}`); + const result = spawnSync( + "pnpm", + [ + "exec", + "node", + ...resolveVitestNodeArgs(process.env), + resolveVitestCliEntry(), + "run", + "--config", + config, + ], + { + env: process.env, + stdio: "inherit", + }, + ); + if ((result.status ?? 1) !== 0) { + process.exit(result.status ?? 1); + } + } + EOF + + checks-node-test: + name: checks-node-test + needs: [preflight, checks-node-test-shard] + if: always() && needs.preflight.outputs.run_checks == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + timeout-minutes: 5 + steps: + - name: Verify node test shards + env: + SHARD_RESULT: ${{ needs.checks-node-test-shard.result }} + run: | + if [ "$SHARD_RESULT" != "success" ]; then + echo "Node test shards failed: $SHARD_RESULT" >&2 + exit 1 + fi + extension-fast: name: "extension-fast" needs: [preflight] diff --git a/scripts/lib/ci-node-test-plan.mjs b/scripts/lib/ci-node-test-plan.mjs new file mode 100644 index 00000000000..be06611c64c --- /dev/null +++ b/scripts/lib/ci-node-test-plan.mjs @@ -0,0 +1,30 @@ +import { fullSuiteVitestShards } from "../../test/vitest/vitest.test-shards.mjs"; + +const EXCLUDED_FULL_SUITE_SHARDS = new Set([ + "test/vitest/vitest.full-core-contracts.config.ts", + "test/vitest/vitest.full-core-bundled.config.ts", + "test/vitest/vitest.full-extensions.config.ts", +]); + +const EXCLUDED_PROJECT_CONFIGS = new Set(["test/vitest/vitest.channels.config.ts"]); + +export function createNodeTestShards() { + return fullSuiteVitestShards.flatMap((shard) => { + if (EXCLUDED_FULL_SUITE_SHARDS.has(shard.config)) { + return []; + } + + const configs = shard.projects.filter((config) => !EXCLUDED_PROJECT_CONFIGS.has(config)); + if (configs.length === 0) { + return []; + } + + return [ + { + checkName: `checks-node-test-${shard.name}`, + shardName: shard.name, + configs, + }, + ]; + }); +}