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

推荐订阅源

罗磊的独立博客
Recent Announcements
Recent Announcements
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
有赞技术团队
有赞技术团队
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
U
Unit 42
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 三生石上(FineUI控件)
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
腾讯CDC
I
InfoQ
GbyAI
GbyAI
博客园_首页

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
qa: harden parity gate execution (#70045) · openclaw/open...
BunsDev · 2026-04-22 · via Recent Commits to openclaw:main

@@ -83,9 +83,30 @@ import type { QaProviderModeInput } from "./run-config.js";

83838484

describe("qa cli runtime", () => {

8585

let stdoutWrite: ReturnType<typeof vi.spyOn>;

86-87-

beforeEach(() => {

86+

let stderrWrite: ReturnType<typeof vi.spyOn>;

87+

let suiteArtifactsDir: string;

88+

let suiteReportPath: string;

89+

let suiteSummaryPath: string;

90+91+

beforeEach(async () => {

92+

suiteArtifactsDir = await fs.mkdtemp(path.join(os.tmpdir(), "qa-suite-runtime-"));

93+

suiteReportPath = path.join(suiteArtifactsDir, "qa-suite-report.md");

94+

suiteSummaryPath = path.join(suiteArtifactsDir, "qa-suite-summary.json");

95+

await fs.writeFile(suiteReportPath, "# QA Suite Report\n", "utf8");

96+

await fs.writeFile(

97+

suiteSummaryPath,

98+

JSON.stringify({

99+

counts: {

100+

total: 1,

101+

passed: 1,

102+

failed: 0,

103+

},

104+

scenarios: [],

105+

}),

106+

"utf8",

107+

);

88108

stdoutWrite = vi.spyOn(process.stdout, "write").mockReturnValue(true);

109+

stderrWrite = vi.spyOn(process.stderr, "write").mockReturnValue(true);

89110

runQaSuiteFromRuntime.mockReset();

90111

runQaCharacterEval.mockReset();

91112

runQaManualLane.mockReset();

@@ -101,8 +122,8 @@ describe("qa cli runtime", () => {

101122

);

102123

runQaSuiteFromRuntime.mockResolvedValue({

103124

watchUrl: "http://127.0.0.1:43124",

104-

reportPath: "/tmp/report.md",

105-

summaryPath: "/tmp/summary.json",

125+

reportPath: suiteReportPath,

126+

summaryPath: suiteSummaryPath,

106127

scenarios: [],

107128

});

108129

runQaCharacterEval.mockResolvedValue({

@@ -153,9 +174,11 @@ describe("qa cli runtime", () => {

153174

});

154175

});

155176156-

afterEach(() => {

177+

afterEach(async () => {

157178

stdoutWrite.mockRestore();

179+

stderrWrite.mockRestore();

158180

vi.clearAllMocks();

181+

await fs.rm(suiteArtifactsDir, { recursive: true, force: true });

159182

});

160183161184

it("resolves suite repo-root-relative paths before dispatching", async () => {

@@ -307,10 +330,22 @@ describe("qa cli runtime", () => {

307330

it("sets a failing exit code when host suite scenarios fail", async () => {

308331

const priorExitCode = process.exitCode;

309332

process.exitCode = undefined;

333+

await fs.writeFile(

334+

suiteSummaryPath,

335+

JSON.stringify({

336+

counts: {

337+

total: 1,

338+

passed: 0,

339+

failed: 1,

340+

},

341+

scenarios: [{ name: "channel chat baseline", status: "fail" }],

342+

}),

343+

"utf8",

344+

);

310345

runQaSuiteFromRuntime.mockResolvedValueOnce({

311346

watchUrl: "http://127.0.0.1:43124",

312-

reportPath: "/tmp/report.md",

313-

summaryPath: "/tmp/summary.json",

347+

reportPath: suiteReportPath,

348+

summaryPath: suiteSummaryPath,

314349

scenarios: [

315350

{

316351

name: "channel chat baseline",

@@ -333,10 +368,22 @@ describe("qa cli runtime", () => {

333368

it("keeps host suite exit code clear when --allow-failures is set", async () => {

334369

const priorExitCode = process.exitCode;

335370

process.exitCode = undefined;

371+

await fs.writeFile(

372+

suiteSummaryPath,

373+

JSON.stringify({

374+

counts: {

375+

total: 1,

376+

passed: 0,

377+

failed: 1,

378+

},

379+

scenarios: [{ name: "channel chat baseline", status: "fail" }],

380+

}),

381+

"utf8",

382+

);

336383

runQaSuiteFromRuntime.mockResolvedValueOnce({

337384

watchUrl: "http://127.0.0.1:43124",

338-

reportPath: "/tmp/report.md",

339-

summaryPath: "/tmp/summary.json",

385+

reportPath: suiteReportPath,

386+

summaryPath: suiteSummaryPath,

340387

scenarios: [

341388

{

342389

name: "channel chat baseline",

@@ -357,6 +404,203 @@ describe("qa cli runtime", () => {

357404

}

358405

});

359406407+

it("retries host suite runs once for retryable infra failures", async () => {

408+

runQaSuiteFromRuntime

409+

.mockRejectedValueOnce(new Error("agent.wait timeout while waiting for transport ready"))

410+

.mockResolvedValueOnce({

411+

watchUrl: "http://127.0.0.1:43124",

412+

reportPath: suiteReportPath,

413+

summaryPath: suiteSummaryPath,

414+

scenarios: [],

415+

});

416+417+

await runQaSuiteCommand({

418+

repoRoot: "/tmp/openclaw-repo",

419+

});

420+421+

expect(runQaSuiteFromRuntime).toHaveBeenCalledTimes(2);

422+

expect(stderrWrite).toHaveBeenCalledWith(

423+

expect.stringContaining("[qa-suite] infra retry 1/1: agent.wait timeout"),

424+

);

425+

});

426+427+

it("retries host suite runs once for qa-channel readiness timeouts", async () => {

428+

runQaSuiteFromRuntime

429+

.mockRejectedValueOnce(

430+

new Error(

431+

"timed out after 180000ms waiting for qa-channel ready; last status: no qa-channel accounts reported",

432+

),

433+

)

434+

.mockResolvedValueOnce({

435+

watchUrl: "http://127.0.0.1:43124",

436+

reportPath: suiteReportPath,

437+

summaryPath: suiteSummaryPath,

438+

scenarios: [],

439+

});

440+441+

await runQaSuiteCommand({

442+

repoRoot: "/tmp/openclaw-repo",

443+

});

444+445+

expect(runQaSuiteFromRuntime).toHaveBeenCalledTimes(2);

446+

expect(stderrWrite).toHaveBeenCalledWith(

447+

expect.stringContaining(

448+

"[qa-suite] infra retry 1/1: timed out after 180000ms waiting for qa-channel ready",

449+

),

450+

);

451+

});

452+453+

it("does not retry host suite runs for generic timeout wording", async () => {

454+

runQaSuiteFromRuntime.mockRejectedValueOnce(

455+

new Error("approval-turn timed out waiting for post-approval read"),

456+

);

457+458+

await expect(

459+

runQaSuiteCommand({

460+

repoRoot: "/tmp/openclaw-repo",

461+

}),

462+

).rejects.toThrow("approval-turn timed out waiting for post-approval read");

463+464+

expect(runQaSuiteFromRuntime).toHaveBeenCalledTimes(1);

465+

});

466+467+

it("does not retry host suite runs for semantic failures", async () => {

468+

const priorExitCode = process.exitCode;

469+

process.exitCode = undefined;

470+

await fs.writeFile(

471+

suiteSummaryPath,

472+

JSON.stringify({

473+

counts: {

474+

total: 1,

475+

passed: 0,

476+

failed: 1,

477+

},

478+

scenarios: [{ name: "channel chat baseline", status: "fail" }],

479+

}),

480+

"utf8",

481+

);

482+

runQaSuiteFromRuntime.mockResolvedValueOnce({

483+

watchUrl: "http://127.0.0.1:43124",

484+

reportPath: suiteReportPath,

485+

summaryPath: suiteSummaryPath,

486+

scenarios: [

487+

{

488+

name: "channel chat baseline",

489+

status: "fail",

490+

steps: [],

491+

},

492+

],

493+

});

494+495+

try {

496+

await runQaSuiteCommand({

497+

repoRoot: "/tmp/openclaw-repo",

498+

});

499+

expect(runQaSuiteFromRuntime).toHaveBeenCalledTimes(1);

500+

expect(process.exitCode).toBe(1);

501+

} finally {

502+

process.exitCode = priorExitCode;

503+

}

504+

});

505+506+

it("runs a host-only parity preflight against the sentinel scenario", async () => {

507+

await runQaSuiteCommand({

508+

repoRoot: "/tmp/openclaw-repo",

509+

providerMode: "mock-openai",

510+

primaryModel: "openai/gpt-5.4",

511+

alternateModel: "anthropic/claude-opus-4-6",

512+

preflight: true,

513+

});

514+515+

expect(runQaSuiteFromRuntime).toHaveBeenCalledWith({

516+

repoRoot: path.resolve("/tmp/openclaw-repo"),

517+

outputDir: expect.stringMatching(

518+

/^\/tmp\/openclaw-repo\/\.artifacts\/qa-e2e\/preflight\/suite-/,

519+

),

520+

transportId: "qa-channel",

521+

providerMode: "mock-openai",

522+

primaryModel: "openai/gpt-5.4",

523+

alternateModel: "anthropic/claude-opus-4-6",

524+

scenarioIds: ["approval-turn-tool-followthrough"],

525+

concurrency: 1,

526+

});

527+

expect(stdoutWrite).toHaveBeenCalledWith(

528+

expect.stringContaining("QA parity preflight summary:"),

529+

);

530+

});

531+532+

it("throws when parity preflight finds a failing sentinel scenario", async () => {

533+

await fs.writeFile(

534+

suiteSummaryPath,

535+

JSON.stringify({

536+

counts: {

537+

total: 1,

538+

passed: 0,

539+

failed: 1,

540+

},

541+

scenarios: [{ name: "approval turn tool followthrough", status: "fail" }],

542+

}),

543+

"utf8",

544+

);

545+

runQaSuiteFromRuntime.mockResolvedValueOnce({

546+

watchUrl: "http://127.0.0.1:43124",

547+

reportPath: suiteReportPath,

548+

summaryPath: suiteSummaryPath,

549+

scenarios: [{ name: "approval turn tool followthrough", status: "fail", steps: [] }],

550+

});

551+552+

await expect(

553+

runQaSuiteCommand({

554+

repoRoot: "/tmp/openclaw-repo",

555+

preflight: true,

556+

}),

557+

).rejects.toThrow("QA parity preflight failed with 1 failing scenario.");

558+

});

559+560+

it("keeps parity preflight exit code clear when --allow-failures is set", async () => {

561+

const priorExitCode = process.exitCode;

562+

process.exitCode = undefined;

563+

await fs.writeFile(

564+

suiteSummaryPath,

565+

JSON.stringify({

566+

counts: {

567+

total: 1,

568+

passed: 0,

569+

failed: 1,

570+

},

571+

scenarios: [{ name: "approval turn tool followthrough", status: "fail" }],

572+

}),

573+

"utf8",

574+

);

575+

runQaSuiteFromRuntime.mockResolvedValueOnce({

576+

watchUrl: "http://127.0.0.1:43124",

577+

reportPath: suiteReportPath,

578+

summaryPath: suiteSummaryPath,

579+

scenarios: [{ name: "approval turn tool followthrough", status: "fail", steps: [] }],

580+

});

581+582+

try {

583+

await runQaSuiteCommand({

584+

repoRoot: "/tmp/openclaw-repo",

585+

preflight: true,

586+

allowFailures: true,

587+

});

588+

expect(process.exitCode).toBeUndefined();

589+

} finally {

590+

process.exitCode = priorExitCode;

591+

}

592+

});

593+594+

it("rejects preflight on the multipass runner", async () => {

595+

await expect(

596+

runQaSuiteCommand({

597+

repoRoot: "/tmp/openclaw-repo",

598+

runner: "multipass",

599+

preflight: true,

600+

}),

601+

).rejects.toThrow("--preflight requires --runner host.");

602+

});

603+360604

it("passes host suite CLI auth mode through", async () => {

361605

await runQaSuiteCommand({

362606

repoRoot: "/tmp/openclaw-repo",