跳到主要内容

第 5 章:SBFL 故障定位、验证与选补丁

这章讲三件围绕「测试信号」的事:开局怎么用测试缩小搜索范围(SBFL)、补丁写完怎么用测试判它没弄坏东西(回归验证)、多个候选补丁怎么选一个交差。

5.1 SBFL:用覆盖率猜「哪行最可疑」

它要解决的小问题。 检索靠 LLM 读 issue 找位置;但如果有测试套件,还有一条更客观的线索:经常被失败测试覆盖、却很少被通过测试覆盖的代码行,最可疑。这就是 SBFL(Spectrum-Based Fault Localization,基于频谱的故障定位)。

怎么算。 ACR 跑测试 + 覆盖率,统计每一行被「通过 / 失败」测试覆盖的次数,套 Ochiai 公式打分(ExecStats.ochiai,sbfl.py:147):

# app/analysis/sbfl.py:147-152
@staticmethod
def ochiai(failed, passed, total_fail, total_pass):
top = failed
bottom = math.sqrt(total_fail * (failed + passed))
if bottom == 0:
return 0
return top / bottom

直觉:一行被越多失败测试覆盖(failed 大)、被越少测试整体覆盖(分母小),分数越高 → 越可疑。分子只看失败次数,所以「只被失败测试碰过」的行得分最高。代码里也提供了 tarantula(sbfl.py:155)作为备选公式。

从「行」到「方法」。 打完分后:rank_lines(按分数降序,sbfl.py:195-196)→ collate_results(过滤测试文件里的行 + 把相邻行并成区间,sbfl.py:544)→ map_collated_results_to_methods(把可疑区间映射到方法,sbfl.py:659)。最终 _form_sbfl_output(manage.py:81)取 top-5 可疑方法,格式化成 <file>..</file> <class>..</class> <func>..</func> 喂给检索 agent。

它是「建议」不是「命令」。 SBFL 结果是作为一段额外提示给 search agent 的(agent_search.py:103-107),prompt 明说「你可以选择用这个工具的结果,如果你觉得有用」。所以 SBFL 不强制定位,只是给 LLM 多一个客观线索。SBFL 默认关闭(config.enable_sbfl = False,config.py:15)。

跨 conda 环境的工程坑。 文件头注释点出一个现实约束(sbfl.py:4-9):测试要在任务自己的 conda 环境里跑(带 coverage),但覆盖率数据的分析在 ACR 自己的环境里做。不同项目的测试名格式还不一样,所以有 canonicalize_testname_django_runner / _sympy_bin_test / _pytest 三套规整(sbfl.py:48/39/74)。

5.2 回归验证:补丁有没有弄坏别的测试

它要解决的小问题。 补丁可能「修了 issue 但弄坏了三个原本通过的测试」。验证就是抓这种回归

怎么判。 evaluate_patch(validation.py:191)在 config.enable_validation 开启时调 task.validate,核心是 _run_test_suite_for_regression_docker(task.py:223):

  1. 先用一个 noop 补丁(make_noop_patch,task.py:256,只给 .gitignore 加个换行的空改动)跑一遍测试,拿到基线失败集合
  2. 再用真实补丁跑一遍,拿到新失败集合
  3. 判定:have_additional_failures = bool(failures - orig_failures)(task.py:243)——只要没有「新增的」失败就算通过。
# app/task.py:243-252(节选)
have_additional_failures = bool(failures - orig_failures)
if have_additional_failures:
msg = "The patch caused some pre-existing tests to fail."
else:
msg = "The patch passed pre-existing tests."
return not have_additional_failures, msg, orig_log_file

注意这是**「不引入新失败」**的弱判定,不是「修好了 issue」——它只保证补丁没破坏现状。测试经由 SWE-bench-docker 跑(_run_test_suite_docker,task.py:288),且对相同补丁内容做了缓存(_regression_cache,task.py:289-307)避免重复跑。

5.3 选补丁:从多个候选里挑一个

它要解决的小问题。 整个工作流会因为「整体重试」(overall_retry_limit,默认 3,模型还会轮换,inference.py:108-110)产出多个候选补丁(extracted_patch_*.diff)。最后得选一个交差。

选择逻辑select_patch(inference.py:144),优先级从高到低:

  1. 评审通过的。 遍历候选,读它对应的 review_p*_t*.json,如果 patch-correct == "yes" 就选它,理由 reviewer-approved(inference.py:163-170)。
  2. 能过回归的(candidate_patches)。 候选先经 may_pass_regression_tests(inference.py:243)过滤——读 regression_*.jsonno_additional_failure,没有就现跑一次验证。
    • 多个能过回归 → 交给 agent 投票选择(agent_select.run,inference.py:189),理由 agent-selected,multiple-pass-regression
    • 恰好一个能过 → 直接选,no-agent,single-pass-regression
  3. 一个都过不了回归。 退回到对全部候选做 agent 选择,agent-selected,none-pass-regression

agent 选择长什么样。 agent_select.run(agent_select.py:16)用 gpt-4 当「PR 评审者」:先让它分析 issue 根因和解法,再把所有候选补丁摆出来,问「哪个最好」,跑 3 次取多数票(Counter,agent_select.py:65-87),并强调「若多个都行,选改动最少的」(agent_select.py:60-63)。

一个读代码会踩的坑: 代码里有两处 if False:(inference.py:183inference.py:212)把「按补丁内容做多数投票」的分支关掉了——这是实验留下的死分支,实际永远走 agent 选择。别被它误导以为有内容多数票在生效。

5.4 三者怎么串起来

把这章三件事放回主线(对应第 0 章全景的阶段 B 和收尾):

开局:SBFL(可选)──给 search agent 一份 top-5 可疑方法

……检索 + 补丁 + 评审(第 1-4 章)……

收尾:每个候选补丁 ──回归验证──> regression_*.json(过/不过)

select_patch:评审通过? → 能过回归? → agent 投票

selected_patch.json(选了谁 + 为什么)

run_one_task(inference.py:98)在所有重试跑完后调一次 select_patch,把结果写进 selected_patch.json(inference.py:136-139)。这就是一个 ACR 任务的最终交付。

5.5 边界与局限

  • 验证是「不退步」而非「修好了」。 回归验证只查没引入新失败(task.py:243),真正「issue 修没修好」要靠复现-评审线(第 4 章)或外部 SWE-bench 评测。
  • SBFL 依赖能跑的测试 + 覆盖率。 没有覆盖数据会抛 NoCoverageData(sbfl.py:34),SBFL 直接失败返回。
  • 选补丁里有死代码。 见上文 if False:,读源码时注意区分「实际路径」和「实验残留」。

5.6 代码地图

主题文件符号
Ochiai / Tarantula 打分app/analysis/sbfl.pyExecStats.ochiaiExecStats.tarantularank_lines
行→区间→方法app/analysis/sbfl.pycollate_resultsmap_collated_results_to_methods
测试名规整app/analysis/sbfl.pycanonicalize_testnamecanonicalize_testname_django_runner
SBFL 输出成 promptapp/manage.pyProjectApiManager.fault_localization_form_sbfl_output
回归验证app/api/validation.pyapp/task.pyevaluate_patch_run_test_suite_for_regression_dockermake_noop_patch
选补丁app/inference.pyselect_patchmay_pass_regression_tests
agent 投票选补丁app/agents/agent_select.pyrun