运行 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(以及 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)。这是一个有用的宏,因为慢速属性是最常用的。

可以通过在套件定义中设置 “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

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

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