测试

本文档包含有关如何测试内核中的 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 文档测试,必须在

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 测试套件。这意味着文档测试会被编译为 Rust 内核目标文件,从而允许它们针对已构建的内核运行。

这种 KUnit 集成的一个好处是,Rust 文档测试可以重用现有的测试设施。例如,内核日志看起来会像这样

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 进行编译,从而也能受益于额外的静态代码检查。

为了让开发者能够轻松查看是哪一行文档测试代码导致了失败,日志中会打印一行 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 不支持在其他任务中进行断言。因此,如果断言实际失败,我们目前只是在内核日志中打印一个错误。此外,非公开函数不会运行文档测试。

由于这些测试是示例,即它们是文档的一部分,因此它们通常应该像“真正的代码”一样编写。因此,例如,不要使用 unwrap()expect(),而应使用 ? 运算符。有关更多背景信息,请参见

#[test] 测试

此外,还有 #[test] 测试。与文档测试一样,这些测试也非常类似于你从用户空间所期望的那样,并且它们也被映射到 KUnit。

这些测试由 kunit_tests 过程宏引入,该宏接受测试套件的名称作为参数。

每个测试套件都应由 rust/kernel/Kconfig.test 中的一个 Kconfig 选项进行保护。

例如,假设我们要测试文档测试部分中的函数 f。我们可以在包含该函数的同一文件中编写

#[cfg(CONFIG_RUST_MYMOD_KUNIT_TEST)]
#[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。同样,? 运算符也得到支持,即测试函数可以返回空(即单元类型 ())或 Result(即任何 Result<T, E>)。例如

#[cfg(CONFIG_RUST_MYMOD_KUNIT_TEST)]
#[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 内核自测试 (Linux Kernel Selftests)