运行 KUnit 测试的技巧

使用 kunit.py run (“kunit tool”)

从任何目录运行

创建一个像这样的 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(以及 buildconfig)支持 --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)。这是一个有用的宏,因为 slow 属性是最常用的。

可以通过在套件定义中设置 “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 时,可以通过传入 module_param kunit.action=list_attr 来访问此报告。

过滤

用户可以使用 --filter 命令行标志来过滤测试。例如:

kunit.py run --filter speed=slow

您还可以在过滤器上使用以下操作:“<”、“>”、“<=”、“>=”、“!=” 和 “=”。示例:

kunit.py run --filter "speed>slow"

此示例将运行所有速度快于 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

此属性指示测试是否使用 init 数据或函数。

此属性自动保存为一个布尔值,并且也可以使用此属性过滤测试。