惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
WordPress大学
WordPress大学
Recent Announcements
Recent Announcements
G
Google Developers Blog
I
InfoQ
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
Check Point Blog
J
Java Code Geeks
T
Tailwind CSS Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Microsoft Security Blog
Microsoft Security Blog
MongoDB | Blog
MongoDB | Blog
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
量子位
A
About on SuperTechFans
D
DataBreaches.Net
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
perf(scripts): parallelize test group reports · openclaw/...
vincentkoc · 2026-06-01 · via Recent Commits to openclaw:main

@@ -1,4 +1,4 @@

1-

import { spawnSync } from "node:child_process";

1+

import { spawn } from "node:child_process";

22

import fs from "node:fs";

33

import os from "node:os";

44

import path from "node:path";

@@ -13,7 +13,10 @@ import {

1313

} from "./lib/test-group-report.mjs";

1414

import { formatMs } from "./lib/vitest-report-cli-utils.mjs";

1515

import { resolveVitestNodeArgs } from "./run-vitest.mjs";

16-

import { buildFullSuiteVitestRunPlans } from "./test-projects.test-support.mjs";

16+

import {

17+

applyParallelVitestCachePaths,

18+

buildFullSuiteVitestRunPlans,

19+

} from "./test-projects.test-support.mjs";

17201821

const DEFAULT_OUTPUT = ".artifacts/test-perf/group-report.json";

1922

const DEFAULT_COMPARE_OUTPUT = ".artifacts/test-perf/group-report-compare.json";

