☰
rsync 测试套件的预期跳过清单(Expected-Skip Lists):从 CI 冲突到可合并的逐行维护
2026/10/13 2:46:43 网站建设 项目流程
  • CLI
  • 数据同步
  • 网络

【免费下载链接】rsync

An open source utility that provides fast incremental file transfer. It also has useful features for backup and restore operations among many other use cases.

项目地址:https://gitcode.com/gh_mirrors/rs/rsync
点击查看免费下载

testsuite/skiplist/README.md定义了 rsync 项目 Python 测试套件中一套精密的"预期跳过(expected-skip)"机制:它以RSYNC_EXPECT_SKIPPED环境变量为媒介,把"哪些测试应当被跳过"声明为精确集合,由 runtests.py 在完整运行时强制校验,防止某个测试悄悄退化为永久 no-op 而不被察觉。本文以该文档为骨架,结合 runtests.py 的解析与校验实现、testsuite/skiplist-spec_test.py 的守卫测试以及 testsuite/fleettest.py 的舰队测试逻辑,说明这份清单的用途、格式规则、维护规范,以及同目录下backport.txt为何是性质完全不同的另一类文件。读完你可以理解并正确维护任意平台的跳过清单,并弄清"预期跳过"与"排除不跑"两种机制的根本区别。

一、为什么需要"预期跳过"清单:防止测试悄悄变成永久 no-op

runtests.py在完整运行(full run)时会把实际跳过的测试集合与预期跳过的测试集合做精确比对,二者必须完全相等。这个"预言机(oracle)"的价值在于:一旦某个测试开始跳过——探测(probe)回归、辅助程序缺失、环境变化——运行就会失败,而不是继续报告"全绿"。换句话说,make check不再容忍任何"静默的跳过"。

实现上,集合比对发生在 runtests.py:跳过结果按测试原本的执行顺序排序输出,随后把got与want转为集合做无序比较,任何差异都会把退出码置为 1。这正是文档所述"若某测试开始跳过,运行即失败"的代码级证据。

历史上,这套预期集合是通过一条横跨七个 CI 工作流步骤的超长逗号分隔行传入的。这意味着:任何新增跳过测试的分支,都要在同一文件同一行上做修改,于是两个同时添加跳过测试的分支必然产生合并冲突。为消除这种冲突,清单被拆分为一个测试名一行的文件,集中存放于testsuite/skiplist/,工作流步骤只做一次引用:

run: RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check

于是"新增一个跳过测试"变成"修改一行、一个文件",两个分支各加各的不同测试可以干净合并——这正是 runtests.py 注释中反复强调的设计动机:清单保持"一个测试名一行",是避免分支冲突的关键。

二、清单文件一览:按平台与协议分层

文件内容
common.txt在所有运行预言机的平台上都跳过的测试——主要是require_tcp(真实 TCP 传输)与require_asan(AddressSanitizer 构建)类测试,默认的 stdio-pipemake check无法满足这些前置条件
linux.txt仅 Linux 的追加项
macos.txt仅 macOS 的追加项
cygwin.txt仅 Cygwin 的追加项
proto29.txt针对--protocol=29运行的追加项,与平台无关
proto30.txt针对--protocol=30运行的追加项,与平台无关

