요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
Writing Tests
=============
Test Cases
----------
The fundamental unit in KUnit is the test case. A test case is a function with
the signature ``void (*)(struct kunit *test)``. It calls the function under test
and then sets *expectations* for what should happen. For example:
.. code-block:: c
void example_test_success(struct kunit *test)
{
}
void example_test_failure(struct kunit *test)
{
KUNIT_FAIL(test, "This test never passes.");
}
In the above example, ``example_test_success`` always passes because it does
nothing; no expectations are set, and therefore all expectations pass. On the
other hand ``example_test_failure`` always fails because it calls ``KUNIT_FAIL``,
which is a special expectation that logs a message and causes the test case to
fail.
Expectations
~~~~~~~~~~~~
An *expectation* specifies that we expect a piece of code to do something in a
test. An expectation is called like a function. A test is made by setting
expectations about the behavior of a piece of code under test. When one or more
expectations fail, the test case fails and information about the failure is
logged. For 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));
}
In the above example, ``add_test_basic`` makes a number of assertions about the
behavior of a function called ``add``. The first parameter is always of type
``struct kunit *``, which contains information about the current test context.
The second parameter, in this case, is what the value is expected to be. The
last value is what the value actually is. If ``add`` passes all of these
expectations, the test case, ``add_test_basic`` will pass; if any one of these
expectations fails, the test case will fail.
A test case *fails* when any expectation is violated; however, the test will
continue to run, and try other expectations until the test case ends or is
otherwise terminated. This is as opposed to *assertions* which are discussed
later.
To learn about more KUnit expectations, see Documentation/dev-tools/kunit/api/test.rst.
.. note::
A single test case should be short, easy to understand, and focused on a
single behavior.
For example, if we want to rigorously test the ``add`` function above, create
additional tests cases which would test each property that an ``add`` function
should have as shown below:
.. 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));
}
void add_test_negative(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 0, add(-1, 1));
}
void add_test_max(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
}
void add_test_overflow(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, INT_MIN, add(INT_MAX, 1));
}
Assertions
~~~~~~~~~~
An assertion is like an expectation, except that the assertion immediately
terminates the test case if the condition is not satisfied. For example:
.. code-block:: c
static void test_sort(struct kunit *test)
{
int *a, i, r = 1;
a = kunit_kmalloc_array(test, TEST_LEN, sizeof(*a), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, a);
for (i = 0; i < TEST_LEN; i++) {
r = (r * 725861) % 6599;
a[i] = r;
}
sort(a, TEST_LEN, sizeof(*a), cmpint, NULL);
for (i = 0; i < TEST_LEN-1; i++)
KUNIT_EXPECT_LE(test, a[i], a[i + 1]);
}
In this example, we need to be able to allocate an array to test the ``sort()``
function. So we use ``KUNIT_ASSERT_NOT_ERR_OR_NULL()`` to abort the test if
there's an allocation error.
.. note::
In other test frameworks, ``ASSERT`` macros are often implemented by calling
``return`` so they only work from the test function. In KUnit, we stop the
current kthread on failure, so you can call them from anywhere.
.. note::
Warning: There is an exception to the above rule. You shouldn't use assertions
in the suite's exit() function, or in the free function for a resource. These
run when a test is shutting down, and an assertion here prevents further
cleanup code from running, potentially leading to a memory leak.
Customizing error messages
--------------------------
Each of the ``KUNIT_EXPECT`` and ``KUNIT_ASSERT`` macros have a ``_MSG``
variant. These take a format string and arguments to provide additional
context to the automatically generated error messages.
.. code-block:: c
char some_str[41];
generate_sha1_hex_string(some_str);
/* Before. Not easy to tell why the test failed. */
KUNIT_EXPECT_EQ(test, strlen(some_str), 40);
/* After. Now we see the offending string. */
KUNIT_EXPECT_EQ_MSG(test, strlen(some_str), 40, "some_str='%s'", some_str);
Alternatively, one can take full control over the error message by using
``KUNIT_FAIL()``, e.g.
.. code-block:: c
/* Before */
KUNIT_EXPECT_EQ(test, some_setup_function(), 0);
/* After: full control over the failure message. */
if (some_setup_function())
KUNIT_FAIL(test, "Failed to setup thing for testing");
Test Suites
~~~~~~~~~~~
We need many test cases covering all the unit's behaviors. It is common to have
many similar tests. In order to reduce duplication in these closely related
tests, most unit testing frameworks (including KUnit) provide the concept of a
*test suite*. A test suite is a collection of test cases for a unit of code
with optional setup and teardown functions that run before/after the whole
suite and/or every test case.
.. note::
A test case will only run if it is associated with a test suite.
For example:
.. code-block:: c
static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_test_foo),
KUNIT_CASE(example_test_bar),
KUNIT_CASE(example_test_baz),
{}
};
static struct kunit_suite example_test_suite = {
.name = "example",
.init = example_test_init,
.exit = example_test_exit,
.suite_init = example_suite_init,
.suite_exit = example_suite_exit,
.test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);
In the above example, the test suite ``example_test_suite`` would first run
``example_suite_init``, then run the test cases ``example_test_foo``,
``example_test_bar``, and ``example_test_baz``. Each would have
``example_test_init`` called immediately before it and ``example_test_exit``
called immediately after it. Finally, ``example_suite_exit`` would be called
after everything else. ``kunit_test_suite(example_test_suite)`` registers the
test suite with the KUnit test framework.
.. note::
The ``exit`` and ``suite_exit`` functions will run even if ``init`` or
``suite_init`` fail. Make sure that they can handle any inconsistent
state which may result from ``init`` or ``suite_init`` encountering errors
or exiting early.
``kunit_test_suite(...)`` is a macro which tells the linker to put the
specified test suite in a special linker section so that it can be run by KUnit
either after ``late_init``, or when the test module is loaded (if the test was
built as a module).
For more information, see Documentation/dev-tools/kunit/api/test.rst.
.. _kunit-on-non-uml:
Writing Tests For Other Architectures
-------------------------------------
It is better to write tests that run on UML to tests that only run under a
particular architecture. It is better to write tests that run under QEMU or
another easy to obtain (and monetarily free) software environment to a specific
piece of hardware.
Nevertheless, there are still valid reasons to write a test that is architecture
or hardware specific. For example, we might want to test code that really
belongs in ``arch/some-arch/*``. Even so, try to write the test so that it does
not depend on physical hardware. Some of our test cases may not need hardware,
only few tests actually require the hardware to test it. When hardware is not
available, instead of disabling tests, we can skip them.
Now that we have narrowed down exactly what bits are hardware specific, the
actual procedure for writing and running the tests is same as writing normal
KUnit tests.
.. important::
We may have to reset hardware state. If this is not possible, we may only
be able to run one test case per invocation.
.. TODO([email protected]): Add an actual example of an architecture-
dependent KUnit test.
Common Patterns
===============
Isolating Behavior
------------------
Unit testing limits the amount of code under test to a single unit. It controls
what code gets run when the unit under test calls a function. Where a function
is exposed as part of an API such that the definition of that function can be
changed without affecting the rest of the code base. In the kernel, this comes
from two constructs: classes, which are structs that contain function pointers
provided by the implementer, and architecture-specific functions, which have
definitions selected at compile time.
Classes
~~~~~~~
Classes are not a construct that is built into the C programming language;
however, it is an easily derived concept. Accordingly, in most cases, every
project that does not use a standardized object oriented library (like GNOME's
GObject) has their own slightly different way of doing object oriented
programming; the Linux kernel is no exception.
The central concept in kernel object oriented programming is the class. In the
kernel, a *class* is a struct that contains function pointers. This creates a
contract between *implementers* and *users* since it forces them to use the
same function signature without having to call the function directly. To be a
class, the function pointers must specify that a pointer to the class, known as
a *class handle*, be one of the parameters. Thus the member functions (also
known as *methods*) have access to member variables (also known as *fields*)
allowing the same implementation to have multiple *instances*.
A class can be *overridden* by *child classes* by embedding the *parent class*
in the child class. Then when the child class *method* is called, the child
implementation knows that the pointer passed to it is of a parent contained
within the child. Thus, the child can compute the pointer to itself because the
pointer to the parent is always a fixed offset from the pointer to the child.
This offset is the offset of the parent contained in the child struct. For
example:
.. code-block:: c
struct shape {
int (*area)(struct shape *this);
};
struct rectangle {
struct shape parent;
int length;
int width;
};
int rectangle_area(struct shape *this)
{
struct rectangle *self = container_of(this, struct rectangle, parent);
return self->length * self->width;
};
void rectangle_new(struct rectangle *self, int length, int width)
{
self->parent.area = rectangle_area;
self->length = length;
self->width = width;
}
In this example, computing the pointer to the child from the pointer to the
parent is done by ``container_of``.
Faking Classes
~~~~~~~~~~~~~~
In order to unit test a piece of code that calls a method in a class, the
behavior of the method must be controllable, otherwise the test ceases to be a
unit test and becomes an integration test.
A fake class implements a piece of code that is different than what runs in a
production instance, but behaves identical from the standpoint of the callers.
This is done to replace a dependency that is hard to deal with, or is slow. For
example, implementing a fake EEPROM that stores the "contents" in an
internal buffer. Assume we have a class that represents an EEPROM:
.. code-block:: c
struct eeprom {
ssize_t (*read)(struct eeprom *this, size_t offset, char *buffer, size_t count);
ssize_t (*write)(struct eeprom *this, size_t offset, const char *buffer, size_t count);
};
And we want to test code that buffers writes to the EEPROM:
.. code-block:: c
struct eeprom_buffer {
ssize_t (*write)(struct eeprom_buffer *this, const char *buffer, size_t count);
int flush(struct eeprom_buffer *this);
size_t flush_count; /* Flushes when buffer exceeds flush_count. */
};
struct eeprom_buffer *new_eeprom_buffer(struct eeprom *eeprom);
void destroy_eeprom_buffer(struct eeprom *eeprom);
We can test this code by *faking out* the underlying EEPROM:
.. code-block:: c
struct fake_eeprom {
struct eeprom parent;
char contents[FAKE_EEPROM_CONTENTS_SIZE];
};
ssize_t fake_eeprom_read(struct eeprom *parent, size_t offset, char *buffer, size_t count)
{
struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent);
count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset);
memcpy(buffer, this->contents + offset, count);
return count;
}
ssize_t fake_eeprom_write(struct eeprom *parent, size_t offset, const char *buffer, size_t count)
{
struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent);
count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset);
memcpy(this->contents + offset, buffer, count);
return count;
}
void fake_eeprom_init(struct fake_eeprom *this)
{
this->parent.read = fake_eeprom_read;
this->parent.write = fake_eeprom_write;
memset(this->contents, 0, FAKE_EEPROM_CONTENTS_SIZE);
}
We can now use it to test ``struct eeprom_buffer``:
.. code-block:: c
struct eeprom_buffer_test {
struct fake_eeprom *fake_eeprom;
struct eeprom_buffer *eeprom_buffer;
};
static void eeprom_buffer_test_does_not_write_until_flush(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
char buffer[] = {0xff};
eeprom_buffer->flush_count = SIZE_MAX;
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0);
eeprom_buffer->flush(eeprom_buffer);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
}
static void eeprom_buffer_test_flushes_after_flush_count_met(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
char buffer[] = {0xff};
eeprom_buffer->flush_count = 2;
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
}
static void eeprom_buffer_test_flushes_increments_of_flush_count(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
char buffer[] = {0xff, 0xff};
eeprom_buffer->flush_count = 2;
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);
eeprom_buffer->write(eeprom_buffer, buffer, 2);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
/* Should have only flushed the first two bytes. */
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[2], 0);
}
static int eeprom_buffer_test_init(struct kunit *test)
{
struct eeprom_buffer_test *ctx;
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
ctx->fake_eeprom = kunit_kzalloc(test, sizeof(*ctx->fake_eeprom), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->fake_eeprom);
fake_eeprom_init(ctx->fake_eeprom);
ctx->eeprom_buffer = new_eeprom_buffer(&ctx->fake_eeprom->parent);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->eeprom_buffer);
test->priv = ctx;
return 0;
}
static void eeprom_buffer_test_exit(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
destroy_eeprom_buffer(ctx->eeprom_buffer);
}
Testing Against Multiple Inputs
-------------------------------
Testing just a few inputs is not enough to ensure that the code works correctly,
for example: testing a hash function.
We can write a helper macro or function. The function is called for each input.
For example, to test ``sha1sum(1)``, we can write:
.. code-block:: c
#define TEST_SHA1(in, want) \
sha1sum(in, out); \
KUNIT_EXPECT_STREQ_MSG(test, out, want, "sha1sum(%s)", in);
char out[40];
TEST_SHA1("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
TEST_SHA1("hello world!", "430ce34d020724ed75a196dfc2ad67c77772d169");
Note the use of the ``_MSG`` version of ``KUNIT_EXPECT_STREQ`` to print a more
detailed error and make the assertions clearer within the helper macros.
The ``_MSG`` variants are useful when the same expectation is called multiple
times (in a loop or helper function) and thus the line number is not enough to
identify what failed, as shown below.
In complicated cases, we recommend using a *table-driven test* compared to the
helper macro variation, for example:
.. code-block:: c
int i;
char out[40];
struct sha1_test_case {
const char *str;
const char *sha1;
};
struct sha1_test_case cases[] = {
{
.str = "hello world",
.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
},
{
.str = "hello world!",
.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
},
};
for (i = 0; i < ARRAY_SIZE(cases); ++i) {
sha1sum(cases[i].str, out);
KUNIT_EXPECT_STREQ_MSG(test, out, cases[i].sha1,
"sha1sum(%s)", cases[i].str);
}
There is more boilerplate code involved, but it can:
* be more readable when there are multiple inputs/outputs (due to field names).
* For example, see ``fs/ext4/inode-test.c``.
* reduce duplication if test cases are shared across multiple tests.
* For example: if we want to test ``sha256sum``, we could add a ``sha256``
field and reuse ``cases``.
* be converted to a "parameterized test".
Parameterized Testing
~~~~~~~~~~~~~~~~~~~~~
To run a test case against multiple inputs, KUnit provides a parameterized
testing framework. This feature formalizes and extends the concept of
table-driven tests discussed previously.
A KUnit test is determined to be parameterized if a parameter generator function
is provided when registering the test case. A test user can either write their
own generator function or use one that is provided by KUnit. The generator
function is stored in ``kunit_case->generate_params`` and can be set using the
macros described in the section below.
To establish the terminology, a "parameterized test" is a test which is run
multiple times (once per "parameter" or "parameter run"). Each parameter run has
both its own independent ``struct kunit`` (the "parameter run context") and
access to a shared parent ``struct kunit`` (the "parameterized test context").
Passing Parameters to a Test
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are three ways to provide the parameters to a test:
Array Parameter Macros:
KUnit provides special support for the common table-driven testing pattern.
By applying either ``KUNIT_ARRAY_PARAM`` or ``KUNIT_ARRAY_PARAM_DESC`` to the
``cases`` array from the previous section, we can create a parameterized test
as shown below:
.. code-block:: c
// This is copy-pasted from above.
struct sha1_test_case {
const char *str;
const char *sha1;
};
static const struct sha1_test_case cases[] = {
{
.str = "hello world",
.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
},
{
.str = "hello world!",
.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
},
};
// Creates `sha1_gen_params()` to iterate over `cases` while using
// the struct member `str` for the case description.
KUNIT_ARRAY_PARAM_DESC(sha1, cases, str);
// Looks no different from a normal test.
static void sha1_test(struct kunit *test)
{
// This function can just contain the body of the for-loop.
// The former `cases[i]` is accessible under test->param_value.
char out[40];
struct sha1_test_case *test_param = (struct sha1_test_case *)(test->param_value);
sha1sum(test_param->str, out);
KUNIT_EXPECT_STREQ_MSG(test, out, test_param->sha1,
"sha1sum(%s)", test_param->str);
}
// Instead of KUNIT_CASE, we use KUNIT_CASE_PARAM and pass in the
// function declared by KUNIT_ARRAY_PARAM or KUNIT_ARRAY_PARAM_DESC.
static struct kunit_case sha1_test_cases[] = {
KUNIT_CASE_PARAM(sha1_test, sha1_gen_params),
{}
};
Custom Parameter Generator Function:
The generator function is responsible for generating parameters one-by-one
and has the following signature:
``const void* (*)(struct kunit *test, const void *prev, char *desc)``.
You can pass the generator function to the ``KUNIT_CASE_PARAM``
or ``KUNIT_CASE_PARAM_WITH_INIT`` macros.
The function receives the previously generated parameter as the ``prev`` argument
(which is ``NULL`` on the first call) and can also access the parameterized
test context passed as the ``test`` argument. KUnit calls this function
repeatedly until it returns ``NULL``, which signifies that a parameterized
test ended.
Below is an example of how it works:
.. code-block:: c
#define MAX_TEST_BUFFER_SIZE 8
// Example generator function. It produces a sequence of buffer sizes that
// are powers of two, starting at 1 (e.g., 1, 2, 4, 8).
static const void *buffer_size_gen_params(struct kunit *test, const void *prev, char *desc)
{
long prev_buffer_size = (long)prev;
long next_buffer_size = 1; // Start with an initial size of 1.
// Stop generating parameters if the limit is reached or exceeded.
if (prev_buffer_size >= MAX_TEST_BUFFER_SIZE)
return NULL;
// For subsequent calls, calculate the next size by doubling the previous one.
if (prev)
next_buffer_size = prev_buffer_size << 1;
return (void *)next_buffer_size;
}
// Simple test to validate that kunit_kzalloc provides zeroed memory.
static void buffer_zero_test(struct kunit *test)
{
long buffer_size = (long)test->param_value;
// Use kunit_kzalloc to allocate a zero-initialized buffer. This makes the
// memory "parameter run managed," meaning it's automatically cleaned up at
// the end of each parameter run.
int *buf = kunit_kzalloc(test, buffer_size * sizeof(int), GFP_KERNEL);
// Ensure the allocation was successful.
KUNIT_ASSERT_NOT_NULL(test, buf);
// Loop through the buffer and confirm every element is zero.
for (int i = 0; i < buffer_size; i++)
KUNIT_EXPECT_EQ(test, buf[i], 0);
}
static struct kunit_case buffer_test_cases[] = {
KUNIT_CASE_PARAM(buffer_zero_test, buffer_size_gen_params),
{}
};
Runtime Parameter Array Registration in the Init Function:
For scenarios where you might need to initialize a parameterized test, you
can directly register a parameter array to the parameterized test context.
To do this, you must pass the parameterized test context, the array itself,
the array size, and a ``get_description()`` function to the
``kunit_register_params_array()`` macro. This macro populates
``struct kunit_params`` within the parameterized test context, effectively
storing a parameter array object. The ``get_description()`` function will
be used for populating parameter descriptions and has the following signature:
``void (*)(struct kunit *test, const void *param, char *desc)``. Note that it
also has access to the parameterized test context.
.. important::
When using this way to register a parameter array, you will need to
manually pass ``kunit_array_gen_params()`` as the generator function to
``KUNIT_CASE_PARAM_WITH_INIT``. ``kunit_array_gen_params()`` is a KUnit
helper that will use the registered array to generate the parameters.
If needed, instead of passing the KUnit helper, you can also pass your
own custom generator function that utilizes the parameter array. To
access the parameter array from within the parameter generator
function use ``test->params_array.params``.
The ``kunit_register_params_array()`` macro should be called within a
``param_init()`` function that initializes the parameterized test and has
the following signature ``int (*)(struct kunit *test)``. For a detailed
explanation of this mechanism please refer to the "Adding Shared Resources"
section that is after this one. This method supports registering both
dynamically built and static parameter arrays.
The code snippet below shows the ``example_param_init_dynamic_arr`` test that
utilizes ``make_fibonacci_params()`` to create a dynamic array, which is then
registered using ``kunit_register_params_array()``. To see the full code
please refer to lib/kunit/kunit-example-test.c.
.. code-block:: c
/*
* Example of a parameterized test param_init() function that registers a dynamic
* array of parameters.
*/
static int example_param_init_dynamic_arr(struct kunit *test)
{
size_t seq_size;
int *fibonacci_params;
kunit_info(test, "initializing parameterized test\n");
seq_size = 6;
fibonacci_params = make_fibonacci_params(test, seq_size);
if (!fibonacci_params)
return -ENOMEM;
/*
* Passes the dynamic parameter array information to the parameterized test
* context struct kunit. The array and its metadata will be stored in
* test->parent->params_array. The array itself will be located in
* params_data.params.
*/
kunit_register_params_array(test, fibonacci_params, seq_size,
example_param_dynamic_arr_get_desc);
return 0;
}
static struct kunit_case example_test_cases[] = {
/*
* Note how we pass kunit_array_gen_params() to use the array we
* registered in example_param_init_dynamic_arr() to generate
* parameters.
*/
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr,
kunit_array_gen_params,
example_param_init_dynamic_arr,
example_param_exit_dynamic_arr),
{}
};
Adding Shared Resources
^^^^^^^^^^^^^^^^^^^^^^^
All parameter runs in this framework hold a reference to the parameterized test
context, which can be accessed using the parent ``struct kunit`` pointer. The
parameterized test context is not used to execute any test logic itself; instead,
it serves as a container for shared resources.
It's possible to add resources to share between parameter runs within a
parameterized test by using ``KUNIT_CASE_PARAM_WITH_INIT``, to which you pass
custom ``param_init()`` and ``param_exit()`` functions. These functions run once
before and once after the parameterized test, respectively.
The ``param_init()`` function, with the signature ``int (*)(struct kunit *test)``,
can be used for adding resources to the ``resources`` or ``priv`` fields of
the parameterized test context, registering the parameter array, and any other
initialization logic.
The ``param_exit()`` function, with the signature ``void (*)(struct kunit *test)``,
can be used to release any resources that were not parameterized test managed (i.e.
not automatically cleaned up after the parameterized test ends) and for any other
exit logic.
Both ``param_init()`` and ``param_exit()`` are passed the parameterized test
context behind the scenes. However, the test case function receives the parameter
run context. Therefore, to manage and access shared resources from within a test
case function, you must use ``test->parent``.
For instance, finding a shared resource allocated by the Resource API requires
passing ``test->parent`` to ``kunit_find_resource()``. This principle extends to
all other APIs that might be used in the test case function, including
``kunit_kzalloc()``, ``kunit_kmalloc_array()``, and others (see
Documentation/dev-tools/kunit/api/test.rst and the
Documentation/dev-tools/kunit/api/resource.rst).
.. note::
The ``suite->init()`` function, which executes before each parameter run,
receives the parameter run context. Therefore, any resources set up in
``suite->init()`` are cleaned up after each parameter run.
The code below shows how you can add the shared resources. Note that this code
utilizes the Resource API, which you can read more about here:
Documentation/dev-tools/kunit/api/resource.rst. To see the full version of this
code please refer to lib/kunit/kunit-example-test.c.
.. code-block:: c
static int example_resource_init(struct kunit_resource *res, void *context)
{
... /* Code that allocates memory and stores context in res->data. */
}
/* This function deallocates memory for the kunit_resource->data field. */
static void example_resource_free(struct kunit_resource *res)
{
kfree(res->data);
}
/* This match function locates a test resource based on defined criteria. */
static bool example_resource_alloc_match(struct kunit *test, struct kunit_resource *res,
void *match_data)
{
return res->data && res->free == example_resource_free;
}
/* Function to initialize the parameterized test. */
static int example_param_init(struct kunit *test)
{
int ctx = 3; /* Data to be stored. */
void *data = kunit_alloc_resource(test, example_resource_init,
example_resource_free,
GFP_KERNEL, &ctx);
if (!data)
return -ENOMEM;
kunit_register_params_array(test, example_params_array,
ARRAY_SIZE(example_params_array));
return 0;
}
/* Example test that uses shared resources in test->resources. */
static void example_params_test_with_init(struct kunit *test)
{
int threshold;
const struct example_param *param = test->param_value;
/* Here we pass test->parent to access the parameterized test context. */
struct kunit_resource *res = kunit_find_resource(test->parent,
example_resource_alloc_match,
NULL);
threshold = *((int *)res->data);
KUNIT_ASSERT_LE(test, param->value, threshold);
kunit_put_resource(res);
}
static struct kunit_case example_test_cases[] = {
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, kunit_array_gen_params,
example_param_init, NULL),
{}
};
As an alternative to using the KUnit Resource API for sharing resources, you can
place them in ``test->parent->priv``. This serves as a more lightweight method
for resource storage, best for scenarios where complex resource management is
not required.
As stated previously ``param_init()`` and ``param_exit()`` get the parameterized
test context. So, you can directly use ``test->priv`` within ``param_init/exit``
to manage shared resources. However, from within the test case function, you must
navigate up to the parent ``struct kunit`` i.e. the parameterized test context.
Therefore, you need to use ``test->parent->priv`` to access those same
resources.
The resources placed in ``test->parent->priv`` will need to be allocated in
memory to persist across the parameter runs. If memory is allocated using the
KUnit memory allocation APIs (described more in the "Allocating Memory" section
below), you won't need to worry about deallocation. The APIs will make the memory
parameterized test 'managed', ensuring that it will automatically get cleaned up
after the parameterized test concludes.
The code below demonstrates example usage of the ``priv`` field for shared
resources:
.. code-block:: c
static const struct example_param {
int value;
} example_params_array[] = {
{ .value = 3, },
{ .value = 2, },
{ .value = 1, },
{ .value = 0, },
};
/* Initialize the parameterized test context. */
static int example_param_init_priv(struct kunit *test)
{
int ctx = 3; /* Data to be stored. */
int arr_size = ARRAY_SIZE(example_params_array);
/*
* Allocate memory using kunit_kzalloc(). Since the `param_init`
* function receives the parameterized test context, this memory
* allocation will be scoped to the lifetime of the parameterized test.
*/
test->priv = kunit_kzalloc(test, sizeof(int), GFP_KERNEL);
/* Assign the context value to test->priv.*/
*((int *)test->priv) = ctx;
/* Register the parameter array. */
kunit_register_params_array(test, example_params_array, arr_size, NULL);
return 0;
}
static void example_params_test_with_init_priv(struct kunit *test)
{
int threshold;
const struct example_param *param = test->param_value;
/* By design, test->parent will not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, test->parent);
/* Here we use test->parent->priv to access the shared resource. */
threshold = *(int *)test->parent->priv;
KUNIT_ASSERT_LE(test, param->value, threshold);
}
static struct kunit_case example_tests[] = {
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_priv,
kunit_array_gen_params,
example_param_init_priv, NULL),
{}
};
Allocating Memory
-----------------
Where you might use ``kzalloc``, you can instead use ``kunit_kzalloc`` as KUnit
will then ensure that the memory is freed once the test completes.
This is useful because it lets us use the ``KUNIT_ASSERT_EQ`` macros to exit
early from a test without having to worry about remembering to call ``kfree``.
For example:
.. code-block:: c
void example_test_allocation(struct kunit *test)
{
char *buffer = kunit_kzalloc(test, 16, GFP_KERNEL);
/* Ensure allocation succeeded. */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buffer);
KUNIT_ASSERT_STREQ(test, buffer, "");
}
Registering Cleanup Actions
---------------------------
If you need to perform some cleanup beyond simple use of ``kunit_kzalloc``,
you can register a custom "deferred action", which is a cleanup function
run when the test exits (whether cleanly, or via a failed assertion).
Actions are simple functions with no return value, and a single ``void*``
context argument, and fulfill the same role as "cleanup" functions in Python
and Go tests, "defer" statements in languages which support them, and
(in some cases) destructors in RAII languages.
These are very useful for unregistering things from global lists, closing
files or other resources, or freeing resources.
For example:
.. code-block:: C
static void cleanup_device(void *ctx)
{
struct device *dev = (struct device *)ctx;
device_unregister(dev);
}
void example_device_test(struct kunit *test)
{
struct my_device dev;
device_register(&dev);
kunit_add_action(test, &cleanup_device, &dev);
}
Note that, for functions like device_unregister which only accept a single
pointer-sized argument, it's possible to automatically generate a wrapper
with the ``KUNIT_DEFINE_ACTION_WRAPPER()`` macro, for example:
.. code-block:: C
KUNIT_DEFINE_ACTION_WRAPPER(device_unregister, device_unregister_wrapper, struct device *);
kunit_add_action(test, &device_unregister_wrapper, &dev);
You should do this in preference to manually casting to the ``kunit_action_t`` type,
as casting function pointers will break Control Flow Integrity (CFI).
``kunit_add_action`` can fail if, for example, the system is out of memory.
You can use ``kunit_add_action_or_reset`` instead which runs the action
immediately if it cannot be deferred.
If you need more control over when the cleanup function is called, you
can trigger it early using ``kunit_release_action``, or cancel it entirely
with ``kunit_remove_action``.
Testing Static Functions
------------------------
If you want to test static functions without exposing those functions outside of
testing, one option is conditionally export the symbol. When KUnit is enabled,
the symbol is exposed but remains static otherwise. To use this method, follow
the template below.
.. code-block:: c
/* In the file containing functions to test "my_file.c" */
#include <kunit/visibility.h>
#include <my_file.h>
...
VISIBLE_IF_KUNIT int do_interesting_thing()
{
...
}
EXPORT_SYMBOL_IF_KUNIT(do_interesting_thing);
/* In the header file "my_file.h" */
#if IS_ENABLED(CONFIG_KUNIT)
int do_interesting_thing(void);
#endif
/* In the KUnit test file "my_file_test.c" */
#include <kunit/visibility.h>
#include <my_file.h>
...
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
...
// Use do_interesting_thing() in tests
For a full example, see this `patch <https://lore.kernel.org/all/[email protected]/>`_
where a test is modified to conditionally expose static functions for testing
using the macros above.
As an **alternative** to the method above, you could conditionally ``#include``
the test file at the end of your .c file. This is not recommended but works
if needed. For example:
.. code-block:: c
/* In "my_file.c" */
static int do_interesting_thing();
#ifdef CONFIG_MY_KUNIT_TEST
#include "my_kunit_test.c"
#endif
Injecting Test-Only Code
------------------------
Similar to as shown above, we can add test-specific logic. For example:
.. code-block:: c
/* In my_file.h */
#ifdef CONFIG_MY_KUNIT_TEST
/* Defined in my_kunit_test.c */
void test_only_hook(void);
#else
void test_only_hook(void) { }
#endif
This test-only code can be made more useful by accessing the current ``kunit_test``
as shown in next section: *Accessing The Current Test*.
Accessing The Current Test
--------------------------
In some cases, we need to call test-only code from outside the test file. This
is helpful, for example, when providing a fake implementation of a function, or
to fail any current test from within an error handler.
We can do this via the ``kunit_test`` field in ``task_struct``, which we can
access using the ``kunit_get_current_test()`` function in ``kunit/test-bug.h``.
``kunit_get_current_test()`` is safe to call even if KUnit is not enabled. If
KUnit is not enabled, or if no test is running in the current task, it will
return ``NULL``. This compiles down to either a no-op or a static key check,
so will have a negligible performance impact when no test is running.
The example below uses this to implement a "mock" implementation of a function, ``foo``:
.. code-block:: c
#include <kunit/test-bug.h> /* for kunit_get_current_test */
struct test_data {
int foo_result;
int want_foo_called_with;
};
static int fake_foo(int arg)
{
struct kunit *test = kunit_get_current_test();
struct test_data *test_data = test->priv;
KUNIT_EXPECT_EQ(test, test_data->want_foo_called_with, arg);
return test_data->foo_result;
}
static void example_simple_test(struct kunit *test)
{
/* Assume priv (private, a member used to pass test data from
* the init function) is allocated in the suite's .init */
struct test_data *test_data = test->priv;
test_data->foo_result = 42;
test_data->want_foo_called_with = 1;
/* In a real test, we'd probably pass a pointer to fake_foo somewhere
* like an ops struct, etc. instead of calling it directly. */
KUNIT_EXPECT_EQ(test, fake_foo(1), 42);
}
In this example, we are using the ``priv`` member of ``struct kunit`` as a way
of passing data to the test from the init function. In general ``priv`` is
pointer that can be used for any user data. This is preferred over static
variables, as it avoids concurrency issues.
Had we wanted something more flexible, we could have used a named ``kunit_resource``.
Each test can have multiple resources which have string names providing the same
flexibility as a ``priv`` member, but also, for example, allowing helper
functions to create resources without conflicting with each other. It is also
possible to define a clean up function for each resource, making it easy to
avoid resource leaks. For more information, see Documentation/dev-tools/kunit/api/resource.rst.
Failing The Current Test
------------------------
If we want to fail the current test, we can use ``kunit_fail_current_test(fmt, args...)``
which is defined in ``<kunit/test-bug.h>`` and does not require pulling in ``<kunit/test.h>``.
For example, we have an option to enable some extra debug checks on some data
structures as shown below:
.. code-block:: c
#include <kunit/test-bug.h>
#ifdef CONFIG_EXTRA_DEBUG_CHECKS
static void validate_my_data(struct data *data)
{
if (is_valid(data))
return;
kunit_fail_current_test("data %p is invalid", data);
/* Normal, non-KUnit, error reporting code here. */
}
#else
static void my_debug_function(void) { }
#endif
``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
KUnit is not enabled, or if no test is running in the current task, it will do
nothing. This compiles down to either a no-op or a static key check, so will
have a negligible performance impact when no test is running.
Managing Fake Devices and Drivers
---------------------------------
When testing drivers or code which interacts with drivers, many functions will
require a ``struct device`` or ``struct device_driver``. In many cases, setting
up a real device is not required to test any given function, so a fake device
can be used instead.
KUnit provides helper functions to create and manage these fake devices, which
are internally of type ``struct kunit_device``, and are attached to a special
``kunit_bus``. These devices support managed device resources (devres), as
described in Documentation/driver-api/driver-model/devres.rst
To create a KUnit-managed ``struct device_driver``, use ``kunit_driver_create()``,
which will create a driver with the given name, on the ``kunit_bus``. This driver
will automatically be destroyed when the corresponding test finishes, but can also
be manually destroyed with ``driver_unregister()``.
To create a fake device, use the ``kunit_device_register()``, which will create
and register a device, using a new KUnit-managed driver created with ``kunit_driver_create()``.
To provide a specific, non-KUnit-managed driver, use ``kunit_device_register_with_driver()``
instead. Like with managed drivers, KUnit-managed fake devices are automatically
cleaned up when the test finishes, but can be manually cleaned up early with
``kunit_device_unregister()``.
The KUnit devices should be used in preference to ``root_device_register()``, and
instead of ``platform_device_register()`` in cases where the device is not otherwise
a platform device.
For example:
.. code-block:: c
#include <kunit/device.h>
static void test_my_device(struct kunit *test)
{
struct device *fake_device;
const char *dev_managed_string;
// Create a fake device.
fake_device = kunit_device_register(test, "my_device");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_device)
// Pass it to functions which need a device.
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");
// Everything is cleaned up automatically when the test ends.
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Test case, expectation과 assertion
1-129SPDX 라이선스 식별자: GPL-2.0
테스트 작성
Test case
KUnit의 기본 단위는 test case입니다. Test case는 `void (*)(struct kunit *test)` signature를 가진 function입니다. 검사할 function을 호출한 뒤 어떤 일이 일어나야 하는지 expectation을 설정합니다.
void example_test_success(struct kunit *test)
{
}
void example_test_failure(struct kunit *test)
{
KUNIT_FAIL(test, "This test never passes.");
}
위 예에서 `example_test_success`는 아무 일도 하지 않아 항상 통과합니다. 설정된 expectation이 없으므로 모든 expectation이 통과한 것으로 처리됩니다. 반면 `example_test_failure`는 message를 기록하고 test case를 실패시키는 특수 expectation인 `KUNIT_FAIL`을 호출하므로 항상 실패합니다.
Expectation
Expectation은 test에서 특정 code가 어떤 동작을 해야 한다고 기대하는 조건입니다. Function처럼 호출하며, 검사 대상 code의 behavior에 관한 expectation들을 설정해 test를 만듭니다. 하나 이상의 expectation이 실패하면 test case가 실패하고 failure 정보가 log에 기록됩니다.
void add_test_basic(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 1, add(1, 0));
KUNIT_EXPECT_EQ(test, 2, add(1, 1));
}
`add_test_basic`은 `add` function의 behavior를 여러 번 확인합니다. 첫 parameter는 항상 현재 test context 정보를 담은 `struct kunit *`입니다. 두 번째 parameter는 expected value이고 마지막 parameter는 actual value입니다.
`add`가 모든 expectation을 만족하면 `add_test_basic`이 통과하고 하나라도 만족하지 못하면 test case가 실패합니다.
Expectation 하나가 위반되면 test case는 실패 상태가 되지만 실행은 계속되어 test case가 끝나거나 다른 방식으로 종료될 때까지 나머지 expectation도 시도합니다. 뒤에서 설명할 assertion과 다른 점입니다.
더 많은 KUnit expectation은 `Documentation/dev-tools/kunit/api/test.rst`를 참조하십시오.
참고: 하나의 test case는 짧고 이해하기 쉬우며 하나의 behavior에 집중해야 합니다.
앞의 `add` function을 엄밀하게 검사하려면 기본 덧셈, 음수, 최대값과 overflow처럼 각 property를 별도 test case로 작성합니다.
void add_test_basic(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 1, add(1, 0));
KUNIT_EXPECT_EQ(test, 2, add(1, 1));
}
void add_test_negative(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 0, add(-1, 1));
}
void add_test_max(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
}
void add_test_overflow(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, INT_MIN, add(INT_MAX, 1));
}
Assertion
Assertion은 expectation과 비슷하지만 조건을 만족하지 않으면 test case를 즉시 종료합니다.
static void test_sort(struct kunit *test)
{
int *a, i, r = 1;
a = kunit_kmalloc_array(test, TEST_LEN, sizeof(*a), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, a);
for (i = 0; i < TEST_LEN; i++) {
r = (r * 725861) % 6599;
a[i] = r;
}
sort(a, TEST_LEN, sizeof(*a), cmpint, NULL);
for (i = 0; i < TEST_LEN-1; i++)
KUNIT_EXPECT_LE(test, a[i], a[i + 1]);
}
이 예에서는 `sort()`를 검사할 array가 반드시 필요합니다. 따라서 allocation error가 나면 `KUNIT_ASSERT_NOT_ERR_OR_NULL()`로 test를 중단합니다. Allocation에 성공한 뒤 정렬 결과의 인접 element가 순서대로인지 expectation으로 모두 확인합니다.
참고: 다른 test framework의 `ASSERT` macro는 흔히 `return`을 호출해 test function 안에서만 동작합니다. KUnit은 failure 시 현재 kthread를 중지하므로 어느 위치에서든 assertion을 호출할 수 있습니다.
경고: 예외가 있습니다. Suite의 `exit()` function이나 resource의 free function에서는 assertion을 사용하면 안 됩니다. 이 function들은 test shutdown 중 실행되므로 assertion이 이후 cleanup code 실행을 막아 memory leak을 일으킬 수 있습니다.
Failure 뒤 실행 흐름과 적합한 용도를 구분했습니다.
Error message와 test suite lifecycle
130-215Error message customizing
각 `KUNIT_EXPECT`와 `KUNIT_ASSERT` macro에는 `_MSG` variant가 있습니다. Format string과 argument를 받아 자동 생성되는 error message에 추가 context를 제공합니다.
char some_str[41];
generate_sha1_hex_string(some_str);
/* Before. Not easy to tell why the test failed. */
KUNIT_EXPECT_EQ(test, strlen(some_str), 40);
/* After. Now we see the offending string. */
KUNIT_EXPECT_EQ_MSG(test, strlen(some_str), 40, "some_str='%s'", some_str);
또는 `KUNIT_FAIL()`을 사용해 error message 전체를 직접 제어할 수 있습니다.
/* Before */
KUNIT_EXPECT_EQ(test, some_setup_function(), 0);
/* After: full control over the failure message. */
if (some_setup_function())
KUNIT_FAIL(test, "Failed to setup thing for testing");
Test suite
Unit의 모든 behavior를 다루려면 많은 test case가 필요하며 비슷한 테스트가 반복되기 쉽습니다. KUnit을 포함한 대부분의 unit testing framework는 중복을 줄이기 위해 test suite를 제공합니다.
Test suite는 하나의 code unit을 검사하는 test case 모음이며, 전체 suite 또는 각 test case 앞뒤에 실행되는 선택적 setup과 teardown function을 포함할 수 있습니다.
참고: Test case는 test suite와 연결되어 있어야만 실행됩니다.
static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_test_foo),
KUNIT_CASE(example_test_bar),
KUNIT_CASE(example_test_baz),
{}
};
static struct kunit_suite example_test_suite = {
.name = "example",
.init = example_test_init,
.exit = example_test_exit,
.suite_init = example_suite_init,
.suite_exit = example_suite_exit,
.test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);
이 예에서 `example_test_suite`는 먼저 `example_suite_init`을 실행하고 `example_test_foo`, `example_test_bar`, `example_test_baz`를 차례로 실행합니다. 각 case 직전에는 `example_test_init`, 직후에는 `example_test_exit`가 호출됩니다. 모든 작업 뒤에는 `example_suite_exit`가 호출됩니다.
`kunit_test_suite(example_test_suite)`는 suite를 KUnit test framework에 등록합니다.
참고: `init` 또는 `suite_init`이 실패해도 `exit`와 `suite_exit`는 실행됩니다. Init function이 error를 만나거나 일찍 종료해 일관되지 않은 state가 생긴 경우도 처리할 수 있게 작성해야 합니다.
`kunit_test_suite(...)` macro는 지정 suite를 special linker section에 넣도록 linker에 지시합니다. KUnit은 `late_init` 뒤 또는 test가 module로 build된 경우 module load 시 suite를 실행합니다.
자세한 내용은 `Documentation/dev-tools/kunit/api/test.rst`를 참조하십시오.
Suite 단위와 case 단위 callback의 호출 순서를 나타냅니다.
다른 architecture용 테스트
216-243원문의 `kunit-on-non-uml` anchor는 이 절을 가리킵니다.
다른 architecture용 테스트 작성
특정 architecture에서만 실행되는 테스트보다 UML에서 실행되는 테스트가 좋습니다. 특정 hardware에서만 실행되는 테스트보다 QEMU처럼 쉽게 무료로 구할 수 있는 software environment에서 실행되는 테스트가 좋습니다.
그럼에도 architecture 또는 hardware에 특화된 테스트가 필요한 타당한 이유가 있습니다. 예를 들어 실제로 `arch/some-arch/*`에 속하는 code를 검사해야 할 수 있습니다.
이 경우에도 physical hardware dependency를 최소화하십시오. 일부 test case는 hardware가 필요하지 않고 실제 hardware가 꼭 필요한 case는 소수일 수 있습니다. Hardware가 없을 때 test를 비활성화하지 말고 skip할 수 있습니다.
Hardware-specific 부분을 정확히 좁힌 뒤에는 일반 KUnit test와 같은 절차로 작성하고 실행합니다.
중요: Hardware state를 reset해야 할 수 있습니다. Reset이 불가능하면 invocation 한 번에 test case 하나만 실행할 수 있습니다.
TODO: Architecture-dependent KUnit test의 실제 예제를 추가해야 합니다.
Behavior 격리와 kernel class
244-312일반 pattern
Behavior 격리
Unit testing은 검사할 code 양을 하나의 unit으로 제한하고, 그 unit이 function을 호출할 때 어떤 code가 실행되는지 제어합니다. API 일부로 노출된 function은 나머지 code base에 영향을 주지 않고 definition을 바꿀 수 있어야 합니다.
Kernel에서는 구현자가 제공한 function pointer를 struct에 담는 class와 compile time에 definition을 선택하는 architecture-specific function이라는 두 construct가 이 역할을 합니다.
Class
Class는 C language에 내장된 construct는 아니지만 쉽게 유도할 수 있는 개념입니다. GNOME GObject 같은 표준 object-oriented library를 사용하지 않는 project마다 조금씩 다른 object-oriented programming 방식을 가지며 Linux kernel도 예외가 아닙니다.
Kernel object-oriented programming의 중심은 class입니다. Kernel에서 class는 function pointer를 포함한 struct입니다. Implementer와 user가 function을 직접 호출하지 않으면서 동일한 function signature를 사용하도록 강제해 양쪽 사이의 contract를 만듭니다.
Class의 function pointer는 class handle이라 부르는 class pointer를 parameter 중 하나로 받아야 합니다. 따라서 method는 field에 접근할 수 있고 같은 implementation이 여러 instance를 가질 수 있습니다.
Child class가 parent class를 자신의 struct 안에 embedding하면 parent class를 override할 수 있습니다. Child method가 호출될 때 전달된 pointer는 child 안의 parent를 가리킵니다. Parent가 child struct 안에서 항상 고정 offset에 있으므로 child implementation은 자신을 가리키는 pointer를 계산할 수 있습니다.
struct shape {
int (*area)(struct shape *this);
};
struct rectangle {
struct shape parent;
int length;
int width;
};
int rectangle_area(struct shape *this)
{
struct rectangle *self = container_of(this, struct rectangle, parent);
return self->length * self->width;
};
void rectangle_new(struct rectangle *self, int length, int width)
{
self->parent.area = rectangle_area;
self->length = length;
self->width = width;
}
이 예에서는 `container_of`가 parent `shape` pointer로부터 child `rectangle` pointer를 계산합니다.
Class fake와 EEPROM buffer 테스트
313-472Class faking
Class method를 호출하는 code를 unit test하려면 method behavior를 제어할 수 있어야 합니다. 제어할 수 없다면 unit test가 아니라 integration test가 됩니다.
Fake class는 production instance와 다른 code를 구현하지만 caller 관점에서는 동일하게 동작합니다. 다루기 어렵거나 느린 dependency를 대체할 때 사용합니다. 예를 들어 EEPROM content를 internal buffer에 저장하는 fake EEPROM을 만들 수 있습니다.
다음은 EEPROM을 나타내는 class입니다.
struct eeprom {
ssize_t (*read)(struct eeprom *this, size_t offset, char *buffer, size_t count);
ssize_t (*write)(struct eeprom *this, size_t offset, const char *buffer, size_t count);
};
검사할 대상은 EEPROM write를 buffering하는 code입니다.
struct eeprom_buffer {
ssize_t (*write)(struct eeprom_buffer *this, const char *buffer, size_t count);
int flush(struct eeprom_buffer *this);
size_t flush_count; /* Flushes when buffer exceeds flush_count. */
};
struct eeprom_buffer *new_eeprom_buffer(struct eeprom *eeprom);
void destroy_eeprom_buffer(struct eeprom *eeprom);
Underlying EEPROM은 다음 fake implementation으로 대체합니다.
struct fake_eeprom {
struct eeprom parent;
char contents[FAKE_EEPROM_CONTENTS_SIZE];
};
ssize_t fake_eeprom_read(struct eeprom *parent, size_t offset, char *buffer, size_t count)
{
struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent);
count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset);
memcpy(buffer, this->contents + offset, count);
return count;
}
ssize_t fake_eeprom_write(struct eeprom *parent, size_t offset, const char *buffer, size_t count)
{
struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent);
count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset);
memcpy(this->contents + offset, buffer, count);
return count;
}
void fake_eeprom_init(struct fake_eeprom *this)
{
this->parent.read = fake_eeprom_read;
this->parent.write = fake_eeprom_write;
memset(this->contents, 0, FAKE_EEPROM_CONTENTS_SIZE);
}
`fake_eeprom`은 실제 device 대신 memory의 `contents` array를 사용하고 parent class의 `read`와 `write` method를 fake function으로 연결합니다.
이제 이를 사용해 `struct eeprom_buffer`를 검사합니다.
struct eeprom_buffer_test {
struct fake_eeprom *fake_eeprom;
struct eeprom_buffer *eeprom_buffer;
};
static void eeprom_buffer_test_does_not_write_until_flush(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
char buffer[] = {0xff};
eeprom_buffer->flush_count = SIZE_MAX;
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0);
eeprom_buffer->flush(eeprom_buffer);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
}
static void eeprom_buffer_test_flushes_after_flush_count_met(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
char buffer[] = {0xff};
eeprom_buffer->flush_count = 2;
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
}
static void eeprom_buffer_test_flushes_increments_of_flush_count(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
char buffer[] = {0xff, 0xff};
eeprom_buffer->flush_count = 2;
eeprom_buffer->write(eeprom_buffer, buffer, 1);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);
eeprom_buffer->write(eeprom_buffer, buffer, 2);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
/* Should have only flushed the first two bytes. */
KUNIT_EXPECT_EQ(test, fake_eeprom->contents[2], 0);
}
static int eeprom_buffer_test_init(struct kunit *test)
{
struct eeprom_buffer_test *ctx;
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
ctx->fake_eeprom = kunit_kzalloc(test, sizeof(*ctx->fake_eeprom), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->fake_eeprom);
fake_eeprom_init(ctx->fake_eeprom);
ctx->eeprom_buffer = new_eeprom_buffer(&ctx->fake_eeprom->parent);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->eeprom_buffer);
test->priv = ctx;
return 0;
}
static void eeprom_buffer_test_exit(struct kunit *test)
{
struct eeprom_buffer_test *ctx = test->priv;
destroy_eeprom_buffer(ctx->eeprom_buffer);
}
첫 case는 `flush_count=SIZE_MAX`에서 두 번 write해도 fake storage가 변하지 않다가 명시적 flush 뒤 두 byte가 `0xff`가 되는지 확인합니다.
두 번째 case는 `flush_count=2`에 도달하면 두 byte가 자동 flush되는지 확인합니다. 세 번째 case는 세 byte가 buffered되어도 threshold의 배수인 앞 두 byte만 flush되는지 확인합니다.
Suite init은 `kunit_kzalloc`으로 context와 fake EEPROM을 할당하고 fake를 초기화한 뒤 `new_eeprom_buffer`에 parent interface를 주입해 `test->priv`에 저장합니다. Exit은 생성한 EEPROM buffer를 destroy합니다.
Production dependency를 memory-backed fake로 바꾸어 buffer behavior만 격리하는 구조입니다.
여러 input과 table-driven test
473-541여러 input에 대한 테스트
Hash function처럼 input 공간이 넓은 code는 몇 개 input만 검사해서 올바른 동작을 보장할 수 없습니다.
각 input마다 호출할 helper macro 또는 function을 작성할 수 있습니다. `sha1sum(1)`을 검사하는 예는 다음과 같습니다.
#define TEST_SHA1(in, want) \
sha1sum(in, out); \
KUNIT_EXPECT_STREQ_MSG(test, out, want, "sha1sum(%s)", in);
char out[40];
TEST_SHA1("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
TEST_SHA1("hello world!", "430ce34d020724ed75a196dfc2ad67c77772d169");
`KUNIT_EXPECT_STREQ`의 `_MSG` variant를 사용해 더 자세한 error를 출력하고 helper macro 안의 assertion 의미를 분명히 합니다.
Loop 또는 helper function에서 같은 expectation을 여러 번 호출하면 source line number만으로 어느 input이 실패했는지 알기 어렵습니다. 이런 경우 `_MSG` variant가 유용합니다.
복잡한 경우에는 helper macro보다 table-driven test를 권장합니다.
int i;
char out[40];
struct sha1_test_case {
const char *str;
const char *sha1;
};
struct sha1_test_case cases[] = {
{
.str = "hello world",
.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
},
{
.str = "hello world!",
.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
},
};
for (i = 0; i < ARRAY_SIZE(cases); ++i) {
sha1sum(cases[i].str, out);
KUNIT_EXPECT_STREQ_MSG(test, out, cases[i].sha1,
"sha1sum(%s)", cases[i].str);
}
Boilerplate는 늘지만 field name 덕분에 input과 output이 여러 개일 때 읽기 쉽습니다. 예는 `fs/ext4/inode-test.c`를 참조하십시오.
여러 test가 test case data를 공유하면 duplication을 줄일 수 있습니다. 예를 들어 `sha256sum`을 검사하려면 `sha256` field를 추가하고 같은 `cases`를 재사용할 수 있습니다.
또한 table을 parameterized test로 변환할 수 있습니다.
Parameterized test와 parameter 제공 방식
542-750Parameterized testing
KUnit은 하나의 test case를 여러 input에 실행하는 parameterized testing framework를 제공합니다. 앞서 설명한 table-driven test 개념을 정형화하고 확장합니다.
Test case를 등록할 때 parameter generator function을 제공하면 KUnit은 이를 parameterized test로 판단합니다. 사용자는 generator를 직접 작성하거나 KUnit 제공 generator를 사용할 수 있습니다. Generator는 `kunit_case->generate_params`에 저장되며 아래 macro로 설정합니다.
Parameterized test는 parameter 또는 parameter run마다 한 번씩 여러 번 실행됩니다. 각 parameter run은 독립적인 `struct kunit`, 즉 parameter run context를 가지며 동시에 공유 parent `struct kunit`, 즉 parameterized test context에 접근할 수 있습니다.
Test에 parameter 전달
Parameter를 제공하는 방법은 세 가지입니다.
Array parameter macro
KUnit은 일반적인 table-driven pattern을 특별 지원합니다. 앞 절의 `cases` array에 `KUNIT_ARRAY_PARAM` 또는 `KUNIT_ARRAY_PARAM_DESC`를 적용해 parameterized test를 만들 수 있습니다.
// This is copy-pasted from above.
struct sha1_test_case {
const char *str;
const char *sha1;
};
static const struct sha1_test_case cases[] = {
{
.str = "hello world",
.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
},
{
.str = "hello world!",
.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
},
};
// Creates `sha1_gen_params()` to iterate over `cases` while using
// the struct member `str` for the case description.
KUNIT_ARRAY_PARAM_DESC(sha1, cases, str);
// Looks no different from a normal test.
static void sha1_test(struct kunit *test)
{
// This function can just contain the body of the for-loop.
// The former `cases[i]` is accessible under test->param_value.
char out[40];
struct sha1_test_case *test_param = (struct sha1_test_case *)(test->param_value);
sha1sum(test_param->str, out);
KUNIT_EXPECT_STREQ_MSG(test, out, test_param->sha1,
"sha1sum(%s)", test_param->str);
}
// Instead of KUNIT_CASE, we use KUNIT_CASE_PARAM and pass in the
// function declared by KUNIT_ARRAY_PARAM or KUNIT_ARRAY_PARAM_DESC.
static struct kunit_case sha1_test_cases[] = {
KUNIT_CASE_PARAM(sha1_test, sha1_gen_params),
{}
};
`KUNIT_ARRAY_PARAM_DESC`는 array를 순회하는 `sha1_gen_params()`를 만들고 `str` member를 case description으로 사용합니다. Test body는 `test->param_value`에서 현재 element를 얻습니다. 등록에는 `KUNIT_CASE` 대신 `KUNIT_CASE_PARAM`과 생성된 generator를 사용합니다.
Custom parameter generator function
Generator는 parameter를 하나씩 생성하며 signature는 `const void* (*)(struct kunit *test, const void *prev, char *desc)`입니다. `KUNIT_CASE_PARAM` 또는 `KUNIT_CASE_PARAM_WITH_INIT`에 전달할 수 있습니다.
Function은 이전에 생성한 parameter를 `prev`로 받으며 첫 호출에서는 NULL입니다. `test` argument로 parameterized test context에도 접근할 수 있습니다. KUnit은 generator가 NULL을 반환해 parameterized test 종료를 알릴 때까지 반복 호출합니다.
#define MAX_TEST_BUFFER_SIZE 8
// Example generator function. It produces a sequence of buffer sizes that
// are powers of two, starting at 1 (e.g., 1, 2, 4, 8).
static const void *buffer_size_gen_params(struct kunit *test, const void *prev, char *desc)
{
long prev_buffer_size = (long)prev;
long next_buffer_size = 1; // Start with an initial size of 1.
// Stop generating parameters if the limit is reached or exceeded.
if (prev_buffer_size >= MAX_TEST_BUFFER_SIZE)
return NULL;
// For subsequent calls, calculate the next size by doubling the previous one.
if (prev)
next_buffer_size = prev_buffer_size << 1;
return (void *)next_buffer_size;
}
// Simple test to validate that kunit_kzalloc provides zeroed memory.
static void buffer_zero_test(struct kunit *test)
{
long buffer_size = (long)test->param_value;
// Use kunit_kzalloc to allocate a zero-initialized buffer. This makes the
// memory "parameter run managed," meaning it's automatically cleaned up at
// the end of each parameter run.
int *buf = kunit_kzalloc(test, buffer_size * sizeof(int), GFP_KERNEL);
// Ensure the allocation was successful.
KUNIT_ASSERT_NOT_NULL(test, buf);
// Loop through the buffer and confirm every element is zero.
for (int i = 0; i < buffer_size; i++)
KUNIT_EXPECT_EQ(test, buf[i], 0);
}
static struct kunit_case buffer_test_cases[] = {
KUNIT_CASE_PARAM(buffer_zero_test, buffer_size_gen_params),
{}
};
예제 generator는 1부터 시작해 2배씩 늘어난 1, 2, 4, 8 buffer size를 생성하고 limit에 도달하면 NULL을 반환합니다. 각 run은 해당 크기의 zero-initialized memory를 할당하고 모든 element가 0인지 확인합니다.
Init function에서 runtime parameter array 등록
Parameterized test 초기화가 필요한 경우 parameterized test context에 array를 직접 등록할 수 있습니다. `kunit_register_params_array()`에 context, array, size와 `get_description()` function을 전달합니다.
이 macro는 parameterized test context의 `struct kunit_params`를 채워 parameter array object를 저장합니다. Description function signature는 `void (*)(struct kunit *test, const void *param, char *desc)`이며 parameterized test context에 접근할 수 있습니다.
중요: 이 방식으로 array를 등록할 때는 `KUNIT_CASE_PARAM_WITH_INIT`의 generator로 `kunit_array_gen_params()`를 직접 전달해야 합니다. 이 KUnit helper가 등록된 array에서 parameter를 생성합니다.
필요하다면 helper 대신 parameter array를 사용하는 custom generator를 전달할 수 있습니다. Generator 안에서는 `test->params_array.params`로 array에 접근합니다.
`kunit_register_params_array()`는 parameterized test를 초기화하는 `param_init()` 안에서 호출해야 합니다. Signature는 `int (*)(struct kunit *test)`입니다. 다음 절의 shared resource mechanism과 연결되며 dynamic array와 static array 모두 등록할 수 있습니다.
다음 `example_param_init_dynamic_arr`는 `make_fibonacci_params()`로 dynamic array를 만들고 등록합니다. 전체 code는 `lib/kunit/kunit-example-test.c`를 참조하십시오.
/*
* Example of a parameterized test param_init() function that registers a dynamic
* array of parameters.
*/
static int example_param_init_dynamic_arr(struct kunit *test)
{
size_t seq_size;
int *fibonacci_params;
kunit_info(test, "initializing parameterized test\n");
seq_size = 6;
fibonacci_params = make_fibonacci_params(test, seq_size);
if (!fibonacci_params)
return -ENOMEM;
/*
* Passes the dynamic parameter array information to the parameterized test
* context struct kunit. The array and its metadata will be stored in
* test->parent->params_array. The array itself will be located in
* params_data.params.
*/
kunit_register_params_array(test, fibonacci_params, seq_size,
example_param_dynamic_arr_get_desc);
return 0;
}
static struct kunit_case example_test_cases[] = {
/*
* Note how we pass kunit_array_gen_params() to use the array we
* registered in example_param_init_dynamic_arr() to generate
* parameters.
*/
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr,
kunit_array_gen_params,
example_param_init_dynamic_arr,
example_param_exit_dynamic_arr),
{}
};
세 가지 generator 구성의 선택 기준을 정리했습니다.
Memory allocation과 cleanup action
925-1001Memory 할당
일반 code에서 `kzalloc`을 사용할 자리에 `kunit_kzalloc`을 사용하면 test가 끝날 때 KUnit이 memory를 해제합니다.
따라서 `KUNIT_ASSERT_EQ` 같은 macro로 test에서 일찍 나가더라도 `kfree` 호출을 잊을 걱정이 없습니다.
void example_test_allocation(struct kunit *test)
{
char *buffer = kunit_kzalloc(test, 16, GFP_KERNEL);
/* Ensure allocation succeeded. */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buffer);
KUNIT_ASSERT_STREQ(test, buffer, "");
}
Cleanup action 등록
`kunit_kzalloc` 이상의 cleanup이 필요하면 custom deferred action을 등록할 수 있습니다. Test가 정상 종료되든 assertion failure로 종료되든 실행되는 cleanup function입니다.
Action은 return value가 없고 하나의 `void *` context argument를 받는 단순 function입니다. Python과 Go test의 cleanup function, defer를 지원하는 language의 defer statement, 일부 RAII language의 destructor와 같은 역할을 합니다.
Global list에서 object를 unregister하거나 file과 다른 resource를 닫고 resource를 해제할 때 유용합니다.
static void cleanup_device(void *ctx)
{
struct device *dev = (struct device *)ctx;
device_unregister(dev);
}
void example_device_test(struct kunit *test)
{
struct my_device dev;
device_register(&dev);
kunit_add_action(test, &cleanup_device, &dev);
}
`device_unregister`처럼 pointer 크기 argument 하나만 받는 function은 `KUNIT_DEFINE_ACTION_WRAPPER()` macro로 wrapper를 자동 생성할 수 있습니다.
KUNIT_DEFINE_ACTION_WRAPPER(device_unregister, device_unregister_wrapper, struct device *);
kunit_add_action(test, &device_unregister_wrapper, &dev);
Function pointer cast는 Control Flow Integrity, 즉 CFI를 깨뜨리므로 `kunit_action_t`로 수동 casting하지 말고 wrapper macro를 사용해야 합니다.
System memory 부족 같은 이유로 `kunit_add_action`이 실패할 수 있습니다. 대신 `kunit_add_action_or_reset`을 사용하면 defer할 수 없을 때 action을 즉시 실행합니다.
Cleanup 호출 시점을 더 세밀하게 제어하려면 `kunit_release_action`으로 일찍 실행하거나 `kunit_remove_action`으로 완전히 취소할 수 있습니다.
Resource 종류와 필요한 lifetime 제어에 맞는 API를 정리했습니다.
Static function 테스트
1002-1055Static function 테스트
Static function을 test 외부에 항상 노출하지 않고 검사하려면 symbol을 조건부 export할 수 있습니다. KUnit이 활성화된 경우 symbol을 노출하고 그렇지 않을 때는 static으로 유지합니다.
/* In the file containing functions to test "my_file.c" */
#include <kunit/visibility.h>
#include <my_file.h>
...
VISIBLE_IF_KUNIT int do_interesting_thing()
{
...
}
EXPORT_SYMBOL_IF_KUNIT(do_interesting_thing);
/* In the header file "my_file.h" */
#if IS_ENABLED(CONFIG_KUNIT)
int do_interesting_thing(void);
#endif
/* In the KUnit test file "my_file_test.c" */
#include <kunit/visibility.h>
#include <my_file.h>
...
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
...
// Use do_interesting_thing() in tests
Production source는 `VISIBLE_IF_KUNIT`과 `EXPORT_SYMBOL_IF_KUNIT`으로 function을 조건부 노출합니다. Header는 `IS_ENABLED(CONFIG_KUNIT)`일 때 declaration을 제공하고 test module은 `MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING")`로 namespace를 import합니다.
전체 예는 `https://lore.kernel.org/all/[email protected]/` patch를 참조하십시오.
대안으로 `.c` file 끝에서 test file을 조건부 `#include`할 수 있습니다. 권장하지 않지만 필요한 경우 동작합니다.
/* In "my_file.c" */
static int do_interesting_thing();
#ifdef CONFIG_MY_KUNIT_TEST
#include "my_kunit_test.c"
#endif
Test-only code 주입
1056-1074Test-only code 주입
앞의 조건부 노출 방식과 비슷하게 test-specific logic을 추가할 수 있습니다.
/* In my_file.h */
#ifdef CONFIG_MY_KUNIT_TEST
/* Defined in my_kunit_test.c */
void test_only_hook(void);
#else
void test_only_hook(void) { }
#endif
`CONFIG_MY_KUNIT_TEST`가 활성화되면 test file이 정의한 `test_only_hook()`을 사용하고 그렇지 않으면 빈 implementation을 사용합니다.
다음 절의 current `kunit_test` 접근 방법을 결합하면 test-only code를 더 유용하게 만들 수 있습니다.
현재 test context 접근
1075-1134현재 test 접근
Fake function implementation을 제공하거나 error handler 안에서 현재 test를 실패시키는 경우처럼 test file 밖에서 test-only code를 호출해야 할 수 있습니다.
`task_struct`의 `kunit_test` field를 `kunit/test-bug.h`의 `kunit_get_current_test()` function으로 접근할 수 있습니다.
KUnit이 활성화되지 않았거나 현재 task에서 test가 실행 중이 아니면 `kunit_get_current_test()`는 NULL을 반환하므로 언제나 안전하게 호출할 수 있습니다. No-op 또는 static key check로 compile되어 test가 없을 때 performance impact는 무시할 정도입니다.
다음은 이를 사용한 `foo`의 mock implementation입니다.
#include <kunit/test-bug.h> /* for kunit_get_current_test */
struct test_data {
int foo_result;
int want_foo_called_with;
};
static int fake_foo(int arg)
{
struct kunit *test = kunit_get_current_test();
struct test_data *test_data = test->priv;
KUNIT_EXPECT_EQ(test, test_data->want_foo_called_with, arg);
return test_data->foo_result;
}
static void example_simple_test(struct kunit *test)
{
/* Assume priv (private, a member used to pass test data from
* the init function) is allocated in the suite's .init */
struct test_data *test_data = test->priv;
test_data->foo_result = 42;
test_data->want_foo_called_with = 1;
/* In a real test, we'd probably pass a pointer to fake_foo somewhere
* like an ops struct, etc. instead of calling it directly. */
KUNIT_EXPECT_EQ(test, fake_foo(1), 42);
}
예제는 init function에서 test로 data를 전달하기 위해 `struct kunit`의 `priv` member를 사용합니다. `priv`는 임의 user data를 담을 수 있는 pointer입니다. Concurrency 문제를 피하므로 static variable보다 권장됩니다.
더 유연한 방법으로 이름 있는 `kunit_resource`를 사용할 수 있습니다. 각 test는 string name을 가진 resource를 여러 개 둘 수 있어 helper function끼리 충돌하지 않고 resource를 만들 수 있습니다. Resource마다 cleanup function을 정의해 leak도 쉽게 피할 수 있습니다.
자세한 내용은 `Documentation/dev-tools/kunit/api/resource.rst`를 참조하십시오.
현재 test 실패 처리
1135-1165현재 test 실패
현재 test를 실패시키려면 `<kunit/test-bug.h>`의 `kunit_fail_current_test(fmt, args...)`를 사용합니다. `<kunit/test.h>`를 include할 필요가 없습니다.
다음 예는 data structure에 추가 debug check를 활성화하는 option에서 잘못된 data를 발견하면 현재 KUnit test를 실패시킵니다.
#include <kunit/test-bug.h>
#ifdef CONFIG_EXTRA_DEBUG_CHECKS
static void validate_my_data(struct data *data)
{
if (is_valid(data))
return;
kunit_fail_current_test("data %p is invalid", data);
/* Normal, non-KUnit, error reporting code here. */
}
#else
static void my_debug_function(void) { }
#endif
KUnit이 활성화되지 않았거나 현재 task에서 실행 중인 test가 없으면 `kunit_fail_current_test()`는 아무 일도 하지 않으므로 안전하게 호출할 수 있습니다. No-op 또는 static key check로 compile되어 일반 실행의 performance impact는 무시할 정도입니다.
Fake device와 driver 관리
1166-1214Fake device와 driver 관리
Driver 또는 driver와 상호작용하는 code를 검사할 때 많은 function이 `struct device`나 `struct device_driver`를 요구합니다. 개별 function을 검사하는 데 real device setup이 필요하지 않은 경우 fake device를 사용할 수 있습니다.
KUnit은 내부 type이 `struct kunit_device`이고 special `kunit_bus`에 연결되는 fake device 생성 및 관리 helper를 제공합니다. 이 device들은 `Documentation/driver-api/driver-model/devres.rst`의 managed device resource, 즉 devres를 지원합니다.
KUnit-managed `struct device_driver`는 `kunit_driver_create()`로 만듭니다. 주어진 이름으로 `kunit_bus`에 driver를 만들며 test 종료 시 자동 destroy됩니다. 필요하면 `driver_unregister()`로 직접 제거할 수도 있습니다.
`kunit_device_register()`는 `kunit_driver_create()`가 만든 새 KUnit-managed driver를 사용해 fake device를 생성하고 등록합니다. 특정 non-KUnit-managed driver를 제공하려면 `kunit_device_register_with_driver()`를 사용합니다.
Managed fake device는 test 종료 시 자동 cleanup되며 `kunit_device_unregister()`로 일찍 제거할 수도 있습니다.
Device가 실제 platform device가 아니라면 `root_device_register()`보다 KUnit device를 사용하고 `platform_device_register()` 대신에도 KUnit device를 사용해야 합니다.
#include <kunit/device.h>
static void test_my_device(struct kunit *test)
{
struct device *fake_device;
const char *dev_managed_string;
// Create a fake device.
fake_device = kunit_device_register(test, "my_device");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_device)
// Pass it to functions which need a device.
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");
// Everything is cleaned up automatically when the test ends.
}
예제는 `kunit_device_register()`로 fake device를 만들고 device가 필요한 function에 전달합니다. `devm_kstrdup`로 만든 managed string을 포함한 모든 resource는 test 종료 시 자동 cleanup됩니다.
요약과 해설
usage.rst:1-1214KUnit test는 짧은 case에 expectation과 assertion을 배치하고 suite lifecycle로 setup과 teardown을 관리합니다. Unit boundary를 유지하려면 function pointer 기반 class와 fake implementation으로 느리거나 hardware-dependent한 dependency를 대체합니다.
여러 input은 table-driven 또는 parameterized test로 다루며 parent context에 shared resource를 둡니다. KUnit-managed allocation과 deferred action은 조기 assertion failure에도 cleanup을 보장하고, visibility macro, current-test API와 fake device helper는 kernel 내부 code를 production 동작에 미치는 영향 없이 검사하도록 돕습니다.