Linux 核心自測

核心在 tools/testing/selftests/ 目錄下包含一組“自測”。 這些測試旨在成為小型測試,以執行核心中的各個程式碼路徑。 測試旨在在構建、安裝和啟動核心後執行。

主線的 Kselftest 可以在較舊的穩定核心上執行。 從主線執行測試可提供最佳覆蓋率。 幾個測試環在穩定版本上執行主線 kselftest 套件。 原因是當新增新測試來測試現有程式碼以迴歸測試錯誤時,我們應該能夠在較舊的核心上執行該測試。 因此,重要的是保留仍然可以測試較舊核心的程式碼,並確保它在新版本上優雅地跳過測試。

您可以在 Kselftest wiki 上找到有關 Kselftest 框架的更多資訊,以及如何使用該框架編寫新測試

https://kselftest.wiki.kernel.org/

在某些系統中,熱插拔測試可能會永遠掛起,等待 CPU 和記憶體準備好離線。 建立了一個特殊的熱插拔目標來執行全範圍的熱插拔測試。 在預設模式下,熱插拔測試以安全模式執行,範圍有限。 在有限模式下,cpu-hotplug 測試在單個 CPU 上執行,而不是在所有支援熱插拔的 CPU 上執行,並且記憶體熱插拔測試在支援熱插拔的記憶體的 2% 上執行,而不是 10%。

kselftest 作為使用者空間程序執行。 可以在使用者空間中編寫/執行的測試可能希望使用測試工具。 需要在核心空間中執行的測試可能希望使用測試模組

關於測試的文件

有關 kselftest 本身的文件,請參見

執行自測(熱插拔測試在有限模式下執行)

要構建測試

$ make headers
$ make -C tools/testing/selftests

要執行測試

$ make -C tools/testing/selftests run_tests

要使用單個命令構建和執行測試,請使用

$ make kselftest

請注意,某些測試需要 root 許可權。

Kselftest 支援將輸出檔案儲存在單獨的目錄中,然後執行測試。 為了將輸出檔案定位在單獨的目錄中,支援兩種語法。 在這兩種情況下,工作目錄都必須是核心 src 的根目錄。 這適用於下面的“執行自測的子集”部分。

要構建,請使用 O= 將輸出檔案儲存在單獨的目錄中

$ make O=/tmp/kselftest kselftest

要構建,請使用 KBUILD_OUTPUT 將輸出檔案儲存在單獨的目錄中

$ export KBUILD_OUTPUT=/tmp/kselftest; make kselftest

O= 分配優先於 KBUILD_OUTPUT 環境變數。

預設情況下,上述命令執行測試並列印完整的透過/失敗報告。 Kselftest 支援“summary”選項,以便更容易理解測試結果。 如果指定了 summary 選項,請在 /tmp/testname 檔案中查詢每個測試的詳細的單個測試結果。 這適用於下面的“執行自測的子集”部分。

要啟用 summary 選項來執行 kselftest

$ make summary=1 kselftest

執行自測的子集

您可以在 make 命令列上使用“TARGETS”變數來指定要執行的單個測試,或要執行的測試列表。

要僅運行針對單個子系統的測試

$ make -C tools/testing/selftests TARGETS=ptrace run_tests

您可以指定多個測試來構建和執行

$  make TARGETS="size timers" kselftest

要構建,請使用 O= 將輸出檔案儲存在單獨的目錄中

$ make O=/tmp/kselftest TARGETS="size timers" kselftest

要構建,請使用 KBUILD_OUTPUT 將輸出檔案儲存在單獨的目錄中

$ export KBUILD_OUTPUT=/tmp/kselftest; make TARGETS="size timers" kselftest

此外,您可以在 make 命令列上使用“SKIP_TARGETS”變數來指定要從 TARGETS 列表中排除的一個或多個目標。

要執行所有測試,但單個子系統除外

$ make -C tools/testing/selftests SKIP_TARGETS=ptrace run_tests

您可以指定多個要跳過的測試

$  make SKIP_TARGETS="size timers" kselftest

您還可以指定要執行的受限測試列表以及專用的跳過列表

$  make TARGETS="breakpoints size timers" SKIP_TARGETS=size kselftest

有關所有可能目標的列表,請參見頂級的 tools/testing/selftests/Makefile。

執行全範圍熱插拔自測

要構建熱插拔測試

$ make -C tools/testing/selftests hotplug

要執行熱插拔測試

$ make -C tools/testing/selftests run_hotplug

請注意,某些測試需要 root 許可權。

安裝自測

