Skip to content

ApplyPatch语法与回归测试

解析 Apply Patch 的 parser、fixture、CLI、行尾、no-follow、runtime 与 Core 集成测试如何形成验证链。

基于rust-v0.150.0
CodexRustExecutionApplyPatchTesting

ApplyPatch语法与回归测试 ​

Apply Patch 的测试不能只断言 parser 返回了某个 enum。当前仓库至少分成六层:crate 单元测试、25 个 scenario 最终目录快照、CLI 参数/stdin 契约、tool 的行尾与部分失败矩阵、Unix no-follow/TOCTOU 测试, 以及 Core handler/runtime/SSE 集成。它们分别证明语法与算法、文件状态、公开命令、兼容模式、安全重检 和上层事件/审批装配。

本文承接ApplyPatch安全与原子性、ApplyPatch流式识别和ExecServer并发与集成测试,面向需要修改 patch parser 或新增回归场景的读者。范围是测试组织和覆盖边界,不重复实现算法。读完后,你应能为一个新语法或失败案例选择合适 fixture,并写出输入、断言和未证明范围。

1. Fixture场景 ​

1.1 目录契约 ​

scenario 由可选 input、必需 patch.txt 和 expected 组成;runner 将 input 复制到临时目录,执行 standalone apply_patch,并设置 CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS=1,再比较完整目录快照。当前目录有 25 个 场景,覆盖新增、删除、更新、move、失败后部分成功、Unicode、EOF、CRLF 与 mixed line endings。

源码位置:codex-rs/apply-patch/tests/suite/scenarios.rs :: test_apply_patch_scenarios、run_apply_patch_scenario

rust
let tmp = tempdir()?;
let input_dir = dir.join("input");
if input_dir.is_dir() {
    copy_dir_recursive(&input_dir, tmp.path())?;
}
let patch = fs::read_to_string(dir.join("patch.txt"))?;
Command::new(codex_utils_cargo_bin::cargo_bin("apply_patch")?)
    .arg(patch)
    .env(CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR, "1")
    .current_dir(tmp.path())
    .output()?;
let expected_snapshot = snapshot_dir(&dir.join("expected"))?;
let actual_snapshot = snapshot_dir(tmp.path())?;
assert_eq!(actual_snapshot, expected_snapshot);

runner 故意不把 exit status 作为唯一断言,scenario 的核心契约是最终文件树;因此失败场景也必须准备 expected 状态。

1.2 快照边界 ​

快照记录相对路径、目录和文件 bytes;读取 metadata 会跟随测试环境中 Buck2 materialized symlink,使 Cargo/Buck2 得到相同 fixture 语义。

2. CLI契约 ​

CLI tests 用 assert_cmd 分别通过 argv 和 stdin 提交 patch,检查成功 stdout、exit status 和实际文件内容; 它们覆盖最小公开命令契约,不依赖内部 Hunk 结构。更完整的失败 stderr、行尾和 multi-operation 断言位于 tool suite。

源码位置:codex-rs/apply-patch/tests/suite/cli.rs :: test_apply_patch_cli_add_and_update、test_apply_patch_cli_stdin_add_and_update

rust
apply_patch_command()?
    .arg(add_patch)
    .current_dir(tmp.path())
    .assert()
    .success()
    .stdout(format!("Success. Updated the following files:\nA {file}\n"));
assert_eq!(fs::read_to_string(&absolute_path)?, "hello\n");

CLI 基础测试没有设置 preserve-line-endings 环境变量,因此使用 standalone 默认 LF 模式;不能用这两项 测试证明 CRLF 保留。

3. Tool与stdin ​

tool tests 通过 argv 调用 standalone binary,并默认设置 preserve-line-endings 环境变量。它们验证多操作 summary、multiple chunks、EOF overlap、CRLF/CR/mixed endings、move overwrite、missing context、目录删除 失败和 partial success。专门的 legacy 测试移除环境变量,断言 CRLF 更新会回到 LF。

源码位置:codex-rs/apply-patch/tests/suite/tool.rs :: run_apply_patch_in_dir、test_apply_patch_cli_applies_multiple_operations

rust
run_apply_patch_in_dir(tmp.path(), patch)?.success().stdout(
    "Success. Updated the following files:\nA nested/new.txt\nM modify.txt\nD delete.txt\n",
);
assert_eq!(fs::read_to_string(add_path)?, "created\n");
assert_eq!(fs::read_to_string(&modify_path)?, "line1\nchanged\n");
assert!(!delete_path.exists());

4. No-follow回归 ​

