Skip to content

[Tests] ResearchService 终态等待预算过短,慢环境下健康 run 被误判为未收敛 #156

Description

@wxrbyte

问题

packages/shared/src/research/service.test.ts 里的 waitForTerminal()固定次数轮询等待 run 进入终态:

for (let i = 0; i < 100; i += 1) {
  const run = await service.getRun(runId);
  if (run && terminal.has(run.status)) return run.status;
  await new Promise((resolve) => setTimeout(resolve, 5));
}
throw new Error(`run ${runId} did not reach a terminal status`);

每次循环都包含一次 service.getRun()(回读磁盘上的 run 存储)加一次 setTimeout(5),因此 100 次循环实际只买到约 500–700ms 墙钟时间。而 ResearchService 的流水线本身在慢机器 / 满载 CI Runner 上跑完就要 700ms 以上,于是健康的 run 被判定为「did not reach a terminal status」,把本来通过的行为报成失败。

这是等待预算的问题,不是被测行为的问题:run 最终确实会收敛,只是轮询窗口比流水线耗时还短。

复现

在干净 main7c9b550,2026-09-22)上直接跑:

bun test packages/shared/src/research/service.test.ts --isolate

本机(Windows 11 / Bun 1.3.x)连跑 3 次:

次数 pass fail 失败项
1 4 3 runs a report end-to-end and persists itlists runs newest-firstplans from the strategy and persists strategyId onto the report
2 5 2 runs a report end-to-end and persists itplans from the strategy and persists strategyId onto the report
3 5 2 同上

报错一致:error: run research-<uuid> did not reach a terminal status,耗时 709–769ms(> 100×5ms 预算)。

同一问题也在 GitHub Actions 上出现过:PR #138Full unit tests (advisory) 作业(run 35678224696 / job 106589165526)以 1605 pass / 2 fail 失败,两个失败项正是

  • ResearchService > lists runs newest-first(538.59ms)
  • ResearchService > plans from the strategy and persists strategyId onto the report(620.87ms)

根因

轮询预算用「迭代次数」而不是「墙钟时间」表达。迭代的真实墙钟成本(≈7ms/次,含一次磁盘回读)远大于 setTimeout(5) 的 5ms,所以 100 次迭代覆盖的时间随机器负载漂移;一旦流水线耗时超过这个漂移窗口,测试就假失败。

预期 / 实际

  • 预期:run 只要在合理时间内收敛,waitForTerminal() 就返回终态;测试只因「行为不对」而失败。
  • 实际:run 尚未收敛时预算已耗尽,抛 did not reach a terminal status;失败与被测行为无关,且在慢环境上稳定复现。

影响

Full unit tests (advisory) 是 PR 门禁里的可观测信号。这类假失败会淹没真实回归,也让贡献者无法从 advisory 红灯判断自己的改动是否安全。

建议修复

把固定 100 次迭代改成墙钟 deadline(例如 TERMINAL_WAIT_MS = 5000)驱动轮询,并补一条「模拟 800ms 后才收敛的流水线」的回归用例——该用例在旧的固定预算下必然失败,可钉住这个行为。

范围

packages/shared/src/research/service.test.ts(测试辅助函数 + 新增用例),不触碰生产代码。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

claimedClaimed by a contributor and currently in progress

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions