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:setup 函数可以使用它来存储任意测试用户数据。

  • ->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, property[, message])

  • {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 文件(以及任何特定于体系结构的选择)生成内核 .configqemu_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 输出,解析测试结果,并打印摘要。 对于失败的测试,将包含任何诊断输出。