KUnit(内核单元测试框架)为 Linux 内核中的单元测试提供了一个通用框架。使用 KUnit,您可以定义称为测试套件的测试用例组。这些测试要么在内核启动时运行(如果内置),要么作为模块加载。 KUnit 自动在内核日志中标记并报告失败的测试用例。
KUnit 测试是内核的一部分,用 C(编程)语言编写,并且测试内核实现的部分(例如:C 语言函数)。排除构建时间,从调用到完成,KUnit 可以在不到 10 秒的时间内运行大约 100 个测试。 KUnit 可以测试任何内核组件,例如:文件系统、系统调用、内存管理、设备驱动程序等。
KUnit 遵循白盒测试方法。该测试可以访问内部系统功能。 KUnit 在内核空间中运行,并且不限于暴露于用户空间的事物。
此外,KUnit 还有 kunit_tool,一个tools/testing/kunit/kunit.py配置 Linux 内核的脚本 ( ),在 QEMU 或 UML(用户模式 Linux)下运行 KUnit 测试,解析测试结果并以用户友好的方式显示它们。
特点
- 提供编写单元测试的框架。
- 在任何内核架构上运行测试。
- 以毫秒为单位运行测试。
先决条件
- 任何 Linux 内核兼容的硬件。
- 对于测试的内核,Linux 内核版本 5.5 或更高版本。
一、KUnit 入门
1.1安装依赖项
KUnit 与 Linux 内核具有相同的依赖关系。只要你能构建内核,就可以运行 KUnit。
使用 kunit_tool 运行测试
kunit_tool 是一个 Python 脚本,用于配置和构建内核、运行测试并格式化测试结果。从内核存储库中,您可以运行 kunit_tool:
./tools/testing/kunit/kunit.py run
笔记——您可能会看到以下错误:“源代码树不干净,请运行 'make ARCH=um mrproper'”发生这种情况是因为 kunit.py 在内部通过 参数指定.kunit (默认选项)作为命令中的构建目录。因此,在开始树外构建之前,源树必须是干净的。make O=output/dir--build_dir管理指南的“构建内核目录”部分中也提到了相同的警告,即它的使用,它必须用于make.好消息是,它确实可以通过运行来解决 ,只是要注意这会删除当前配置和所有生成的文件。make ARCH=um mrproper
如果一切正常,您应该看到以下内容:
Configuring KUnit Kernel ... Building KUnit Kernel ... Starting KUnit Kernel ...
测试将通过或失败。
笔记——由于这是第一次构建大量源,因此该步骤可能需要一段时间。Building KUnit Kernel
选择要运行的测试
默认情况下,kunit_tool 运行使用最小配置即可到达的所有测试,即使用大多数 kconfig 选项的默认值。但是,您可以选择要运行的测试:
- 自定义用于编译内核的Kconfig ,或者
- 按名称过滤测试以专门选择要运行的已编译测试。
1.2自定义Kconfig
.kunitconfigKUnit 默认配置是一个很好的起点。如果您尚未运行,可以通过运行以下命令来生成它:kunit.pyrun
cd $PATH_TO_LINUX_REPO tools/testing/kunit/kunit.py config cat .kunit/.kunitconfig
笔记.kunitconfig
存在于--build_dir
kunit.py 使用的.kunit
默认情况下。
在运行测试之前,kunit_tool 确保.kunitconfig在 kernel 中设置所有配置选项.config。如果您没有包含所使用选项的依赖项,它会警告您。
有多种方法可以自定义配置:
编辑.kunit/.kunitconfig。该文件应包含运行所需测试所需的 kconfig 选项列表,包括它们的依赖项。您可能需要从其中删除 CONFIG_KUNIT_ALL_TESTS,.kunitconfig因为它将启用许多您可能不想要的其他测试。如果您需要在 UML 以外的体系结构上运行。
启用额外的 kconfig 选项.kunit/.kunitconfig。例如,要包含内核的链表测试,您可以运行:
./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_LIST_KUNIT_TEST=y
提供树中一个或多个 .kunitconfig 文件的路径。例如,要仅运行FAT_FS
和EXT4
测试,您可以运行:
./tools/testing/kunit/kunit.py run \ --kunitconfig ./fs/fat/.kunitconfig \ --kunitconfig ./fs/ext4/.kunitconfig
如果更改.kunitconfig,kunit.py 将触发文件的重建.config。但您可以.config直接编辑文件或使用.只要它是 的超集,kunit.py 就不会覆盖您的更改。makemenuconfigO=.kunit.kunitconfig
笔记——找到满意的配置后保存 .kunitconfig:make savedefconfig O=.kunit cp .kunit/defconfig .kunit/.kunitconfig
按名称过滤测试
如果您想要比 Kconfig 提供的更具体,还可以通过传递 glob 过滤器来选择在引导时执行哪些测试(阅读联机帮助页glob(7)中有关模式的说明)。如果过滤器中有"."(句点),它将被解释为测试套件名称和测试用例之间的分隔符,否则,它将被解释为测试套件名称。例如,假设我们使用默认配置:
告知测试套件的名称,例如"kunit_executor_test",以运行它包含的每个测试用例:
./tools/testing/kunit/kunit.py run "kunit_executor_test"
通知以其测试套件为前缀的测试用例的名称,例如"example.example_simple_test",以专门运行该测试用例:
./tools/testing/kunit/kunit.py run "example.example_simple_test"
使用通配符 ( *?[) 运行与该模式匹配的任何测试用例,就像运行任何测试套件中名称中"*.*64*"包含的测试用例一样:"64"
./tools/testing/kunit/kunit.py run "*.*64*"
在没有 KUnit 包装器的情况下运行测试
如果您不想使用 KUnit Wrapper(例如:您希望测试中的代码与其他系统集成,或使用不同/不支持的架构或配置),KUnit 可以包含在任何内核中,并读出结果并手动解析。
笔记——
CONFIG_KUNIT
不应在生产环境中启用。启用 KUnit 会禁用内核地址空间布局随机化 (KASLR),测试可能会以不适合生产的方式影响内核状态。
1.3配置内核
要启用 KUnit 本身,您需要启用CONFIG_KUNITKconfig 选项(在 内核黑客/内核测试和覆盖 下 menuconfig)。从那里,您可以启用任何 KUnit 测试。它们通常具有以_KUNIT_TEST.
KUnit 和 KUnit 测试可以编译为模块。模块中的测试将在模块加载时运行。
运行测试(没有 KUnit 包装器)
构建并运行您的内核。在内核日志中,测试输出以TAP格式打印出来。仅当 KUnit/tests 是内置的时,默认情况下才会发生这种情况。否则需要加载该模块。
笔记一些线路和/或数据可能散布在 TAP 输出中
编写你的第一个测试
在您的内核存储库中,我们添加一些可以测试的代码。
- 创建一个文件drivers/misc/example.h,其中包括:
int misc_example_add(int left, int right);
2.创建一个文件drivers/misc/example.c,其中包括:
#include "example.h" int misc_example_add(int left, int right) { return left + right; }
3.将以下行添加到drivers/misc/Kconfig:
config MISC_EXAMPLE bool "My example"
4.将以下行添加到drivers/misc/Makefile:
obj-$(CONFIG_MISC_EXAMPLE) += example.o
现在我们准备编写测试用例:
- 添加以下测试用例
drivers/misc/example_test.c
:
#include "example.h" /* Define the test cases. */ static void misc_example_add_test_basic(struct kunit *test) { KUNIT_EXPECT_EQ(test, 1, misc_example_add(1, 0)); KUNIT_EXPECT_EQ(test, 2, misc_example_add(1, 1)); KUNIT_EXPECT_EQ(test, 0, misc_example_add(-1, 1)); KUNIT_EXPECT_EQ(test, INT_MAX, misc_example_add(0, INT_MAX)); KUNIT_EXPECT_EQ(test, -1, misc_example_add(INT_MAX, INT_MIN)); } static void misc_example_test_failure(struct kunit *test) { KUNIT_FAIL(test, "This test never passes."); } static struct kunit_case misc_example_test_cases[] = { KUNIT_CASE(misc_example_add_test_basic), KUNIT_CASE(misc_example_test_failure), {} }; static struct kunit_suite misc_example_test_suite = { .name = "misc-example", .test_cases = misc_example_test_cases, }; kunit_test_suite(misc_example_test_suite); MODULE_LICENSE("GPL");
2.将以下行添加到drivers/misc/Kconfig:
config MISC_EXAMPLE_TEST tristate "Test for my example" if !KUNIT_ALL_TESTS depends on MISC_EXAMPLE && KUNIT default KUNIT_ALL_TESTS
注意:如果您的测试不支持构建为可加载模块(不鼓励这样做),请用 bool 替换 tristate,并依赖 KUNIT=y 而不是 KUNIT。
3.将以下行添加到drivers/misc/Makefile:
obj-$(CONFIG_MISC_EXAMPLE_TEST) += example_test.o
4.将以下行添加到.kunit/.kunitconfig
:
CONFIG_MISC_EXAMPLE=y CONFIG_MISC_EXAMPLE_TEST=y
5.运行测试:
./tools/testing/kunit/kunit.py run
您应该看到以下失败:
... [16:08:57] [PASSED] misc-example:misc_example_add_test_basic [16:08:57] [FAILED] misc-example:misc_example_test_failure [16:08:57] EXPECTATION FAILED at drivers/misc/example-test.c:17 [16:08:57] This test never passes. ...
恭喜!您刚刚编写了第一个 KUnit 测试。
【文章福利】小编推荐自己的Linux内核技术交流群:【 865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!
二、KUnit架构
KUnit架构分为两部分:
- 内核内测试框架
- kunit_tool(命令行测试工具)
2.1内核内测试框架
内核测试库支持使用 KUnit 以 C 语言编写的 KUnit 测试。这些 KUnit 测试是内核代码。 KUnit 执行以下任务:
- 组织测试
- 报告测试结果
- 提供测试实用程序
测试用例
测试用例是KUnit 中的基本单元。 KUnit 测试用例被组织成套件。 KUnit 测试用例是具有类型签名的函数 。这些测试用例函数包装在一个名为 的结构中。void (*)(struct kunit *test)struct kunit_case
每个 KUnit 测试用例都会接收一个跟踪正在运行的测试的上下文对象。 KUnit 断言宏和其他 KUnit 实用程序使用 上下文对象。作为例外,有两个字段:struct kunitstruct kunit
->priv
:设置功能可以使用它来存储任意测试用户数据。->param_value
:它包含可以在参数化测试中检索的参数值。
测试套件
KUnit 套件包括一组测试用例。 KUnit 套件由.例如:structkunit_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_foo、example_test_bar和 example_test_baz。在运行测试之前,example_test_init 调用 ,在运行测试之后example_test_exit调用 。将kunit_test_suite(example_test_suite)测试套件注册到 KUnit 测试框架。
执行者
KUnit 执行器可以在启动时列出并运行内置 KUnit 测试。测试套件存储在名为 的链接器部中.kunit_test_suites。链接器部分由指向 的指针数组组成 ,并由宏填充 。 KUnit 执行器迭代链接器节数组,以便运行编译到内核中的所有测试。struct kunit_suitekunit_test_suites()。
KUnit套件内存图
在内核启动时,KUnit 执行器使用此部分的开始和结束地址来迭代并运行所有测试。当构建为模块时,kunit_test_suites()宏定义一个module_init()函数,该函数在编译单元中运行所有测试,而不是利用执行器。
在 KUnit 测试中,某些错误类不会影响其他测试或内核部分,每个 KUnit 案例都在单独的线程上下文中执行。
断言宏
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 与 KUnit 和 Kselftest 配合使用。 KUnit 执行器将 KTAP 结果打印到 dmesg 和 debugfs(如果已配置)。
参数化测试
每个 KUnit 参数化测试都与一组参数相关联。该测试被多次调用,每个参数值调用一次,并且参数存储在字段中param_value。测试用例包含一个KUNIT_CASE_PARAM()接受生成器函数的宏。生成器函数传递前一个参数并返回下一个参数。它还包括一个用于生成基于数组的常见情况生成器的宏。
2.2kunit_tool(命令行测试工具)
kunit_tool是一个Python脚本,可以在tools/testing/kunit/kunit.py.它用于配置、构建、执行、解析测试结果并以正确的顺序运行所有先前的命令(即配置、构建、执行和解析)。您有两种运行 KUnit 测试的选项:在启用 KUnit 的情况下构建内核并手动解析结果。
configure.config命令从 文件(以及任何特定于体系结构的选项)生成内核.kunitconfig。qemu_configs文件夹(例如)中提供的 Python 脚本包含针对特定体系结构的其他配置选项。它解析现有文件和文件以确保它是.如果没有,它将结合两者并运行以重新生成文件。然后它检查是否已成为超集。这将验证文件中是否正确指定了所有 Kconfig 依赖项。该脚本包含解析 Kconfigs 的代码。运行的代码是脚本的一部分 。
您可以通过以下方式调用该命令:
tools/testing/kunit/qemu configs/powerpc.py.config.kunitconfig.config.kunitconfigmake olddefconfig.config.config.kunitconfigkunit_config.pymake olddefconfigkunit_kernel.py./tools/testing/kunit/kunit.py config并生成一个.config文件。
buildmake使用所需选项(取决于体系结构和某些选项,例如:build_dir)在内核树上运行并报告任何错误。要从当前的 构建 KUnit 内核.config,您可以使用 build参数:。./tools/testing/kunit/kunit.py build
exec命令直接(使用用户模式 Linux 配置)或通过 QEMU 等模拟器执行内核结果。它使用标准输出 (stdout) 从日志中读取结果,并将其传递给parse进行解析。如果您已经构建了带有内置 KUnit 测试的内核,则可以使用参数运行该内核并显示测试结果exec :。./tools/testing/kunit/kunit.py exec
parse从内核日志中提取 KTAP 输出,解析测试结果并打印摘要。对于失败的测试,将包括任何诊断输出。
1)使用 kunit_tool 运行测试
我们可以使用 kunit_tool 运行 KUnit 测试,也可以手动运行测试,然后使用 kunit_tool 解析结果。要手动运行测试,请参阅:不使用 kunit_tool 运行测试。只要我们能够构建内核,我们就可以运行 KUnit。
kunit_tool 是一个 Python 脚本,用于配置和构建内核、运行测试并格式化测试结果。
运行命令:
./tools/testing/kunit/kunit.py run
我们应该看到以下内容:
Configuring KUnit Kernel ... Building KUnit kernel... Starting KUnit kernel...
我们可能想要使用以下选项:
./tools/testing/kunit/kunit.py run --timeout=30 --jobs=`nproc --all`
--timeout
设置测试运行的最长时间。--jobs
设置构建内核的线程数。
如果不存在其他文件(在构建目录中) , kunit_tool 将生成.kunitconfig具有默认配置的文件。.kunitconfig此外,它还验证生成的.config文件是否CONFIG包含 .kunitconfig.也可以将单独的.kunitconfig片段传递给 kunit_tool。如果我们有多个不同的测试组想要独立运行,或者如果我们想要对某些子系统使用预定义的测试配置,这非常有用。
要使用不同的.kunitconfig文件(例如为测试特定子系统而提供的文件),请将其作为选项传递:
./tools/testing/kunit/kunit.py run --kunitconfig=fs/ext4/.kunitconfig
要查看 kunit_tool 标志(可选命令行参数),请运行:
./tools/testing/kunit/kunit.py run --help
创建.kunitconfig文件
如果我们想运行一组特定的测试(而不是 KUnit 中列出的测试defconfig
),我们可以在 .kunitconfig
文件中提供 Kconfig 选项。
A.kunitconfig是 a minconfig(通过运行生成的 .config ),用于运行一组特定的测试。该文件包含具有特定测试目标的常规内核配置。它还包含测试所需的任何其他配置选项(例如:测试中功能的依赖项、启用/禁用某些代码块的配置、架构配置等)。
make savedefconfig.kunitconfig要创建.kunitconfig,使用 KUnit defconfig:
cd $PATH_TO_LINUX_REPO cp tools/testing/kunit/configs/default.config .kunit/.kunitconfig
然后我们可以添加任何其他 Kconfig 选项。例如:
CONFIG_LIST_KUNIT_TEST=y
kunit_tool 确保在运行测试之前.kunitconfig
在内核中设置所有配置选项。.config
如果我们没有包含选项依赖项,它会发出警告。
笔记从中删除某些内容.kunitconfig
不会重建.仅当不是 的子集时才会更新配置。这意味着我们可以使用其他工具(例如:)来调整其他配置选项。需要设置构建目录才能工作,因此默认情况下使用..config
file.kunitconfig.configmake
menuconfigmake
menuconfigmake
O=.kunit
menuconfig
配置、构建和运行测试
如果我们想手动更改 KUnit 构建过程,我们可以独立运行部分 KUnit 构建过程。当运行 kunit_tool 时,.kunitconfig我们可以.config使用config参数从 a 生成 a:
./tools/testing/kunit/kunit.py config
要从当前构建 KUnit 内核.config
,我们可以使用以下build
参数:
./tools/testing/kunit/kunit.py build
如果我们已经构建了带有内置 KUnit 测试的 UML 内核,我们可以运行该内核,并使用参数显示测试结果exec
:
./tools/testing/kunit/kunit.py exec
使用 kunit_tool 运行测试run
部分中讨论的命令相当于按顺序运行上述三个命令。
解析测试结果
KUnit 测试输出以 TAP(测试任何协议)格式显示结果。运行测试时,kunit_tool 会解析此输出并打印摘要。要查看 TAP 格式的原始测试结果,我们可以传递参数--raw_output:
./tools/testing/kunit/kunit.py run --raw_output
parse
如果我们有原始 TAP 格式的 KUnit 结果,我们可以使用kunit_tool 命令解析它们并打印人类可读的摘要。这接受参数的文件名,或者将从标准输入读取。
# Reading from a file ./tools/testing/kunit/kunit.py parse /var/log/dmesg # Reading from stdin dmesg | ./tools/testing/kunit/kunit.py parse
过滤测试
通过将 bash 样式的全局过滤器传递给exec
orrun
命令,我们可以运行内核中内置的测试子集。例如:如果我们只想运行 KUnit 资源测试,请使用:
./tools/testing/kunit/kunit.py run 'kunit-resource*'
这使用带有通配符的标准 glob 格式。
在QEMU上运行测试
kunit_tool 支持在 qemu 上以及通过 UML 运行测试。要在 qemu 上运行测试,默认情况下需要两个标志:
--arch
:选择一个配置集合(Kconfig、qemu 配置选项等),允许 KUnit 测试以最小的方式在指定的架构上运行。架构参数与传递给ARCH
Kbuild 使用的变量的选项名称相同。当前并非所有架构都支持此标志,但我们可以用来--qemu_config
处理它。如果um
通过(或者忽略该标志),测试将通过 UML 运行。非UML架构,例如:i386、x86_64、arm等;在 qemu 上运行。--cross_compile
:指定 Kbuild 工具链。它传递的参数与传递给CROSS_COMPILE
Kbuild 使用的变量的参数相同。提醒一下,这将是 GCC 等工具链二进制文件的前缀。例如:
sparc64-linux-gnu
如果我们的系统上安装了 sparc 工具链。$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux
如果我们已经从 0day 网站下载了 microblaze 工具链到我们的主目录中名为 toolchains 的目录中。
这意味着对于大多数架构来说,在 qemu 下运行非常简单:
./tools/testing/kunit/kunit.py run --arch=x86_64
交叉编译时,我们可能需要指定不同的工具链,例如:
./tools/testing/kunit/kunit.py run \ --arch=s390 \ --cross_compile=s390x-linux-gnu-
如果我们想要在该--arch
标志不支持的架构上运行 KUnit 测试,或者想要使用非默认配置在 qemu 上运行 KUnit 测试;然后我们可以编写自己的``QemuConfig``。这些QemuConfigs
是用 Python 编写的。它们在文件顶部有一个导入行 。该文件必须包含一个名为 的变量,该变量已为其分配了一个实例。请参阅以下示例:
from..qemu_config import QemuArchParamsQEMU_ARCHQemuArchParamstools/testing/kunit/qemu_configs/x86_64.py
一旦我们有了一个QemuConfig
,我们就可以使用该--qemu_config
标志将其传递到 kunit_tool 中。使用时,该标志会替换--arch
标志。例如:使用tools/testing/kunit/qemu_configs/x86_64.py
,调用显示为
./tools/testing/kunit/kunit.py run \ --timeout=60 \ --jobs=12 \ --qemu_config=./tools/testing/kunit/qemu_configs/x86_64.py
运行命令行参数
kunit_tool 有许多其他命令行参数,它们对我们的测试环境很有用。以下是最常用的命令行参数:
--help:列出所有可用选项。要列出常用选项,请放在--help命令之前。要列出特定于该命令的选项,请放在--help该命令之后。
笔记:不同的命令(config
、build
、run
等)具有不同的支持选项。
--build_dir
:指定 kunit_tool 构建目录。它包括.kunitconfig
、.config
文件和已编译的内核。--make_options
:指定在编译内核时(使用build
或run
命令)传递给 make 的附加选项。例如:要启用编译器警告,我们可以传递.--make_options
W=1
--alltests
:启用一组预定义的选项,以便构建尽可能多的测试。
笔记——已启用选项的列表可以在 中找到tools/testing/kunit/configs/all_tests.config
。如果您只想启用具有其他满足的依赖项的所有测试,请将其添加CONFIG_KUNIT_ALL_TESTS=y
到您的.kunitconfig
.
--kunitconfig
:指定文件的路径或目录.kunitconfig
。例如:
lib/kunit/.kunitconfig
可以是文件的路径。lib/kunit
可以是文件所在的目录。
该文件用于构建和运行一组预定义的测试及其依赖项。例如,为给定子系统运行测试。
--kconfig_add
:指定要附加到.kunitconfig
文件的其他配置选项。例如:
./tools/testing/kunit/kunit.py run --kconfig_add CONFIG_KASAN=y--arch
:在指定的架构上运行测试。架构参数与 Kbuild ARCH 环境变量相同。例如,i386、x86_64、arm、um 等。非 UML 架构在 qemu 上运行。默认是嗯。--cross_compile
:指定 Kbuild 工具链。它传递的参数与传递给CROSS_COMPILE
Kbuild 使用的变量的参数相同。这将是 GCC 等工具链二进制文件的前缀。例如:
sparc64-linux-gnu-
如果我们的系统上安装了 sparc 工具链。$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux
如果我们已经从 0day 网站下载了 microblaze 工具链到我们的主目录中名为 toolchains 的指定路径。
--qemu_config
:指定包含自定义 qemu 架构定义的文件的路径。这应该是一个包含QemuArchParams对象的 python 文件。--qemu_args
:指定其他 qemu 参数,例如。-smp
8
--jobs
:指定同时运行的作业(命令)数量。默认情况下,这设置为系统上的核心数。--timeout
:指定允许所有测试运行的最大秒数。这不包括构建测试所需的时间。--kernel_args
:指定附加内核命令行参数。可能会重复。--run_isolated
:如果设置,则为每个单独的套件/测试启动内核。这对于调试非密封测试非常有用,该测试可能根据之前运行的内容而通过/失败。--raw_output
:如果设置,则从内核生成未格式化的输出。可能的选项有:
all
:要查看完整的内核输出,请使用--raw_output=all
.kunit
:这是默认选项并过滤到 KUnit 输出。使用--raw_output
或--raw_output=kunit
.
--json
:如果设置,则以 JSON 格式存储测试结果并打印到stdout或保存到文件(如果指定了文件名)。--filter
:指定测试属性的过滤器,例如speed!=slow
。通过将输入括在引号中并用逗号分隔过滤器,可以使用多个过滤器。示例:.--filter
"speed>slow,
module=example"
--filter_action
:如果设置为skip
,过滤后的测试将在输出中显示为已跳过,而不是不显示输出。--list_tests
:如果设置,则列出将运行的所有测试。--list_tests_attr
:如果设置,则列出将运行的所有测试及其所有属性。
2)不使用 kunit_tool 运行测试
如果我们不想使用kunit_tool(例如:我们想与其他系统集成,或者在真实硬件上运行测试),我们可以将KUnit包含在任何内核中,读出结果并手动解析。
笔记——KUnit 并非设计用于生产系统。测试可能会降低系统的稳定性或安全性。
配置内核
KUnit 测试可以在没有 kunit_tool 的情况下运行。如果满足以下条件,这可能很有用:
- 我们有一个现有的内核配置需要测试。
- 需要在真实硬件上运行(或者使用模拟器/VM kunit_tool 不支持)。
- 希望与一些现有的测试系统集成。
KUnit 使用该CONFIG_KUNIT
选项进行配置,并且还可以通过在我们的.config
. KUnit 测试通常(但并不总是)具有以_KUNIT_TEST
.大多数测试可以构建为模块,也可以构建到内核中。
笔记——我们可以启用
KUNIT_ALL_TESTS
配置选项来自动启用具有满足依赖关系的所有测试。这是快速测试适用于当前配置的所有内容的好方法。
一旦我们构建了内核(和/或模块),运行测试就很简单。如果测试是内置的,它们将在内核启动时自动运行。结果将以dmesg
TAP 格式写入内核日志 ( )。
如果测试构建为模块,则它们将在加载模块时运行。
# modprobe example-test
结果将以 TAP 格式显示在dmesg
。
笔记——如果CONFIG_KUNIT_DEBUGFS
启用,则可以从debugfs
文件系统(如果已安装)访问 KUnit 测试结果。它们将采用/sys/kernel/debug/kunit/<test_suite>/results
TAP 格式。
三、Kunit编写测试
3.1测试用例
KUnit 中的基本单元是测试用例。测试用例是带有签名的函数。它调用被测试的函数,然后设置对应该发生的情况的期望。例如:void(*)(structkunit*test)
void example_test_success(struct kunit *test) { } void example_test_failure(struct kunit *test) { KUNIT_FAIL(test, "This test never passes."); }
在上面的例子中,example_test_success总是通过,因为它什么也没做;没有设定任何期望,因此所有期望都会过去。另一方面,example_test_failure总是会失败,因为它调用KUNIT_FAIL,这是一种特殊的期望,会记录消息并导致测试用例失败。
期望
期望指定我们期望一段代码在测试中执行某些操作。期望像函数一样被调用。测试是通过设置对被测代码的行为的期望来进行的。当一个或多个期望失败时,测试用例就会失败并记录有关失败的信息。例如:
void add_test_basic(struct kunit *test) { KUNIT_EXPECT_EQ(test, 1, add(1, 0)); KUNIT_EXPECT_EQ(test, 2, add(1, 1)); }
在上面的示例中,add_test_basic对名为 的函数的行为做出了许多断言add。第一个参数始终为 类型 ,其中包含有关当前测试上下文的信息。在本例中,第二个参数是预期值。最后一个值是实际值。如果满足所有这些期望,则测试用例将通过;如果这些期望中的任何一个失败,测试用例就会失败。struct kunit *addadd_test_basic
当任何期望被违反时,测试用例就会失败;但是,测试将继续运行,并尝试其他期望,直到测试用例结束或以其他方式终止。这与稍后讨论的断言相反。
笔记——单个测试用例应该简短、易于理解并且专注于单个行为。
例如,如果我们想严格测试add
上面的函数,请创建额外的测试用例来测试函数应add
具有的每个属性,如下所示:
void add_test_basic(struct kunit *test) { KUNIT_EXPECT_EQ(test, 1, add(1, 0)); KUNIT_EXPECT_EQ(test, 2, add(1, 1)); } void add_test_negative(struct kunit *test) { KUNIT_EXPECT_EQ(test, 0, add(-1, 1)); } void add_test_max(struct kunit *test) { KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX)); KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN)); } void add_test_overflow(struct kunit *test) { KUNIT_EXPECT_EQ(test, INT_MIN, add(INT_MAX, 1)); }
断言
断言类似于期望,只不过如果条件不满足,断言会立即终止测试用例。例如:
static void test_sort(struct kunit *test) { int *a, i, r = 1; a = kunit_kmalloc_array(test, TEST_LEN, sizeof(*a), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, a); for (i = 0; i < TEST_LEN; i++) { r = (r * 725861) % 6599; a[i] = r; } sort(a, TEST_LEN, sizeof(*a), cmpint, NULL); for (i = 0; i < TEST_LEN-1; i++) KUNIT_EXPECT_LE(test, a[i], a[i + 1]); }
在此示例中,我们需要能够分配一个数组来测试该sort()函数。因此,如果出现分配错误,我们通常KUNIT_ASSERT_NOT_ERR_OR_NULL()会中止测试。
笔记——在其他测试框架中,ASSERT
宏通常通过调用来实现return
,因此它们只能在测试函数中工作。在 KUnit 中,我们在失败时停止当前的 kthread,因此您可以从任何地方调用它们。警告:上述规则有一个例外。您不应在套件的 exit() 函数或资源的 free 函数中使用断言。它们在测试关闭时运行,并且此处的断言会阻止进一步的清理代码运行,从而可能导致内存泄漏。