apply-patch 可执行文件集成测试
这一阶段是在给 apply-patch 这个独立命令行小工具做“上机验收”,属于幕后保证质量的测试部分。all.rs 和 mod.rs 像点名册,把真正的测试都登记进来。cli.rs 检查用户把补丁写在命令里,或从标准输入喂进去时,能不能正确改文件。tool.rs 像功能清单,逐项验证新增、修改、移动、删除和报错提示。scenarios.rs 则按真实案例跑完整流程,只看最后文件夹是不是变成该有的样子。
测试套件入口点
这些文件定义集成测试二进制文件,并组装可执行文件侧测试所运行的共享套件模块。
apply-patch/tests/all.rs源码 ↗
这个文件很小,但作用像一本测试目录的封面。Rust 的集成测试通常会把 tests/ 下面的每个测试文件当成一个单独的测试程序;这里选择只放一个总测试程序,然后用 mod suite; 引入真正的测试集合。这样做的好处是测试结构更集中:具体测试可以分门别类放在 tests/suite/ 里,而测试运行器只需要从这个入口开始。没有它,suite 里的测试模块可能不会被这个集成测试二进制包含进去,也就不会按预期参与测试。可以把它理解成电影院入口:电影不在入口播放,但观众必须从这里进去,才能看到里面各个影厅的内容。
apply-patch/tests/suite/mod.rs源码 ↗
可以把这个文件想成一本测试手册的目录。它把 cli、scenarios 这两个测试模块挂进来,让测试工具知道要检查命令行行为和各种使用场景。它还在非 Windows 系统上额外启用 tool 模块,因为那部分测试可能依赖 Unix/Linux/macOS 才有的系统行为;在 Windows 上就跳过,避免因为平台差异导致测试无意义地失败。这个文件没有函数,也不直接做断言。它的重要性在于“组装测试范围”:哪些测试会进入整套测试,哪些测试只在合适的操作系统上运行,都由这里决定。
CLI 调用覆盖
这些测试检验 apply_patch 可执行文件的直接命令行用法,涵盖基本调用模式以及更广泛的用户可见成功和失败行为。
apply-patch/tests/suite/cli.rs源码 ↗
这个测试文件像一位验收员,专门从用户角度检查 apply_patch 命令好不好使。它不会直接调用内部代码,而是真的启动 apply_patch 这个可执行程序,在一个临时文件夹里操作,避免弄脏真实项目。测试先造一段“补丁文本”,让命令新增一个文件,确认命令成功、屏幕输出写着新增了哪个文件,并且磁盘上真的出现了内容。然后再造一段修改补丁,把 hello 改成 world,同样检查输出和文件内容。文件里还重复测试了另一条入口:补丁不是作为参数传入,而是从标准输入传入。标准输入可以理解成“从管道里喂给程序的一段文字”。这样可以保证用户不管用哪种方式调用命令,结果都可靠。
apply_patch_command5–9 ↗
fn apply_patch_command() -> anyhow::Result<Command>
作用:这个小工具函数用来创建一个可以运行 apply_patch 命令的测试对象。测试函数不用每次都自己找可执行文件,只要调用它就能拿到准备好的命令。
数据流:进去的是当前测试环境里能找到的 apply_patch 二进制程序名字 → 它通过 cargo_bin 找到这个程序的真实路径,再用 Command 包成一个可执行的命令对象 → 出来的是一个可以继续加参数、设置目录、写入输入并运行的 Command;如果找不到程序,就返回错误。
调用关系:它是两个测试函数的共同准备步骤。test_apply_patch_cli_add_and_update 和 test_apply_patch_cli_stdin_add_and_update 都先调用它拿到命令对象,然后分别把补丁作为参数或标准输入交给这个命令去跑。
调用图:被 2 处调用(test_apply_patch_cli_add_and_update, test_apply_patch_cli_stdin_add_and_update);外部调用 2 个(new, cargo_bin)。
test_apply_patch_cli_add_and_update12–50 ↗
fn test_apply_patch_cli_add_and_update() -> anyhow::Result<()>
作用:这个测试确认 apply_patch 在“补丁文本作为命令行参数传入”时能正常工作。它检查新增文件和修改文件两步都成功,并且输出信息和真实文件内容都对。
数据流:进去的是测试临时目录、目标文件名,以及两段现场拼出来的补丁文字 → 它先运行 apply_patch 添加文件,检查命令成功、输出显示 A 表示新增,并读取磁盘文件确认内容是 hello;接着再运行 apply_patch 修改同一个文件,检查输出显示 M 表示修改,并确认文件内容变成 world → 出来的结果是测试通过或失败;过程中会在临时目录里创建并改动一个测试文件。
调用关系:这是从命令行用户视角跑的一条完整流程。它先用 tempdir 建一个安全的临时工作区,再用 apply_patch_command 启动真正的 apply_patch 程序,最后用断言检查程序输出和文件内容是否一致。
调用图:调用 1 个内部函数(apply_patch_command);外部调用 3 个(assert_eq!, format!, tempdir)。
test_apply_patch_cli_stdin_add_and_update53–91 ↗
fn test_apply_patch_cli_stdin_add_and_update() -> anyhow::Result<()>
作用:这个测试确认 apply_patch 在“补丁文本从标准输入传入”时也能正常工作。也就是说,用户把补丁通过管道喂给程序,而不是写成命令参数,程序仍然应该正确新增和修改文件。
数据流:进去的是一个临时目录、目标文件名,以及两段补丁文字 → 它把第一段补丁写进程序的标准输入,让 apply_patch 新增文件,并检查成功输出和文件内容;再把第二段补丁写进标准输入,让程序把 hello 改成 world,并再次检查输出和文件内容 → 出来的是测试通过或失败;临时目录里的测试文件会经历从不存在到新增、再到被修改的过程。
调用关系:它和 test_apply_patch_cli_add_and_update 测的是同一类功能,但入口不同。它同样通过 apply_patch_command 拿到命令对象,只是把补丁交给 write_stdin,而不是作为 arg 参数传入,用来覆盖另一种真实用户会用到的调用方式。
调用图:调用 1 个内部函数(apply_patch_command);外部调用 3 个(assert_eq!, format!, tempdir)。
apply-patch/tests/suite/tool.rs源码 ↗
apply_patch 是一个通过命令行接收“补丁文本”的工具。这个测试文件不直接调用内部代码,而是像真实用户一样启动 apply_patch 程序,在临时目录里准备文件,传入补丁,再检查磁盘上的文件和屏幕输出是否符合预期。这样能测到完整链路:命令行参数、文件读写、错误提示都算在内。文件前面有几个小帮手:一个负责在指定目录运行工具,一个负责先建好命令方便测试失败场景,一个负责把临时路径转成真实绝对路径,便于比较错误信息。后面的测试覆盖了成功情况,比如一次补丁做多件事、多段修改、移动文件、覆盖已有文件;也覆盖了失败情况,比如空补丁、找不到要改的内容、删除不存在的文件、把目录当文件删、补丁头写错。一个重要点是:这里还明确验证了失败不一定会回滚,前面已经成功创建的文件会留下来。
run_apply_patch_in_dir8–12 ↗
fn run_apply_patch_in_dir(dir: &Path, patch: &str) -> anyhow::Result<assert_cmd::assert::Assert>
作用:这是测试里的快捷按钮:在某个临时目录里运行 apply_patch,并把一整段补丁文本当参数传进去。成功类测试常用它,因为它会立刻返回可继续检查的运行结果。
数据流:进去的是一个目录路径和一段补丁文本。它先找到测试用的 apply_patch 可执行程序,创建一个命令,把当前工作目录切到传入目录,再把补丁文本作为命令参数放进去。出来的是命令执行后的断言对象,测试可以继续检查它是否成功、标准输出是什么。
调用关系:它被多个成功路径测试调用,比如新增、修改、移动、覆盖和补换行的测试。它自己把启动程序这件事交给外部的 Command 创建逻辑和 cargo_bin 查找逻辑,调用者只需要关心“补丁执行后结果对不对”。
调用图:被 6 处调用(test_apply_patch_cli_add_overwrites_existing_file, test_apply_patch_cli_applies_multiple_chunks, test_apply_patch_cli_applies_multiple_operations, test_apply_patch_cli_move_overwrites_existing_destination, test_apply_patch_cli_moves_file_to_new_directory, test_apply_patch_cli_updates_file_appends_trailing_newline);外部调用 2 个(new, cargo_bin)。
apply_patch_command14–18 ↗
fn apply_patch_command(dir: &Path) -> anyhow::Result<Command>
作用:这是另一个测试用快捷按钮:只准备好 apply_patch 命令,但不马上塞补丁。失败类测试常用它,因为这些测试要自己追加参数,再检查失败状态和错误文字。
数据流:进去的是要运行命令的目录。它找到 apply_patch 可执行程序,创建命令,并把工作目录设成这个目录。出来的是还没执行的命令对象,后续测试会继续加参数、启动它、检查结果。
调用关系:它服务于各种错误场景测试,比如空补丁、缺文件、错误补丁头、删除目录等。它把“怎样启动 apply_patch”统一起来,让每个测试只描述自己关心的错误。
调用图:被 8 处调用(test_apply_patch_cli_delete_directory_fails, test_apply_patch_cli_failure_after_partial_success_leaves_changes, test_apply_patch_cli_rejects_empty_patch, test_apply_patch_cli_rejects_empty_update_hunk, test_apply_patch_cli_rejects_invalid_hunk_header, test_apply_patch_cli_rejects_missing_file_delete, test_apply_patch_cli_reports_missing_context, test_apply_patch_cli_requires_existing_file_for_update);外部调用 2 个(new, cargo_bin)。
resolved_under20–22 ↗
fn resolved_under(root: &Path, path: &str) -> anyhow::Result<PathBuf>
作用:这个小工具用来拼出临时目录下某个文件的真实绝对路径。它主要是为了让测试能准确匹配 apply_patch 打印出来的错误路径。
数据流:进去的是一个根目录和一个相对文件名。它先把根目录转换成系统认可的真实路径,再把相对文件名接到后面。出来的是完整路径;它不改文件,只做路径计算。
调用关系:它被需要比较错误信息的测试调用。因为错误信息里会显示完整路径,测试先用它算出同样的路径,再和程序输出做精确比较。
调用图:被 5 处调用(test_apply_patch_cli_delete_directory_fails, test_apply_patch_cli_failure_after_partial_success_leaves_changes, test_apply_patch_cli_rejects_missing_file_delete, test_apply_patch_cli_reports_missing_context, test_apply_patch_cli_requires_existing_file_for_update);外部调用 1 个(canonicalize)。
test_apply_patch_cli_applies_multiple_operations25–45 ↗
fn test_apply_patch_cli_applies_multiple_operations() -> anyhow::Result<()>
作用:这个测试确认一个补丁里可以同时做三件事:新增文件、修改文件、删除文件。它保证 apply_patch 不只能处理单一动作。
数据流:开始时它创建临时目录,写好一个待修改文件和一个待删除文件。然后传入包含新增、删除、更新三段内容的补丁。执行后,它检查命令成功、输出列出了 A/M/D 三种变化,并确认新文件出现、旧内容被改、删除目标消失。
调用关系:这是测试运行器自动执行的用例。它通过 run_apply_patch_in_dir 启动真实命令,再用文件读取和断言检查最终结果。
调用图:调用 1 个内部函数(run_apply_patch_in_dir);外部调用 4 个(assert!, assert_eq!, write, tempdir)。
test_apply_patch_cli_applies_multiple_chunks48–65 ↗
fn test_apply_patch_cli_applies_multiple_chunks() -> anyhow::Result<()>
作用:这个测试确认同一个文件可以在一个补丁里被改两处。它防止工具只应用第一段修改、漏掉后面的修改。
数据流:进去前,临时文件有四行内容。测试传入两段修改块,分别把第二行和第四行改掉。出来后,它检查命令成功,并确认文件内容变成第一行不变、第二行改了、第三行不变、第四行也改了。
调用关系:它由测试运行器执行,使用 run_apply_patch_in_dir 跑 apply_patch。这个用例专门覆盖“同一文件多段修改”的流程。
调用图:调用 1 个内部函数(run_apply_patch_in_dir);外部调用 3 个(assert_eq!, write, tempdir)。
test_apply_patch_cli_moves_file_to_new_directory68–85 ↗
fn test_apply_patch_cli_moves_file_to_new_directory() -> anyhow::Result<()>
作用:这个测试确认 apply_patch 能把文件移动到一个新的目录里,同时修改文件内容。它验证“改名/搬家”和“改内容”能一起完成。
数据流:它先在临时目录里创建 old/name.txt,并写入旧内容。然后传入补丁,要求把它移动到 renamed/dir/name.txt,并把内容改成新内容。执行后,它检查旧路径不存在,新路径文件存在且内容正确。
调用关系:测试运行器会运行它。它调用 run_apply_patch_in_dir 执行工具,调用目录创建和文件写入函数准备现场,再用断言确认移动结果。
调用图:调用 1 个内部函数(run_apply_patch_in_dir);外部调用 5 个(assert!, assert_eq!, create_dir_all, write, tempdir)。
test_apply_patch_cli_rejects_empty_patch88–98 ↗
fn test_apply_patch_cli_rejects_empty_patch() -> anyhow::Result<()>
作用:这个测试确认空补丁会被拒绝。也就是说,如果补丁没有真正改任何文件,工具应该清楚地告诉用户“没有文件被修改”。
数据流:它创建一个空临时目录,准备一段只有开始和结束标记、没有实际操作的补丁。执行后,它期待命令失败,并在错误输出里看到固定文字“No files were modified.”。
调用关系:它通过 apply_patch_command 先拿到命令,再自己传入补丁并检查失败结果。这个用例属于输入校验测试。
调用图:调用 1 个内部函数(apply_patch_command);外部调用 1 个(tempdir)。
test_apply_patch_cli_reports_missing_context101–118 ↗
fn test_apply_patch_cli_reports_missing_context() -> anyhow::Result<()>
作用:这个测试确认当补丁里说要替换的原文在文件里找不到时,工具会报清楚错误,并且不会乱改文件。
数据流:它先创建 modify.txt,内容是 line1 和 line2。补丁却要求把 missing 这一行改掉。执行后,它期待失败,错误信息指出在哪个文件里没找到 missing,同时再读文件,确认原内容仍然没变。
调用关系:它使用 apply_patch_command 启动失败场景,用 resolved_under 算出错误信息里应该出现的完整路径。它验证的是补丁匹配失败时的保护行为。
调用图:调用 2 个内部函数(apply_patch_command, resolved_under);外部调用 4 个(assert_eq!, format!, write, tempdir)。
test_apply_patch_cli_rejects_missing_file_delete121–135 ↗
fn test_apply_patch_cli_rejects_missing_file_delete() -> anyhow::Result<()>
作用:这个测试确认删除一个不存在的文件会失败。它保证工具不会假装删除成功,避免用户误以为补丁已经正确执行。
数据流:它创建临时目录,但不创建 missing.txt。然后传入删除 missing.txt 的补丁。执行后,它期待命令失败,并检查错误信息里写着无法删除该完整路径。
调用关系:它通过 apply_patch_command 准备命令,通过 resolved_under 生成预期路径。这个用例覆盖删除操作的错误处理。
调用图:调用 2 个内部函数(apply_patch_command, resolved_under);外部调用 2 个(format!, tempdir)。
test_apply_patch_cli_rejects_empty_update_hunk138–148 ↗
fn test_apply_patch_cli_rejects_empty_update_hunk() -> anyhow::Result<()>
作用:这个测试确认“更新文件”不能没有具体修改内容。补丁只说要更新某文件,但没有写怎么更新,就应该被判为无效。
数据流:它创建临时目录,传入一个只有“Update File: foo.txt”但没有任何修改块的补丁。执行后,它期待失败,并检查错误输出明确指出这个更新块是空的。
调用关系:它使用 apply_patch_command 来运行命令并检查失败。这个测试守住补丁格式的基本规则。
调用图:调用 1 个内部函数(apply_patch_command);外部调用 1 个(tempdir)。
test_apply_patch_cli_requires_existing_file_for_update151–165 ↗
fn test_apply_patch_cli_requires_existing_file_for_update() -> anyhow::Result<()>
作用:这个测试确认更新文件时,目标文件必须已经存在。否则工具应该报读文件失败,而不是悄悄新建一个。
数据流:它不创建 missing.txt,却传入更新 missing.txt 的补丁。执行后,它期待失败,错误信息说明读取要更新的文件失败,并带上系统的“没有这个文件”错误。
调用关系:它通过 apply_patch_command 发起命令,用 resolved_under 拼出预期错误路径。这个用例区分了“新增文件”和“更新文件”两种操作。
调用图:调用 2 个内部函数(apply_patch_command, resolved_under);外部调用 2 个(format!, tempdir)。
test_apply_patch_cli_move_overwrites_existing_destination168–188 ↗
fn test_apply_patch_cli_move_overwrites_existing_destination() -> anyhow::Result<()>
作用:这个测试确认移动文件时,如果目标位置已经有文件,apply_patch 会用新结果覆盖它。这个行为很重要,因为它决定了冲突时工具到底怎么做。
数据流:它先创建旧文件 old/name.txt,内容是 from;也创建目标文件 renamed/dir/name.txt,内容是 existing。补丁要求移动旧文件到目标位置并改成 new。执行后,它检查旧文件消失,目标文件内容变成 new。
调用关系:它由测试运行器调用,使用 run_apply_patch_in_dir 跑真实命令。它专门覆盖“移动到已有目标”的边界情况。
调用图:调用 1 个内部函数(run_apply_patch_in_dir);外部调用 5 个(assert!, assert_eq!, create_dir_all, write, tempdir)。
test_apply_patch_cli_add_overwrites_existing_file191–206 ↗
fn test_apply_patch_cli_add_overwrites_existing_file() -> anyhow::Result<()>
作用:这个测试确认新增文件操作遇到同名文件时,会覆盖已有内容。它把这个可能让人意外的行为固定下来,避免以后改动时含糊不清。
数据流:它先创建 duplicate.txt,写入旧内容。然后传入 Add File 补丁,写入新内容。执行后,它检查命令成功,并确认 duplicate.txt 的内容已经变成新内容。
调用关系:它调用 run_apply_patch_in_dir 来执行工具,再用文件读取断言结果。它和移动覆盖测试一起说明 apply_patch 对已有目标的处理方式。
调用图:调用 1 个内部函数(run_apply_patch_in_dir);外部调用 3 个(assert_eq!, write, tempdir)。
test_apply_patch_cli_delete_directory_fails209–225 ↗
fn test_apply_patch_cli_delete_directory_fails() -> anyhow::Result<()>
作用:这个测试确认 Delete File 只能删文件,不能把目录当文件删。这样可以避免补丁误删整个目录这类危险情况。
数据流:它先创建一个名为 dir 的目录,然后传入删除 dir 的补丁。执行后,它期待失败,并检查错误信息说明无法删除这个路径。
调用关系:它使用 apply_patch_command 运行失败场景,用 resolved_under 得到错误信息里的完整目录路径。这个用例覆盖文件和目录混淆时的保护。
调用图:调用 2 个内部函数(apply_patch_command, resolved_under);外部调用 3 个(format!, create_dir, tempdir)。
test_apply_patch_cli_rejects_invalid_hunk_header228–238 ↗
fn test_apply_patch_cli_rejects_invalid_hunk_header() -> anyhow::Result<()>
作用:这个测试确认补丁段落的开头必须是工具认识的格式。乱写一个操作名时,工具应该告诉用户哪些格式才合法。
数据流:它传入一个包含“Frobnicate File”这种无效头部的补丁。执行后,它期待失败,并检查错误输出说明这一行不是合法头部,同时列出 Add、Delete、Update 三种有效写法。
调用关系:它通过 apply_patch_command 构造命令。这个测试覆盖补丁解析阶段,也就是工具还没碰文件之前的格式检查。
调用图:调用 1 个内部函数(apply_patch_command);外部调用 1 个(tempdir)。
test_apply_patch_cli_updates_file_appends_trailing_newline241–258 ↗
fn test_apply_patch_cli_updates_file_appends_trailing_newline() -> anyhow::Result<()>
作用:这个测试确认更新后的文件会以换行符结尾。很多文本工具默认文件最后要有换行,这个测试把这个细节固定下来。
数据流:它先创建一个没有末尾换行的文件。补丁把这段内容替换成两行新内容。执行后,它读取文件,先检查内容确实以换行符结尾,再检查完整内容是 first line 和 second line 两行。
调用关系:它调用 run_apply_patch_in_dir 运行成功场景,再用读取文件和断言确认结果。它关注的是文本格式的细节,而不只是内容替换是否发生。
调用图:调用 1 个内部函数(run_apply_patch_in_dir);外部调用 5 个(assert!, assert_eq!, read_to_string, write, tempdir)。
test_apply_patch_cli_failure_after_partial_success_leaves_changes261–279 ↗
fn test_apply_patch_cli_failure_after_partial_success_leaves_changes() -> anyhow::Result<()>
作用:这个测试确认 apply_patch 不是“全有或全无”的事务。也就是说,如果前面的操作已经成功,后面的操作失败,前面的改动会留下来。
数据流:它传入一个补丁:先新增 created.txt,再尝试更新不存在的 missing.txt。执行后,命令整体失败,并输出读取 missing.txt 失败的错误;但测试随后确认 created.txt 已经被创建,内容是 hello。
调用关系:它通过 apply_patch_command 运行这个混合场景,用 resolved_under 生成错误路径,再检查磁盘结果。这个测试告诉读者工具失败时不会自动回滚已经完成的文件操作。
调用图:调用 2 个内部函数(apply_patch_command, resolved_under);外部调用 3 个(assert_eq!, format!, tempdir)。
基于 fixture 的场景
此场景套件将补丁应用到复制的 fixture 树,并验证最终文件系统快照,以提供端到端回归覆盖。
apply-patch/tests/suite/scenarios.rs源码 ↗
这个测试文件把每个测试场景都当成一个小实验:先准备一个临时文件夹,把场景里的 input 文件复制进去;再读取 patch.txt,把它交给 apply_patch 命令运行;最后把运行后的临时文件夹和 expected 文件夹逐项比较。这里比较的不是简单的“命令成功没成功”,而是最终磁盘上的真实结果:哪些目录存在、哪些文件存在、文件里的字节内容是什么。为了让比较稳定,它用 BTreeMap(一种会按顺序保存键值的表)记录快照,避免文件系统读取顺序不同导致误判。它还特意用会跟随符号链接的 metadata,兼容 Cargo 和 Buck2 这两种构建环境。简单说,这个文件像验收员:不关心工人怎么干活,只看最后房子是不是和图纸一模一样。
test_apply_patch_scenarios11–26 ↗
fn test_apply_patch_scenarios() -> anyhow::Result<()>
作用:这是整个场景测试的入口。它找到所有测试场景文件夹,并逐个交给真正执行测试的函数去跑。
数据流:它先通过项目根目录找到 fixtures/scenarios 这个场景总目录,然后读取里面的每个条目;如果某个条目是文件夹,就把这个文件夹路径交给 run_apply_patch_scenario。全部场景跑完且没有报错,就返回成功。
调用关系:它是测试框架最先调用的函数。它自己不细看补丁内容,也不比较文件结果,只负责巡场,把每个场景分派给 run_apply_patch_scenario。
调用图:调用 1 个内部函数(run_apply_patch_scenario);外部调用 2 个(repo_root, read_dir)。
run_apply_patch_scenario30–63 ↗
fn run_apply_patch_scenario(dir: &Path) -> anyhow::Result<()>
作用:这个函数负责跑完一个完整的测试场景。它把输入文件放进临时目录,运行 apply_patch,然后检查最后结果是否和 expected 目录完全一样。
数据流:它接收一个场景目录路径;先创建临时目录,再把 input 目录里的文件复制进去;接着读取 patch.txt,把补丁文本作为参数传给 apply_patch 可执行程序,并让它在临时目录里运行;最后分别给 expected 目录和临时目录拍“快照”,比较两边是否完全相同。比较失败时,会指出是哪一个场景不符合预期。
调用关系:它由 test_apply_patch_scenarios 对每个场景调用。准备输入时它会请 copy_dir_recursive 递归复制文件;验证结果时它会请 snapshot_dir 生成目录快照;最终用断言把实际结果和预期结果对上。
调用图:调用 2 个内部函数(copy_dir_recursive, snapshot_dir);被 1 处调用(test_apply_patch_scenarios);外部调用 6 个(join, assert_eq!, new, cargo_bin, read_to_string, tempdir)。
snapshot_dir71–77 ↗
fn snapshot_dir(root: &Path) -> anyhow::Result<BTreeMap<PathBuf, Entry>>
作用:这个函数给一个目录做整体快照,方便之后比较两个目录是否一样。这里的快照可以理解成一张清单:每个相对路径对应的是目录,还是某个文件及其内容。
数据流:它接收一个根目录路径;如果这个路径确实是目录,就创建一个空的有序表,然后调用 snapshot_dir_recursive 往里面填入所有子目录和文件;最后返回这张快照表。如果根目录不存在或不是目录,就返回空快照。
调用关系:它由 run_apply_patch_scenario 调用两次:一次给 expected 目录拍照,一次给 apply_patch 跑完后的临时目录拍照。真正深入每一层目录的工作交给 snapshot_dir_recursive。
调用图:调用 1 个内部函数(snapshot_dir_recursive);被 1 处调用(run_apply_patch_scenario);外部调用 2 个(new, is_dir)。
snapshot_dir_recursive79–105 ↗
fn snapshot_dir_recursive(
base: &Path,
dir: &Path,
entries: &mut BTreeMap<PathBuf, Entry>,
) -> anyhow::Result<()>
作用:这个函数负责深入目录的每一层,把所有目录和文件都记进快照。它是 snapshot_dir 背后真正“走遍文件夹”的工人。
数据流:它接收基准目录、当前正在查看的目录,以及一张正在填写的快照表;它读取当前目录下的每个条目,算出相对路径;如果是目录,就记录为目录并继续往里走;如果是文件,就读取文件的原始字节内容并记录下来。过程中它用会跟随符号链接的 metadata,所以在不同构建工具生成的文件树下也能得到一致结果。
调用关系:它由 snapshot_dir 调用,并且在遇到子目录时继续调用自己,这叫递归,也就是“看到文件夹就进去再重复同样的事”。run_apply_patch_scenario 依赖它生成的快照来判断测试是否通过。
调用图:被 1 处调用(snapshot_dir);外部调用 4 个(File, metadata, read, read_dir)。
copy_dir_recursive107–126 ↗
fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()>
作用:这个函数把一个目录里的内容完整复制到另一个目录里,包括子目录和文件。它用来把每个测试场景的 input 原样搬进临时测试现场。
数据流:它接收源目录和目标目录;读取源目录下的每个条目,为每个条目算出目标位置;如果是目录,就先创建对应目录,再继续复制里面的内容;如果是文件,就确保目标父目录存在,然后把文件复制过去。执行完成后,目标目录里会有一份和源目录对应的输入文件树。
调用关系:它由 run_apply_patch_scenario 在运行 apply_patch 之前调用。它只负责搭建测试前的文件环境,不参与补丁执行,也不参与最后比较。遇到子目录时,它也会调用自己继续往下复制。
调用图:被 1 处调用(run_apply_patch_scenario);外部调用 5 个(join, copy, create_dir_all, metadata, read_dir)。