Let's_start_KUnit!
Agenda
What_is_KUnit
バージョン5.5.0で追加された新機能らしい。
VM等の用意なしにカーネルのユニットテストが出来る軽量なフレームワーク。
Getting_started
Pythonのラッパー使ってシュッとやる。
気になったらtools/testing/kunit.py
読めばわかる。
とりあえずカーネルのソースが置いてあるディレクトリへ行って
./tools/testing/kunit/kunit.py run --defconfig
ってやると、シュッと色々生えてくる。早い
Generating .config ...
[00:00:39] Building KUnit Kernel ...
[00:00:50] Starting KUnit Kernel ...
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] kunit-try-catch-test ========
[00:00:50] [PASSED] kunit_test_try_catch_successful_try_no_catch
[00:00:50] [PASSED] kunit_test_try_catch_unsuccessful_try_does_catch
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] kunit-resource-test ========
[00:00:50] [PASSED] kunit_resource_test_init_resources
[00:00:50] [PASSED] kunit_resource_test_alloc_resource
[00:00:50] [PASSED] kunit_resource_test_destroy_resource
[00:00:50] [PASSED] kunit_resource_test_cleanup_resources
[00:00:50] [PASSED] kunit_resource_test_proper_free_ordering
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] string-stream-test ========
[00:00:50] [PASSED] string_stream_test_empty_on_creation
[00:00:50] [PASSED] string_stream_test_not_empty_after_add
[00:00:50] [PASSED] string_stream_test_get_string
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] example ========
[00:00:50] [PASSED] example_simple_test
[00:00:50] ============================================================
[00:00:50] Testing complete. 11 tests run. 0 failed. 0 crashed.
[00:00:50] Elapsed time: 74.073s total, 2.343s configuring, 71.565s building, 0.166s running
これやるとcwd下に.kunitconfig
が生える。内容は
CONFIG_KUNIT=y
CONFIG_KUNIT_TEST=y
CONFIG_KUNIT_EXAMPLE_TEST=y
Using_KUnit
さっきやったデフォルトのを元にやると良い。
とりあえずさっきので.kunitconfig
が生えてるので、
./tools/testing/kunit/kunit.py run
できる
が、現状では結果は当然さっきと同じ出力。
理解のため、シンプルなサンプルを作っていく。
PIDからtask_struct
を取得、そのtaskのPIDを返す関数を用意してやる
各ファイルの内容は
drivers/misc/task_pid.h
に
#include <linux/sched.h>
#include <linux/fs_struct.h>
#include <linux/dcache.h>
int dump_task_by_pid(int nr, struct task_struct *taskbuf);
drivers/misc/task_pid.c
に
#include <linux/pid.h>
#include "task_pid.h"
int dump_task_by_pid(int nr, struct task_struct *taskbuf)
{
struct pid *pid = find_get_pid(nr);
if(!pid)
return -1;
struct task_struct *tmp = pid_task(pid, PIDTYPE_PID);
if(!tmp)
return -1;
else
*taskbuf = *tmp;
return taskbuf->pid;
}
を書き込む。
drivers/misc/Kconfig
に
...
config TASK_FROM_PID
bool "Get task from pid and return task's pid"
config TASK_FROM_PID_TEST
bool "Test get task from pid"
depends on TASK_FROM_PID && KUNIT
...
を、
drivers/misc/Makefile
に
...
obj-$(CONFIG_TASK_FROM_PID) += task_pid.o
obj-$(CONFIG_TASK_FROM_PID_TEST) += task-from-pid-test.o
...
を書き足す。
これでもうテストが書けるようになる。
drivers/misc/task-from-pid-test.c
#include <kunit/test.h>
#include "task_pid.h"
static void task_from_pid_test(struct kunit *test)
{
struct task_struct taskbuf;
KUNIT_EXPECT_EQ(test, 1, dump_task_by_pid(1, &taskbuf));
KUNIT_EXPECT_EQ(test, 1, taskbuf.pid);
KUNIT_EXPECT_EQ(test, '/', taskbuf.fs->root.dentry->d_name.name[0]);
}
static struct kunit_case task_dump_test_cases[] = {
KUNIT_CASE(task_from_pid_test),
{}
};
static struct kunit_suite task_dump_test_suite = {
.name = "dump task from pid",
.test_cases = task_dump_test_cases,
};
kunit_test_suite(task_dump_test_suite);
見ての通りCで書く。
見た感じいつもの超絶マクロなので解説は後で
drivers/misc/Makefile
に
obj-$(CONFIG_MISC_EXAMPLE_TEST) += example-test.o
を、
.kunitconfig
に
CONFIG_TASK_FROM_PID=y
CONFIG_TASK_FROM_PID_TEST=y
を書き足すと完成
./tools/testing/kunit/kunit.py run
で出来る
結果(抜粋)
[00:00:38] ============================================================
[00:00:38] ======== [PASSED] dump task from pid ========
[00:00:38] [PASSED] task_from_pid_test
[00:00:38] ============================================================
Macros
とりあえずinclude/kunit/test.h
を見てみる。
各種テスト用マクロが定義されてる
たとえばさっきのKUNIT_ASSERT_EQ
/**
* KUNIT_ASSERT_EQ() - Sets an assertion that @left and @right are equal.
* @test: The test context object.
* @left: an arbitrary expression that evaluates to a primitive C type.
* @right: an arbitrary expression that evaluates to a primitive C type.
*
* Sets an assertion that the values that @left and @right evaluate to are
* equal. This is the same as KUNIT_EXPECT_EQ(), except it causes an assertion
* failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met.
*/
#define KUNIT_ASSERT_EQ(test, left, right) \
KUNIT_BINARY_EQ_ASSERTION(test, KUNIT_ASSERTION, left, right)
#define KUNIT_ASSERT_EQ_MSG(test, left, right, fmt, ...) \
KUNIT_BINARY_EQ_MSG_ASSERTION(test, \
KUNIT_ASSERTION, \
left, \
right, \
fmt, \
##__VA_ARGS__)
他にもKUNIT_ASSERT_GT
(Greater than)とかKUNIT_ASSERT_PTR_EQ
(ポインタ比較)とか、1500行に渡って便利なマクロが定義されてる。
また、KUNIT_CASE
もここで定義されており、その内容は簡単で
kunit_case
のプライベートにしたい要素を直接触らせない工夫で、関数ポインタを渡せば勝手にポインタと名前のフィールドだけを埋めてくれる。
/**
* struct kunit_case - represents an individual test case.
*
* @run_case: the function representing the actual test case.
* @name: the name of the test case.
*
* A test case is a function with the signature,
* ``void (*)(struct kunit *)``
* that makes expectations and assertions (see KUNIT_EXPECT_TRUE() and
* KUNIT_ASSERT_TRUE()) about code under test. Each test case is associated
* with a &struct kunit_suite and will be run after the suite's init
* function and followed by the suite's exit function.
*
* A test case should be static and should only be created with the
* KUNIT_CASE() macro; additionally, every array of test cases should be
* terminated with an empty test case.
*
* Example:
*
* .. code-block:: c
*
* void add_test_basic(struct kunit *test)
* {
* KUNIT_EXPECT_EQ(test, 1, add(1, 0));
* KUNIT_EXPECT_EQ(test, 2, add(1, 1));
* KUNIT_EXPECT_EQ(test, 0, add(-1, 1));
* KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
* KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
* }
*
* static struct kunit_case example_test_cases[] = {
* KUNIT_CASE(add_test_basic),
* {}
* };
*
*/
struct kunit_case {
void (*run_case)(struct kunit *test);
const char *name;
/* private: internal use only. */
bool success;
};
/**
* KUNIT_CASE - A helper for creating a &struct kunit_case
*
* @test_name: a reference to a test case function.
*
* Takes a symbol for a function representing a test case and creates a
* &struct kunit_case object from it. See the documentation for
* &struct kunit_case for an example on how to use it.
*/
#define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
テストの登録に使ったkunit_suite
は名前とテストケース以外にも2つの要素を持ち、それぞれ各テストケースの前後に実行する関数を設定できる。
/**
* struct kunit_suite - describes a related collection of &struct kunit_case
*
* @name: the name of the test. Purely informational.
* @init: called before every test case.
* @exit: called after every test case.
* @test_cases: a null terminated array of test cases.
*
* A kunit_suite is a collection of related &struct kunit_case s, such that
* @init is called before every test case and @exit is called after every
* test case, similar to the notion of a *test fixture* or a *test class*
* in other unit testing frameworks like JUnit or Googletest.
*
* Every &struct kunit_case must be associated with a kunit_suite for KUnit
* to run it.
*/
struct kunit_suite {
const char name[256];
int (*init)(struct kunit *test);
void (*exit)(struct kunit *test);
struct kunit_case *test_cases;
};
随所に用いられている構造体kunit
はこんな定義。
/**
* struct kunit - represents a running instance of a test.
*
* @priv: for user to store arbitrary data. Commonly used to pass data
* created in the init function (see &struct kunit_suite).
*
* Used to store information about the current context under which the test
* is running. Most of this data is private and should only be accessed
* indirectly via public functions; the one exception is @priv which can be
* used by the test writer to store arbitrary data.
*/
struct kunit {
void *priv;
/* private: internal use only. */
const char *name; /* Read only after initialization! */
struct kunit_try_catch try_catch;
/*
* success starts as true, and may only be set to false during a
* test case; thus, it is safe to update this across multiple
* threads using WRITE_ONCE; however, as a consequence, it may only
* be read after the test case finishes once all threads associated
* with the test case have terminated.
*/
bool success; /* Read only after test_case finishes! */
spinlock_t lock; /* Guards all mutable test state. */
/*
* Because resources is a list that may be updated multiple times (with
* new resources) from any thread associated with a test case, we must
* protect it with some type of lock.
*/
struct list_head resources; /* Protected by lock. */
};
詳しくはまた別でまとめる。
参考
https://github.com/torvalds/linux
https://kunit.dev/usage/index.html
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/kunit?h=v5.5
https://kunit.dev/third_party/kernel/docs/
https://lwn.net/Articles/780985/