lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CABVgOSnueVgb1zmJwTThduizPLvtqXGdgyNX7FCEe6LetxqANA@mail.gmail.com>
Date: Wed, 13 Aug 2025 17:18:05 +0800
From: David Gow <davidgow@...gle.com>
To: Marie Zhussupova <marievic@...gle.com>
Cc: rmoar@...gle.com, shuah@...nel.org, brendan.higgins@...ux.dev, 
	mark.rutland@....com, elver@...gle.com, dvyukov@...gle.com, 
	lucas.demarchi@...el.com, thomas.hellstrom@...ux.intel.com, 
	rodrigo.vivi@...el.com, linux-kselftest@...r.kernel.org, 
	kunit-dev@...glegroups.com, kasan-dev@...glegroups.com, 
	intel-xe@...ts.freedesktop.org, dri-devel@...ts.freedesktop.org, 
	linux-kernel@...r.kernel.org
Subject: Re: [PATCH v2 7/7] Documentation: kunit: Document new parameterized
 test features

On Tue, 12 Aug 2025 at 06:18, Marie Zhussupova <marievic@...gle.com> wrote:
>
> This patch updates the KUnit docs to show how to use the new
> parameterized test context to share resources
> between parameter runs. It documents and show examples
> of different ways the test user can pass parameter
> arrays to a parameterized test. Finally, it specifies the
> parameterized testing terminology.
>
> Signed-off-by: Marie Zhussupova <marievic@...gle.com>
> ---

Thanks very much: I think this is a definite improvement.

I've added some notes below, but I won't be offended if you don't like
my suggestions.

Reviewed-by: David Gow <davidgow@...gle.com>

Cheers,
-- David

>
> Changes in v2:
>
> - The documentation was updated to establish the parameterized
>   testing terminology and reflect all the patch series changes.
> - The references to other parts of the KUnit Documentation were
>   not changed from being "Documentation/dev-tools/kunit/api/test.rst"
>   to ":ref:`kunit-resource`" links as originally planned. This is
>   because the existing way shows up as a link to a webpage and it
>   would be hard for people reading the documentation as an .rst
>   file to find the referred section without having the file path.
> - The code examples were made more concise.
> - Minor edits to titles and formatting.
>
> ---
>  Documentation/dev-tools/kunit/usage.rst | 342 +++++++++++++++++++++++-
>  1 file changed, 336 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst
> index 066ecda1dd98..b236bb07aaca 100644
> --- a/Documentation/dev-tools/kunit/usage.rst
> +++ b/Documentation/dev-tools/kunit/usage.rst
> @@ -542,11 +542,29 @@ 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.
> -
> -By reusing the same ``cases`` array from above, we can write the test as a
> -"parameterized test" with the following.
> +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.

I'd possibly split the last sentence out into its own paragraph (and
maybe expand it a little to mention that this can be either
user-written, or a standard KUnit one can be used instead). And maybe
mention that it lives in kunit_case->generate_params, and can be set
by one of the macros mentioned below.


> +
> +To establish the terminology, "parameterized test" refers to the group of all
> +runs of a single test function with different parameters. "Parameter run" refers
> +to the execution of the test case function with a single parameter.
> +"Parameterized test context" is the ``struct kunit`` that holds the
> +context for the entire parameterized test. Finally, "parameter run context" is
> +the ``struct kunit`` that holds the context of the individual parameter run.

I think this bit is a little clunky: we could probably simplify it by
just saying something like "a 'parameterized test' is a test which is
run multple 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")."

But it's not wrong as-is, so if you'd rather not change it, that's fine by me.

> +
> +Passing Parameters to a Test
> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Annoyingly, this level of heading is pretty indistinguishable from
body text in the current kernel theme. Still, it's definitely
registered as a heading (and has the anchor link, et al), so it's
fine. Maybe something to think about for future theme changes, though.

> +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
>
> @@ -555,7 +573,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",
> @@ -590,6 +608,318 @@ By reusing the same ``cases`` array from above, we can write the test as a
>                 {}
>         };
>
> +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 more complex scenarios, you can directly register a parameter array
> +   to the parameterized test context.

Do we need to describe what these "more complex scenarios" are.
Additionally, the generator function can also be used for some complex
scenarios.

> +
> +   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 starting at line 396.

You should avoid putting a line number here, as it will quickly get
out of date. Maybe just mention
"A more complete example lives in lib/kunit/kunit-example-test.c",
possibly with the test name instead
("example_params_test_with_init_dynamic_arr").


> +
> +.. 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 starting at line 280.
> +
> +.. 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
>  -----------------
>
> --
> 2.51.0.rc0.205.g4a044479a3-goog
>

Download attachment "smime.p7s" of type "application/pkcs7-signature" (5281 bytes)

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