您可以使用“make”的“install”目標(它呼叫 kselftest_install.sh 工具)將自測安裝在預設位置(tools/testing/selftests/kselftest_install)中,或者透過 INSTALL_PATH “make”變數安裝在使用者指定的位置中。

要在預設位置安裝自測

$ make -C tools/testing/selftests install

要在使用者指定的位置安裝自測

$ make -C tools/testing/selftests install INSTALL_PATH=/some/other/path

執行已安裝的自測

在安裝目錄中,以及在 Kselftest tarball 中,有一個名為 run_kselftest.sh 的指令碼來執行測試。

您可以簡單地執行以下操作來執行已安裝的 Kselftests。 請注意,某些測試需要 root 許可權

$ cd kselftest_install
$ ./run_kselftest.sh

要檢視可用測試的列表,可以使用 -l 選項

$ ./run_kselftest.sh -l

可以使用 -c 選項來執行來自測試集合的所有測試,或者可以使用 -t 選項來執行特定的單個測試。 這兩個選項都可以多次使用

$ ./run_kselftest.sh -c size -c seccomp -t timers:posix_timers -t timer:nanosleep

有關其他功能,請參見指令碼用法輸出,可透過 -h 選項檢視。

自測超時

自測被設計為快速的,因此每個測試都使用 45 秒的預設超時。 測試可以透過在其目錄中新增設定檔案並在那裡設定超時變數來覆蓋預設超時,以配置測試所需的上限超時。 只有少數測試使用大於 45 秒的值覆蓋超時,自測力求保持這種方式。 自測中的超時不被認為是致命的,因為執行測試的系統可能會更改,這也會改變執行測試的預期時間。 如果您可以控制將執行測試的系統,則可以在這些系統上配置測試執行程式,以使用更大或更小的命令列超時,如 -o--override-timeout 引數所示。 例如,要使用 165 秒而不是 1 秒,可以使用

$ ./run_kselftest.sh --override-timeout 165

您可以檢視 TAP 輸出以檢視是否遇到超時。 知道測試必須在特定時間內執行的測試執行程式可以選擇將這些超時視為致命錯誤。

打包自測

在某些情況下,需要打包,例如當需要在不同的系統上執行測試時。 要打包自測,請執行

$ make -C tools/testing/selftests gen_tar

這將在 INSTALL_PATH/kselftest-packages 目錄中生成一個 tarball。 預設情況下,使用 .gz 格式。 可以透過指定 FORMAT make 變數來覆蓋 tar 壓縮格式。 支援 tar 的自動壓縮選項識別的任何值,例如

$ make -C tools/testing/selftests gen_tar FORMAT=.xz

make gen_tar 呼叫 make install,因此您可以使用它來打包自測的子集,方法是使用執行自測的子集部分中指定的變數

$ make -C tools/testing/selftests gen_tar TARGETS="size" FORMAT=.xz

貢獻新測試

通常,自測的規則是

  • 如果您不是 root 使用者,請儘可能多地執行操作;

  • 不要花費太長時間;

  • 不要破壞任何架構上的構建,並且

  • 如果您的功能未配置,則不要導致頂級的“make run_tests”失敗。

  • 測試的輸出必須符合 TAP 標準,以確保高質量的測試並捕獲具有特定詳細資訊的失敗/錯誤。 kselftest.h 和 kselftest_harness.h 標頭提供用於輸出測試結果的包裝器。 這些包裝器應該用於 pass、fail、exit 和 skip 訊息。 CI 系統可以輕鬆解析 TAP 輸出訊息以檢測測試結果。

