From b3127cfdacb52a1ab645432bb02569dd85c514bd Mon Sep 17 00:00:00 2001 From: wangzhenjia Date: Tue, 22 Sep 2026 10:34:48 +0800 Subject: [PATCH] =?UTF-8?q?test(research):=20=E7=BB=88=E6=80=81=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E6=94=B9=E4=B8=BA=E5=A2=99=E9=92=9F=E9=A2=84=E7=AE=97?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E6=85=A2=E7=8E=AF=E5=A2=83=E4=B8=8B?= =?UTF-8?q?=E8=AF=AF=E6=8A=A5=E6=9C=AA=E6=94=B6=E6=95=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResearchService 测试里的 waitForTerminal 用固定 100 次 × 5ms 的轮询预算, 实际只有约 500–700ms 墙钟时间;而 run 本身需要落盘后再轮询回读,在慢机器或 满载 CI 上完整跑完所需时间会超过这个预算,于是健康的 run 被判定为 "did not reach a terminal status"。 - waitForTerminal 改为基于墙钟 deadline(TERMINAL_WAIT_MS = 5000ms)轮询 - 新增回归用例:模拟 800ms 后才收敛的流水线,旧实现必然超时失败 影响:消除 Full unit tests (advisory) 因该等待窗口过短产生的偶发红灯 (如 PR #138 的 advisory 作业 106589165526,2 fail / 1605 pass)。 范围:仅测试辅助函数与新增用例,无生产代码改动。 --- packages/shared/src/research/service.test.ts | 35 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/research/service.test.ts b/packages/shared/src/research/service.test.ts index ecb6a49a..0ef6e1bd 100644 --- a/packages/shared/src/research/service.test.ts +++ b/packages/shared/src/research/service.test.ts @@ -37,6 +37,17 @@ function makeService( }); } +/** + * Poll until the run reaches a terminal status. + * + * The budget is wall-clock based, not iteration based: a fixed `100 × 5ms` + * loop only buys ~500–700ms on a loaded machine, which is shorter than the + * time the research pipeline itself needs once every poll round-trips through + * the on-disk run store. On slower machines / saturated CI runners that made + * otherwise healthy runs look like "did not reach a terminal status" failures. + */ +const TERMINAL_WAIT_MS = 5_000; + async function waitForTerminal( service: ResearchService, runId: string @@ -47,11 +58,12 @@ async function waitForTerminal( 'failed', 'cancelled', ]); - for (let i = 0; i < 100; i += 1) { + const deadline = Date.now() + TERMINAL_WAIT_MS; + do { const run = await service.getRun(runId); if (run && terminal.has(run.status)) return run.status; await new Promise((resolve) => setTimeout(resolve, 5)); - } + } while (Date.now() < deadline); throw new Error(`run ${runId} did not reach a terminal status`); } @@ -165,6 +177,25 @@ describe('ResearchService', () => { expect(reports[0].strategyId).toBe('value'); }); + it('keeps polling past the old fixed 500ms budget when the pipeline is slow', async () => { + const service = makeService(RESEARCH_CAPABILITY_PLAN.map((id) => [id, 'success' as const])); + const realGetRun = service.getRun.bind(service); + const startedAt = Date.now(); + // Emulate a pipeline that only settles after 800ms of wall-clock — longer + // than the previous fixed `100 × 5ms` poll budget could ever cover. The + // run itself is healthy, so waiting must still resolve to `completed`. + service.getRun = async (runId: string) => { + const run = await realGetRun(runId); + if (run && Date.now() - startedAt < 800) { + return { ...run, status: 'running' as ResearchRunStatus }; + } + return run; + }; + + const queued = await service.start('NVDA.US'); + expect(await waitForTerminal(service, queued.id)).toBe('completed'); + }); + it('rejects an unknown strategy id', async () => { const service = makeService([['company.profile', 'success']]); try {