1*9b31970eSBen Guo.. SPDX-License-Identifier: GPL-2.0 2*9b31970eSBen Guo.. include:: ../disclaimer-zh_CN.rst 3*9b31970eSBen Guo 4*9b31970eSBen Guo:Original: Documentation/rust/testing.rst 5*9b31970eSBen Guo 6*9b31970eSBen Guo:翻译: 7*9b31970eSBen Guo 8*9b31970eSBen Guo 郭杰 Ben Guo <benx.guo@gmail.com> 9*9b31970eSBen Guo 10*9b31970eSBen Guo测试 11*9b31970eSBen Guo==== 12*9b31970eSBen Guo 13*9b31970eSBen Guo本文介绍了如何在内核中测试 Rust 代码。 14*9b31970eSBen Guo 15*9b31970eSBen Guo有三种测试类型: 16*9b31970eSBen Guo 17*9b31970eSBen Guo- KUnit 测试 18*9b31970eSBen Guo- ``#[test]`` 测试 19*9b31970eSBen Guo- Kselftests 20*9b31970eSBen Guo 21*9b31970eSBen GuoKUnit 测试 22*9b31970eSBen Guo---------- 23*9b31970eSBen Guo 24*9b31970eSBen Guo这些测试来自 Rust 文档中的示例。它们会被转换为 KUnit 测试。 25*9b31970eSBen Guo 26*9b31970eSBen Guo使用 27*9b31970eSBen Guo**** 28*9b31970eSBen Guo 29*9b31970eSBen Guo这些测试可以通过 KUnit 运行。例如,在命令行中使用 ``kunit_tool`` ( ``kunit.py`` ):: 30*9b31970eSBen Guo 31*9b31970eSBen Guo ./tools/testing/kunit/kunit.py run --make_options LLVM=1 --arch x86_64 --kconfig_add CONFIG_RUST=y 32*9b31970eSBen Guo 33*9b31970eSBen Guo或者,KUnit 也可以在内核启动时以内置方式运行。获取更多 KUnit 信息,请参阅 34*9b31970eSBen GuoDocumentation/dev-tools/kunit/index.rst。 35*9b31970eSBen Guo关于内核内置与命令行测试的详细信息,请参阅 Documentation/dev-tools/kunit/architecture.rst。 36*9b31970eSBen Guo 37*9b31970eSBen Guo要使用这些 KUnit 文档测试,需要在内核配置中启用以下选项:: 38*9b31970eSBen Guo 39*9b31970eSBen Guo CONFIG_KUNIT 40*9b31970eSBen Guo Kernel hacking -> Kernel Testing and Coverage -> KUnit - Enable support for unit tests 41*9b31970eSBen Guo CONFIG_RUST_KERNEL_DOCTESTS 42*9b31970eSBen Guo Kernel hacking -> Rust hacking -> Doctests for the `kernel` crate 43*9b31970eSBen Guo 44*9b31970eSBen GuoKUnit 测试即文档测试 45*9b31970eSBen Guo******************** 46*9b31970eSBen Guo 47*9b31970eSBen Guo文档测试( *doctests* )一般用于展示函数、结构体或模块等的使用方法。 48*9b31970eSBen Guo 49*9b31970eSBen Guo它们非常方便,因为它们就写在文档旁边。例如: 50*9b31970eSBen Guo 51*9b31970eSBen Guo.. code-block:: rust 52*9b31970eSBen Guo 53*9b31970eSBen Guo /// 求和两个数字。 54*9b31970eSBen Guo /// 55*9b31970eSBen Guo /// ``` 56*9b31970eSBen Guo /// assert_eq!(mymod::f(10, 20), 30); 57*9b31970eSBen Guo /// ``` 58*9b31970eSBen Guo pub fn f(a: i32, b: i32) -> i32 { 59*9b31970eSBen Guo a + b 60*9b31970eSBen Guo } 61*9b31970eSBen Guo 62*9b31970eSBen Guo在用户空间中,这些测试由 ``rustdoc`` 负责收集并运行。单独使用这个工具已经很有价值, 63*9b31970eSBen Guo因为它可以验证示例能否成功编译(确保和代码保持同步), 64*9b31970eSBen Guo同时还可以运行那些不依赖内核 API 的示例。 65*9b31970eSBen Guo 66*9b31970eSBen Guo然而,在内核中,这些测试会转换成 KUnit 测试套件。 67*9b31970eSBen Guo这意味着文档测试会被编译成 Rust 内核对象,从而可以在构建的内核环境中运行。 68*9b31970eSBen Guo 69*9b31970eSBen Guo通过与 KUnit 集成,Rust 的文档测试可以复用内核现有的测试设施。 70*9b31970eSBen Guo例如,内核日志会显示:: 71*9b31970eSBen Guo 72*9b31970eSBen Guo KTAP version 1 73*9b31970eSBen Guo 1..1 74*9b31970eSBen Guo KTAP version 1 75*9b31970eSBen Guo # Subtest: rust_doctests_kernel 76*9b31970eSBen Guo 1..59 77*9b31970eSBen Guo # rust_doctest_kernel_build_assert_rs_0.location: rust/kernel/build_assert.rs:13 78*9b31970eSBen Guo ok 1 rust_doctest_kernel_build_assert_rs_0 79*9b31970eSBen Guo # rust_doctest_kernel_build_assert_rs_1.location: rust/kernel/build_assert.rs:56 80*9b31970eSBen Guo ok 2 rust_doctest_kernel_build_assert_rs_1 81*9b31970eSBen Guo # rust_doctest_kernel_init_rs_0.location: rust/kernel/init.rs:122 82*9b31970eSBen Guo ok 3 rust_doctest_kernel_init_rs_0 83*9b31970eSBen Guo ... 84*9b31970eSBen Guo # rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150 85*9b31970eSBen Guo ok 59 rust_doctest_kernel_types_rs_2 86*9b31970eSBen Guo # rust_doctests_kernel: pass:59 fail:0 skip:0 total:59 87*9b31970eSBen Guo # Totals: pass:59 fail:0 skip:0 total:59 88*9b31970eSBen Guo ok 1 rust_doctests_kernel 89*9b31970eSBen Guo 90*9b31970eSBen Guo文档测试中,也可以正常使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,例如: 91*9b31970eSBen Guo 92*9b31970eSBen Guo.. code-block:: rust 93*9b31970eSBen Guo 94*9b31970eSBen Guo /// ``` 95*9b31970eSBen Guo /// # use kernel::{spawn_work_item, workqueue}; 96*9b31970eSBen Guo /// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?; 97*9b31970eSBen Guo /// # Ok::<(), Error>(()) 98*9b31970eSBen Guo /// ``` 99*9b31970eSBen Guo 100*9b31970eSBen Guo这些测试和普通代码一样,也可以在 ``CLIPPY=1`` 条件下通过 Clippy 进行编译, 101*9b31970eSBen Guo因此可以从额外的 lint 检查中获益。 102*9b31970eSBen Guo 103*9b31970eSBen Guo为了便于开发者定位文档测试出错的具体行号,日志会输出一条 KTAP 诊断信息。 104*9b31970eSBen Guo其中标明了原始测试的文件和行号(不是 ``rustdoc`` 生成的临时 Rust 文件位置):: 105*9b31970eSBen Guo 106*9b31970eSBen Guo # rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150 107*9b31970eSBen Guo 108*9b31970eSBen GuoRust 测试中常用的断言宏是来自 Rust 标准库( ``core`` )中的 ``assert!`` 和 ``assert_eq!`` 宏。 109*9b31970eSBen Guo内核提供了一个定制版本,这些宏的调用会被转发到 KUnit。 110*9b31970eSBen Guo和 KUnit 测试不同的是,这些宏不需要传递上下文参数( ``struct kunit *`` )。 111*9b31970eSBen Guo这使得它们更易于使用,同时文档的读者无需关心底层用的是什么测试框架。 112*9b31970eSBen Guo此外,这种方式未来也许可以让我们更容易测试第三方代码。 113*9b31970eSBen Guo 114*9b31970eSBen Guo当前有一个限制:KUnit 不支持在其他任务中执行断言。 115*9b31970eSBen Guo因此,如果断言真的失败了,我们只是简单地把错误打印到内核日志里。 116*9b31970eSBen Guo另外,文档测试不适用于非公开的函数。 117*9b31970eSBen Guo 118*9b31970eSBen Guo作为文档中的测试示例,应当像 “实际代码” 一样编写。 119*9b31970eSBen Guo例如:不要使用 ``unwrap()`` 或 ``expect()``,请使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符。 120*9b31970eSBen Guo更多背景信息,请参阅: 121*9b31970eSBen Guo 122*9b31970eSBen Guo https://rust.docs.kernel.org/kernel/error/type.Result.html#error-codes-in-c-and-rust 123*9b31970eSBen Guo 124*9b31970eSBen Guo``#[test]`` 测试 125*9b31970eSBen Guo---------------- 126*9b31970eSBen Guo 127*9b31970eSBen Guo此外,还有 ``#[test]`` 测试。与文档测试类似,这些测试与用户空间中的测试方式也非常相近,并且同样会映射到 KUnit。 128*9b31970eSBen Guo 129*9b31970eSBen Guo这些测试通过 ``kunit_tests`` 过程宏引入,该宏将测试套件的名称作为参数。 130*9b31970eSBen Guo 131*9b31970eSBen Guo例如,假设想要测试前面文档测试示例中的函数 ``f``,我们可以在定义该函数的同一文件中编写: 132*9b31970eSBen Guo 133*9b31970eSBen Guo.. code-block:: rust 134*9b31970eSBen Guo 135*9b31970eSBen Guo #[kunit_tests(rust_kernel_mymod)] 136*9b31970eSBen Guo mod tests { 137*9b31970eSBen Guo use super::*; 138*9b31970eSBen Guo 139*9b31970eSBen Guo #[test] 140*9b31970eSBen Guo fn test_f() { 141*9b31970eSBen Guo assert_eq!(f(10, 20), 30); 142*9b31970eSBen Guo } 143*9b31970eSBen Guo } 144*9b31970eSBen Guo 145*9b31970eSBen Guo如果我们执行这段代码,内核日志会显示:: 146*9b31970eSBen Guo 147*9b31970eSBen Guo KTAP version 1 148*9b31970eSBen Guo # Subtest: rust_kernel_mymod 149*9b31970eSBen Guo # speed: normal 150*9b31970eSBen Guo 1..1 151*9b31970eSBen Guo # test_f.speed: normal 152*9b31970eSBen Guo ok 1 test_f 153*9b31970eSBen Guo ok 1 rust_kernel_mymod 154*9b31970eSBen Guo 155*9b31970eSBen Guo与文档测试类似, ``assert!`` 和 ``assert_eq!`` 宏被映射回 KUnit 并且不会发生 panic。 156*9b31970eSBen Guo同样,支持 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符, 157*9b31970eSBen Guo测试函数可以什么都不返回(单元类型 ``()``)或 ``Result`` (任何 ``Result<T, E>``)。例如: 158*9b31970eSBen Guo 159*9b31970eSBen Guo.. code-block:: rust 160*9b31970eSBen Guo 161*9b31970eSBen Guo #[kunit_tests(rust_kernel_mymod)] 162*9b31970eSBen Guo mod tests { 163*9b31970eSBen Guo use super::*; 164*9b31970eSBen Guo 165*9b31970eSBen Guo #[test] 166*9b31970eSBen Guo fn test_g() -> Result { 167*9b31970eSBen Guo let x = g()?; 168*9b31970eSBen Guo assert_eq!(x, 30); 169*9b31970eSBen Guo Ok(()) 170*9b31970eSBen Guo } 171*9b31970eSBen Guo } 172*9b31970eSBen Guo 173*9b31970eSBen Guo如果我们运行测试并且调用 ``g`` 失败,那么内核日志会显示:: 174*9b31970eSBen Guo 175*9b31970eSBen Guo KTAP version 1 176*9b31970eSBen Guo # Subtest: rust_kernel_mymod 177*9b31970eSBen Guo # speed: normal 178*9b31970eSBen Guo 1..1 179*9b31970eSBen Guo # test_g: ASSERTION FAILED at rust/kernel/lib.rs:335 180*9b31970eSBen Guo Expected is_test_result_ok(test_g()) to be true, but is false 181*9b31970eSBen Guo # test_g.speed: normal 182*9b31970eSBen Guo not ok 1 test_g 183*9b31970eSBen Guo not ok 1 rust_kernel_mymod 184*9b31970eSBen Guo 185*9b31970eSBen Guo如果 ``#[test]`` 测试可以对用户起到示例作用,那就应该改用文档测试。 186*9b31970eSBen Guo即使是 API 的边界情况,例如错误或边界问题,放在示例中展示也同样有价值。 187*9b31970eSBen Guo 188*9b31970eSBen Guo``rusttest`` 宿主机测试 189*9b31970eSBen Guo----------------------- 190*9b31970eSBen Guo 191*9b31970eSBen Guo这类测试运行在用户空间,可以通过 ``rusttest`` 目标在构建内核的宿主机中编译并运行:: 192*9b31970eSBen Guo 193*9b31970eSBen Guo make LLVM=1 rusttest 194*9b31970eSBen Guo 195*9b31970eSBen Guo当前操作需要内核 ``.config``。 196*9b31970eSBen Guo 197*9b31970eSBen Guo目前,它们主要用于测试 ``macros`` crate 的示例。 198*9b31970eSBen Guo 199*9b31970eSBen GuoKselftests 200*9b31970eSBen Guo---------- 201*9b31970eSBen Guo 202*9b31970eSBen GuoKselftests 可以在 ``tools/testing/selftests/rust`` 文件夹中找到。 203*9b31970eSBen Guo 204*9b31970eSBen Guo测试所需的内核配置选项列在 ``tools/testing/selftests/rust/config`` 文件中, 205*9b31970eSBen Guo可以借助 ``merge_config.sh`` 脚本合并到现有配置中:: 206*9b31970eSBen Guo 207*9b31970eSBen Guo ./scripts/kconfig/merge_config.sh .config tools/testing/selftests/rust/config 208*9b31970eSBen Guo 209*9b31970eSBen GuoKselftests 会在内核源码树中构建,以便在运行相同版本内核的系统上执行测试。 210*9b31970eSBen Guo 211*9b31970eSBen Guo一旦安装并启动了与源码树匹配的内核,测试即可通过以下命令编译并执行:: 212*9b31970eSBen Guo 213*9b31970eSBen Guo make TARGETS="rust" kselftest 214*9b31970eSBen Guo 215*9b31970eSBen Guo请参阅 Documentation/dev-tools/kselftest.rst 文档以获取更多信息。 216