diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19b3cc497e7..99e839abf10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -208,10 +208,6 @@ jobs: with: install-bun: "${{ matrix.runtime == 'bun' }}" - - name: Configure vitest JSON reports - if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node' - run: echo "OPENCLAW_VITEST_REPORT_DIR=$RUNNER_TEMP/vitest-reports" >> "$GITHUB_ENV" - - name: Configure Node test resources if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node' run: | @@ -224,21 +220,6 @@ jobs: if: matrix.runtime != 'bun' || github.event_name != 'push' run: ${{ matrix.command }} - - name: Summarize slowest tests - if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node' - run: | - node scripts/vitest-slowest.mjs --dir "$OPENCLAW_VITEST_REPORT_DIR" --top 50 --out "$RUNNER_TEMP/vitest-slowest.md" > /dev/null - echo "Slowest test summary written to $RUNNER_TEMP/vitest-slowest.md" - - - name: Upload vitest reports - if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node' - uses: actions/upload-artifact@v4 - with: - name: vitest-reports-${{ runner.os }}-${{ matrix.runtime }} - path: | - ${{ env.OPENCLAW_VITEST_REPORT_DIR }} - ${{ runner.temp }}/vitest-slowest.md - # Types, lint, and format check. check: name: "check" @@ -513,28 +494,9 @@ jobs: echo "OPENCLAW_TEST_SHARDS=${{ matrix.shard_count }}" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_SHARD_INDEX=${{ matrix.shard_index }}" >> "$GITHUB_ENV" - - name: Configure vitest JSON reports - if: matrix.task == 'test' - run: echo "OPENCLAW_VITEST_REPORT_DIR=$RUNNER_TEMP/vitest-reports" >> "$GITHUB_ENV" - - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) run: ${{ matrix.command }} - - name: Summarize slowest tests - if: matrix.task == 'test' - run: | - node scripts/vitest-slowest.mjs --dir "$OPENCLAW_VITEST_REPORT_DIR" --top 50 --out "$RUNNER_TEMP/vitest-slowest.md" > /dev/null - echo "Slowest test summary written to $RUNNER_TEMP/vitest-slowest.md" - - - name: Upload vitest reports - if: matrix.task == 'test' - uses: actions/upload-artifact@v4 - with: - name: vitest-reports-${{ runner.os }}-${{ matrix.runtime }}-shard${{ matrix.shard_index }}of${{ matrix.shard_count }} - path: | - ${{ env.OPENCLAW_VITEST_REPORT_DIR }} - ${{ runner.temp }}/vitest-slowest.md - # Consolidated macOS job: runs TS tests + Swift lint/build/test sequentially # on a single runner. GitHub limits macOS concurrent jobs to 5 per org; # running 4 separate jobs per PR (as before) starved the queue. One job diff --git a/scripts/test-parallel.mjs b/scripts/test-parallel.mjs index e866ef712ab..d6b96c13382 100644 --- a/scripts/test-parallel.mjs +++ b/scripts/test-parallel.mjs @@ -1,7 +1,6 @@ import { spawn } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; -import path from "node:path"; // On Windows, `.cmd` launchers can fail with `spawn EINVAL` when invoked without a shell // (especially under GitHub Actions + Git Bash). Use `shell: true` and let the shell resolve pnpm. @@ -313,49 +312,9 @@ const maxOldSpaceSizeMb = (() => { return null; })(); -function resolveReportDir() { - const raw = process.env.OPENCLAW_VITEST_REPORT_DIR?.trim(); - if (!raw) { - return null; - } - try { - fs.mkdirSync(raw, { recursive: true }); - } catch { - return null; - } - return raw; -} - -function buildReporterArgs(entry, extraArgs) { - const reportDir = resolveReportDir(); - if (!reportDir) { - return []; - } - - // Vitest supports both `--shard 1/2` and `--shard=1/2`. We use it in the - // split-arg form, so we need to read the next arg to avoid overwriting reports. - const shardIndex = extraArgs.findIndex((arg) => arg === "--shard"); - const inlineShardArg = extraArgs.find( - (arg) => typeof arg === "string" && arg.startsWith("--shard="), - ); - const shardValue = - shardIndex >= 0 && typeof extraArgs[shardIndex + 1] === "string" - ? extraArgs[shardIndex + 1] - : typeof inlineShardArg === "string" - ? inlineShardArg.slice("--shard=".length) - : ""; - const shardSuffix = shardValue - ? `-shard${String(shardValue).replaceAll("/", "of").replaceAll(" ", "")}` - : ""; - - const outputFile = path.join(reportDir, `vitest-${entry.name}${shardSuffix}.json`); - return ["--reporter=default", "--reporter=json", "--outputFile", outputFile]; -} - const runOnce = (entry, extraArgs = []) => new Promise((resolve) => { const maxWorkers = maxWorkersForRun(entry.name); - const reporterArgs = buildReporterArgs(entry, extraArgs); // vmForks with a single worker has shown cross-file leakage in extension suites. // Fall back to process forks when we intentionally clamp that lane to one worker. const entryArgs = @@ -368,11 +327,10 @@ const runOnce = (entry, extraArgs = []) => "--maxWorkers", String(maxWorkers), ...silentArgs, - ...reporterArgs, ...windowsCiArgs, ...extraArgs, ] - : [...entryArgs, ...silentArgs, ...reporterArgs, ...windowsCiArgs, ...extraArgs]; + : [...entryArgs, ...silentArgs, ...windowsCiArgs, ...extraArgs]; const nodeOptions = process.env.NODE_OPTIONS ?? ""; const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce( (acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()), diff --git a/scripts/vitest-slowest.mjs b/scripts/vitest-slowest.mjs deleted file mode 100644 index 21de70325f9..00000000000 --- a/scripts/vitest-slowest.mjs +++ /dev/null @@ -1,160 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; - -function parseArgs(argv) { - const out = { - dir: "", - top: 50, - outFile: "", - }; - for (let i = 2; i < argv.length; i += 1) { - const arg = argv[i]; - if (arg === "--dir") { - out.dir = argv[i + 1] ?? ""; - i += 1; - continue; - } - if (arg === "--top") { - out.top = Number.parseInt(argv[i + 1] ?? "", 10); - if (!Number.isFinite(out.top) || out.top <= 0) { - out.top = 50; - } - i += 1; - continue; - } - if (arg === "--out") { - out.outFile = argv[i + 1] ?? ""; - i += 1; - continue; - } - } - return out; -} - -function readJson(filePath) { - const raw = fs.readFileSync(filePath, "utf8"); - return JSON.parse(raw); -} - -function toMs(value) { - if (typeof value !== "number" || !Number.isFinite(value)) { - return 0; - } - return value; -} - -function safeRel(baseDir, filePath) { - try { - const rel = path.relative(baseDir, filePath); - return rel.startsWith("..") ? filePath : rel; - } catch { - return filePath; - } -} - -function main() { - const args = parseArgs(process.argv); - const dir = args.dir?.trim(); - if (!dir) { - console.error( - "usage: node scripts/vitest-slowest.mjs --dir [--top 50] [--out out.md]", - ); - process.exit(2); - } - if (!fs.existsSync(dir)) { - console.error(`vitest report dir not found: ${dir}`); - process.exit(2); - } - - const entries = fs - .readdirSync(dir) - .filter((name) => name.endsWith(".json")) - .map((name) => path.join(dir, name)); - if (entries.length === 0) { - console.error(`no vitest json reports in ${dir}`); - process.exit(2); - } - - const fileRows = []; - const testRows = []; - - for (const filePath of entries) { - let payload; - try { - payload = readJson(filePath); - } catch (err) { - fileRows.push({ - kind: "report", - name: safeRel(dir, filePath), - ms: 0, - note: `failed to parse: ${String(err)}`, - }); - continue; - } - const suiteResults = Array.isArray(payload.testResults) ? payload.testResults : []; - for (const suite of suiteResults) { - const suiteName = typeof suite?.name === "string" ? suite.name : "(unknown)"; - const startTime = toMs(suite?.startTime); - const endTime = toMs(suite?.endTime); - const suiteMs = Math.max(0, endTime - startTime); - fileRows.push({ - kind: "file", - name: safeRel(process.cwd(), suiteName), - ms: suiteMs, - note: safeRel(dir, filePath), - }); - - const assertions = Array.isArray(suite?.assertionResults) ? suite.assertionResults : []; - for (const assertion of assertions) { - const title = typeof assertion?.title === "string" ? assertion.title : "(unknown)"; - const duration = toMs(assertion?.duration); - testRows.push({ - name: `${safeRel(process.cwd(), suiteName)} :: ${title}`, - ms: duration, - suite: safeRel(process.cwd(), suiteName), - title, - }); - } - } - } - - fileRows.sort((a, b) => b.ms - a.ms); - testRows.sort((a, b) => b.ms - a.ms); - - const topFiles = fileRows.slice(0, args.top); - const topTests = testRows.slice(0, args.top); - - const lines = []; - lines.push(`# Vitest Slowest (${new Date().toISOString()})`); - lines.push(""); - lines.push(`Reports: ${entries.length}`); - lines.push(""); - lines.push("## Slowest Files"); - lines.push(""); - lines.push("| ms | file | report |"); - lines.push("|---:|:-----|:-------|"); - for (const row of topFiles) { - lines.push(`| ${Math.round(row.ms)} | \`${row.name}\` | \`${row.note}\` |`); - } - lines.push(""); - lines.push("## Slowest Tests"); - lines.push(""); - lines.push("| ms | test |"); - lines.push("|---:|:-----|"); - for (const row of topTests) { - lines.push(`| ${Math.round(row.ms)} | \`${row.name}\` |`); - } - lines.push(""); - lines.push( - `Notes: file times are (endTime-startTime) per suite; test times come from assertion duration (may exclude setup/import).`, - ); - lines.push(""); - - const outText = lines.join("\n"); - if (args.outFile?.trim()) { - fs.writeFileSync(args.outFile, outText, "utf8"); - } - process.stdout.write(outText); -} - -main();