SuperAPI AI 工具集

cmux-test-bisect

测试与质量

查看项目

更新于 10-15 15:25

技能介绍

用途

cmux-test-bisect 用于查找 main 分支上 Swift 包测试套件何时以及为何失败。由于 PR 的 CI 只运行选定测试,某个包测试套件可能在 main 上持续报红数周而无人发现。该技能通过 CI 对旧提交进行二分查找(bisect),定位每个失败背后的提交,全程在 CI 上执行,不在本地 Mac 上构建完整 cmux。

App-host(cmuxTests)在 main 上的失败有独立的自动 bisect(#14510)。本技能用于 Packages/iOS 与 Packages/Shared 下可由 test-ios.yml 运行的 SwiftPM 包套件:CMUXMobileCore、CmuxSyncStore、CmuxMobilePairedMac、CmuxMobileChanges、CmuxMobileShell、CmuxMobileShellModel。

开始之前

  • 从红运行中获取今日失败集:gh run view <run> --repo manaflow-ai/cmux --log-failed,然后 grep ✘ Test .* failed after。
  • 如果尚未执行,请将完整套件运行两次。只出现在一次运行中的测试是 flake 候选,而非回归。
  • 搜索已有修复:gh search prs --repo manaflow-ai/cmux --state open '<test name>'。
  • 从 upstream/main 在你自己的 worktree 中工作。如果 git log 在近期提交处停止,说明 checkout 是浅克隆:git fetch --shallow-since=<date> upstream main。

运行 bisect

T=scripts/ci/package_bisect.py
python3 $T --package CmuxMobileShell start --points 6 <old-sha>..upstream/main
python3 $T --package CmuxMobileShell status --wait     # up to 45 min
python3 $T --package CmuxMobileShell next --dispatch   # split each break window
python3 $T --package CmuxMobileShell cleanup           # delete probe branches

每个探针会推送 bisect/<name>/<sha10>(name 默认为包名):旧提交叠加今日 iOS CI 文件,并去掉 package-lint 关卡。旧提交携带旧 CI 脚本,这就是为什么普通的 -f ref=<sha> dispatch 会在早期失败。

探针运行器采用自动选择(优先空闲自有 mini,Blacksmith 作为溢出)。不要固定运行器。

adopt <sha> <run-id> 会统计一次已存在的、带 swift_package=<pkg> 的 test-ios.yml 运行(main 上的 dispatch,或无法读取 id start 的探针运行),而不再次 dispatch。ci.yml 运行没有可读取的 package job。

--filter <regex> 在只追查少数测试时缩小套件范围;--paths 改变哪些提交算作中点候选(默认:iOS 与 Shared 包)。

--patch <sha>(可重复)将修复提交应用到每个探针。当旧提交因已知原因挂起或构建失败时使用它:bisect 你要问的问题,而不是已知的中断。

--bisect <name> 在第一个实验旁保留第二个实验(例如同样提交加 --patch)。--package 与 --bisect 放在子命令之前,之后该 bisect 的每个命令也都需要它们。

状态由该 checkout 的每个 worktree 共享,位于 <git-common-dir>/package-bisect/<name>.json。已完成作业日志缓存在其旁,按运行尝试区分,因此 status --refetch 每个探针只需一次运行查询,且只下载未见过的日志。

阅读矩阵

status 每个测试打印一行,每个探针打印一列,最旧的在前:X 失败,. 通过,- 在该探针从未运行,? 待定,E 完全没有测试运行(编译或运行器失败;检查日志,重跑该运行,然后 status --refetch;重跑是新的尝试,因此其日志会被重新读取)。

先检查探针列表。标记为 INCOMPLETE 的探针从未打印其 "Test run with" 摘要:挂起测试看门狗杀掉了它,或作业超时,因此该点之后的每个测试都显示为 -。- 绝非通过:早期挂起的套件会让整个集群看起来像在修复挂起的那个提交处 "中断"。在第二个 bisect 中应用挂起修复后重新探测这些提交:

python3 $T --package <pkg> --bisect <pkg>-patched start --patch <hang fix> <shas>

判定与含义:

判定 含义 下一步
broken by <sha> 在前一探针通过,从此处开始失败 阅读该 PR 针对该测试的 diff
broken in a..b (n watched commits) 窗口尚未缩小到一个提交宽 next --dispatch
failing at the oldest probe 比 bisect 更早 从更早处重新开始,或用 git log -S '<symbol>' 追踪
fixed by / fixed in 失败后又被修复 通常忽略;next --fixed 也会拆分这些
flaky 在失败之间通过 视为 flake;修复前用聚焦重跑确认

同一窗口内中断的测试通常共享一个原因。一起修复该集群。

陈旧测试还是回归

阅读罪魁 PR,然后逐测试决定:

  • 陈旧测试:PR 有意改变了行为,并说明了这一点(PR 正文、代码注释,或同一方向更新的其他测试)。修复测试以在新行为下表达其原始意图。不要删除断言。如果旧 fixture 现在描述的是另一种情况,为每种情况各保留一个测试。
  • 回归:测试意图仍然成立,PR 中没有任何内容可以否定它。修复代码,并保持测试原样。

不确定时,优先选择让测试名称保持真实的解释,并在 PR 中说明你选择了哪种解释。

落地

  • 每个罪魁或集群一个小的 PR,推送到组织远程(git push mf <branch>、gh pr create --head <branch>)。
  • 致谢改变了行为的 PR(链接它,并提及作者)。
  • 用聚焦套件证明:gh workflow run test-ios.yml --repo manaflow-ai/cmux --ref <branch> -f swift_package=<pkg> -f test_filter='<SuiteA|SuiteB>'。
  • 在最后一个 PR 上完成一次完整套件运行。
  • 运行 cleanup,确保组织远程上不留 bisect/ 分支。