diff options
Diffstat (limited to 'Documentation/dev-tools/kunit/usage.rst')
| -rw-r--r-- | Documentation/dev-tools/kunit/usage.rst | 522 |
1 files changed, 503 insertions, 19 deletions
diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst index 9faf2b4153fc..ebd06f5ea455 100644 --- a/Documentation/dev-tools/kunit/usage.rst +++ b/Documentation/dev-tools/kunit/usage.rst @@ -121,6 +121,12 @@ there's an allocation error. ``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 -------------------------- @@ -160,7 +166,12 @@ 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. For example: +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 @@ -190,7 +201,10 @@ after everything else. ``kunit_test_suite(example_test_suite)`` registers the test suite with the KUnit test framework. .. note:: - A test case will only run if it is associated with a test suite. + 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 @@ -528,11 +542,31 @@ There is more boilerplate code involved, but it can: Parameterized Testing ~~~~~~~~~~~~~~~~~~~~~ -The table-driven testing pattern is common enough that KUnit has special -support for it. +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"). -By reusing the same ``cases`` array from above, we can write the test as a -"parameterized test" with the following. +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 @@ -541,7 +575,7 @@ By reusing the same ``cases`` array from above, we can write the test as a const char *str; const char *sha1; }; - const struct sha1_test_case cases[] = { + static const struct sha1_test_case cases[] = { { .str = "hello world", .sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", @@ -552,13 +586,9 @@ By reusing the same ``cases`` array from above, we can write the test as a }, }; - // Need a helper function to generate a name for each test case. - static void case_to_desc(const struct sha1_test_case *t, char *desc) - { - strcpy(desc, t->str); - } - // Creates `sha1_gen_params()` to iterate over `cases`. - KUNIT_ARRAY_PARAM(sha1, cases, case_to_desc); + // 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) @@ -574,12 +604,324 @@ By reusing the same ``cases`` array from above, we can write the test as a } // Instead of KUNIT_CASE, we use KUNIT_CASE_PARAM and pass in the - // function declared by KUNIT_ARRAY_PARAM. + // 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 ----------------- @@ -601,17 +943,109 @@ For example: 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 we do not want to expose functions or variables for testing, one option is to -conditionally ``#include`` the test file at the end of your .c file. For -example: +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/20221207014024.340230-3-rmoar@google.com/>`_ +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 */ + /* In "my_file.c" */ static int do_interesting_thing(); @@ -728,3 +1162,53 @@ structures as shown below: 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. + }
\ No newline at end of file |