貢獻新測試(詳細資訊)

  • 在您的 Makefile 中,透過包含 lib.mk 來使用 lib.mk 中的工具,而不是重新發明輪子。 在包含 lib.mk 之前,根據需要指定標誌和二進位制生成標誌。

    CFLAGS = $(KHDR_INCLUDES)
    TEST_GEN_PROGS := close_range_test
    include ../lib.mk
    
  • 如果編譯期間生成了此類二進位制檔案或檔案,請使用 TEST_GEN_XXX。

    TEST_PROGS、TEST_GEN_PROGS 意味著它是預設情況下測試的可執行檔案。

    需要模組在測試開始之前構建的測試應使用 TEST_GEN_MODS_DIR。 該變數將包含包含模組的目錄的名稱。

    需要自定義構建規則並阻止使用常見構建規則的測試應使用 TEST_CUSTOM_PROGS。

    TEST_PROGS 用於測試 shell 指令碼。 請確保 shell 指令碼設定了其 exec 位。 否則,lib.mk run_tests 將生成警告。

    TEST_CUSTOM_PROGS 和 TEST_PROGS 將由常見的 run_tests 執行。

    TEST_PROGS_EXTENDED、TEST_GEN_PROGS_EXTENDED 意味著它不是預設情況下測試的可執行檔案。

    TEST_FILES、TEST_GEN_FILES 意味著它是測試使用的檔案。

    TEST_INCLUDES 類似於 TEST_FILES,它列出了在匯出或安裝測試時應包含的檔案,但有以下區別

    • 保留指向其他目錄中的檔案的符號連結

    • 將檔案複製到輸出目錄時,保留工具/測試/自測/下面的路徑部分

    TEST_INCLUDES 旨在列出位於自測層次結構的其他目錄中的依賴項。

  • 首先使用核心原始碼和/或 git 倉庫中的標頭,然後使用系統標頭。 應該主要關注核心版本標頭,而不是系統上由發行版安裝的標頭,以便能夠找到迴歸。 在 Makefile 中使用 KHDR_INCLUDES 以包含來自核心原始碼的標頭。

  • 如果測試需要啟用特定的核心配置選項,請在測試目錄中新增一個配置檔案以啟用它們。

    例如:tools/testing/selftests/android/config

  • 在測試目錄中建立一個 .gitignore 檔案,並將所有生成的物件新增到其中。

  • 在自測/Makefile 中的 TARGETS 中新增新的測試名稱

    TARGETS += android
    
  • 所有更改都應透過

    kselftest-{all,install,clean,gen_tar}
    kselftest-{all,install,clean,gen_tar} O=abo_path
    kselftest-{all,install,clean,gen_tar} O=rel_path
    make -C tools/testing/selftests {all,install,clean,gen_tar}
    make -C tools/testing/selftests {all,install,clean,gen_tar} O=abs_path
    make -C tools/testing/selftests {all,install,clean,gen_tar} O=rel_path
    

測試模組

Kselftest 從使用者空間測試核心。 有時需要從核心內部進行測試,一種方法是建立測試模組。 我們可以透過使用 shell 指令碼測試執行程式將模組繫結到 kselftest 框架。 kselftest/module.sh 旨在促進此過程。 還有一個標標頭檔案可幫助編寫用於 kselftest 的核心模組

  • tools/testing/selftests/kselftest_module.h

  • tools/testing/selftests/kselftest/module.sh

請注意,測試模組應該用 TAINT_TEST 汙染核心。 對於 tools/testing/ 目錄中的模組,或者對於使用上面的 kselftest_module.h 標頭的模組,將自動發生這種情況。 否則,您需要將 MODULE_INFO(test, "Y") 新增到您的模組原始碼中。 通常,不載入模組的自測不應汙染核心,但是在載入非測試模組的情況下,可以透過寫入 /proc/sys/kernel/tainted 從使用者空間應用 TEST_TAINT。

如何使用

在這裡,我們展示了建立測試模組並將其繫結到 kselftest 的典型步驟。 我們以 lib/ 的 kselftest 為例。

  1. 建立測試模組

  2. 建立將執行(載入/解除安裝)模組的測試指令碼,例如 tools/testing/selftests/lib/bitmap.sh

  3. 向配置檔案新增行,例如 tools/testing/selftests/lib/config

  4. 將測試指令碼新增到 makefile,例如 tools/testing/selftests/lib/Makefile

  5. 驗證它是否有效

# Assumes you have booted a fresh build of this kernel tree
cd /path/to/linux/tree
make kselftest-merge
make modules
sudo make modules_install
make TARGETS=lib kselftest

示例模組

一個最基本的測試模組可能如下所示

// SPDX-License-Identifier: GPL-2.0+

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include "../tools/testing/selftests/kselftest_module.h"

KSTM_MODULE_GLOBALS();

/*
 * Kernel module for testing the foobinator
 */

static int __init test_function()
{
        ...
}

static void __init selftest(void)
{
        KSTM_CHECK_ZERO(do_test_case("", 0));
}

KSTM_MODULE_LOADERS(test_foo);
MODULE_AUTHOR("John Developer <jd@fooman.org>");
MODULE_LICENSE("GPL");
MODULE_INFO(test, "Y");

示例測試指令碼

#!/bin/bash
# SPDX-License-Identifier: GPL-2.0+
$(dirname $0)/../kselftest/module.sh "foo" test_foo

測試工具

kselftest_harness.h 檔案包含用於構建測試的有用助手。 測試工具用於使用者空間測試,有關核心空間測試,請參見上面的測試模組

