20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Linuxカーネルのユニットテスト機構KUnitを活用するためのメモ

Last updated at Posted at 2020-01-31

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/

20
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?