运行 KUnit 测试的技巧¶
使用 kunit.py run
(“kunit 工具”)¶
从任何目录运行¶
创建一个像这样的 bash 函数会很方便
function run_kunit() {
( cd "$(git rev-parse --show-toplevel)" && ./tools/testing/kunit/kunit.py run "$@" )
}
注意
kunit.py
的早期版本(5.6 之前)除非从内核根目录运行,否则无法工作,因此使用了子 shell 和 cd
。
运行测试子集¶
kunit.py run
接受一个可选的 glob 参数来过滤测试。格式为 "<suite_glob>[.test_glob]"
。
假设我们想运行 sysctl 测试,我们可以通过以下方式进行
$ echo -e 'CONFIG_KUNIT=y\nCONFIG_KUNIT_ALL_TESTS=y' > .kunit/.kunitconfig
$ ./tools/testing/kunit/kunit.py run 'sysctl*'
我们可以通过以下方式过滤到仅 “write” 测试
$ echo -e 'CONFIG_KUNIT=y\nCONFIG_KUNIT_ALL_TESTS=y' > .kunit/.kunitconfig
$ ./tools/testing/kunit/kunit.py run 'sysctl*.*write*'
我们正在为构建比我们需要更多的测试付出代价,但这比摆弄 .kunitconfig
文件或注释掉 kunit_suite
更容易。
但是,如果我们想以一种不太临时的方式定义一组测试,下一个技巧很有用。
定义一组测试¶
kunit.py run
(以及 build
和 config
)支持 --kunitconfig
标志。因此,如果您有一组您想定期运行的测试(尤其是在它们有其他依赖项的情况下),您可以为它们创建一个特定的 .kunitconfig
。
例如,kunit 为其测试提供了一个
$ ./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit/.kunitconfig
或者,如果您遵循将文件命名为 .kunitconfig
的约定,您只需传入目录,例如
$ ./tools/testing/kunit/kunit.py run --kunitconfig=lib/kunit
注意
这是一个相对较新的功能 (5.12+),因此我们还没有关于哪些文件应该签入与仅在本地保留的约定。是否提交一个配置(因此必须维护)足够有用,由您和您的维护者决定。
注意
在父目录和子目录中包含 .kunitconfig
片段是不可靠的。有人讨论在这些文件中添加一个 “import” 语句,以便可以从所有子目录运行顶级配置中的测试。但这将意味着 .kunitconfig
文件不再只是简单的 .config 片段。
另一种方法是让 kunit 工具自动递归地合并配置,但是理论上测试可能依赖于不兼容的选项,因此处理起来会很棘手。
设置内核命令行参数¶
您可以使用 --kernel_args
来传递任意内核参数,例如
$ ./tools/testing/kunit/kunit.py run --kernel_args=param=42 --kernel_args=param2=false
在 UML 下生成代码覆盖率报告¶
注意
TODO(brendanhiggins@google.com): UML 和 gcc 7 及更高版本存在各种问题。您可能会遇到缺少 .gcda
文件或编译错误。
这与在 在 Linux 内核中使用 gcov 中记录的获取覆盖率信息的 “正常” 方式不同。
我们可以设置这些选项,而不是启用 CONFIG_GCOV_KERNEL=y
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_GCOV=y
将其整合到一个可复制粘贴的命令序列中
# Append coverage options to the current config
$ ./tools/testing/kunit/kunit.py run --kunitconfig=.kunit/ --kunitconfig=tools/testing/kunit/configs/coverage_uml.config
# Extract the coverage information from the build dir (.kunit/)
$ lcov -t "my_kunit_tests" -o coverage.info -c -d .kunit/
# From here on, it's the same process as with CONFIG_GCOV_KERNEL=y
# E.g. can generate an HTML report in a tmp dir like so:
$ genhtml -o /tmp/coverage_html coverage.info
如果您的已安装的 gcc 版本不起作用,您可以调整步骤
$ ./tools/testing/kunit/kunit.py run --make_options=CC=/usr/bin/gcc-6
$ lcov -t "my_kunit_tests" -o coverage.info -c -d .kunit/ --gcov-tool=/usr/bin/gcov-6
或者,也可以使用基于 LLVM 的工具链
# Build with LLVM and append coverage options to the current config
$ ./tools/testing/kunit/kunit.py run --make_options LLVM=1 --kunitconfig=.kunit/ --kunitconfig=tools/testing/kunit/configs/coverage_uml.config
$ llvm-profdata merge -sparse default.profraw -o default.profdata
$ llvm-cov export --format=lcov .kunit/vmlinux -instr-profile default.profdata > coverage.info
# The coverage.info file is in lcov-compatible format and it can be used to e.g. generate HTML report
$ genhtml -o /tmp/coverage_html coverage.info
手动运行测试¶
在不使用 kunit.py run
的情况下运行测试也是一个重要的用例。目前,如果您想在 UML 以外的架构上进行测试,这是您唯一的选择。
由于在 UML 下运行测试相当简单(配置和编译内核,运行 ./linux
二进制文件),本节将重点介绍非 UML 架构的测试。
运行内置测试¶
当将测试设置为 =y
时,测试将在启动时运行,并以 TAP 格式将结果打印到 dmesg 中。因此,您只需将测试添加到 .config
中,像往常一样构建和启动内核。
因此,如果我们使用以下配置编译内核
CONFIG_KUNIT=y
CONFIG_KUNIT_EXAMPLE_TEST=y
那么我们会在 dmesg 中看到如下输出,表明测试已运行并通过
TAP version 14
1..1
# Subtest: example
1..1
# example_simple_test: initializing
ok 1 - example_simple_test
ok 1 - example
将测试作为模块运行¶
根据测试的不同,您可以将它们构建为可加载的模块。
例如,我们会将之前的配置选项更改为
CONFIG_KUNIT=y
CONFIG_KUNIT_EXAMPLE_TEST=m
然后在启动到内核后,我们可以通过以下方式运行测试
$ modprobe kunit-example-test
这将导致它将 TAP 输出打印到 stdout。
注意
如果任何测试失败,modprobe
将不会具有非零退出代码(截至 5.13)。但是 kunit.py parse
会,请参见下文。
注意
您也可以设置 CONFIG_KUNIT=m
,但是,某些功能将无法工作,因此某些测试可能会中断。理想情况下,测试会在其 Kconfig
中指定它们依赖于 KUNIT=y
,但这是一个大多数测试作者不会考虑的极端情况。截至 5.13,唯一的区别是 current->kunit_test
将不存在。
美化打印结果¶
您可以使用 kunit.py parse
来解析 dmesg 以获取测试输出,并以与 kunit.py run
相同的熟悉格式打印结果。
$ ./tools/testing/kunit/kunit.py parse /var/log/dmesg
检索每个套件的结果¶
无论您如何运行测试,您都可以启用 CONFIG_KUNIT_DEBUGFS
来公开每个套件的 TAP 格式结果
CONFIG_KUNIT=y
CONFIG_KUNIT_EXAMPLE_TEST=m
CONFIG_KUNIT_DEBUGFS=y
每个套件的结果都将公开在 /sys/kernel/debug/kunit/<suite>/results
下。因此,使用我们的示例配置
$ modprobe kunit-example-test > /dev/null
$ cat /sys/kernel/debug/kunit/example/results
... <TAP output> ...
# After removing the module, the corresponding files will go away
$ modprobe -r kunit-example-test
$ cat /sys/kernel/debug/kunit/example/results
/sys/kernel/debug/kunit/example/results: No such file or directory
生成代码覆盖率报告¶
有关如何执行此操作的详细信息,请参阅 在 Linux 内核中使用 gcov。
这里唯一与 KUnit 相关的建议是,您可能希望将测试构建为模块。这样,您可以将测试的覆盖率与启动期间执行的其他代码隔离开,例如
# Reset coverage counters before running the test.
$ echo 0 > /sys/kernel/debug/gcov/reset
$ modprobe kunit-example-test
测试属性和过滤¶
测试套件和用例可以使用测试属性进行标记,例如测试速度。这些属性稍后将打印在测试输出中,并可用于过滤测试执行。
标记测试属性¶
通过在测试定义中包含 kunit_attributes
对象来标记测试的属性。
可以使用 KUNIT_CASE_ATTR(test_name, attributes)
宏来标记测试用例,以定义测试用例,而不是使用 KUNIT_CASE(test_name)
。
static const struct kunit_attributes example_attr = {
.speed = KUNIT_VERY_SLOW,
};
static struct kunit_case example_test_cases[] = {
KUNIT_CASE_ATTR(example_test, example_attr),
};
注意
要将测试用例标记为慢速,您还可以使用 KUNIT_CASE_SLOW(test_name)
。这是一个有用的宏,因为慢速属性是最常用的。
可以通过在套件定义中设置 “attr” 字段来标记具有属性的测试套件。
static const struct kunit_attributes example_attr = {
.speed = KUNIT_VERY_SLOW,
};
static struct kunit_suite example_test_suite = {
...,
.attr = example_attr,
};
注意
并非所有属性都需要在 kunit_attributes
对象中设置。未设置的属性将保持未初始化状态,并且表现得好像该属性设置为 0 或 NULL。因此,如果将属性设置为 0,则将其视为未设置。这些未设置的属性不会被报告,并且可能充当过滤的默认值。
报告属性¶
当用户运行测试时,属性将出现在原始内核输出中(采用 KTAP 格式)。请注意,默认情况下,所有通过的测试的属性将在 kunit.py 输出中隐藏,但可以使用 --raw_output
标志访问原始内核输出。以下是测试用例的测试属性在内核输出中的格式示例
# example_test.speed: slow
ok 1 example_test
以下是测试套件的测试属性在内核输出中的格式示例
KTAP version 2
# Subtest: example_suite
# module: kunit_example_test
1..3
...
ok 1 example_suite
此外,用户可以使用命令行标志 --list_tests_attr
输出带有属性的测试的完整属性报告。
kunit.py run "example" --list_tests_attr
注意
通过传入模块参数 kunit.action=list_attr
,可以在手动运行 KUnit 时访问此报告。
过滤¶
用户可以在运行测试时使用 --filter
命令行标志来过滤测试。例如
kunit.py run --filter speed=slow
您还可以在过滤器上使用以下操作:“<”、“>”、“<=”、“>=”、“!=” 和 “=”。示例
kunit.py run --filter "speed>slow"
此示例将运行所有速度快于慢速的测试。请注意,字符 < 和 > 通常由 shell 解释,因此可能需要引用或转义它们,如上所示。
此外,您可以一次使用多个过滤器。只需使用逗号分隔过滤器即可。示例
kunit.py run --filter "speed>slow, module=kunit_example_test"
注意
您可以在手动运行 KUnit 时使用此过滤功能,方法是将过滤器作为模块参数传递:kunit.filter="speed>slow, speed<=normal"
。
经过滤的测试将不会运行或显示在测试输出中。您可以使用 --filter_action=skip
标志来跳过经过滤的测试。这些测试将显示在测试输出中,但不会运行。要在手动运行 KUnit 时使用此功能,请使用模块参数 kunit.filter_action=skip
。
过滤过程的规则¶
由于套件和测试用例都可以具有属性,因此在过滤期间属性之间可能会发生冲突。过滤过程遵循以下规则
过滤始终在每个测试级别进行。
如果测试设置了属性,则将过滤测试的值。
否则,该值将回退到套件的值。
如果两者均未设置,则该属性具有全局 “默认” 值,该值将使用。
当前属性列表¶
speed
此属性指示测试执行的速度(测试的慢或快程度)。
此属性保存为枚举,具有以下类别:“normal”、“slow” 或 “very_slow”。测试的假定默认速度为 “normal”。这表示无论在哪个机器上运行,测试都需要相对较短的时间(少于 1 秒)。任何比这慢的测试都可以标记为 “slow” 或 “very_slow”。
可以使用宏 KUNIT_CASE_SLOW(test_name)
轻松地将测试用例的速度设置为 “slow”。
module
此属性指示与测试关联的模块的名称。
此属性自动保存为字符串,并为每个套件打印。也可以使用此属性过滤测试。
is_init
此属性指示测试是否使用初始化数据或函数。
此属性自动保存为布尔值,并且也可以使用此属性过滤测试。