Rustdoc 合并测试

摘要

详情

在 2024 版本之前,rustdoc 的 “test” 模式会将文档中的每个代码块编译成一个单独的可执行文件。尽管这种实现方式相对简单,但当存在大量文档测试时,会导致显著的性能负担。从 2024 版本开始,rustdoc 将尝试将文档测试合并成一个单独的二进制文件,从而显著减少编译文档测试的开销。

#![allow(unused)]
fn main() {
/// Adds two numbers
///
/// ```
/// assert_eq!(add(1, 1), 2);
/// ```
pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

/// Subtracts two numbers
///
/// ```
/// assert_eq!(subtract(2, 1), 1);
/// ```
pub fn subtract(left: u64, right: u64) -> u64 {
    left - right
}
}

在这个例子中,这两个文档测试现在将被编译成一个单独的可执行文件。Rustdoc 本质上会将每个示例放置在一个单独的函数中,而这些函数都在同一个二进制文件中。测试仍然像以前一样在独立的进程中运行,因此任何全局状态(如全局静态变量)仍然应该继续正常工作。1

此更改仅在 2024 版本中可用,以避免与可能无法在合并的可执行文件中工作的现有文档测试产生潜在的不兼容性。但是,这些不兼容性预计极其罕见。

1

有关此工作原理的更多详细信息,请参阅 “文档测试 - 它们是如何改进的?”

standalone_crate 标签

在某些情况下,rustdoc 无法将示例合并到单个可执行文件中。Rustdoc 将尝试自动检测是否无法合并。例如,如果一个测试符合以下条件,则不会与其他测试合并:

  • 使用 compile_fail 标签,这表示该示例应该编译失败。
  • 使用 edition 标签,这表示该示例的版本。2
  • 使用全局属性,例如 global_allocator 属性,这可能会干扰其他测试。
  • 定义任何 crate 级别的属性(例如 #![feature(...)])。
  • 定义使用 $crate 的宏,因为 $crate 路径将无法正确工作。

但是,rustdoc 无法自动确定所有示例无法与其他示例合并的情况。在这些情况下,您可以添加 standalone_crate 语言标签来指示该示例应作为单独的可执行文件构建。例如

#![allow(unused)]
fn main() {
//! ```
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 5);
//! ```
}

这对于示例的编译代码结构很敏感,并且不适用于“合并”方法,因为行号会根据文档测试的合并方式而变化。在这些情况下,您可以添加 standalone_crate 标签以强制示例像以前的版本一样单独构建。例如:

#![allow(unused)]
fn main() {
//! ```standalone_crate
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 5);
//! ```
}
2

请注意,rustdoc 仅在整个 crate 为 2024 版本或更高版本时才会合并测试。在旧版本中使用 edition2024 标签不会导致这些测试被合并。

迁移

没有自动迁移来确定哪些文档测试需要使用 standalone_crate 标签进行注释。任何给定的文档测试在迁移后都无法正常工作的可能性非常小。我们建议您将您的 crate 更新到 2024 版本,然后运行您的文档测试,看看是否有任何测试失败。如果确实有失败的测试,您将需要分析它是否可以重写以与合并方法兼容,或者,您可以添加 standalone_crate 标签以保留以前的行为。

一些需要注意和避免的事项是

  • 检查 std::panic::Location 的值或使用 Location 的事物。代码的位置现在不同了,因为多个测试现在位于同一个测试 crate 中。
  • 检查 std::any::type_name 的值,它现在具有不同的模块路径。