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/