執行 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 資料或函式。

此屬性自動儲存為一個布林值,並且也可以使用此屬性過濾測試。