各清单以逗号组合,结果是并集,因此同一测试名出现在多个文件中无害。裸测试名可以与@FILE条目混用。common.txt的头部注释(testsuite/skiplist/common.txt#L6-L10)还特别说明:各平台可能因不同原因跳过同一测试,清单中记录的原因只是本机报告的那个。

分层组合的实际语义

  • 一个 Linux 的常规 stdio-pipe 运行使用common.txt + linux.txt,如开篇的 YAML 所示;
  • macOS 运行使用common.txt + macos.txt,其中macos.txt收录了pseudo-paths(procfs 伪路径仅 Linux 有)、partial-protected-regular-retry-policy(确定性 EACCES 恢复依赖 dyld interposing)等平台特定项;
  • Cygwin 运行使用common.txt + cygwin.txt,可见大量基于 symlink/ACL/socket 的测试(如daemon-stdin-local-socket注明 "Cygwin Python has no AF_UNIX socketpair");
  • check29/check30步骤在平台清单之上追加proto29.txt/proto30.txt。以 proto29.txt 为例,这些测试传输的内容超出了协议 29 线格式所能承载的范围(ACL、xattr),或驱动了只讲 30+ 协议的辅助程序,因此与平台无关地按协议门控——acls-unpinnable # ACL transfer requires protocol 30+、scanner-batch-flag-mismatch # xattrs (-X) need protocol 30+。有趣的是write-touched-blocks同时出现在两个协议文件中,原因是其逻辑块统计需要协议 33+,在 29 和 30 下都会跳过。

三、格式规则与强制校验:排序不是装饰

清单文件的格式规则非常严格,每一行都是一条可验证的约束:

  • 一个测试名一行;#起注释;行尾的# reason是记录跳过原因的唯一位置,被明确鼓励;
  • 每个文件必须排序且无重复;排序不是装饰——正是"有序"让两个独立的新增落在不同行上,从而可无冲突合并;
  • 每个名字都必须是真实的裸测试名,对应存在testsuite/<name>_test.py的测试。

这些规则的强制力来自 runtests.py 的expand_skip_spec():任何格式问题都通过die()以退出码 2 终止——包括不可读的文件、空文件或纯注释文件、空条目(a,,b,即未设置变量展开的结果)、未排序或重复的名字、过期(stale)名字。代码注释点明其动机:"任何格式错误都不得悄悄缩小预期集合,否则会解除预言机的武装"(runtests.py#L370-L374)。

为什么空条目必须报错?因为RSYNC_EXPECT_SKIPPED来自 shell 环境变量,未设置变量会展开成空串,而a,,b中的空段正是这种失误的痕迹。文档同时强调:整体为空的RSYNC_EXPECT_SKIPPED仍是合法的"预期无任何跳过",与"条目中出现空段"截然不同——前者是显式意图,后者是配置事故。

精确的集合比较意味着清单被截断绝不会蒙混过关:它会表现为一整页"unexpected skips(意外跳过)",把清单丢失的每个测试都点名出来,读起来像套件级回归而不是清单损坏。在源头失败(退出码 2)能准确说出实际发生了什么,这正是校验选择"解析期即报错"而非"运行期再发现"的原因。

-name减法:允许特定主机从清单中移除测试

除加法外,规格还支持-name形式的减法条目,用于"某主机确实能运行其平台清单预期会跳过的测试"的场景。减法必须实际移除某样东西——runtests.py#L443-L452规定,移除一个规格从未添加的名字(清单已不再期望该跳过,或拼写错误)与重复移除都会报错退出,因为"悄悄缩小预期集合"正是本解析器要杜绝的失败模式。减法在全部加法之后应用,因此顺序无关。skiplist-spec_test.py#L153-L172专门验证了这一语义:-victim恰好移除一个名字、移除顺序不影响结果、过期移除会被拒绝。

四、相对路径与srcdir:让外部构建与make installcheck可用

@FILE条目中的相对路径基于srcdir解析,而不是当前工作目录。这保证了:

  • 外部(out-of-tree)构建:在构建目录里执行make check时,清单文件仍在源码树中;
  • make installcheck:此时srcdir本身也可能是相对路径(如--srcdir=../src)。

runtests.py 的处理是:非绝对路径一律os.path.join(srcdir, path)。skiplist-spec_test.py 用两组用例分别验证了"相对 srcdir"与"安装检查场景下嵌套相对 srcdir"两种情形都能正确解析。

五、格式违规的完整守卫:skiplist-spec_test.py

文档中"exit 2 on anything malformed"并非空话——testsuite/skiplist-spec_test.py 是一份针对解析器的自检测试,逐条验证了这些失败模式确实会硬报错而非静默放行:

  • 缺失文件、未知测试名、未排序清单、重复条目、清单中的过期名字;
  • 空文件、纯注释文件、非 UTF-8 文本(二进制);
  • 空条目(acls,,sparse)、前导/尾随逗号;
  • 名字中夹带路径(../testsuite/acls)、一行两个名字、名字含逗号、目录冒充测试。

它甚至构造了一个 "fakesuite" 站桩目录(skiplist-spec_test.py#L45-L51),让各种畸形名字看起来像真实测试,从而确保"某个守卫被移除时不会被其他检查掩盖"。同时,它还核对已提交的清单本身可解析、非空,并保证每个已提交清单都被某个工作流引用、每个工作流引用都能展开——孤儿清单(无任何工作流指向)意味着它静默停止被强制执行,同样是失败(skiplist-spec_test.py#L130-L152)。

六、修改清单的准则:修复优先于登记

文档对"往清单里加名字"给出明确的行为准则:

  1. 优先修复测试使其不再跳过——新出现的跳过通常是该测试环境设置中的真实回归;
  2. 跳过确实正当(legitimate)时,把名字加到**最窄(适用范围最小)**的匹配文件中,并附原因;
  3. 不要用无法解释的名字来掩盖不匹配——意外跳过通常意味着该测试前置条件的真实退化。

这背后是"预期清单即声明"的设计哲学:清单不只告诉 CI "这些会跳过",还承诺"除此之外任何跳过都是故障"。因此登记一个名字等于签署一份"此跳过有正当理由"的声明,而理由只能写在行尾注释里。

七、舰队测试(fleettest.py)如何复用这些清单

testsuite/fleettest.py 在 ssh 舰队上构建 rsync 并以 stdio-pipe 与--use-tcp两种传输并行跑套件。它读取这些清单的路径与runtests.py不同:

  • 每个目标(target)镜像某个.github/workflows/*.yml作业,其RSYNC_EXPECT_SKIPPED规格直接从对应工作流解析(testsuite/README.md#L159-L164、fleettest.py);
  • 在解析结果之上,按fleettest.json中的expect_skip_extra合并单机事实——即只对某一台机器成立、而非对整个平台成立的跳过(如某台机器没有独立文件系统而导致的跨设备用例差异);
  • 反过来,expect_skip_omit可从清单中移除名字,用于"某目标反而能跑平台清单预期跳过的测试"(例如testsuite/fleettest.json.example#L262-L267中 SIMD 目标专门移除simd-checksum,因为该目标就是为启用 SIMD 而建的)。

fleettest.py通过正则从工作流步骤中解析字面规格(fleettest.py#L323附近),并用-name减法把expect_skip_omit表达为runtests能理解的规格。

协议 pass 的预言机取自同名工作流步骤

每个protocols目标跑一次额外的 stdio-pipe pass(runtests --protocol=N),而每个 pass 的预期跳过规格取自同名工作流步骤(testsuite/README.md#L194-L201)。这意味着:

  • 目标声明"protocols": [29]时,只有其工作流确实有make check29步骤并组合了proto29.txt,预言机才生效;
  • 目前只有 Ubuntu 工作流做check29/check30;macOS 或 Cygwin 目标即使声明运行协议 29,也不会得到该 pass 的错误预言机——宁缺毋滥,不会用一个"错误的预言机"去校验。

八、backport.txt:同目录下的另一类文件

testsuite/skiplist/backport.txt与前述清单性质完全不同。回移植分支(backport branch,如v3.4.1-sec-patches3、v3.2.7-sec-patches3)会用比其自身更新的测试套件来测试:fleettest.py --repo <backport> --testsuite-repo <3.5.0>。这样的树无法通过它未携带的修复对应的测试,也无法构建其 Makefile 从未听说过的单元测试辅助程序。因此这些分支各自携带自己的testsuite/skiplist/backport.txt,其语义差异必须分清:

  • 目录中其他文件是RSYNC_EXPECT_SKIPPED预言机——"这些测试应当跳过,若有变化请告诉我";
  • backport.txt是RSYNC_EXCLUDE清单——"这些测试根本不要运行"。

它必须是"排除"而非"预期跳过",原因直白:部分测试在较老树上是失败而非跳过,而预期跳过清单无法描述失败。

从被构建的树读取,而不是从提供套件的树读取

runtests.py 的read_backport_exclude()与 fleettest.py 一致:backport.txt从被构建(BUILD)的树读取,而非提供套件的树——因为只有被构建的树知道自己缺什么。把较新testsuite/覆盖到较老树上的 overlay 是不含删除的合并,因此只存在于回移植分支上的文件(如backport.txt)能在覆盖后幸存。这也正是它不能作为@FILE出现在预期跳过规格中的原因:@FILE按srcdir(套件树)解析,而backport.txt属于构建树(runtests.py 注释明确说明这一点)。

相应地,skiplist-spec_test.py 把backport.txt从"每个已提交清单必须被工作流引用"的规则中豁免:没有任何东西引用它,这是设计使然。

已接受的破损(accepted breakage)与缺失特性(absent features)必须区分

backport.txt中的条目分两类,且必须保持可区分:

  • absent(缺失)——分支没有的修复、特性或辅助程序。测试永远不会通过,也不提供任何信息;
  • accepted breakage(已接受的破损)——该分支上真实存在的缺陷,但项目选择带着它发布。测试本可以提示问题,排除它是一个决策而非记账。

第二类必须放在带注释的独立小节中,注明缺陷名称及其书面记录位置。文档给出了实例:v3.2.7-sec-patches3当前有一例(fake-super-acl-xattr,即--remote-option本地传输 bug)。若清单不区分这两类,约一个发布周期内,"我们知道且已接受"就会退化成"此测试在此从未适用过"——排除的理由将不可追溯。

回移植分支 CI 的配套义务

当回移植分支的 CI 被配置为用此套件测试它们时,必须与fleettest.py保持一致:从被构建的分支读取backport.txt,并以RSYNC_EXCLUDE传入。不这样做的 CI 作业会在每一条上失败,最终被关闭——也就是说,"正确读取回移植排除清单"不是可选项,而是此类 CI 存活的前提。

九、实践要点总结

  • 新增跳过测试:优先修测试;确需跳过时,把名字加到最窄适用的清单文件(平台级 → 平台文件,协议级 → 协议文件),保持排序、无重复,并写# reason。
  • 验证清单:任何格式错误都会让runtests.py以退出码 2 终止;完整运行的集合比对是精确的,截断清单会表现为整页"unexpected skips"。
  • 主机级例外:用-name减法或fleettest.json的expect_skip_omit/expect_skip_extra表达单机事实,而非改动平台清单。
  • 回移植分支:backport.txt走RSYNC_EXCLUDE通道,从构建树读取,区分"缺失特性"与"已接受破损",后者务必附缺陷说明。

这套机制的价值在于:把"跳过"从 CI 日志里无人问津的灰色地带,变成一份被强制校验、逐行可追溯、跨分支可合并的精确契约。testsuite/skiplist/目录是它的唯一真源,而 runtests.py、testsuite/skiplist-spec_test.py 与 testsuite/fleettest.py 分别从解析、守卫与舰队复用三个层面保证了它始终可信。

  • CLI
  • 数据同步
  • 网络

【免费下载链接】rsync

An open source utility that provides fast incremental file transfer. It also has useful features for backup and restore operations among many other use cases.

项目地址:https://gitcode.com/gh_mirrors/rs/rsync
点击查看免费下载

相关推荐

上一篇:VisualCppRedist AIO:一站式解决Windows应用程序兼容性问题
下一篇:免费风扇控制终极指南:用FanControl彻底解决电脑噪音和散热难题

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询