@@ -35,6 +38,8 @@ function usage() {

3538

" --limit <count> Number of groups/configs to print (default: 25)",

3639

" --top-files <count> Number of files to print (default: 25)",

3740

" --max-test-ms <ms> Fail when any individual test exceeds this duration",

41+

" --concurrency <count> Run this many config reports at once (default: 2 for",

42+

" repeated explicit configs, 1 for full-suite)",

3843

" --allow-failures Write a report even when a Vitest run exits non-zero",

3944

" --no-rss Skip max RSS measurement",

4045

" --help Show this help",

@@ -55,6 +60,7 @@ export function parseTestGroupReportArgs(argv) {

5560

const args = {

5661

allowFailures: false,

5762

compare: null,

63+

concurrency: null,

5864

configs: [],

5965

fullSuite: false,

6066

groupBy: "area",

@@ -127,6 +133,11 @@ export function parseTestGroupReportArgs(argv) {

127133

index += 1;

128134

continue;

129135

}

136+

if (arg === "--concurrency") {

137+

args.concurrency = parsePositiveInt(argv[index + 1], args.concurrency);

138+

index += 1;

139+

continue;

140+

}

130141

if (arg === "--top-files") {

131142

args.topFiles = parsePositiveInt(argv[index + 1], args.topFiles);

132143

index += 1;

@@ -185,7 +196,45 @@ function parseMaxRssBytes(output) {

185196

return null;

186197

}

187198188-

function runVitestJsonReport(params) {

199+

function spawnText(command, args, options) {

200+

const maxBuffer = 1024 * 1024 * 64;

201+

return new Promise((resolve) => {

202+

const child = spawn(command, args, {

203+

cwd: options.cwd,

204+

env: options.env,

205+

stdio: ["ignore", "pipe", "pipe"],

206+

});

207+

let output = "";

208+

let outputExceeded = false;

209+

const appendOutput = (chunk) => {

210+

if (outputExceeded) {

211+

return;

212+

}

213+

output += chunk.toString("utf8");

214+

if (Buffer.byteLength(output) > maxBuffer) {

215+

outputExceeded = true;

216+

child.kill("SIGTERM");

217+

}

218+

};

219+

child.stdout?.on("data", appendOutput);

220+

child.stderr?.on("data", appendOutput);

221+

child.on("error", (error) => {

222+

output += `${String(error)}\n`;

223+

});

224+

child.on("close", (code, signal) => {

225+

if (outputExceeded) {

226+

output += `\n[test-group-report] output exceeded ${String(maxBuffer)} bytes\n`;

227+

}

228+

resolve({

229+

status: outputExceeded ? 1 : (code ?? 1),

230+

signal,

231+

output,

232+

});

233+

});

234+

});

235+

}

236+237+

async function runVitestJsonReport(params) {

189238

fs.mkdirSync(path.dirname(params.reportPath), { recursive: true });

190239

fs.mkdirSync(path.dirname(params.logPath), { recursive: true });

191240

const command = [

@@ -204,9 +253,8 @@ function runVitestJsonReport(params) {

204253

const spawnCommand = params.rss

205254

? resolveTimeArgs(command)

206255

: { command: command[0], args: command.slice(1) };

207-

const result = spawnSync(spawnCommand.command, spawnCommand.args, {

256+

const result = await spawnText(spawnCommand.command, spawnCommand.args, {

208257

cwd: process.cwd(),

209-

encoding: "utf8",

210258

env: {

211259

...process.env,

212260

...params.env,

@@ -219,10 +267,9 @@ function runVitestJsonReport(params) {

219267

.filter(Boolean)

220268

.join(" "),

221269

},

222-

maxBuffer: 1024 * 1024 * 64,

223270

});

224271

const elapsedMs = Number.parseFloat(String(process.hrtime.bigint() - startedAt)) / 1_000_000;

225-

const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;

272+

const output = result.output;

226273

fs.writeFileSync(params.logPath, output, "utf8");

227274

return {

228275

config: params.config,

@@ -231,7 +278,7 @@ function runVitestJsonReport(params) {

231278

logPath: params.logPath,

232279

maxRssBytes: params.rss ? parseMaxRssBytes(output) : null,

233280

reportPath: params.reportPath,

234-

status: result.status ?? 1,

281+

status: result.status,

235282

};

236283

}

237284

@@ -328,12 +375,103 @@ export function resolveFullSuiteVitestEnv(args, env = process.env, label = "") {

328375

};

329376

}

330377378+

export function resolveRunPlanConcurrency(args, runPlanCount) {

379+

if (runPlanCount <= 1) {

380+

return 1;

381+

}

382+

if (args.concurrency !== null) {

383+

return Math.min(args.concurrency, runPlanCount);

384+

}

385+

if (args.fullSuite) {

386+

return 1;

387+

}

388+

return Math.min(2, runPlanCount);

389+

}

390+391+

export function resolveReportRunSpecs(args, runPlans, params = {}) {

392+

const concurrency = params.concurrency ?? resolveRunPlanConcurrency(args, runPlans.length);

393+

const env = params.env ?? process.env;

394+

const specs = runPlans.map((plan) => ({

395+

...plan,

396+

env: resolveFullSuiteVitestEnv(args, env, plan.label),

397+

}));

398+

if (concurrency <= 1) {

399+

return specs;

400+

}

401+

return applyParallelVitestCachePaths(specs, {

402+

cwd: params.cwd ?? process.cwd(),

403+

env,

404+

});

405+

}

406+331407

function printRunLine(run) {

332408

console.log(

333409

`[test-group-report] ${run.label} status=${run.status} wall=${formatMs(run.elapsedMs)} rss=${formatBytesAsMb(run.maxRssBytes)} report=${run.reportPath}`,

334410

);

335411

}

336412413+

async function runReportPlans(params) {

414+

const concurrency = resolveRunPlanConcurrency(params.args, params.runPlans.length);

415+

const runSpecs = resolveReportRunSpecs(params.args, params.runPlans, { concurrency });

416+

const results = [];

417+

results.length = runSpecs.length;

418+

let nextIndex = 0;

419+

let failed = false;

420+

let exitCode = 0;

421+422+

async function worker() {

423+

while (nextIndex < runSpecs.length && exitCode === 0) {

424+

const index = nextIndex;

425+

nextIndex += 1;

426+

const plan = runSpecs[index];

427+

const slug = sanitizePathSegment(plan.label);

428+

const run = await runVitestJsonReport({

429+

config: plan.config,

430+

forwardedArgs: plan.forwardedArgs,

431+

env: plan.env,

432+

label: plan.label,

433+

logPath: path.join(params.logDir, `${slug}.log`),

434+

reportPath: path.join(params.reportDir, `${slug}.json`),

435+

rss: params.args.rss,

436+

vitestArgs: params.args.vitestArgs,

437+

});

438+

printRunLine(run);

439+

let includeEntry = true;

440+

if (run.status !== 0) {

441+

failed = true;

442+

if (!fs.existsSync(run.reportPath)) {

443+

console.error(

444+

`[test-group-report] missing JSON report for failed config; see ${run.logPath}`,

445+

);

446+

includeEntry = false;

447+

} else {

448+

console.error(

449+

`[test-group-report] config failed; keeping partial report from ${run.reportPath}`,

450+

);

451+

}

452+

if (!params.args.allowFailures) {

453+

exitCode = run.status;

454+

}

455+

}

456+

results[index] = includeEntry

457+

? { config: plan.label, reportPath: run.reportPath, run }

458+

: null;

459+

}

460+

}

461+462+

await Promise.all(

463+

Array.from({ length: concurrency }, async () => {

464+

await worker();

465+

}),

466+

);

467+468+

return {

469+

failed,

470+

exitCode,

471+

runEntries: results.filter(Boolean),

472+

};

473+

}

474+337475

async function main() {

338476

const args = parseTestGroupReportArgs(process.argv.slice(2));

339477

if (args.help) {

@@ -377,40 +515,11 @@ async function main() {

377515

});

378516

}

379517380-

for (const plan of runPlans) {

381-

const slug = sanitizePathSegment(plan.label);

382-

const run = runVitestJsonReport({

383-

config: plan.config,

384-

forwardedArgs: plan.forwardedArgs,

385-

env: resolveFullSuiteVitestEnv(args, process.env, plan.label),

386-

label: plan.label,

387-

logPath: path.join(logDir, `${slug}.log`),

388-

reportPath: path.join(reportDir, `${slug}.json`),

389-

rss: args.rss,

390-

vitestArgs: args.vitestArgs,

391-

});

392-

printRunLine(run);

393-

if (run.status !== 0) {

394-

failed = true;

395-

if (!fs.existsSync(run.reportPath)) {

396-

console.error(

397-

`[test-group-report] missing JSON report for failed config; see ${run.logPath}`,

398-

);

399-

if (!args.allowFailures) {

400-

exitCode = run.status;

401-

break;

402-

}

403-

continue;

404-

}

405-

console.error(

406-

`[test-group-report] config failed; keeping partial report from ${run.reportPath}`,

407-

);

408-

if (!args.allowFailures) {

409-

exitCode = run.status;

410-

break;

411-

}

412-

}

413-

runEntries.push({ config: plan.label, reportPath: run.reportPath, run });

518+

if (runPlans.length > 0) {

519+

const result = await runReportPlans({ args, logDir, reportDir, runPlans });

520+

failed = result.failed;

521+

exitCode = result.exitCode;

522+

runEntries.push(...result.runEntries);

414523

}

415524416525

if (exitCode !== 0) {