文章要求先确认两次测试的采样输入摘要一致,再区分属性断言失败、测试数据漂移和测试框架偶发故障。只有输入一致且没有已记录的断言失败、存在框架故障时,才允许申请冻结,并提供决策表和标准库分类器。
Agent 补丁失败只有同时满足两个条件,才能归为偶发失败(flake):两次运行的 sampler 输出必须具有相同的摘要,而且标红必须源于测试执行框架(harness)的噪声,而不是属性判定器(oracle)检查未通过。属性检查失败和 fixture 漂移都不能冻结。下面提供一套决策表规则,以及一个只依赖标准库、可在本地运行的分类器。
Agent 补丁任务通常受三套时钟支配。生成器采样出字节。fixture 文件保存这些字节。随后,属性 oracle 根据这个文件检查打过补丁的代码,而不是重新采样后再检查。
CI 往往用同一个词表示这三种失败:FAILED。于是,超时、completion 发生变化、不变量被破坏,在任务列表里看起来完全一样。把它们混为一谈,会让测试套件逐渐把真正的缺陷也屏蔽掉。
计算摘要这一步必须放在任何冻结决策之前。没有摘要,第二次运行就不是重跑,而是一个恰好使用相同测试名称的新输入。
在写入冻结记录之前,先给这对运行结果分类。只有一种分类允许进入冻结流程。
从左到右理解这些判定规则。摘要相同,oracle 的结果才具有可比性。摘要不同,说明生成器的输出变了;此时冻结,就是把新输入改称为基础设施噪声。
同一摘要对应两次失败,意味着补丁有问题,或者属性检查不够可靠。即使同一次运行中服务器很慢,它也仍然属于检查失败。传输没有故障、oracle 结果却一过一败,也不能算作 flake。在另行制定重放策略之前,这对结果应保持未分类。本文不定义反例缩减、重放预算或见证样本大小。
harness_flake 的定义刻意收得很窄:摘要一致,没有任何 oracle 结果为 False,且至少有一次运行记录了传输故障,例如超时、连接重置,或者调用已完成,但未通过事先声明的长度检查。此时,分类器才可以允许提出冻结请求。它仍然不会设置到期时间,也不会合并补丁。
把原始 completion 写入 fixture 路径。使用 SHA-256 对这些字节计算哈希,并将摘要与运行 id 一起保存。
即使响应体为空,也要这样做。零字节的哈希也是有效的固定标识。缺少哈希则是另一种状态,分类器会将其视为未分类。
在任务继续之前,用标准库确认这个固定标识:
python -c "import hashlib,pathlib; p=pathlib.Path('fixtures/sample.json'); print(hashlib.sha256(p.read_bytes()).hexdigest())"
输出一行 64 个字符的十六进制字符串,意味着这个文件可以用于比较。如果路径不存在,无论日志红得多显眼,这次运行都不具备冻结资格。
oracle 接收的是路径。它不调用模型,也不向生成器主机建立 socket 连接。
如果某个属性需要第二个样本,就给那个样本单独分配一个 fixture id。oracle 内部隐藏的实时调用会让摘要失去意义,因为实际检查的输入已经不再是那个固定下来的文件。
传输记录应当精简、明确。建议包含 transport_ok、状态码和超时标记。这些字段应由客户端填写,而不是通过解析 oracle 消息来推断。
oracle 输出一行“缺少某个键”,可能意味着补丁存在产品缺陷。根据这段文本推断出 flake,就会把缺陷洗成偶发故障。如果客户端悄悄重试过,应把重试次数单独记录为一个字段。隐藏重试会让 transport_ok 看起来正常,将真正的 harness 故障推入 unclassified;这种误分类相对更安全。
加载两条运行记录。缺少摘要,就停在 unclassified。摘要不同,就停在 fixture_drift,此时应该创建 fixture 更新工单,而不是冻结。
摘要相同且两次 oracle 检查都失败,就停在 property_miss。继续阻止补丁通过。一过一败同样不能进入冻结流程,即使传输状态存在问题也不例外,因为检查失败可能是真的。只有定义严格的 harness_flake 分类,才允许把冻结标记设为 true。
函数返回一个分类和一个布尔值。它不决定冻结时长、配额或机器规格。把到期时间、负责人和解除冻结的条件放进评审者平时就会阅读的工作流配置中。
布尔值为 true,意味着这次标红结果可以被屏蔽,直到外部设定的到期时间。它不代表补丁正确,也不会清除其他任务中的属性检查失败。
这个模块是用于本地复现的独立示例。它不调用托管模型,也不是生产运行报告。请把控制流程视为规范。如果修改了某个返回值,就在同一个 commit 中更新下一节的验证脚本。
import hashlib
from dataclasses import dataclass
from enum import Enum
class FailureClass(str, Enum):
PROPERTY_MISS = "property_miss"
FIXTURE_DRIFT = "fixture_drift"
HARNESS_FLAKE = "harness_flake"
UNCLASSIFIED = "unclassified"
@dataclass(frozen=True)
class RunRecord:
fixture_sha256: str | None
oracle_passed: bool | None
transport_ok: bool | None
def pin_fixture(payload: bytes) -> str:
return hashlib.sha256(payload).hexdigest()
def classify(left: RunRecord, right: RunRecord) -> FailureClass:
if not left.fixture_sha256 or not right.fixture_sha256:
return FailureClass.UNCLASSIFIED
if left.fixture_sha256 != right.fixture_sha256:
return FailureClass.FIXTURE_DRIFT
if left.oracle_passed is False and right.oracle_passed is False:
return FailureClass.PROPERTY_MISS
any_miss = left.oracle_passed is False or right.oracle_passed is False
transport_bad = left.transport_ok is False or right.transport_ok is False
if any_miss:
return FailureClass.UNCLASSIFIED
if transport_bad:
return FailureClass.HARNESS_FLAKE
return FailureClass.UNCLASSIFIED
def freeze_allowed(kind: FailureClass) -> bool:
return kind is FailureClass.HARNESS_FLAKE
最后的 return 会拒绝放行。两次都通过的结果,本来就不应该传给 classify。即使传进来了,拒绝冻结仍然是正确的默认行为:没有什么需要屏蔽。
将模块保存为 freeze_gate.py。验证脚本只使用标准库。Python 3.11 或更新版本支持 str | None 类型提示。在 3.9 或 3.10 中,将这些类型提示改为 typing 中的 Optional,控制流程保持不变。
这份草稿中的验证脚本尚未执行。在信任自己派生的模块版本之前,请先在本地运行它。
python - <<'PY'
from freeze_gate import FailureClass, RunRecord, classify, freeze_allowed, pin_fixture
digest = pin_fixture(b'{"patch":"return sorted(xs)"}')
other = pin_fixture(b'{"patch":"return xs"}')
cases = {
"property_miss": (
RunRecord(digest, False, True),
RunRecord(digest, False, True),
FailureClass.PROPERTY_MISS,
),
"fixture_drift": (
RunRecord(digest, False, True),
RunRecord(other, False, True),
FailureClass.FIXTURE_DRIFT,
),
"harness_flake": (
RunRecord(digest, None, False),
RunRecord(digest, True, True),
FailureClass.HARNESS_FLAKE,
),
"split_oracle": (
RunRecord(digest, True, True),
RunRecord(digest, False, True),
FailureClass.UNCLASSIFIED,
),
}
for name, (left, right, expected) in cases.items():
got = classify(left, right)
assert got is expected, (name, got)
assert freeze_allowed(got) is (expected is FailureClass.HARNESS_FLAKE)
print("four local cases passed")
PY
预期 stdout 为 four local cases passed。断言出错时,会打印用例名称和实际返回的分类。不要为了迁就一个判定更宽松的函数而修改预期分类。应该修改函数,或者承认这条规则采用了另一套策略。
先看 harness_flake。fixture 哈希一致,说明两次运行看到的是同一份 sampler 字节。第一次运行因为传输失败,根本没有执行到 oracle。第二次运行执行了 oracle,并且通过。这次标红任务具备提出冻结请求的资格,但不能被悄悄永久跳过。
再看 property_miss。哈希相同,oracle 两次都失败,传输标记正常。硬件慢也不会改变分类。不变量在固定输入上检查失败,应阻止补丁通过。
第三种是 fixture_drift。示例中的两个 completion 只差一个表达式,摘要也随之不同。应更新 fixture,或者分配一个新的 fixture id。在这里写入冻结记录,就是把生成器输出的变化藏进基础设施故障的标签中。
最后看 split_oracle。字节相同,传输正常,却一过一败。原因可能是属性检查内部依赖时钟,被测代码存在竞争条件,或者 oracle 仍然在调用外部服务。冻结它会保护错误的层。应保持未分类,并修复测试,直到 oracle 成为仅由 fixture 文件决定结果的纯函数。
披露:本文是 MonkeyCode 产品推广活动的一部分。
MonkeyCode 上的免费模型通道可以作为第 1 步的 sampler。它提供的免费服务器通道可以用于执行第 2 步。这两项都是为这份草稿提供的可用选项。任何一项都不能让两次 completion 自动成为相同输入,也不能把属性检查失败变成 flake。
把 sampler 调用限制在第 1 步。持久化字节,计算摘要,然后停止使用网络。第 2 步在禁用出站网络访问后,仍然应该通过。如果不能,说明 oracle 与生成器存在耦合;在解除这种耦合之前,这套判定规则不适用。
这里没有给出模型名称、token 配额、硬件规格或保留期限。这些数据在这份草稿中都未经核实。如果控制台显示某个限制,应将它视为第 1 步采集输入时需要遵守的约束,而不是后续 completion 与 fixture 一致的证据。
如果生成器调用已经与断言分离,那么这个免费模型通道和免费服务器通道就足以让你在临时分支上试用分类器,再将它接入合并门禁。
摘要不会评价属性检查的质量。一个永远不会失败的检查,在固定文件上同样会通过,却什么也没测到。属性检查需要单独评审。凡是仅凭断言文本就能推导出预期结果的检查,都应拒绝。
fixture 存储会不断增长。内容寻址可以消除重复字节,但不会删除废弃的固定样本。把保留期限放进存储策略中。不要把删除逻辑藏进 classify,否则旧运行的分类会在你不知情时发生变化。
传输标记是否可信,取决于客户端记录得是否诚实。静默重试、改写状态码,以及返回 200 后响应体又被截断,都会污染分类依据。缺少这些字段时,宁可归为 unclassified,也不要猜测它是 flake。
这个示例不会缩减反例,不会选择到期时间,也不会授予合并权限。补丁仍然由评审者负责。即使布尔值为 false,任务摘要也应显示分类,让属性检查失败与另一个分片中被屏蔽的 harness 故障同时可见。
如果测试没有可分离的 sampler 输入,就跳过这个门禁。纯单元测试套件可以不依赖 fixture 摘要来判定 flake。
如果属性检查按设计就必须调用实时模型,也应跳过。固定输入会在每次运行中与这个设计冲突,fixture 漂移将成为主要分类,而其原因并不能通过具体行动解决。
如果冻结被当作批准,也应跳过。冻结标记只是一个具有外部到期时间的屏蔽请求,不是对补丁、模型或服务器安全性的保证。
打印四行。只有 FAILED 的摘要无法支撑这套判定规则。
fixture_sha256=<64 hex chars>
failure_class=harness_flake
freeze_allowed=true
fixture_path=fixtures/<digest>.json
在增加下一层重试封装之前,先加上这几行。没有摘要的重试,比较的对象就是错的,随后却要求冻结门禁宽恕这次错误比较。
如需进一步采取行动,可以考虑屏蔽此人和/或举报滥用行为。