简体中文
证据与验证
专家声称存在问题。验证器是 workflow 里的普通 Go 代码,通过运行代码来检验这个说法;裁判是一个 agent,负责阅读无法运行的那些。两者都不决定一条发现是否展示,只决定它有多可信。
验证器对一条发现回答四个问题:
- 问题是真的吗?为它写的测试在改动上每次都失败,而且是因为所说的原因。
- 是这次改动引入的吗?同一个测试在基准提交上不会以同样的方式失败。
- 修复有效吗?加上建议的修复后测试通过,别的测试也没坏。
- 凭什么说测试是对的?它期望的行为有 agent 以外的出处。
只有通过第一、第二、第四项的发现才能阻塞。
一个例子
改动让 is_expired 返回 now > session.expires_at。专家报告:会话在过期那一秒仍然有效。复现 agent 写出:
python
def test_expired_at_exact_boundary():
assert is_expired(Session(expires_at=100), now=100)测试在改动上失败,改成 >= 后通过。如果 pull request 写了“会话在 expires_at 过期”,这条发现就是带修复的 issue (blocking)。如果没有任何地方说明边界在哪,同一个测试就变成给作者的 question。
判定依据
测试失败只说明代码和测试对不上,不说明测试是对的。所以每个复现都要声明它的判定依据(oracle):期望行为从哪里来。四个种类取自 Barr 等人关于测试预言问题的综述(见参考资料)。哪几种可以阻塞是 eyeful 自己的规则,它带来的变化由评估来衡量。
| 判定依据 | 在 eyeful 里 | 结果 |
|---|---|---|
specified | 发现引用的文字:issue、描述、文档 | fail |
derived | 仓库里已有、现在失败的测试 | fail |
derived | 在基准提交上通过的新测试 | review |
implicit | 崩溃、未处理的错误、数据竞争、sanitizer 报告、超时 | fail |
human | 只有 agent 自己对代码的理解 | review |
验证器自己核对判定依据:
specified:到指明的文档文件里,或者到描述和提交信息里查找引文。找不到、不足三个词、或出自代码文件的引文,都降为human。implicit:失败文本必须来自运行时:panic、信号、竞争或 sanitizer 报告、超时,或者抛出的错误名。如果测试文件里就有这段文本,或者它是断言失败、测试无法导入,降为human。derived:已有的测试原样运行。新测试只有在基准提交上通过才算derived。
结果为 review 的发现不能阻塞,它会变成附带失败测试的 question。
这些核对读的是文本。它们能拦住标错类别的判定依据,但不能证明引文支持测试的期望,所以阻塞评论的读者仍然要自己看一眼引文。
证据
evidence 说明一条发现可以怎样检验,用的是 ISO/IEC/IEEE 29148 的验证方法(见参考资料):
| 方法 | 由谁产生 | 含义 |
|---|---|---|
test | correctness、skeptic、security | 调用代码的测试能证明它 |
analysis | skeptic、security | 专家沿着路径推理 |
inspection | 每个专家 | 专家对照某项标准阅读代码 |
标准里的第四种方法 demonstration 随打开页面的工具一起规划(见层级)。专家只说明测试能证明问题,测试由另一个 agent 事后编写。
类别与严重度
两套刻度都来自 SARIF 2.1.0。
类别(§3.27.9)表示一条发现有多确定,由验证器和裁判设定:
| 类别 | 评论在什么情况下属于它 |
|---|---|
fail | 复现在改动上失败、在基准上不失败,且判定依据的结果是 fail |
review | 什么都没运行、裁判确认了它,或复现依靠的是结果为 review 的判定依据 |
open | 裁判对它有异议 |
只有 fail 的评论才是 issue (blocking)(见审查反馈)。
严重度(§3.27.10)是专家对“照现在这样合并会有多大代价”的估计:
| 严重度 | 用于 |
|---|---|
error | 数据丢失或泄露、系统不可用、常用路径上的错误结果或崩溃 |
warning | 同样的问题出现在少有人走的路径上,或需要特殊输入、状态 |
note | 行为没有任何变化:命名、风格、措辞 |
被改动弄坏的测试
在 execution 下,专家开始之前,验证器先在改动上运行项目的 test 命令。失败的话再在基准提交上运行一次。在那里通过、在改动上失败的就是回归。这一步在审查之前,是为了给审查省事:专家直接知道哪些测试已经失败,不用自己去找。
能报告 test 发现的专家会拿到失败的测试和输出的末尾。解释了其中某个失败的发现,以那个测试作为复现,判定依据是 derived。无论有没有专家解释,反馈都会列出被弄坏的测试。在本地,反馈只说明套件坏了,因为工作区分不清一条命令里的各个测试。
同一个问题
专家各自独立工作,所以会有几个专家报告同一个问题,也会有不同的问题落在同样的几行上。在验证之前,eyeful 就同一文件两行以内的每两条发现问 Jev:一次修复能不能同时解决它们。概率在 0.5 及以上的发现连成一个问题:一起验证,合成一条评论。请求失败时不合并任何发现,不确定性清单会写明。
验证器
验证器是 eyeful 里唯一运行项目代码的部分,而且只运行用户确认过的项目命令。它按严重度、再按报告同一个问题的专家数量依次处理发现。note 发现不验证。
对每条 test 发现,复现 agent 写一个测试,总是新文件。测试要依次通过这些关卡:
| 关卡 | 验证器做什么 | 不成立时 |
|---|---|---|
runs | 在改动的一份全新副本上运行测试 | 交给裁判阅读 |
not_flaky | 运行五次,结果必须一致 | 交给裁判阅读 |
fails_as_claimed | 在输出里查找 fails 文本 | 交给裁判阅读 |
introduced | 在基准提交上运行,不能以同样的文本失败 | 原本就有,永不阻塞 |
fail_to_pass | 应用建议的修复后运行测试 | 已复现,修复未经验证 |
pass_to_pass | 带着修复运行项目的测试,原来通过的不能失败 | 已复现,修复未经验证 |
这些关卡来自 SWE-bench(fail-to-pass、pass-to-pass)、Defects4J(基准提交)和 Meta 的 TestGen-LLM(重复运行),见参考资料。它们的顺序和 fails 文本是 eyeful 自己的。
六道关卡支撑的是两个不同的结论。前四道说明问题是真的,而且是这次改动引入的。后两道说明建议的修复可用。修复通过只算“看起来可行”,不算被证明正确,因为修复和测试都是 agent 写的,所以一条发现能否阻塞只看前一个结论和判定依据。DARPA 的 AI Cyber Challenge 也是这两部分:先给出能触发漏洞的输入,再给出补丁,补丁要能消除漏洞并让功能测试继续通过(见参考资料)。
fails是测试因所说原因失败时打印的一小段文本。除非判定依据是implicit,它必须是测试文件自己写出的信息,所以FAIL这类任何失败都会打印的文本会被拒绝。验证器在输出的最后 8,000 个字符里查找它。- 头几次运行失败却没有这段文本时,会让复现 agent 改一次测试。
- 修复由另一个 agent 编写,它看得到发现和测试,看不到专家的推理。改动了测试的修复不算通过验证。
- 属于同一个问题的发现一起验证,只为最严重的那条写复现。评论的文字取自复现成立的那条发现。
- pass to pass 比较带着修复时套件的失败和改动上原本的失败,所以修复前就失败的测试不会挡住它。失败的测试从套件输出里读出:pytest 的
FAILED和ERROR行,go test的--- FAIL:行。输出里一个都读不出时,只有带着修复整个套件都通过,修复才算验证过。 - 每次运行从快照的文件开始。被 git 忽略的文件(比如构建产物)在各次运行之间保留,写到被忽略路径的修改会被拒绝。
- 每次运行记录为
PASSED、FAILED(带fails文本)或ERROR,和 SWE-bench 的 harness 一样。
setup 失败或项目没有测试命令时,什么都不运行,所有发现都交给裁判。
裁判
裁判阅读无法运行的部分:read_only 下的每条发现,以及 execution 下没有测试能证明的发现。三点都成立时它才认定一条发现有效:
- 问题存在于改动的代码里;
- 是这次改动引入或暴露的;
- 它改变了可观察的行为,或违反了明文约定。
一个裁判一起阅读计划里同一个分组的发现,一次最多八条,最多八个裁判同时运行。被否定的发现不会被删除:它以 open 留在反馈里,折叠在 “Disputed” 下,旁边是裁判的理由。执行检验的是同一个判断,所以评估里各条件的差别在于证据,而不在于什么算缺陷。
用 SARIF 保存发现
每条发现都保存为一个 SARIF 2.1.0 result。审查评论、GitHub code scanning 和评估用的是同一个文件。
| SARIF 字段 | 存放的内容 |
|---|---|
ruleId、taxa | 规则,以及有的话 CWE 编号 |
kind | fail、review 或 open |
level | fail 结果的严重度;其他情况按标准要求为 none |
baselineState | new;基准提交也失败时为 unchanged |
message | 评论的主题和讨论 |
locations | 文件和行 |
relatedLocations | 同一问题出现的其他位置 |
codeFlows | 安全问题的触发路径 |
fixes | 建议的修复 |
properties | expert、severity、evidence、oracle、label、decorations、corroboratedBy、repro、objection |
与 CI 的关系
eyeful 为每条发现写一个测试,并运行项目自己的测试。它不生成测试套件,也不统计覆盖率。它没有运行的部分,以项目的 CI 为准。
