测试¶
本文档包含有关如何在内核中测试 Rust 代码的有用信息。
有三种测试
KUnit 测试。
#[test]
测试。Kselftests。
KUnit 测试¶
这些测试来自 Rust 文档中的示例。它们被转换为 KUnit 测试。
用法¶
这些测试可以通过 KUnit 运行。例如,通过命令行上的 kunit_tool
(kunit.py
)
./tools/testing/kunit/kunit.py run --make_options LLVM=1 --arch x86_64 --kconfig_add CONFIG_RUST=y
或者,KUnit 可以在启动时将它们作为内核内置运行。有关常规 KUnit 文档,请参阅 KUnit - Linux 内核单元测试;有关内核内置与命令行测试的详细信息,请参阅 KUnit 架构。
要使用这些 KUnit doctest,必须启用以下内容
CONFIG_KUNIT
Kernel hacking -> Kernel Testing and Coverage -> KUnit - Enable support for unit tests
CONFIG_RUST_KERNEL_DOCTESTS
Kernel hacking -> Rust hacking -> Doctests for the `kernel` crate
在内核配置系统中。
KUnit 测试是文档测试¶
这些文档测试通常是任何项目(例如函数、结构体、模块...)的用法示例。
它们非常方便,因为它们只是与文档一起编写。例如
/// Sums two numbers.
///
/// ```
/// assert_eq!(mymod::f(10, 20), 30);
/// ```
pub fn f(a: i32, b: i32) -> i32 {
a + b
}
在用户空间中,测试通过 rustdoc
收集和运行。按原样使用该工具已经很有用,因为它允许验证示例是否编译(从而强制它们与它们记录的代码保持同步),以及运行那些不依赖于内核 API 的示例。
但是,对于内核,这些测试会转换为 KUnit 测试套件。这意味着 doctest 被编译为 Rust 内核对象,允许它们针对已构建的内核运行。
这种 KUnit 集成的一个好处是 Rust doctest 可以重用现有的测试工具。例如,内核日志将如下所示
KTAP version 1
1..1
KTAP version 1
# Subtest: rust_doctests_kernel
1..59
# rust_doctest_kernel_build_assert_rs_0.location: rust/kernel/build_assert.rs:13
ok 1 rust_doctest_kernel_build_assert_rs_0
# rust_doctest_kernel_build_assert_rs_1.location: rust/kernel/build_assert.rs:56
ok 2 rust_doctest_kernel_build_assert_rs_1
# rust_doctest_kernel_init_rs_0.location: rust/kernel/init.rs:122
ok 3 rust_doctest_kernel_init_rs_0
...
# rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150
ok 59 rust_doctest_kernel_types_rs_2
# rust_doctests_kernel: pass:59 fail:0 skip:0 total:59
# Totals: pass:59 fail:0 skip:0 total:59
ok 1 rust_doctests_kernel
也像往常一样支持使用 ? 运算符的测试,例如
/// ```
/// # use kernel::{spawn_work_item, workqueue};
/// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?;
/// # Ok::<(), Error>(())
/// ```
测试也使用 CLIPPY=1
在 Clippy 下编译,就像普通代码一样,因此也可以从额外的 linting 中受益。
为了让开发人员能够轻松查看 doctest 代码的哪一行导致了失败,KTAP 诊断行会打印到日志中。这包含原始测试的位置(文件和行)(即,而不是生成的 Rust 文件中的位置)
# rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150
Rust 测试似乎使用来自 Rust 标准库 (core
) 的常用 assert!
和 assert_eq!
宏进行断言。我们提供了一个自定义版本,该版本将调用转发到 KUnit。重要的是,这些宏不需要传递上下文,不像 KUnit 测试的那些宏(即 struct kunit *
)。这使得它们更容易使用,并且文档的读者不需要关心使用哪个测试框架。此外,它可能允许我们在将来更轻松地测试第三方代码。
当前的限制是 KUnit 不支持其他任务中的断言。因此,如果断言实际上失败了,我们目前只是将错误打印到内核日志中。此外,doctest 不会为非公共函数运行。
由于这些测试是示例,即它们是文档的一部分,因此它们通常应该像“真实代码”一样编写。因此,例如,不要使用 unwrap()
或 expect()
,而是使用 ?
运算符。有关更多背景信息,请参阅
#[test]
测试¶
此外,还有 #[test]
测试。与文档测试一样,这些测试也与您期望从用户空间获得的测试非常相似,并且它们也映射到 KUnit。
这些测试由 kunit_tests
过程宏引入,该宏将测试套件的名称作为参数。
例如,假设我们要测试文档测试部分中的函数 f
。我们可以在与我们的函数相同的文件中编写
#[kunit_tests(rust_kernel_mymod)]
mod tests {
use super::*;
#[test]
fn test_f() {
assert_eq!(f(10, 20), 30);
}
}
如果我们运行它,内核日志将如下所示
KTAP version 1
# Subtest: rust_kernel_mymod
# speed: normal
1..1
# test_f.speed: normal
ok 1 test_f
ok 1 rust_kernel_mymod
与文档测试一样,assert!
和 assert_eq!
宏映射回 KUnit,并且不会 panic。类似地,支持 ? 运算符,即测试函数可以返回 nothing(即 unit 类型 ()
)或 Result
(即任何 Result<T, E>
)。例如
#[kunit_tests(rust_kernel_mymod)]
mod tests {
use super::*;
#[test]
fn test_g() -> Result {
let x = g()?;
assert_eq!(x, 30);
Ok(())
}
}
如果我们运行测试并且对 g
的调用失败,则内核日志将显示
KTAP version 1
# Subtest: rust_kernel_mymod
# speed: normal
1..1
# test_g: ASSERTION FAILED at rust/kernel/lib.rs:335
Expected is_test_result_ok(test_g()) to be true, but is false
# test_g.speed: normal
not ok 1 test_g
not ok 1 rust_kernel_mymod
如果 #[test]
测试可以作为用户的示例,请改用文档测试。即使是 API 的边缘情况,例如错误或边界情况,也可以在示例中显示。
rusttest
主机测试¶
这些是用户空间测试,可以使用 rusttest
Make 目标在主机(即执行内核构建的主机)中构建和运行
make LLVM=1 rusttest
这需要内核 .config
。
目前,它们主要用于测试 macros
crate 的示例。
Kselftests¶
Kselftests 也在 tools/testing/selftests/rust
文件夹中可用。
测试所需的内核配置选项在 tools/testing/selftests/rust/config
文件中列出,并且可以使用 merge_config.sh
脚本包含它们
./scripts/kconfig/merge_config.sh .config tools/testing/selftests/rust/config
kselftests 在内核源代码树中构建,旨在在运行相同内核的系统上执行。
一旦安装并启动了与源代码树匹配的内核,就可以使用以下命令编译和执行测试
make TARGETS="rust" kselftest
有关常规 Kselftest 文档,请参阅 Linux 内核自测试。