KUnit 架构

KUnit 架构分为两部分

内核内测试框架

内核测试库使用 KUnit 支持用 C 编写的 KUnit 测试。这些 KUnit 测试是内核代码。KUnit 执行以下任务

  • 组织测试

  • 报告测试结果

  • 提供测试实用程序

测试用例

测试用例是 KUnit 中的基本单元。KUnit 测试用例被组织成套件。KUnit 测试用例是具有类型签名 void (*)(struct kunit *test) 的函数。这些测试用例函数被封装在一个名为 struct kunit_case 的结构体中。

每个 KUnit 测试用例都接收一个 struct kunit 上下文对象,用于跟踪正在运行的测试。KUnit 断言宏和其他 KUnit 实用程序使用 struct kunit 上下文对象。作为例外,有两个字段

  • ->priv:设置函数可以使用它来存储任意测试用户数据。

  • ->param_value:它包含参数值,可以在参数化测试中检索。

测试套件

KUnit 套件包括一组测试用例。KUnit 套件由 struct kunit_suite 表示。例如

static struct kunit_case example_test_cases[] = {
        KUNIT_CASE(example_test_foo),
        KUNIT_CASE(example_test_bar),
        KUNIT_CASE(example_test_baz),
        {}
};

static struct kunit_suite example_test_suite = {
        .name = "example",
        .init = example_test_init,
        .exit = example_test_exit,
        .test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);

在上面的示例中,测试套件 example_test_suite 运行测试用例 example_test_fooexample_test_barexample_test_baz。在运行测试之前,会调用 example_test_init,在运行测试之后,会调用 example_test_exitkunit_test_suite(example_test_suite) 将测试套件注册到 KUnit 测试框架。

执行器

KUnit 执行器可以在启动时列出并运行内置的 KUnit 测试。测试套件存储在名为 .kunit_test_suites 的链接器节中。有关代码,请参见 include/asm-generic/vmlinux.lds.h 中的 KUNIT_TABLE() 宏定义。链接器节由指向 struct kunit_suite 的指针数组组成,并通过 kunit_test_suites() 宏填充。KUnit 执行器迭代链接器节数组,以便运行编译到内核中的所有测试。

KUnit Suite Memory

KUnit 套件内存图

在内核启动时,KUnit 执行器使用此节的起始和结束地址来迭代并运行所有测试。有关执行器的实现,请参见 lib/kunit/executor.c。当作为模块构建时,kunit_test_suites() 宏会定义一个 module_init() 函数,该函数运行编译单元中的所有测试,而不是利用执行器。

在 KUnit 测试中,一些错误类不会影响其他测试或内核的其他部分,每个 KUnit 用例都在单独的线程上下文中执行。请参见 lib/kunit/try-catch.c 中的 kunit_try_catch_run() 函数。

断言宏

KUnit 测试使用期望/断言来验证状态。所有期望/断言的格式如下:KUNIT_{EXPECT|ASSERT}_<op>[_MSG](kunit, 属性[, 消息])

  • {EXPECT|ASSERT} 确定检查是断言还是期望。如果发生故障,测试流程会有所不同,如下所示

    • 对于期望,测试被标记为失败,并且会记录失败。

    • 另一方面,失败的断言会导致测试用例立即终止。

      • 断言调用以下函数:void __noreturn __kunit_abort(struct kunit *)

      • __kunit_abort 调用以下函数:void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)

      • kunit_try_catch_throw 调用以下函数:void kthread_complete_and_exit(struct completion *, long) __noreturn; 并终止特殊的线程上下文。

  • <op> 表示带有选项的检查:TRUE (提供的属性具有布尔值 “true”)、EQ (两个提供的属性相等)、NOT_ERR_OR_NULL (提供的指针不为空,并且不包含 “err” 值)。

  • [_MSG] 在失败时打印自定义消息。

测试结果报告

KUnit 以 KTAP 格式打印测试结果。KTAP 基于 TAP14,请参见 内核测试协议 (KTAP),版本 1。KTAP 与 KUnit 和 Kselftest 一起使用。KUnit 执行器将 KTAP 结果打印到 dmesg 和 debugfs (如果已配置)。

参数化测试

每个 KUnit 参数化测试都与一组参数相关联。该测试会多次调用,每个参数值调用一次,并且该参数存储在 param_value 字段中。测试用例包括一个 KUNIT_CASE_PARAM() 宏,它接受一个生成器函数。生成器函数会传递前一个参数并返回下一个参数。它还包括一个用于生成基于数组的通用用例生成器的宏。

kunit_tool (命令行测试工具)

kunit_tool 是一个 Python 脚本,位于 tools/testing/kunit/kunit.py 中。它用于配置、构建、执行、解析测试结果并按正确的顺序运行所有先前的命令(即,配置、构建、执行和解析)。您有两个选项来运行 KUnit 测试:要么启用 KUnit 构建内核并手动解析结果(请参见 不使用 kunit_tool 运行测试),要么使用 kunit_tool(请参见 使用 kunit_tool 运行测试)。

  • configure 命令从 .kunitconfig 文件(以及任何特定于架构的选项)生成内核 .config 文件。 qemu_configs 文件夹中提供的 Python 脚本(例如,tools/testing/kunit/qemu configs/powerpc.py)包含特定架构的附加配置选项。它会解析现有的 .config.kunitconfig 文件,以确保 .config.kunitconfig 的超集。如果不是,它将合并这两个文件并运行 make olddefconfig 来重新生成 .config 文件。然后它会检查 .config 是否已成为超集。这验证了所有 Kconfig 依赖项是否在 .kunitconfig 文件中正确指定。kunit_config.py 脚本包含用于解析 Kconfig 的代码。运行 make olddefconfig 的代码是 kunit_kernel.py 脚本的一部分。您可以通过以下命令调用此命令: ./tools/testing/kunit/kunit.py config 并生成一个 .config 文件。

  • build 使用所需的选项(取决于架构和一些选项,例如:build_dir)在内核树上运行 make 并报告任何错误。要从当前的 .config 构建 KUnit 内核,您可以使用 build 参数:./tools/testing/kunit/kunit.py build

  • exec 命令直接(使用用户模式 Linux 配置)或通过 QEMU 等模拟器执行内核结果。它使用标准输出(stdout)从日志中读取结果,并将它们传递给 parse 进行解析。如果您已经构建了一个包含内置 KUnit 测试的内核,则可以使用 exec 参数运行内核并显示测试结果:./tools/testing/kunit/kunit.py exec

  • parse 从内核日志中提取 KTAP 输出,解析测试结果,并打印摘要。对于失败的测试,将包含任何诊断输出。