來自 tools/testing/selftests/seccomp/seccomp_bpf.c 的測試可以用作示例。

示例

#include "../kselftest_harness.h"

TEST(standalone_test) {
  do_some_stuff;
  EXPECT_GT(10, stuff) {
     stuff_state_t state;
     enumerate_stuff_state(&state);
     TH_LOG("expectation failed with state: %s", state.msg);
  }
  more_stuff;
  ASSERT_NE(some_stuff, NULL) TH_LOG("how did it happen?!");
  last_stuff;
  EXPECT_EQ(0, last_stuff);
}

FIXTURE(my_fixture) {
  mytype_t *data;
  int awesomeness_level;
};
FIXTURE_SETUP(my_fixture) {
  self->data = mytype_new();
  ASSERT_NE(NULL, self->data);
}
FIXTURE_TEARDOWN(my_fixture) {
  mytype_free(self->data);
}
TEST_F(my_fixture, data_is_good) {
  EXPECT_EQ(1, is_my_data_good(self->data));
}

TEST_HARNESS_MAIN

助手

TH_LOG

TH_LOG (fmt, ...)

引數

fmt

格式字串

...

可選引數

描述

TH_LOG(format, ...)

可選的除錯日誌記錄功能,可用於測試。 可以透過定義 TH_LOG_ENABLED 來啟用或停用日誌記錄。 例如,#define TH_LOG_ENABLED 1

如果未提供定義,則預設情況下啟用日誌記錄。

TEST

TEST (test_name)

定義測試函式並建立註冊存根

引數

test_name

測試名稱

描述

TEST(name) { implementation }

按名稱定義測試。 名稱必須是唯一的,並且測試不能並行執行。 包含塊的實現是一個函式,並且應將作用域視為這樣。 可以使用裸“return;”語句提前返回。

EXPECT_* 和 ASSERT_* 在 TEST() { } 上下文中有效。

TEST_SIGNAL

TEST_SIGNAL (test_name, signal)

引數

test_name

測試名稱

signal

訊號編號

描述

TEST_SIGNAL(name, signal) { implementation }

按名稱和預期終止訊號定義測試。 名稱必須是唯一的,並且測試不能並行執行。 包含塊的實現是一個函式,並且應將作用域視為這樣。 可以使用裸“return;”語句提前返回。

EXPECT_* 和 ASSERT_* 在 TEST() { } 上下文中有效。

FIXTURE_DATA

FIXTURE_DATA (datatype_name)

包裝結構名稱,以便我們可以減少要傳遞的引數

引數

datatype_name

資料型別名稱

描述

FIXTURE_DATA(datatype_name)

幾乎總是,您只需要 FIXTURE()(請參見下文)。 當需要 fixture 資料的型別時,可以使用此呼叫。 一般來說,除非將 self 直接傳遞給助手,否則不需要這樣做。

FIXTURE

FIXTURE (fixture_name)

每個 fixture 呼叫一次以設定資料並註冊

引數

fixture_name

fixture 名稱

描述

FIXTURE(fixture_name) {
  type property1;
  ...
};

定義提供給 TEST_F()-定義的測試的資料作為 self。 它應該使用 FIXTURE_SETUP()FIXTURE_TEARDOWN() 進行填充和清理。

FIXTURE_SETUP

FIXTURE_SETUP (fixture_name)

為 fixture 準備設定函式。 包含 _metadata,以便 EXPECT_*、ASSERT_* 等正常工作。

引數

fixture_name

fixture 名稱

描述

FIXTURE_SETUP(fixture_name) { implementation }

填充 fixture 所需的“setup”函式。 使用 FIXTURE_DATA() 定義的資料型別的例項將作為 self 暴露給實現。

ASSERT_* 在此上下文中有效,並將搶佔任何依賴 fixture 測試的執行。

可以使用裸“return;”語句提前返回。

FIXTURE_TEARDOWN

FIXTURE_TEARDOWN (fixture_name)

引數

fixture_name

fixture 名稱

描述

包含 _metadata,以便 EXPECT_*、ASSERT_* 等正常工作。

FIXTURE_TEARDOWN(fixture_name) { implementation }

填充 fixture 所需的“teardown”函式。 使用 FIXTURE_DATA() 定義的資料型別的例項將作為 self 暴露給實現以進行清理。

可以使用裸“return;”語句提前返回。

FIXTURE_VARIANT

FIXTURE_VARIANT (fixture_name)

每個 fixture 可選擇呼叫一次以宣告 fixture 變體

引數

fixture_name

fixture 名稱

描述