Unix-only no_follow suite 不只测试 symlink leaf,还遍历 ancestor link、move source/destination 和缺失父目录 创建;另一个测试先 verify 普通目录,再把祖先目录替换为 symlink,确认 apply 阶段会重新检查。第三个 测试证明普通文件在 no-follow 下仍可成功,而 standalone 默认行为仍会 follow link。

源码位置:codex-rs/apply-patch/tests/suite/no_follow.rs :: no-follow test module

text
cargo test -p codex-apply-patch --test all suite::no_follow -- --test-threads=1

5. 失败回归 ​

5.1 语法失败 ​

parser/streaming parser 测试断言错误类型和 line number;它们证明输入无法形成合法 Hunk,不证明文件状态。

5.2 应用失败 ​

CLI tests 对 missing context 断言目标文件保持原内容;lib tests 对 write failure、unreadable destination、symlink delete 和 failed move 断言 ApplyPatchFailure.delta() 的 exactness。

源码位置:codex-rs/apply-patch/src/lib.rs :: test_failed_move_returns_committed_destination_delta

rust
let failure = apply_patch(/* patch, fs */).await.expect_err("source removal should fail");
assert!(failure
    .delta()
    .changes()
    .iter()
    .any(|change| change.path == destination));

6. Core集成 ​

Core 的 handler/runtime 单测验证 Environment ID、行尾 feature、审批键、sandbox context 和权限路径; apply_patch_custom_tool_streaming_emits_updated_changes 使用 mock Responses SSE,验证流式参数到 PatchApplyUpdated,并在完整调用后断言文件内容。它和 scenario 的边界不同:前者证明 Core 装配和事件, 后者证明 standalone 最终目录。

源码位置:

  • codex-rs/core/src/tools/handlers/apply_patch_tests.rs :: handler tests
  • codex-rs/core/src/tools/runtimes/apply_patch_tests.rs :: runtime tests
  • codex-rs/core/tests/suite/apply_patch_cli.rs :: apply_patch_custom_tool_streaming_emits_updated_changes
rust
let patch = "*** Begin Patch\n*** Add File: streamed.txt\n+hello\n+world\n*** End Patch";
mount_sse_sequence(/* input delta events + final tool call */).await;
submit_without_wait(&harness, "create streamed file").await?;
// collect PatchApplyUpdated events and assert call_id sequence

7. 验证 ​

7.1 全场景 ​

scenario runner 当前一次遍历所有 scenario 目录;新增目录即可进入完整回归,但必须同时提供 input、patch.txt 和 expected,且 expected 要表达失败后的真实状态。

源码位置:codex-rs/apply-patch/tests/suite/scenarios.rs :: test_apply_patch_scenarios

text
cd codex-rs
cargo test -p codex-apply-patch --test all scenarios -- --test-threads=1

7.2 分层命令 ​

text
cd codex-rs
cargo test -p codex-apply-patch --lib -- --test-threads=1
cargo test -p codex-apply-patch --test all cli -- --test-threads=1
cargo test -p codex-apply-patch --test all tool -- --test-threads=1
cargo test -p codex-apply-patch --test all suite::no_follow -- --test-threads=1
cargo test -p codex-core --lib tools::handlers::apply_patch::tests -- --test-threads=1
cargo test -p codex-core --lib tools::runtimes::apply_patch::tests -- --test-threads=1
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all apply_patch_custom_tool_streaming_emits_updated_changes -- --test-threads=1

这些命令分别证明 fixture state、公开 CLI、行尾/失败矩阵、no-follow、Core approval/runtime 和 streaming event;它们不证明所有平台 sandbox backend,也不证明模型一定生成正确 patch。

8. 源码排查 ​

text
rg -n "test_apply_patch_scenarios|snapshot_dir|expected_snapshot" codex-rs/apply-patch/tests/suite/scenarios.rs
rg -n "assert\(\)|stdout|stderr|missing_context|failed_move" codex-rs/apply-patch/tests/suite codex-rs/apply-patch/src/lib.rs
rg -n "no_follow|preserves_crlf|mixed_line|legacy_line" codex-rs/apply-patch/tests/suite
rg -n "PatchApplyUpdated|streaming_emits_updated_changes" codex-rs/core/tests/suite/apply_patch_cli.rs

Apply Patch 测试闭环是:crate unit 验证语法与算法,scenario 验证最终目录,CLI/tool 验证公开命令与行尾 契约,no-follow 证明路径重检,Core 集成证明 environment/approval/runtime 与流式事件;失败场景必须明确 是 unchanged、partial commit 还是 inexact delta。下一篇CodeMode架构总览进入 Code Mode 执行子系列。