FIXTURE_VARIANT(fixture_name) {
  type property1;
  ...
};

定義提供給 FIXTURE_SETUP()TEST_F() 和 FIXTURE_TEARDOWN 的常量引數的型別作為 variant。 變體允許使用不同的引數執行相同的測試。

FIXTURE_VARIANT_ADD

FIXTURE_VARIANT_ADD (fixture_name, variant_name)

每個 fixture 變體呼叫一次以設定和註冊資料

引數

fixture_name

fixture 名稱

variant_name

引數集的名稱

描述

FIXTURE_VARIANT_ADD(fixture_name, variant_name) {
  .property1 = val1,
  ...
};

定義測試 fixture 的變體,提供給 FIXTURE_SETUP()TEST_F() 作為 variant。 每個 fixture 的測試將為每個變體執行一次。

TEST_F

TEST_F (fixture_name, test_name)

為基於 fixture 的測試用例發出測試註冊和助手

引數

fixture_name

fixture 名稱

test_name

測試名稱

描述

TEST_F(fixture, name) { implementation }

定義一個依賴於 fixture 的測試(例如,是測試用例的一部分)。 與 TEST() 非常相似,除了 self 是暴露給實現使用的 fixture 的資料型別的設定例項。

_metadata 物件與所有可能的派生程序共享 (MAP_SHARED),這使它們能夠使用 EXCEPT_*() 和 ASSERT_*()。

只有在使用 FIXTURE_TEARDOWN_PARENT() 而不是 FIXTURE_TEARDOWN() 時,self 物件才與可能的派生程序共享。

TEST_HARNESS_MAIN

TEST_HARNESS_MAIN

執行測試工具的簡單包裝器

描述

TEST_HARNESS_MAIN

使用一次將 main() 附加到測試檔案。

運算子

用於 TEST()TEST_F() 中的運算子。 ASSERT_* 呼叫將立即停止測試執行。 EXPECT_* 呼叫將發出失敗警告,記錄它,然後繼續。

ASSERT_EQ

ASSERT_EQ (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_EQ(expected, measured): expected == measured

ASSERT_NE

ASSERT_NE (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_NE(expected, measured): expected != measured

ASSERT_LT

ASSERT_LT (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_LT(expected, measured): expected < measured

ASSERT_LE

ASSERT_LE (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_LE(expected, measured): expected <= measured

ASSERT_GT

ASSERT_GT (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_GT(expected, measured): expected > measured

ASSERT_GE

ASSERT_GE (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_GE(expected, measured): expected >= measured

ASSERT_NULL

ASSERT_NULL (seen)

引數

seen

測量值

描述

ASSERT_NULL(measured): NULL == measured

ASSERT_TRUE

ASSERT_TRUE (seen)

引數

seen

測量值

描述

ASSERT_TRUE(measured): measured != 0

ASSERT_FALSE

ASSERT_FALSE (seen)

引數

seen

測量值

描述

ASSERT_FALSE(measured): measured == 0

ASSERT_STREQ

ASSERT_STREQ (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_STREQ(expected, measured): !strcmp(expected, measured)

ASSERT_STRNE

ASSERT_STRNE (expected, seen)

引數

expected

期望值

seen

測量值

描述

ASSERT_STRNE(expected, measured): strcmp(expected, measured)

EXPECT_EQ

EXPECT_EQ (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_EQ(expected, measured): expected == measured

EXPECT_NE

EXPECT_NE (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_NE(expected, measured): expected != measured

EXPECT_LT

EXPECT_LT (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_LT(expected, measured): expected < measured

EXPECT_LE

EXPECT_LE (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_LE(expected, measured): expected <= measured

EXPECT_GT

EXPECT_GT (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_GT(expected, measured): expected > measured

EXPECT_GE

EXPECT_GE (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_GE(expected, measured): expected >= measured

EXPECT_NULL

EXPECT_NULL (seen)

引數

seen

測量值

描述

EXPECT_NULL(measured): NULL == measured

EXPECT_TRUE

EXPECT_TRUE (seen)

引數

seen

測量值

描述

EXPECT_TRUE(measured): 0 != measured

EXPECT_FALSE

EXPECT_FALSE (seen)

引數

seen

測量值

描述

EXPECT_FALSE(measured): 0 == measured

EXPECT_STREQ

EXPECT_STREQ (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_STREQ(expected, measured): !strcmp(expected, measured)

EXPECT_STRNE

EXPECT_STRNE (expected, seen)

引數

expected

期望值

seen

測量值

描述

EXPECT_STRNE(expected, measured): strcmp(expected, measured)