Hello!
KUnit offers a parameterized testing framework, where tests can be run multiple times with different inputs.
Currently, the same `struct kunit` is used for each parameter execution. After each run, the test instance gets cleaned up. This creates the following limitations:
a. There is no way to store resources that are accessible across the individual parameter test executions. b. It's not possible to pass additional context besides the previous parameter to `generate_params()` to get the next parameter. c. Test users are restricted to using pre-defined static arrays of parameter objects or `generate_params()` to define their parameters. There is no flexibility to pass a custom dynamic array without using `generate_params()`, which can be complex if generating the next parameter depends on more than just the single previous parameter (e.g., two or more previous parameters).
This patch series resolves these limitations by:
1. [P 1] Giving each parameterized test execution its own `struct kunit`. This aligns more with the definition of a `struct kunit` as a running instance of a test. It will also remove the need to manage state, such as resetting the `test->priv` field or the `test->status_comment` after every parameter run.
2. [P 1] Introducing a parent pointer of type `struct kunit`. Behind the scenes, a parent instance for the parameterized tests will be created. It won't be used to execute any test logic, but will instead be used as a context for shared resources. Each individual running instance of a test will now have a reference to that parent instance and thus, have access to those resources.
3. [P 2] Introducing `param_init()` and `param_exit()` functions that can set up and clean up the parent instance of the parameterized tests. They will run once before and after the parameterized series and provide a way for the user to access the parent instance to add the parameter array or any other resources to it, including custom ones to the `test->parent->priv` field or to `test->parent->resources` via the Resource API (link below).
https://elixir.bootlin.com/linux/v6.16-rc7/source/include/kunit/resource.h
4. [P 3, 4 & 5] Passing the parent `struct kunit` as an additional parameter to `generate_params()`. This provides `generate_params()` with more available context, making parameter generation much more flexible. The `generate_params()` implementations in the KCSAN and drm/xe tests have been adapted to match the new function pointer signature.
5. [P 6] Introducing a `params_data` field in `struct kunit`. This will allow the parent instance of a test to have direct storage of the parameter array, enabling features like using dynamic parameter arrays or using context beyond just the previous parameter.
Thank you! -Marie
Marie Zhussupova (9): kunit: Add parent kunit for parameterized test context kunit: Introduce param_init/exit for parameterized test shared context management kunit: Pass additional context to generate_params for parameterized testing kcsan: test: Update parameter generator to new signature drm/xe: Update parameter generator to new signature kunit: Enable direct registration of parameter arrays to a KUnit test kunit: Add example parameterized test with shared resources and direct static parameter array setup kunit: Add example parameterized test with direct dynamic parameter array setup Documentation: kunit: Document new parameterized test features
Documentation/dev-tools/kunit/usage.rst | 455 +++++++++++++++++++++++- drivers/gpu/drm/xe/tests/xe_pci.c | 2 +- include/kunit/test.h | 98 ++++- kernel/kcsan/kcsan_test.c | 2 +- lib/kunit/kunit-example-test.c | 207 +++++++++++ lib/kunit/test.c | 82 ++++- 6 files changed, 818 insertions(+), 28 deletions(-)
Currently, KUnit parameterized tests lack a mechanism to share resources across individual test invocations because the same `struct kunit` instance is reused for each test.
This patch refactors kunit_run_tests() to provide each parameterized test with its own `struct kunit` instance. A new parent pointer is added to `struct kunit`, allowing individual parameterized tests to reference a shared parent kunit instance. Resources added to this parent will then be accessible to all individual parameter test executions.
Signed-off-by: Marie Zhussupova marievic@google.com --- include/kunit/test.h | 12 ++++++++++-- lib/kunit/test.c | 32 +++++++++++++++++++------------- 2 files changed, 29 insertions(+), 15 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 39c768f87dc9..a42d0c8cb985 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -268,14 +268,22 @@ struct kunit_suite_set { * * @priv: for user to store arbitrary data. Commonly used to pass data * created in the init function (see &struct kunit_suite). + * @parent: for user to store data that they want to shared across + * parameterized tests. * * Used to store information about the current context under which the test * is running. Most of this data is private and should only be accessed - * indirectly via public functions; the one exception is @priv which can be - * used by the test writer to store arbitrary data. + * indirectly via public functions; the two exceptions are @priv and @parent + * which can be used by the test writer to store arbitrary data or data that is + * available to all parameter test executions, respectively. */ struct kunit { void *priv; + /* + * Reference to the parent struct kunit for storing shared resources + * during parameterized testing. + */ + struct kunit *parent;
/* private: internal use only. */ const char *name; /* Read only after initialization! */ diff --git a/lib/kunit/test.c b/lib/kunit/test.c index f3c6b11f12b8..4d6a39eb2c80 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -647,6 +647,7 @@ int kunit_run_tests(struct kunit_suite *suite) struct kunit_case *test_case; struct kunit_result_stats suite_stats = { 0 }; struct kunit_result_stats total_stats = { 0 }; + const void *curr_param;
/* Taint the kernel so we know we've run tests. */ add_taint(TAINT_TEST, LOCKDEP_STILL_OK); @@ -679,36 +680,39 @@ int kunit_run_tests(struct kunit_suite *suite) } else { /* Get initial param. */ param_desc[0] = '\0'; - test.param_value = test_case->generate_params(NULL, param_desc); + /* TODO: Make generate_params try-catch */ + curr_param = test_case->generate_params(NULL, param_desc); test_case->status = KUNIT_SKIPPED; kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT "KTAP version 1\n"); kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT "# Subtest: %s", test_case->name);
- while (test.param_value) { - kunit_run_case_catch_errors(suite, test_case, &test); + while (curr_param) { + struct kunit param_test = { + .param_value = curr_param, + .param_index = ++test.param_index, + .parent = &test, + }; + kunit_init_test(¶m_test, test_case->name, test_case->log); + kunit_run_case_catch_errors(suite, test_case, ¶m_test);
if (param_desc[0] == '\0') { snprintf(param_desc, sizeof(param_desc), "param-%d", test.param_index); }
- kunit_print_ok_not_ok(&test, KUNIT_LEVEL_CASE_PARAM, - test.status, - test.param_index + 1, + kunit_print_ok_not_ok(¶m_test, KUNIT_LEVEL_CASE_PARAM, + param_test.status, + param_test.param_index, param_desc, - test.status_comment); + param_test.status_comment);
- kunit_update_stats(¶m_stats, test.status); + kunit_update_stats(¶m_stats, param_test.status);
/* Get next param. */ param_desc[0] = '\0'; - test.param_value = test_case->generate_params(test.param_value, param_desc); - test.param_index++; - test.status = KUNIT_SUCCESS; - test.status_comment[0] = '\0'; - test.priv = NULL; + curr_param = test_case->generate_params(curr_param, param_desc); } }
@@ -723,6 +727,8 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_update_stats(&suite_stats, test_case->status); kunit_accumulate_stats(&total_stats, param_stats); + /* TODO: Put this kunit_cleanup into a try-catch. */ + kunit_cleanup(&test); }
if (suite->suite_exit)
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
Currently, KUnit parameterized tests lack a mechanism to share resources across individual test invocations because the same `struct kunit` instance is reused for each test.
This patch refactors kunit_run_tests() to provide each parameterized test with its own `struct kunit` instance. A new parent pointer is added to `struct kunit`, allowing individual parameterized tests to reference a shared parent kunit instance. Resources added to this parent will then be accessible to all individual parameter test executions.
Signed-off-by: Marie Zhussupova marievic@google.com
I'm definitely a fan of this, though it's not without its downsides.
For most tests, I don't think this will be a noticeable difference, since the "shared" struct kunit is reset after each parameter anyway, so this is a pretty safe change.
Having a 'parent' struct kunit, and hence potentially a hierarchy, does give us a good way of implementing more complicated parameterised tests which might need some more persistent state. (Ultimately, we could have another level for suites, and then allow suite_init/suite_exit to setup something persistent, too.)
Anyway, this looks good to me. I've left some small notes below, but nothing I think is actionable.
Reviewed-by: David Gow davidgow@google.com
Cheers, -- David
include/kunit/test.h | 12 ++++++++++-- lib/kunit/test.c | 32 +++++++++++++++++++------------- 2 files changed, 29 insertions(+), 15 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 39c768f87dc9..a42d0c8cb985 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -268,14 +268,22 @@ struct kunit_suite_set {
- @priv: for user to store arbitrary data. Commonly used to pass data
created in the init function (see &struct kunit_suite).
- @parent: for user to store data that they want to shared across
parameterized tests.
A part of me would prefer this not to explicitly call out parameterized tests here, as the obvious extensions to this (having suites have a struct kunit, or other forms of test hierarchy) would still make use of this in non-parameterised use-cases.
But since parameterised tests are the only current use-case for it, I can live with it if you'd prefer.
- Used to store information about the current context under which the test
- is running. Most of this data is private and should only be accessed
- indirectly via public functions; the one exception is @priv which can be
- used by the test writer to store arbitrary data.
- indirectly via public functions; the two exceptions are @priv and @parent
- which can be used by the test writer to store arbitrary data or data that is
*/
- available to all parameter test executions, respectively.
struct kunit { void *priv;
/*
* Reference to the parent struct kunit for storing shared resources
* during parameterized testing.
*/
struct kunit *parent; /* private: internal use only. */ const char *name; /* Read only after initialization! */
diff --git a/lib/kunit/test.c b/lib/kunit/test.c index f3c6b11f12b8..4d6a39eb2c80 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -647,6 +647,7 @@ int kunit_run_tests(struct kunit_suite *suite) struct kunit_case *test_case; struct kunit_result_stats suite_stats = { 0 }; struct kunit_result_stats total_stats = { 0 };
const void *curr_param; /* Taint the kernel so we know we've run tests. */ add_taint(TAINT_TEST, LOCKDEP_STILL_OK);
@@ -679,36 +680,39 @@ int kunit_run_tests(struct kunit_suite *suite) } else { /* Get initial param. */ param_desc[0] = '\0';
test.param_value = test_case->generate_params(NULL, param_desc);
/* TODO: Make generate_params try-catch */
Thanks for adding the TODO here: this isn't a regression, but it's good to note that we should get around to fixing it.
curr_param = test_case->generate_params(NULL, param_desc); test_case->status = KUNIT_SKIPPED; kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT "KTAP version 1\n"); kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT "# Subtest: %s", test_case->name);
while (test.param_value) {
kunit_run_case_catch_errors(suite, test_case, &test);
while (curr_param) {
struct kunit param_test = {
.param_value = curr_param,
.param_index = ++test.param_index,
.parent = &test,
};
kunit_init_test(¶m_test, test_case->name, test_case->log);
kunit_run_case_catch_errors(suite, test_case, ¶m_test); if (param_desc[0] == '\0') { snprintf(param_desc, sizeof(param_desc), "param-%d", test.param_index); }
kunit_print_ok_not_ok(&test, KUNIT_LEVEL_CASE_PARAM,
test.status,
test.param_index + 1,
kunit_print_ok_not_ok(¶m_test, KUNIT_LEVEL_CASE_PARAM,
param_test.status,
param_test.param_index, param_desc,
test.status_comment);
param_test.status_comment);
kunit_update_stats(¶m_stats, test.status);
kunit_update_stats(¶m_stats, param_test.status); /* Get next param. */ param_desc[0] = '\0';
test.param_value = test_case->generate_params(test.param_value, param_desc);
test.param_index++;
test.status = KUNIT_SUCCESS;
test.status_comment[0] = '\0';
test.priv = NULL;
curr_param = test_case->generate_params(curr_param, param_desc); } }
@@ -723,6 +727,8 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_update_stats(&suite_stats, test_case->status); kunit_accumulate_stats(&total_stats, param_stats);
/* TODO: Put this kunit_cleanup into a try-catch. */
kunit_cleanup(&test);
Hmm... it is a shame we can't easily include this in the existing try-catch mechanism. Definitely worth fixing in a follow-up.
} if (suite->suite_exit)
-- 2.50.1.552.g942d659e1b-goog
Add `param_init` and `param_exit` function pointers to `struct kunit_case`. Users will be able to set them via the new `KUNIT_CASE_PARAM_WITH_INIT` macro.
These functions are invoked by kunit_run_tests() once before and once after the entire parameterized test series, respectively. They will receive the parent kunit test instance, allowing users to register and manage shared resources. Resources added to this parent kunit test will be accessible to all individual parameterized tests, facilitating init and exit for shared state.
Signed-off-by: Marie Zhussupova marievic@google.com --- include/kunit/test.h | 33 ++++++++++++++++++++++++++++++++- lib/kunit/test.c | 23 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index a42d0c8cb985..d8dac7efd745 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -92,6 +92,8 @@ struct kunit_attributes { * @name: the name of the test case. * @generate_params: the generator function for parameterized tests. * @attr: the attributes associated with the test + * @param_init: The init function to run before parameterized tests. + * @param_exit: The exit function to run after parameterized tests. * * A test case is a function with the signature, * ``void (*)(struct kunit *)`` @@ -129,6 +131,13 @@ struct kunit_case { const void* (*generate_params)(const void *prev, char *desc); struct kunit_attributes attr;
+ /* + * Optional user-defined functions: one to register shared resources once + * before the parameterized test series, and another to release them after. + */ + int (*param_init)(struct kunit *test); + void (*param_exit)(struct kunit *test); + /* private: internal use only. */ enum kunit_status status; char *module_name; @@ -218,6 +227,27 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) .generate_params = gen_params, \ .attr = attributes, .module_name = KBUILD_MODNAME}
+/** + * KUNIT_CASE_PARAM_WITH_INIT() - Define a parameterized KUnit test case with custom + * init and exit functions. + * @test_name: The function implementing the test case. + * @gen_params: The function to generate parameters for the test case. + * @init: The init function to run before parameterized tests. + * @exit: The exit function to run after parameterized tests. + * + * Provides the option to register init and exit functions that take in the + * parent of the parameterized tests and run once before and once after the + * parameterized test series. The init function can be used to add any resources + * to share between the parameterized tests or to pass parameter arrays. The + * exit function can be used to clean up any resources that are not managed by + * the test. + */ +#define KUNIT_CASE_PARAM_WITH_INIT(test_name, gen_params, init, exit) \ + { .run_case = test_name, .name = #test_name, \ + .generate_params = gen_params, \ + .param_init = init, .param_exit = exit, \ + .module_name = KBUILD_MODNAME} + /** * struct kunit_suite - describes a related collection of &struct kunit_case * @@ -269,7 +299,8 @@ struct kunit_suite_set { * @priv: for user to store arbitrary data. Commonly used to pass data * created in the init function (see &struct kunit_suite). * @parent: for user to store data that they want to shared across - * parameterized tests. + * parameterized tests. Typically, the data is provided in + * the param_init function (see &struct kunit_case). * * Used to store information about the current context under which the test * is running. Most of this data is private and should only be accessed diff --git a/lib/kunit/test.c b/lib/kunit/test.c index 4d6a39eb2c80..d80b5990d85d 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -641,6 +641,19 @@ static void kunit_accumulate_stats(struct kunit_result_stats *total, total->total += add.total; }
+static void __kunit_init_parent_test(struct kunit_case *test_case, struct kunit *test) +{ + if (test_case->param_init) { + int err = test_case->param_init(test); + + if (err) { + kunit_err(test_case, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT + "# failed to initialize parent parameter test."); + test_case->status = KUNIT_FAILURE; + } + } +} + int kunit_run_tests(struct kunit_suite *suite) { char param_desc[KUNIT_PARAM_DESC_SIZE]; @@ -668,6 +681,8 @@ int kunit_run_tests(struct kunit_suite *suite) struct kunit_result_stats param_stats = { 0 };
kunit_init_test(&test, test_case->name, test_case->log); + __kunit_init_parent_test(test_case, &test); + if (test_case->status == KUNIT_SKIPPED) { /* Test marked as skip */ test.status = KUNIT_SKIPPED; @@ -677,7 +692,7 @@ int kunit_run_tests(struct kunit_suite *suite) test_case->status = KUNIT_SKIPPED; kunit_run_case_catch_errors(suite, test_case, &test); kunit_update_stats(¶m_stats, test.status); - } else { + } else if (test_case->status != KUNIT_FAILURE) { /* Get initial param. */ param_desc[0] = '\0'; /* TODO: Make generate_params try-catch */ @@ -727,6 +742,12 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_update_stats(&suite_stats, test_case->status); kunit_accumulate_stats(&total_stats, param_stats); + /* + * TODO: Put into a try catch. Since we don't need suite->exit + * for it we can't reuse kunit_try_run_cleanup for this yet. + */ + if (test_case->param_exit) + test_case->param_exit(&test); /* TODO: Put this kunit_cleanup into a try-catch. */ kunit_cleanup(&test); }
Hi Marie,
kernel test robot noticed the following build errors:
[auto build test ERROR on shuah-kselftest/kunit] [also build test ERROR on shuah-kselftest/kunit-fixes drm-xe/drm-xe-next linus/master v6.16 next-20250730] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Marie-Zhussupova/kunit-Add-pa... base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit patch link: https://lore.kernel.org/r/20250729193647.3410634-3-marievic%40google.com patch subject: [PATCH 2/9] kunit: Introduce param_init/exit for parameterized test shared context management config: x86_64-rhel-9.4-rust (https://download.01.org/0day-ci/archive/20250730/202507302114.xQU4zmX5-lkp@i...) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) rustc: rustc 1.88.0 (6b00bc388 2025-06-23) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250730/202507302114.xQU4zmX5-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202507302114.xQU4zmX5-lkp@intel.com/
All errors (new ones prefixed by >>):
error[E0063]: missing fields `param_exit` and `param_init` in initializer of `kunit_case`
--> rust/kernel/kunit.rs:200:5 | 200 | kernel::bindings::kunit_case { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `param_exit` and `param_init` --
error[E0063]: missing fields `param_exit` and `param_init` in initializer of `kunit_case`
--> rust/kernel/kunit.rs:219:5 | 219 | kernel::bindings::kunit_case { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `param_exit` and `param_init`
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
Add `param_init` and `param_exit` function pointers to `struct kunit_case`. Users will be able to set them via the new `KUNIT_CASE_PARAM_WITH_INIT` macro.
These functions are invoked by kunit_run_tests() once before and once after the entire parameterized test series, respectively. They will receive the parent kunit test instance, allowing users to register and manage shared resources. Resources added to this parent kunit test will be accessible to all individual parameterized tests, facilitating init and exit for shared state.
Signed-off-by: Marie Zhussupova marievic@google.com
Thanks: this looks good to me, modulo the issues below (particularly the Rust breakage).
I was initially unsure of the names 'param_init' and 'param_exit', preferring just 'init'/'exit', but have since come to like it, as otherwise it'd be easy to confuse it with the kunit_suite init/exit functions, which behave differently. So happy to keep that.
With the Rust issue fixes, this is: Reviewed-by: David Gow davidgow@google.com
Cheers, -- David
include/kunit/test.h | 33 ++++++++++++++++++++++++++++++++- lib/kunit/test.c | 23 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index a42d0c8cb985..d8dac7efd745 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -92,6 +92,8 @@ struct kunit_attributes {
- @name: the name of the test case.
- @generate_params: the generator function for parameterized tests.
- @attr: the attributes associated with the test
- @param_init: The init function to run before parameterized tests.
- @param_exit: The exit function to run after parameterized tests.
- A test case is a function with the signature,
- ``void (*)(struct kunit *)``
@@ -129,6 +131,13 @@ struct kunit_case { const void* (*generate_params)(const void *prev, char *desc); struct kunit_attributes attr;
/*
* Optional user-defined functions: one to register shared resources once
* before the parameterized test series, and another to release them after.
*/
int (*param_init)(struct kunit *test);
void (*param_exit)(struct kunit *test);
As noted by the test robot, these need to be initialised in rust/kernel/kunit.rs when a kunit_case is being set up. Since parameterised tests aren't used in Rust, they can just be set to None.
/* private: internal use only. */ enum kunit_status status; char *module_name;
@@ -218,6 +227,27 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) .generate_params = gen_params, \ .attr = attributes, .module_name = KBUILD_MODNAME}
+/**
- KUNIT_CASE_PARAM_WITH_INIT() - Define a parameterized KUnit test case with custom
- init and exit functions.
- @test_name: The function implementing the test case.
- @gen_params: The function to generate parameters for the test case.
- @init: The init function to run before parameterized tests.
- @exit: The exit function to run after parameterized tests.
- Provides the option to register init and exit functions that take in the
- parent of the parameterized tests and run once before and once after the
- parameterized test series. The init function can be used to add any resources
- to share between the parameterized tests or to pass parameter arrays. The
- exit function can be used to clean up any resources that are not managed by
- the test.
- */
+#define KUNIT_CASE_PARAM_WITH_INIT(test_name, gen_params, init, exit) \
{ .run_case = test_name, .name = #test_name, \
.generate_params = gen_params, \
.param_init = init, .param_exit = exit, \
.module_name = KBUILD_MODNAME}
/**
- struct kunit_suite - describes a related collection of &struct kunit_case
@@ -269,7 +299,8 @@ struct kunit_suite_set {
- @priv: for user to store arbitrary data. Commonly used to pass data
created in the init function (see &struct kunit_suite).
- @parent: for user to store data that they want to shared across
parameterized tests.
parameterized tests. Typically, the data is provided in
the param_init function (see &struct kunit_case).
- Used to store information about the current context under which the test
- is running. Most of this data is private and should only be accessed
diff --git a/lib/kunit/test.c b/lib/kunit/test.c index 4d6a39eb2c80..d80b5990d85d 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -641,6 +641,19 @@ static void kunit_accumulate_stats(struct kunit_result_stats *total, total->total += add.total; }
+static void __kunit_init_parent_test(struct kunit_case *test_case, struct kunit *test)
There's no fundamental reason this needs to start '__': other internal functions here don't.
(But please keep it static and internal.)
+{
if (test_case->param_init) {
int err = test_case->param_init(test);
if (err) {
kunit_err(test_case, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT
"# failed to initialize parent parameter test.");
test_case->status = KUNIT_FAILURE;
}
}
+}
int kunit_run_tests(struct kunit_suite *suite) { char param_desc[KUNIT_PARAM_DESC_SIZE]; @@ -668,6 +681,8 @@ int kunit_run_tests(struct kunit_suite *suite) struct kunit_result_stats param_stats = { 0 };
kunit_init_test(&test, test_case->name, test_case->log);
__kunit_init_parent_test(test_case, &test);
if (test_case->status == KUNIT_SKIPPED) { /* Test marked as skip */ test.status = KUNIT_SKIPPED;
@@ -677,7 +692,7 @@ int kunit_run_tests(struct kunit_suite *suite) test_case->status = KUNIT_SKIPPED; kunit_run_case_catch_errors(suite, test_case, &test); kunit_update_stats(¶m_stats, test.status);
} else {
} else if (test_case->status != KUNIT_FAILURE) { /* Get initial param. */ param_desc[0] = '\0'; /* TODO: Make generate_params try-catch */
@@ -727,6 +742,12 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_update_stats(&suite_stats, test_case->status); kunit_accumulate_stats(&total_stats, param_stats);
/*
* TODO: Put into a try catch. Since we don't need suite->exit
* for it we can't reuse kunit_try_run_cleanup for this yet.
*/
if (test_case->param_exit)
test_case->param_exit(&test);
Not thrilled that this is introducing another TODO here, but since we already have a number of places around parameterised tests where we're not running things from the separate try-catch thread, I don't think we should hold up useful features on it. The actual test code is still properly handled.
But I'm looking forward to going through and cleaning all of these up at some point.
/* TODO: Put this kunit_cleanup into a try-catch. */ kunit_cleanup(&test); }
-- 2.50.1.552.g942d659e1b-goog
To enable more complex parameterized test scenarios, the `generate_params` function sometimes needs additional context beyond just the previously generated parameter. This patch modifies the `generate_params` function signature to include an extra `struct kunit *test` argument, giving users access to the parent kunit test's context when generating subsequent parameters.
The `struct kunit *test` argument was added as the first parameter to the function signature as it aligns with the convention of other KUnit functions that accept `struct kunit *test` first. This also mirrors the "this" or "self" reference found in object-oriented programming languages.
Signed-off-by: Marie Zhussupova marievic@google.com --- include/kunit/test.h | 9 ++++++--- lib/kunit/test.c | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index d8dac7efd745..4ba65dc35710 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -128,7 +128,8 @@ struct kunit_attributes { struct kunit_case { void (*run_case)(struct kunit *test); const char *name; - const void* (*generate_params)(const void *prev, char *desc); + const void* (*generate_params)(struct kunit *test, + const void *prev, char *desc); struct kunit_attributes attr;
/* @@ -1701,7 +1702,8 @@ do { \ * Define function @name_gen_params which uses @array to generate parameters. */ #define KUNIT_ARRAY_PARAM(name, array, get_desc) \ - static const void *name##_gen_params(const void *prev, char *desc) \ + static const void *name##_gen_params(struct kunit *test, \ + const void *prev, char *desc) \ { \ typeof((array)[0]) *__next = prev ? ((typeof(__next)) prev) + 1 : (array); \ if (__next - (array) < ARRAY_SIZE((array))) { \ @@ -1722,7 +1724,8 @@ do { \ * Define function @name_gen_params which uses @array to generate parameters. */ #define KUNIT_ARRAY_PARAM_DESC(name, array, desc_member) \ - static const void *name##_gen_params(const void *prev, char *desc) \ + static const void *name##_gen_params(struct kunit *test, \ + const void *prev, char *desc) \ { \ typeof((array)[0]) *__next = prev ? ((typeof(__next)) prev) + 1 : (array); \ if (__next - (array) < ARRAY_SIZE((array))) { \ diff --git a/lib/kunit/test.c b/lib/kunit/test.c index d80b5990d85d..f50ef82179c4 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -696,7 +696,7 @@ int kunit_run_tests(struct kunit_suite *suite) /* Get initial param. */ param_desc[0] = '\0'; /* TODO: Make generate_params try-catch */ - curr_param = test_case->generate_params(NULL, param_desc); + curr_param = test_case->generate_params(&test, NULL, param_desc); test_case->status = KUNIT_SKIPPED; kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT "KTAP version 1\n"); @@ -727,7 +727,8 @@ int kunit_run_tests(struct kunit_suite *suite)
/* Get next param. */ param_desc[0] = '\0'; - curr_param = test_case->generate_params(curr_param, param_desc); + curr_param = test_case->generate_params(&test, curr_param, + param_desc); } }
Hi Marie,
kernel test robot noticed the following build errors:
[auto build test ERROR on shuah-kselftest/kunit] [also build test ERROR on shuah-kselftest/kunit-fixes drm-xe/drm-xe-next linus/master v6.16 next-20250730] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Marie-Zhussupova/kunit-Add-pa... base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit patch link: https://lore.kernel.org/r/20250729193647.3410634-4-marievic%40google.com patch subject: [PATCH 3/9] kunit: Pass additional context to generate_params for parameterized testing config: arm64-randconfig-002-20250730 (https://download.01.org/0day-ci/archive/20250730/202507302223.BTl33Nvo-lkp@i...) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250730/202507302223.BTl33Nvo-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202507302223.BTl33Nvo-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from drivers/gpu/drm/xe/xe_migrate.c:1917:
drivers/gpu/drm/xe/tests/xe_migrate.c:772:44: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types]
772 | KUNIT_CASE_PARAM(xe_migrate_sanity_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ In file included from drivers/gpu/drm/xe/xe_migrate.c:1917: drivers/gpu/drm/xe/tests/xe_migrate.c:773:42: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types] 773 | KUNIT_CASE_PARAM(xe_validate_ccs_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ 2 errors generated. -- In file included from drivers/gpu/drm/xe/xe_dma_buf.c:319:
drivers/gpu/drm/xe/tests/xe_dma_buf.c:285:37: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types]
285 | KUNIT_CASE_PARAM(xe_dma_buf_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ 1 error generated. -- In file included from drivers/gpu/drm/xe/xe_bo.c:3128:
drivers/gpu/drm/xe/tests/xe_bo.c:610:41: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types]
610 | KUNIT_CASE_PARAM(xe_ccs_migrate_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ In file included from drivers/gpu/drm/xe/xe_bo.c:3128: drivers/gpu/drm/xe/tests/xe_bo.c:611:38: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types] 611 | KUNIT_CASE_PARAM(xe_bo_evict_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ In file included from drivers/gpu/drm/xe/xe_bo.c:3128: drivers/gpu/drm/xe/tests/xe_bo.c:624:44: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types] 624 | KUNIT_CASE_PARAM_ATTR(xe_bo_shrink_kunit, xe_pci_live_device_gen_param, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:228:24: note: expanded from macro 'KUNIT_CASE_PARAM_ATTR' 228 | .generate_params = gen_params, \ | ^~~~~~~~~~ 3 errors generated. -- In file included from drivers/gpu/drm/xe/xe_mocs.c:799:
drivers/gpu/drm/xe/tests/xe_mocs.c:193:46: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types]
193 | KUNIT_CASE_PARAM(xe_live_mocs_kernel_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ In file included from drivers/gpu/drm/xe/xe_mocs.c:799: drivers/gpu/drm/xe/tests/xe_mocs.c:194:45: error: incompatible function pointer types initializing 'const void *(*)(struct kunit *, const void *, char *)' with an expression of type 'const void *(const void *, char *)' [-Wincompatible-function-pointer-types] 194 | KUNIT_CASE_PARAM(xe_live_mocs_reset_kunit, xe_pci_live_device_gen_param), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/kunit/test.h:215:24: note: expanded from macro 'KUNIT_CASE_PARAM' 215 | .generate_params = gen_params, .module_name = KBUILD_MODNAME} | ^~~~~~~~~~ 2 errors generated.
vim +772 drivers/gpu/drm/xe/tests/xe_migrate.c
54f07cfc016226 Akshata Jahagirdar 2024-07-17 770 0237368193e897 Michal Wajdeczko 2024-07-08 771 static struct kunit_case xe_migrate_tests[] = { 37db1e77628551 Michal Wajdeczko 2024-07-20 @772 KUNIT_CASE_PARAM(xe_migrate_sanity_kunit, xe_pci_live_device_gen_param), 37db1e77628551 Michal Wajdeczko 2024-07-20 773 KUNIT_CASE_PARAM(xe_validate_ccs_kunit, xe_pci_live_device_gen_param), 0237368193e897 Michal Wajdeczko 2024-07-08 774 {} 0237368193e897 Michal Wajdeczko 2024-07-08 775 }; 0237368193e897 Michal Wajdeczko 2024-07-08 776
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
To enable more complex parameterized test scenarios, the `generate_params` function sometimes needs additional context beyond just the previously generated parameter. This patch modifies the `generate_params` function signature to include an extra `struct kunit *test` argument, giving users access to the parent kunit test's context when generating subsequent parameters.
The `struct kunit *test` argument was added as the first parameter to the function signature as it aligns with the convention of other KUnit functions that accept `struct kunit *test` first. This also mirrors the "this" or "self" reference found in object-oriented programming languages.
This matches my prejudices well. :-)
Signed-off-by: Marie Zhussupova marievic@google.com
At last! This will be very useful as a way of having more complicated generator functions (or, indeed, any generator function which wants state without hacking it into an enormously complicated parameter object).
The only thing worth noting is that this breaks the existing tests until the next two patches are accepted, which could be a pain for bisecting. I can live with it, since the breakage is obvious and confined to the span of a couple of patches in the same series and to test-only code, but if others prefer, we can squash these together.
Reviewed-by: David Gow davidgow@google.com
Cheers, -- David
include/kunit/test.h | 9 ++++++--- lib/kunit/test.c | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index d8dac7efd745..4ba65dc35710 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -128,7 +128,8 @@ struct kunit_attributes { struct kunit_case { void (*run_case)(struct kunit *test); const char *name;
const void* (*generate_params)(const void *prev, char *desc);
const void* (*generate_params)(struct kunit *test,
const void *prev, char *desc); struct kunit_attributes attr; /*
@@ -1701,7 +1702,8 @@ do { \
- Define function @name_gen_params which uses @array to generate parameters.
*/ #define KUNIT_ARRAY_PARAM(name, array, get_desc) \
static const void *name##_gen_params(const void *prev, char *desc) \
static const void *name##_gen_params(struct kunit *test, \
const void *prev, char *desc) \ { \ typeof((array)[0]) *__next = prev ? ((typeof(__next)) prev) + 1 : (array); \ if (__next - (array) < ARRAY_SIZE((array))) { \
@@ -1722,7 +1724,8 @@ do { \
- Define function @name_gen_params which uses @array to generate parameters.
*/ #define KUNIT_ARRAY_PARAM_DESC(name, array, desc_member) \
static const void *name##_gen_params(const void *prev, char *desc) \
static const void *name##_gen_params(struct kunit *test, \
const void *prev, char *desc) \ { \ typeof((array)[0]) *__next = prev ? ((typeof(__next)) prev) + 1 : (array); \ if (__next - (array) < ARRAY_SIZE((array))) { \
diff --git a/lib/kunit/test.c b/lib/kunit/test.c index d80b5990d85d..f50ef82179c4 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -696,7 +696,7 @@ int kunit_run_tests(struct kunit_suite *suite) /* Get initial param. */ param_desc[0] = '\0'; /* TODO: Make generate_params try-catch */
curr_param = test_case->generate_params(NULL, param_desc);
curr_param = test_case->generate_params(&test, NULL, param_desc); test_case->status = KUNIT_SKIPPED; kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT "KTAP version 1\n");
@@ -727,7 +727,8 @@ int kunit_run_tests(struct kunit_suite *suite)
/* Get next param. */ param_desc[0] = '\0';
curr_param = test_case->generate_params(curr_param, param_desc);
curr_param = test_case->generate_params(&test, curr_param,
param_desc); } }
-- 2.50.1.552.g942d659e1b-goog
This patch modifies `nthreads_gen_params` in kcsan_test.c to accept an additional `struct kunit *test` argument.
Signed-off-by: Marie Zhussupova marievic@google.com --- kernel/kcsan/kcsan_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/kcsan/kcsan_test.c b/kernel/kcsan/kcsan_test.c index c2871180edcc..fc76648525ac 100644 --- a/kernel/kcsan/kcsan_test.c +++ b/kernel/kcsan/kcsan_test.c @@ -1383,7 +1383,7 @@ static void test_atomic_builtins_missing_barrier(struct kunit *test) * The thread counts are chosen to cover potentially interesting boundaries and * corner cases (2 to 5), and then stress the system with larger counts. */ -static const void *nthreads_gen_params(const void *prev, char *desc) +static const void *nthreads_gen_params(struct kunit *test, const void *prev, char *desc) { long nthreads = (long)prev;
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
This patch modifies `nthreads_gen_params` in kcsan_test.c to accept an additional `struct kunit *test` argument.
Signed-off-by: Marie Zhussupova marievic@google.com
This is a pretty straightforward fix after patch 3. KCSAN folks, would you prefer this kept as a separate patch, or squashed into the previous one (so there's no commit where this is broken)?
Either way, Reviewed-by: David Gow davidgow@google.com
-- David
kernel/kcsan/kcsan_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/kcsan/kcsan_test.c b/kernel/kcsan/kcsan_test.c index c2871180edcc..fc76648525ac 100644 --- a/kernel/kcsan/kcsan_test.c +++ b/kernel/kcsan/kcsan_test.c @@ -1383,7 +1383,7 @@ static void test_atomic_builtins_missing_barrier(struct kunit *test)
- The thread counts are chosen to cover potentially interesting boundaries and
- corner cases (2 to 5), and then stress the system with larger counts.
*/ -static const void *nthreads_gen_params(const void *prev, char *desc) +static const void *nthreads_gen_params(struct kunit *test, const void *prev, char *desc) { long nthreads = (long)prev;
-- 2.50.1.552.g942d659e1b-goog
This patch modifies `xe_pci_live_device_gen_param` in xe_pci.c to accept an additional `struct kunit *test` argument.
Signed-off-by: Marie Zhussupova marievic@google.com --- drivers/gpu/drm/xe/tests/xe_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/xe/tests/xe_pci.c b/drivers/gpu/drm/xe/tests/xe_pci.c index 1d3e2e50c355..62c016e84227 100644 --- a/drivers/gpu/drm/xe/tests/xe_pci.c +++ b/drivers/gpu/drm/xe/tests/xe_pci.c @@ -129,7 +129,7 @@ EXPORT_SYMBOL_IF_KUNIT(xe_pci_fake_device_init); * Return: pointer to the next &struct xe_device ready to be used as a parameter * or NULL if there are no more Xe devices on the system. */ -const void *xe_pci_live_device_gen_param(const void *prev, char *desc) +const void *xe_pci_live_device_gen_param(struct kunit *test, const void *prev, char *desc) { const struct xe_device *xe = prev; struct device *dev = xe ? xe->drm.dev : NULL;
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
This patch modifies `xe_pci_live_device_gen_param` in xe_pci.c to accept an additional `struct kunit *test` argument.
Signed-off-by: Marie Zhussupova marievic@google.com
This is a pretty straightforward fix after patch 3. xe folks, would you prefer this kept as a separate patch, or squashed into patch 3 (which changed the function signature)?
Either way, Reviewed-by: David Gow davidgow@google.com
-- David
drivers/gpu/drm/xe/tests/xe_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/xe/tests/xe_pci.c b/drivers/gpu/drm/xe/tests/xe_pci.c index 1d3e2e50c355..62c016e84227 100644 --- a/drivers/gpu/drm/xe/tests/xe_pci.c +++ b/drivers/gpu/drm/xe/tests/xe_pci.c @@ -129,7 +129,7 @@ EXPORT_SYMBOL_IF_KUNIT(xe_pci_fake_device_init);
- Return: pointer to the next &struct xe_device ready to be used as a parameter
or NULL if there are no more Xe devices on the system.
*/ -const void *xe_pci_live_device_gen_param(const void *prev, char *desc) +const void *xe_pci_live_device_gen_param(struct kunit *test, const void *prev, char *desc) { const struct xe_device *xe = prev; struct device *dev = xe ? xe->drm.dev : NULL; -- 2.50.1.552.g942d659e1b-goog
KUnit parameterized tests currently support two primary methods for getting parameters: 1. Defining custom logic within a `generate_params` function. 2. Using the KUNIT_ARRAY_PARAM and KUNIT_ARRAY_PARAM_DESC macros with pre-defined static arrays.
These methods present limitations when dealing with dynamically generated parameter arrays, or in scenarios where populating parameters sequentially via `generate_params` is inefficient or overly complex.
This patch addresses these limitations by adding a new `params_data` field to `struct kunit`, of the type `kunit_params`. The struct `kunit_params` is designed to store the parameter array itself, along with essential metadata including the parameter count, parameter size, and a `get_description` function for providing custom descriptions for individual parameters.
The `params_data` field can be populated by calling the new `kunit_register_params_array` macro from within a `param_init` function. By attaching the parameter array directly to the parent kunit test instance, these parameters can be iterated over in kunit_run_tests() behind the scenes.
This modification provides greater flexibility to the KUnit framework, allowing testers to easily register and utilize both dynamic and static parameter arrays.
Signed-off-by: Marie Zhussupova marievic@google.com --- include/kunit/test.h | 54 ++++++++++++++++++++++++++++++++++++++++---- lib/kunit/test.c | 26 ++++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 4ba65dc35710..9143f0e22323 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -245,7 +245,8 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) */ #define KUNIT_CASE_PARAM_WITH_INIT(test_name, gen_params, init, exit) \ { .run_case = test_name, .name = #test_name, \ - .generate_params = gen_params, \ + .generate_params = (gen_params) \ + ?: kunit_get_next_param_and_desc, \ .param_init = init, .param_exit = exit, \ .module_name = KBUILD_MODNAME}
@@ -294,6 +295,21 @@ struct kunit_suite_set { struct kunit_suite * const *end; };
+/* Stores the pointer to the parameter array and its metadata. */ +struct kunit_params { + /* + * Reference to the parameter array for the parameterized tests. This + * is NULL if a parameter array wasn't directly passed to the + * parent kunit struct via the kunit_register_params_array macro. + */ + const void *params; + /* Reference to a function that gets the description of a parameter. */ + void (*get_description)(const void *param, char *desc); + + int num_params; + size_t elem_size; +}; + /** * struct kunit - represents a running instance of a test. * @@ -302,12 +318,14 @@ struct kunit_suite_set { * @parent: for user to store data that they want to shared across * parameterized tests. Typically, the data is provided in * the param_init function (see &struct kunit_case). + * @params_data: for users to directly store the parameter array. * * Used to store information about the current context under which the test * is running. Most of this data is private and should only be accessed - * indirectly via public functions; the two exceptions are @priv and @parent - * which can be used by the test writer to store arbitrary data or data that is - * available to all parameter test executions, respectively. + * indirectly via public functions. There are three exceptions to this: @priv, + * @parent, and @params_data. These members can be used by the test writer to + * store arbitrary data, data available to all parameter test executions, and + * the parameter array, respectively. */ struct kunit { void *priv; @@ -316,6 +334,8 @@ struct kunit { * during parameterized testing. */ struct kunit *parent; + /* Stores the params array and all data related to it. */ + struct kunit_params params_data;
/* private: internal use only. */ const char *name; /* Read only after initialization! */ @@ -386,6 +406,8 @@ void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set, struct kunit_suite_set suite_set);
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc); + #if IS_BUILTIN(CONFIG_KUNIT) int kunit_run_all_tests(void); #else @@ -1735,6 +1757,30 @@ do { \ return NULL; \ }
+/** + * kunit_register_params_array() - Register parameters for a KUnit test. + * @test: The KUnit test structure to which parameters will be added. + * @params_arr: An array of test parameters. + * @param_cnt: Number of parameters. + * @get_desc: A pointer to a function that generates a string description for + * a given parameter element. + * + * This macro initializes the @test's parameter array data, storing information + * including the parameter array, its count, the element size, and the parameter + * description function within `test->params_data`. KUnit's built-in + * `kunit_get_next_param_and_desc` function will automatically read this + * data when a custom `generate_params` function isn't provided. + */ +#define kunit_register_params_array(test, params_arr, param_cnt, get_desc) \ + do { \ + struct kunit *_test = (test); \ + const typeof((params_arr)[0]) * _params_ptr = &(params_arr)[0]; \ + _test->params_data.params = _params_ptr; \ + _test->params_data.num_params = (param_cnt); \ + _test->params_data.elem_size = sizeof(*_params_ptr); \ + _test->params_data.get_description = (get_desc); \ + } while (0) + // TODO(dlatypov@google.com): consider eventually migrating users to explicitly // include resource.h themselves if they need it. #include <kunit/resource.h> diff --git a/lib/kunit/test.c b/lib/kunit/test.c index f50ef82179c4..2f4b7087db3f 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -337,6 +337,13 @@ void __kunit_do_failed_assertion(struct kunit *test, } EXPORT_SYMBOL_GPL(__kunit_do_failed_assertion);
+static void __kunit_init_params(struct kunit *test) +{ + test->params_data.params = NULL; + test->params_data.num_params = 0; + test->params_data.elem_size = 0; +} + void kunit_init_test(struct kunit *test, const char *name, struct string_stream *log) { spin_lock_init(&test->lock); @@ -347,6 +354,7 @@ void kunit_init_test(struct kunit *test, const char *name, struct string_stream string_stream_clear(log); test->status = KUNIT_SUCCESS; test->status_comment[0] = '\0'; + __kunit_init_params(test); } EXPORT_SYMBOL_GPL(kunit_init_test);
@@ -641,6 +649,22 @@ static void kunit_accumulate_stats(struct kunit_result_stats *total, total->total += add.total; }
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc) +{ + struct kunit_params *params_arr = &test->params_data; + const void *param; + + if (test->param_index < params_arr->num_params) { + param = (char *)params_arr->params + + test->param_index * params_arr->elem_size; + + if (params_arr->get_description) + params_arr->get_description(param, desc); + return param; + } + return NULL; +} + static void __kunit_init_parent_test(struct kunit_case *test_case, struct kunit *test) { if (test_case->param_init) { @@ -687,7 +711,7 @@ int kunit_run_tests(struct kunit_suite *suite) /* Test marked as skip */ test.status = KUNIT_SKIPPED; kunit_update_stats(¶m_stats, test.status); - } else if (!test_case->generate_params) { + } else if (!test_case->generate_params && !test.params_data.params) { /* Non-parameterised test. */ test_case->status = KUNIT_SKIPPED; kunit_run_case_catch_errors(suite, test_case, &test);
On Tue, Jul 29, 2025 at 3:37 PM Marie Zhussupova marievic@google.com wrote:
KUnit parameterized tests currently support two primary methods for getting parameters:
- Defining custom logic within a `generate_params` function.
- Using the KUNIT_ARRAY_PARAM and KUNIT_ARRAY_PARAM_DESC macros with pre-defined static arrays.
These methods present limitations when dealing with dynamically generated parameter arrays, or in scenarios where populating parameters sequentially via `generate_params` is inefficient or overly complex.
This patch addresses these limitations by adding a new `params_data` field to `struct kunit`, of the type `kunit_params`. The struct `kunit_params` is designed to store the parameter array itself, along with essential metadata including the parameter count, parameter size, and a `get_description` function for providing custom descriptions for individual parameters.
The `params_data` field can be populated by calling the new `kunit_register_params_array` macro from within a `param_init` function. By attaching the parameter array directly to the parent kunit test instance, these parameters can be iterated over in kunit_run_tests() behind the scenes.
This modification provides greater flexibility to the KUnit framework, allowing testers to easily register and utilize both dynamic and static parameter arrays.
Signed-off-by: Marie Zhussupova marievic@google.com
include/kunit/test.h | 54 ++++++++++++++++++++++++++++++++++++++++---- lib/kunit/test.c | 26 ++++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 4ba65dc35710..9143f0e22323 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -245,7 +245,8 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) */ #define KUNIT_CASE_PARAM_WITH_INIT(test_name, gen_params, init, exit) \ { .run_case = test_name, .name = #test_name, \
.generate_params = gen_params, \
.generate_params = (gen_params) \
?: kunit_get_next_param_and_desc, \ .param_init = init, .param_exit = exit, \ .module_name = KBUILD_MODNAME}
@@ -294,6 +295,21 @@ struct kunit_suite_set { struct kunit_suite * const *end; };
+/* Stores the pointer to the parameter array and its metadata. */ +struct kunit_params {
/*
* Reference to the parameter array for the parameterized tests. This
* is NULL if a parameter array wasn't directly passed to the
* parent kunit struct via the kunit_register_params_array macro.
*/
const void *params;
/* Reference to a function that gets the description of a parameter. */
void (*get_description)(const void *param, char *desc);
int num_params;
size_t elem_size;
+};
/**
- struct kunit - represents a running instance of a test.
@@ -302,12 +318,14 @@ struct kunit_suite_set {
- @parent: for user to store data that they want to shared across
parameterized tests. Typically, the data is provided in
the param_init function (see &struct kunit_case).
- @params_data: for users to directly store the parameter array.
- Used to store information about the current context under which the test
- is running. Most of this data is private and should only be accessed
- indirectly via public functions; the two exceptions are @priv and @parent
- which can be used by the test writer to store arbitrary data or data that is
- available to all parameter test executions, respectively.
- indirectly via public functions. There are three exceptions to this: @priv,
- @parent, and @params_data. These members can be used by the test writer to
- store arbitrary data, data available to all parameter test executions, and
*/
- the parameter array, respectively.
struct kunit { void *priv; @@ -316,6 +334,8 @@ struct kunit { * during parameterized testing. */ struct kunit *parent;
/* Stores the params array and all data related to it. */
struct kunit_params params_data; /* private: internal use only. */ const char *name; /* Read only after initialization! */
@@ -386,6 +406,8 @@ void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set, struct kunit_suite_set suite_set);
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc);
Hello!
Thanks for sending out this series! I will do a full review of it. For now, I noticed that I get an error when I try to run KUnit tests as modules. I get the following error: "ERROR: modpost: "kunit_get_next_param_and_desc" [lib/kunit/kunit-example-test.ko] undefined!". As a possible fix, I suggest moving the function definition into the header file and making it a static inline function.
Thanks! -Rae
#if IS_BUILTIN(CONFIG_KUNIT) int kunit_run_all_tests(void); #else @@ -1735,6 +1757,30 @@ do { \ return NULL; \ }
+/**
- kunit_register_params_array() - Register parameters for a KUnit test.
- @test: The KUnit test structure to which parameters will be added.
- @params_arr: An array of test parameters.
- @param_cnt: Number of parameters.
- @get_desc: A pointer to a function that generates a string description for
- a given parameter element.
- This macro initializes the @test's parameter array data, storing information
- including the parameter array, its count, the element size, and the parameter
- description function within `test->params_data`. KUnit's built-in
- `kunit_get_next_param_and_desc` function will automatically read this
- data when a custom `generate_params` function isn't provided.
- */
+#define kunit_register_params_array(test, params_arr, param_cnt, get_desc) \
do { \
struct kunit *_test = (test); \
const typeof((params_arr)[0]) * _params_ptr = &(params_arr)[0]; \
_test->params_data.params = _params_ptr; \
_test->params_data.num_params = (param_cnt); \
_test->params_data.elem_size = sizeof(*_params_ptr); \
_test->params_data.get_description = (get_desc); \
} while (0)
// TODO(dlatypov@google.com): consider eventually migrating users to explicitly // include resource.h themselves if they need it. #include <kunit/resource.h> diff --git a/lib/kunit/test.c b/lib/kunit/test.c index f50ef82179c4..2f4b7087db3f 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -337,6 +337,13 @@ void __kunit_do_failed_assertion(struct kunit *test, } EXPORT_SYMBOL_GPL(__kunit_do_failed_assertion);
+static void __kunit_init_params(struct kunit *test) +{
test->params_data.params = NULL;
test->params_data.num_params = 0;
test->params_data.elem_size = 0;
+}
void kunit_init_test(struct kunit *test, const char *name, struct string_stream *log) { spin_lock_init(&test->lock); @@ -347,6 +354,7 @@ void kunit_init_test(struct kunit *test, const char *name, struct string_stream string_stream_clear(log); test->status = KUNIT_SUCCESS; test->status_comment[0] = '\0';
__kunit_init_params(test);
} EXPORT_SYMBOL_GPL(kunit_init_test);
@@ -641,6 +649,22 @@ static void kunit_accumulate_stats(struct kunit_result_stats *total, total->total += add.total; }
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc) +{
struct kunit_params *params_arr = &test->params_data;
const void *param;
if (test->param_index < params_arr->num_params) {
param = (char *)params_arr->params
+ test->param_index * params_arr->elem_size;
if (params_arr->get_description)
params_arr->get_description(param, desc);
return param;
}
return NULL;
+}
static void __kunit_init_parent_test(struct kunit_case *test_case, struct kunit *test) { if (test_case->param_init) { @@ -687,7 +711,7 @@ int kunit_run_tests(struct kunit_suite *suite) /* Test marked as skip */ test.status = KUNIT_SKIPPED; kunit_update_stats(¶m_stats, test.status);
} else if (!test_case->generate_params) {
} else if (!test_case->generate_params && !test.params_data.params) { /* Non-parameterised test. */ test_case->status = KUNIT_SKIPPED; kunit_run_case_catch_errors(suite, test_case, &test);
-- 2.50.1.552.g942d659e1b-goog
On Tue, Jul 29, 2025 at 4:14 PM Rae Moar rmoar@google.com wrote:
On Tue, Jul 29, 2025 at 3:37 PM Marie Zhussupova marievic@google.com wrote:
KUnit parameterized tests currently support two primary methods for getting parameters:
- Defining custom logic within a `generate_params` function.
- Using the KUNIT_ARRAY_PARAM and KUNIT_ARRAY_PARAM_DESC macros with pre-defined static arrays.
These methods present limitations when dealing with dynamically generated parameter arrays, or in scenarios where populating parameters sequentially via `generate_params` is inefficient or overly complex.
This patch addresses these limitations by adding a new `params_data` field to `struct kunit`, of the type `kunit_params`. The struct `kunit_params` is designed to store the parameter array itself, along with essential metadata including the parameter count, parameter size, and a `get_description` function for providing custom descriptions for individual parameters.
The `params_data` field can be populated by calling the new `kunit_register_params_array` macro from within a `param_init` function. By attaching the parameter array directly to the parent kunit test instance, these parameters can be iterated over in kunit_run_tests() behind the scenes.
This modification provides greater flexibility to the KUnit framework, allowing testers to easily register and utilize both dynamic and static parameter arrays.
Signed-off-by: Marie Zhussupova marievic@google.com
include/kunit/test.h | 54 ++++++++++++++++++++++++++++++++++++++++---- lib/kunit/test.c | 26 ++++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 4ba65dc35710..9143f0e22323 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -245,7 +245,8 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) */ #define KUNIT_CASE_PARAM_WITH_INIT(test_name, gen_params, init, exit) \ { .run_case = test_name, .name = #test_name, \
.generate_params = gen_params, \
.generate_params = (gen_params) \
?: kunit_get_next_param_and_desc, \ .param_init = init, .param_exit = exit, \ .module_name = KBUILD_MODNAME}
@@ -294,6 +295,21 @@ struct kunit_suite_set { struct kunit_suite * const *end; };
+/* Stores the pointer to the parameter array and its metadata. */ +struct kunit_params {
/*
* Reference to the parameter array for the parameterized tests. This
* is NULL if a parameter array wasn't directly passed to the
* parent kunit struct via the kunit_register_params_array macro.
*/
const void *params;
/* Reference to a function that gets the description of a parameter. */
void (*get_description)(const void *param, char *desc);
int num_params;
size_t elem_size;
+};
/**
- struct kunit - represents a running instance of a test.
@@ -302,12 +318,14 @@ struct kunit_suite_set {
- @parent: for user to store data that they want to shared across
parameterized tests. Typically, the data is provided in
the param_init function (see &struct kunit_case).
- @params_data: for users to directly store the parameter array.
- Used to store information about the current context under which the test
- is running. Most of this data is private and should only be accessed
- indirectly via public functions; the two exceptions are @priv and @parent
- which can be used by the test writer to store arbitrary data or data that is
- available to all parameter test executions, respectively.
- indirectly via public functions. There are three exceptions to this: @priv,
- @parent, and @params_data. These members can be used by the test writer to
- store arbitrary data, data available to all parameter test executions, and
*/
- the parameter array, respectively.
struct kunit { void *priv; @@ -316,6 +334,8 @@ struct kunit { * during parameterized testing. */ struct kunit *parent;
/* Stores the params array and all data related to it. */
struct kunit_params params_data; /* private: internal use only. */ const char *name; /* Read only after initialization! */
@@ -386,6 +406,8 @@ void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set, struct kunit_suite_set suite_set);
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc);
Hello!
Thanks for sending out this series! I will do a full review of it. For now, I noticed that I get an error when I try to run KUnit tests as modules. I get the following error: "ERROR: modpost: "kunit_get_next_param_and_desc" [lib/kunit/kunit-example-test.ko] undefined!". As a possible fix, I suggest moving the function definition into the header file and making it a static inline function.
Thanks! -Rae
Hello! Feel free to also use EXPORT_SYMBOL_GPL(). Either solution should work here. Thanks!
Hi Marie,
kernel test robot noticed the following build warnings:
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Marie-Zhussupova/kunit-Add-pa... base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit patch link: https://lore.kernel.org/r/20250729193647.3410634-7-marievic%40google.com patch subject: [PATCH 6/9] kunit: Enable direct registration of parameter arrays to a KUnit test config: nios2-randconfig-r072-20250731 (https://download.01.org/0day-ci/archive/20250731/202507310854.pZvIcswn-lkp@i...) compiler: nios2-linux-gcc (GCC) 8.5.0
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Reported-by: Dan Carpenter dan.carpenter@linaro.org | Closes: https://lore.kernel.org/r/202507310854.pZvIcswn-lkp@intel.com/
New smatch warnings: lib/kunit/test.c:723 kunit_run_tests() error: we previously assumed 'test_case->generate_params' could be null (see line 714)
vim +723 lib/kunit/test.c
914cc63eea6fbe Brendan Higgins 2019-09-23 681 int kunit_run_tests(struct kunit_suite *suite) 914cc63eea6fbe Brendan Higgins 2019-09-23 682 { fadb08e7c7501e Arpitha Raghunandan 2020-11-16 683 char param_desc[KUNIT_PARAM_DESC_SIZE]; 914cc63eea6fbe Brendan Higgins 2019-09-23 684 struct kunit_case *test_case; acd8e8407b8fcc David Gow 2021-08-03 685 struct kunit_result_stats suite_stats = { 0 }; acd8e8407b8fcc David Gow 2021-08-03 686 struct kunit_result_stats total_stats = { 0 }; 8631cd2cf5fbf2 Marie Zhussupova 2025-07-29 687 const void *curr_param; 914cc63eea6fbe Brendan Higgins 2019-09-23 688 c272612cb4a2f7 David Gow 2022-07-01 689 /* Taint the kernel so we know we've run tests. */ c272612cb4a2f7 David Gow 2022-07-01 690 add_taint(TAINT_TEST, LOCKDEP_STILL_OK); c272612cb4a2f7 David Gow 2022-07-01 691 1cdba21db2ca31 Daniel Latypov 2022-04-29 692 if (suite->suite_init) { 1cdba21db2ca31 Daniel Latypov 2022-04-29 693 suite->suite_init_err = suite->suite_init(suite); 1cdba21db2ca31 Daniel Latypov 2022-04-29 694 if (suite->suite_init_err) { 1cdba21db2ca31 Daniel Latypov 2022-04-29 695 kunit_err(suite, KUNIT_SUBTEST_INDENT 1cdba21db2ca31 Daniel Latypov 2022-04-29 696 "# failed to initialize (%d)", suite->suite_init_err); 1cdba21db2ca31 Daniel Latypov 2022-04-29 697 goto suite_end; 1cdba21db2ca31 Daniel Latypov 2022-04-29 698 } 1cdba21db2ca31 Daniel Latypov 2022-04-29 699 } 1cdba21db2ca31 Daniel Latypov 2022-04-29 700 cae56e1740f559 Daniel Latypov 2022-04-29 701 kunit_print_suite_start(suite); 914cc63eea6fbe Brendan Higgins 2019-09-23 702 fadb08e7c7501e Arpitha Raghunandan 2020-11-16 703 kunit_suite_for_each_test_case(suite, test_case) { fadb08e7c7501e Arpitha Raghunandan 2020-11-16 704 struct kunit test = { .param_value = NULL, .param_index = 0 }; acd8e8407b8fcc David Gow 2021-08-03 705 struct kunit_result_stats param_stats = { 0 }; fadb08e7c7501e Arpitha Raghunandan 2020-11-16 706 887d85a0736ff3 Rae Moar 2023-03-08 707 kunit_init_test(&test, test_case->name, test_case->log); 03806177fa4cbb Marie Zhussupova 2025-07-29 708 __kunit_init_parent_test(test_case, &test); 03806177fa4cbb Marie Zhussupova 2025-07-29 709 529534e8cba3e6 Rae Moar 2023-07-25 710 if (test_case->status == KUNIT_SKIPPED) { 529534e8cba3e6 Rae Moar 2023-07-25 711 /* Test marked as skip */ 529534e8cba3e6 Rae Moar 2023-07-25 712 test.status = KUNIT_SKIPPED; 529534e8cba3e6 Rae Moar 2023-07-25 713 kunit_update_stats(¶m_stats, test.status); 44c50ed8e59936 Marie Zhussupova 2025-07-29 @714 } else if (!test_case->generate_params && !test.params_data.params) { ^^^^^^^^^^^^^^^^^^^^^^^^^^ Imagine ->generate_parms is NULL but test.params_data.params is non-NULL.
37dbb4c7c7442d David Gow 2021-11-02 715 /* Non-parameterised test. */ 529534e8cba3e6 Rae Moar 2023-07-25 716 test_case->status = KUNIT_SKIPPED; 37dbb4c7c7442d David Gow 2021-11-02 717 kunit_run_case_catch_errors(suite, test_case, &test); 37dbb4c7c7442d David Gow 2021-11-02 718 kunit_update_stats(¶m_stats, test.status); 03806177fa4cbb Marie Zhussupova 2025-07-29 719 } else if (test_case->status != KUNIT_FAILURE) { fadb08e7c7501e Arpitha Raghunandan 2020-11-16 720 /* Get initial param. */ fadb08e7c7501e Arpitha Raghunandan 2020-11-16 721 param_desc[0] = '\0'; 8631cd2cf5fbf2 Marie Zhussupova 2025-07-29 722 /* TODO: Make generate_params try-catch */ 13ee0c64bd88a3 Marie Zhussupova 2025-07-29 @723 curr_param = test_case->generate_params(&test, NULL, param_desc); ^^^^^^^^^^^^^^^^^^^^^^^^^^ Then this could crash.
I suspect that this is fine, but I bet that in the previous condition, just testing one would probably have been sufficient or maybe we could have change && to ||.
529534e8cba3e6 Rae Moar 2023-07-25 724 test_case->status = KUNIT_SKIPPED; 6c738b52316c58 Rae Moar 2022-11-23 725 kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT 6c738b52316c58 Rae Moar 2022-11-23 726 "KTAP version 1\n"); 44b7da5fcd4c99 David Gow 2021-11-02 727 kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT 44b7da5fcd4c99 David Gow 2021-11-02 728 "# Subtest: %s", test_case->name); fadb08e7c7501e Arpitha Raghunandan 2020-11-16 729 8631cd2cf5fbf2 Marie Zhussupova 2025-07-29 730 while (curr_param) {
On Thu, Jul 31, 2025 at 11:58 AM Dan Carpenter dan.carpenter@linaro.org wrote:
Hi Marie,
kernel test robot noticed the following build warnings:
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Marie-Zhussupova/kunit-Add-pa... base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit patch link: https://lore.kernel.org/r/20250729193647.3410634-7-marievic%40google.com patch subject: [PATCH 6/9] kunit: Enable direct registration of parameter arrays to a KUnit test config: nios2-randconfig-r072-20250731 (https://download.01.org/0day-ci/archive/20250731/202507310854.pZvIcswn-lkp@i...) compiler: nios2-linux-gcc (GCC) 8.5.0
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Reported-by: Dan Carpenter dan.carpenter@linaro.org | Closes: https://lore.kernel.org/r/202507310854.pZvIcswn-lkp@intel.com/
New smatch warnings: lib/kunit/test.c:723 kunit_run_tests() error: we previously assumed 'test_case->generate_params' could be null (see line 714)
vim +723 lib/kunit/test.c
914cc63eea6fbe Brendan Higgins 2019-09-23 681 int kunit_run_tests(struct kunit_suite *suite) 914cc63eea6fbe Brendan Higgins 2019-09-23 682 { fadb08e7c7501e Arpitha Raghunandan 2020-11-16 683 char param_desc[KUNIT_PARAM_DESC_SIZE]; 914cc63eea6fbe Brendan Higgins 2019-09-23 684 struct kunit_case *test_case; acd8e8407b8fcc David Gow 2021-08-03 685 struct kunit_result_stats suite_stats = { 0 }; acd8e8407b8fcc David Gow 2021-08-03 686 struct kunit_result_stats total_stats = { 0 }; 8631cd2cf5fbf2 Marie Zhussupova 2025-07-29 687 const void *curr_param; 914cc63eea6fbe Brendan Higgins 2019-09-23 688 c272612cb4a2f7 David Gow 2022-07-01 689 /* Taint the kernel so we know we've run tests. */ c272612cb4a2f7 David Gow 2022-07-01 690 add_taint(TAINT_TEST, LOCKDEP_STILL_OK); c272612cb4a2f7 David Gow 2022-07-01 691 1cdba21db2ca31 Daniel Latypov 2022-04-29 692 if (suite->suite_init) { 1cdba21db2ca31 Daniel Latypov 2022-04-29 693 suite->suite_init_err = suite->suite_init(suite); 1cdba21db2ca31 Daniel Latypov 2022-04-29 694 if (suite->suite_init_err) { 1cdba21db2ca31 Daniel Latypov 2022-04-29 695 kunit_err(suite, KUNIT_SUBTEST_INDENT 1cdba21db2ca31 Daniel Latypov 2022-04-29 696 "# failed to initialize (%d)", suite->suite_init_err); 1cdba21db2ca31 Daniel Latypov 2022-04-29 697 goto suite_end; 1cdba21db2ca31 Daniel Latypov 2022-04-29 698 } 1cdba21db2ca31 Daniel Latypov 2022-04-29 699 } 1cdba21db2ca31 Daniel Latypov 2022-04-29 700 cae56e1740f559 Daniel Latypov 2022-04-29 701 kunit_print_suite_start(suite); 914cc63eea6fbe Brendan Higgins 2019-09-23 702 fadb08e7c7501e Arpitha Raghunandan 2020-11-16 703 kunit_suite_for_each_test_case(suite, test_case) { fadb08e7c7501e Arpitha Raghunandan 2020-11-16 704 struct kunit test = { .param_value = NULL, .param_index = 0 }; acd8e8407b8fcc David Gow 2021-08-03 705 struct kunit_result_stats param_stats = { 0 }; fadb08e7c7501e Arpitha Raghunandan 2020-11-16 706 887d85a0736ff3 Rae Moar 2023-03-08 707 kunit_init_test(&test, test_case->name, test_case->log); 03806177fa4cbb Marie Zhussupova 2025-07-29 708 __kunit_init_parent_test(test_case, &test); 03806177fa4cbb Marie Zhussupova 2025-07-29 709 529534e8cba3e6 Rae Moar 2023-07-25 710 if (test_case->status == KUNIT_SKIPPED) { 529534e8cba3e6 Rae Moar 2023-07-25 711 /* Test marked as skip */ 529534e8cba3e6 Rae Moar 2023-07-25 712 test.status = KUNIT_SKIPPED; 529534e8cba3e6 Rae Moar 2023-07-25 713 kunit_update_stats(¶m_stats, test.status); 44c50ed8e59936 Marie Zhussupova 2025-07-29 @714 } else if (!test_case->generate_params && !test.params_data.params) { ^^^^^^^^^^^^^^^^^^^^^^^^^^ Imagine ->generate_parms is NULL but test.params_data.params is non-NULL.
37dbb4c7c7442d David Gow 2021-11-02 715 /* Non-parameterised test. */ 529534e8cba3e6 Rae Moar 2023-07-25 716 test_case->status = KUNIT_SKIPPED; 37dbb4c7c7442d David Gow 2021-11-02 717 kunit_run_case_catch_errors(suite, test_case, &test); 37dbb4c7c7442d David Gow 2021-11-02 718 kunit_update_stats(¶m_stats, test.status); 03806177fa4cbb Marie Zhussupova 2025-07-29 719 } else if (test_case->status != KUNIT_FAILURE) { fadb08e7c7501e Arpitha Raghunandan 2020-11-16 720 /* Get initial param. */ fadb08e7c7501e Arpitha Raghunandan 2020-11-16 721 param_desc[0] = '\0'; 8631cd2cf5fbf2 Marie Zhussupova 2025-07-29 722 /* TODO: Make generate_params try-catch */ 13ee0c64bd88a3 Marie Zhussupova 2025-07-29 @723 curr_param = test_case->generate_params(&test, NULL, param_desc); ^^^^^^^^^^^^^^^^^^^^^^^^^^ Then this could crash.
I suspect that this is fine, but I bet that in the previous condition, just testing one would probably have been sufficient or maybe we could have change && to ||.
Hello Dan,
My apologies for the HTML version of this email earlier. Here is the plain text version.
As of now, test.params_data.params can only be populated in a param_init function, which can only be used if we register the test case with a KUNIT_CASE_PARAM_WITH_INIT macro. That macro auto populates test_case->generate_params with a function called kunit_get_next_param_and_desc() (which iterates over the parameter array) if the test user didn't provide their own generator function. So, there shouldn't be a case where test_case->generate_params is NULL but test.params_data.params is NON-NULL.
However, to be robust, we could add a NULL check before calling test_case->generate_params on line 723.
Thank you! -Marie
529534e8cba3e6 Rae Moar 2023-07-25 724 test_case->status = KUNIT_SKIPPED; 6c738b52316c58 Rae Moar 2022-11-23 725 kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT 6c738b52316c58 Rae Moar 2022-11-23 726 "KTAP version 1\n"); 44b7da5fcd4c99 David Gow 2021-11-02 727 kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT 44b7da5fcd4c99 David Gow 2021-11-02 728 "# Subtest: %s", test_case->name); fadb08e7c7501e Arpitha Raghunandan 2020-11-16 729 8631cd2cf5fbf2 Marie Zhussupova 2025-07-29 730 while (curr_param) {
-- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
KUnit parameterized tests currently support two primary methods for getting parameters:
- Defining custom logic within a `generate_params` function.
- Using the KUNIT_ARRAY_PARAM and KUNIT_ARRAY_PARAM_DESC macros with pre-defined static arrays.
These methods present limitations when dealing with dynamically generated parameter arrays, or in scenarios where populating parameters sequentially via `generate_params` is inefficient or overly complex.
This patch addresses these limitations by adding a new `params_data` field to `struct kunit`, of the type `kunit_params`. The struct `kunit_params` is designed to store the parameter array itself, along with essential metadata including the parameter count, parameter size, and a `get_description` function for providing custom descriptions for individual parameters.
The `params_data` field can be populated by calling the new `kunit_register_params_array` macro from within a `param_init` function. By attaching the parameter array directly to the parent kunit test instance, these parameters can be iterated over in kunit_run_tests() behind the scenes.
This modification provides greater flexibility to the KUnit framework, allowing testers to easily register and utilize both dynamic and static parameter arrays.
Signed-off-by: Marie Zhussupova marievic@google.com
A few thoughts: - Refactoring out the parameter data into struct kunit_params makes sense, particularly given the new features. - We now have 3 APIs to handle parameters (all of which use a generator function behind the scenes): a static array (which uses a macro to invent a custom generator function), a dynamic array (which uses kunit_get_next_param_and_desc as a -- possibly implied -- generator function), and a user-provided generator function. I don't think that's a problem, but maybe these can be brought slightly closer together in implementation? - Should users pass NULL or kunit_get_next_param_and_desc as the gen_params function if they're using a dynamic array? We seem to support both here, but it's unsure which is preferred. My gut feeling is that we should either totally pick one or the other: if we say NULL implies kunit_get_next_param_and_desc, we should just set kunit_params.generate_params to NULL, and only check when we call it; if we suggest people use kunit_get_next_param_and_desc, we should not support NULL at all (it goes back to implying a non-parameterised test). My gut preference is for the latter, though it's not without downsides.
More comments below.
Cheers, -- David
include/kunit/test.h | 54 ++++++++++++++++++++++++++++++++++++++++---- lib/kunit/test.c | 26 ++++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 4ba65dc35710..9143f0e22323 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -245,7 +245,8 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) */ #define KUNIT_CASE_PARAM_WITH_INIT(test_name, gen_params, init, exit) \ { .run_case = test_name, .name = #test_name, \
.generate_params = gen_params, \
.generate_params = (gen_params) \
?: kunit_get_next_param_and_desc, \
A part of me wonders whether we need this, or should just tell people to pass kunit_get_next_param_and_desc manually, rather than NULL.
(If so, _maybe_ it'd make sense to rename it to something more obvious, like kunit_get_array_param?)
.param_init = init, .param_exit = exit, \ .module_name = KBUILD_MODNAME}
@@ -294,6 +295,21 @@ struct kunit_suite_set { struct kunit_suite * const *end; };
+/* Stores the pointer to the parameter array and its metadata. */ +struct kunit_params {
/*
* Reference to the parameter array for the parameterized tests. This
* is NULL if a parameter array wasn't directly passed to the
* parent kunit struct via the kunit_register_params_array macro.
*/
Would it make sense to update KUNIT_ARRAY_PARAM(,_DESC) to use this member?
I'd imagine we could get rid of the custom generated function for KUNIT_ARRAY_PARAM, though we'd still need it for the _DESC variant.
(Unless you want to go overboard, add a new offset_of_description member, and make it possible to pass a description field even with dynamic arrays. Though I don't know of anyone who actually needs that.)
The existing "generated function" method is still correct (and maybe slightly faster, though if this is a serious bottleneck, I'll eat my hat), so I don't know that this change is necessary. Maybe worth trying, or looking at for a follow-up.
const void *params;
/* Reference to a function that gets the description of a parameter. */
void (*get_description)(const void *param, char *desc);
int num_params;
size_t elem_size;
+};
/**
- struct kunit - represents a running instance of a test.
@@ -302,12 +318,14 @@ struct kunit_suite_set {
- @parent: for user to store data that they want to shared across
parameterized tests. Typically, the data is provided in
the param_init function (see &struct kunit_case).
- @params_data: for users to directly store the parameter array.
- Used to store information about the current context under which the test
- is running. Most of this data is private and should only be accessed
- indirectly via public functions; the two exceptions are @priv and @parent
- which can be used by the test writer to store arbitrary data or data that is
- available to all parameter test executions, respectively.
- indirectly via public functions. There are three exceptions to this: @priv,
- @parent, and @params_data. These members can be used by the test writer to
- store arbitrary data, data available to all parameter test executions, and
*/
- the parameter array, respectively.
struct kunit { void *priv; @@ -316,6 +334,8 @@ struct kunit { * during parameterized testing. */ struct kunit *parent;
/* Stores the params array and all data related to it. */
struct kunit_params params_data; /* private: internal use only. */ const char *name; /* Read only after initialization! */
@@ -386,6 +406,8 @@ void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set, struct kunit_suite_set suite_set);
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc);
#if IS_BUILTIN(CONFIG_KUNIT) int kunit_run_all_tests(void); #else @@ -1735,6 +1757,30 @@ do { \ return NULL; \ }
+/**
- kunit_register_params_array() - Register parameters for a KUnit test.
- @test: The KUnit test structure to which parameters will be added.
- @params_arr: An array of test parameters.
- @param_cnt: Number of parameters.
- @get_desc: A pointer to a function that generates a string description for
- a given parameter element.
- This macro initializes the @test's parameter array data, storing information
- including the parameter array, its count, the element size, and the parameter
- description function within `test->params_data`. KUnit's built-in
- `kunit_get_next_param_and_desc` function will automatically read this
- data when a custom `generate_params` function isn't provided.
- */
+#define kunit_register_params_array(test, params_arr, param_cnt, get_desc) \
do { \
struct kunit *_test = (test); \
const typeof((params_arr)[0]) * _params_ptr = &(params_arr)[0]; \
_test->params_data.params = _params_ptr; \
_test->params_data.num_params = (param_cnt); \
_test->params_data.elem_size = sizeof(*_params_ptr); \
_test->params_data.get_description = (get_desc); \
} while (0)
// TODO(dlatypov@google.com): consider eventually migrating users to explicitly // include resource.h themselves if they need it. #include <kunit/resource.h> diff --git a/lib/kunit/test.c b/lib/kunit/test.c index f50ef82179c4..2f4b7087db3f 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -337,6 +337,13 @@ void __kunit_do_failed_assertion(struct kunit *test, } EXPORT_SYMBOL_GPL(__kunit_do_failed_assertion);
+static void __kunit_init_params(struct kunit *test) +{
test->params_data.params = NULL;
test->params_data.num_params = 0;
test->params_data.elem_size = 0;
+}
void kunit_init_test(struct kunit *test, const char *name, struct string_stream *log) { spin_lock_init(&test->lock); @@ -347,6 +354,7 @@ void kunit_init_test(struct kunit *test, const char *name, struct string_stream string_stream_clear(log); test->status = KUNIT_SUCCESS; test->status_comment[0] = '\0';
__kunit_init_params(test);
} EXPORT_SYMBOL_GPL(kunit_init_test);
@@ -641,6 +649,22 @@ static void kunit_accumulate_stats(struct kunit_result_stats *total, total->total += add.total; }
+const void *kunit_get_next_param_and_desc(struct kunit *test, const void *prev, char *desc)
As Rae points out, this needs to be exported if we want this to work in modules.
(Unless you want to go all of the way down the "generate_params == NULL implies a parameter array" route, in which case we could get away with it.)
+{
struct kunit_params *params_arr = &test->params_data;
const void *param;
if (test->param_index < params_arr->num_params) {
param = (char *)params_arr->params
+ test->param_index * params_arr->elem_size;
if (params_arr->get_description)
params_arr->get_description(param, desc);
return param;
}
return NULL;
+}
static void __kunit_init_parent_test(struct kunit_case *test_case, struct kunit *test) { if (test_case->param_init) { @@ -687,7 +711,7 @@ int kunit_run_tests(struct kunit_suite *suite) /* Test marked as skip */ test.status = KUNIT_SKIPPED; kunit_update_stats(¶m_stats, test.status);
} else if (!test_case->generate_params) {
} else if (!test_case->generate_params && !test.params_data.params) {
As above, this really depends on if we want generate_params == NULL to imply that we're using a parameter array, or if it should imply that we're a non-parameterised test.
/* Non-parameterised test. */ test_case->status = KUNIT_SKIPPED; kunit_run_case_catch_errors(suite, test_case, &test);
-- 2.50.1.552.g942d659e1b-goog
Add `example_params_test_with_init` to illustrate how to manage shared resources across parameterized KUnit tests. This example showcases the use of the new `param_init` function and its registration to a test using the `KUNIT_CASE_PARAM_WITH_INIT` macro.
Additionally, the test demonstrates: - How to directly assign a static parameter array to a test via `kunit_register_params_array`. - Leveraging the Resource API for test resource management.
Signed-off-by: Marie Zhussupova marievic@google.com --- lib/kunit/kunit-example-test.c | 112 +++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 3056d6bc705d..5bf559e243f6 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -277,6 +277,116 @@ static void example_slow_test(struct kunit *test) KUNIT_EXPECT_EQ(test, 1 + 1, 2); }
+/* + * This custom function allocates memory for the kunit_resource data field. + * The function is passed to kunit_alloc_resource() and executed once + * by the internal helper __kunit_add_resource(). + */ +static int example_resource_init(struct kunit_resource *res, void *context) +{ + int *info = kmalloc(sizeof(*info), GFP_KERNEL); + + if (!info) + return -ENOMEM; + *info = *(int *)context; + res->data = info; + return 0; +} + +/* + * This function deallocates memory for the 'kunit_resource' data field. + * The function is passed to kunit_alloc_resource() and automatically + * executes within kunit_release_resource() when the resource's reference + * count, via kunit_put_resource(), drops to zero. KUnit uses reference + * counting to ensure that resources are not freed prematurely. + */ +static void example_resource_free(struct kunit_resource *res) +{ + kfree(res->data); +} + +/* + * This match function is invoked by kunit_find_resource() to locate + * a test resource based on defined criteria. The current example + * uniquely identifies the resource by its free function; however, + * alternative custom criteria can be implemented. Refer to + * lib/kunit/platform.c and lib/kunit/static_stub.c for further examples. + */ +static bool example_resource_alloc_match(struct kunit *test, + struct kunit_resource *res, + void *match_data) +{ + return res->data && res->free == example_resource_free; +} + +/* + * This is an example of a function that provides a description for each of the + * parameters. + */ +static void example_param_array_get_desc(const void *p, char *desc) +{ + const struct example_param *param = p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "example check if %d is less than or equal to 3", param->value); +} + +/* + * Initializes the parent kunit struct for parameterized KUnit tests. + * This function enables sharing resources across all parameterized + * tests by adding them to the `parent` kunit test struct. It also supports + * registering either static or dynamic arrays of test parameters. + */ +static int example_param_init(struct kunit *test) +{ + int ctx = 3; /* Data to be stored. */ + int arr_size = ARRAY_SIZE(example_params_array); + + /* + * This allocates a struct kunit_resource, sets its data field to + * ctx, and adds it to the kunit struct's resources list. Note that + * this is test managed so we don't need to have a custom exit function + * to free it. + */ + void *data = kunit_alloc_resource(test, example_resource_init, example_resource_free, + GFP_KERNEL, &ctx); + + if (!data) + return -ENOMEM; + /* Pass the static param array information to the parent struct kunit. */ + kunit_register_params_array(test, example_params_array, arr_size, + example_param_array_get_desc); + return 0; +} + +/* + * This is an example of a parameterized test that uses shared resources + * available from the struct kunit parent field of the kunit struct. + */ +static void example_params_test_with_init(struct kunit *test) +{ + int threshold; + struct kunit_resource *res; + const struct example_param *param = test->param_value; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + /* Here we access the parent pointer of the test to find the shared resource. */ + res = kunit_find_resource(test->parent, example_resource_alloc_match, NULL); + + KUNIT_ASSERT_NOT_NULL(test, res); + + /* Since the data field in kunit_resource is a void pointer we need to typecast it. */ + threshold = *((int *)res->data); + + /* Assert that the parameter is less than or equal to a certain threshold. */ + KUNIT_ASSERT_LE(test, param->value, threshold); + + /* This decreases the reference count after calling kunit_find_resource(). */ + kunit_put_resource(res); +} + /* * Here we make a list of all the test cases we want to add to the test suite * below. @@ -296,6 +406,8 @@ static struct kunit_case example_test_cases[] = { KUNIT_CASE(example_static_stub_using_fn_ptr_test), KUNIT_CASE(example_priv_test), KUNIT_CASE_PARAM(example_params_test, example_gen_params), + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, NULL, + example_param_init, NULL), KUNIT_CASE_SLOW(example_slow_test), {} };
Hi Marie,
kernel test robot noticed the following build errors:
[auto build test ERROR on shuah-kselftest/kunit] [also build test ERROR on shuah-kselftest/kunit-fixes drm-xe/drm-xe-next linus/master v6.16 next-20250730] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Marie-Zhussupova/kunit-Add-pa... base: https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit patch link: https://lore.kernel.org/r/20250729193647.3410634-8-marievic%40google.com patch subject: [PATCH 7/9] kunit: Add example parameterized test with shared resources and direct static parameter array setup config: arc-randconfig-001-20250730 (https://download.01.org/0day-ci/archive/20250730/202507302042.9Aw3rrmW-lkp@i...) compiler: arc-linux-gcc (GCC) 8.5.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250730/202507302042.9Aw3rrmW-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202507302042.9Aw3rrmW-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
ERROR: modpost: "kunit_get_next_param_and_desc" [lib/kunit/kunit-example-test.ko] undefined!
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
Add `example_params_test_with_init` to illustrate how to manage shared resources across parameterized KUnit tests. This example showcases the use of the new `param_init` function and its registration to a test using the `KUNIT_CASE_PARAM_WITH_INIT` macro.
Additionally, the test demonstrates:
- How to directly assign a static parameter array to a test via `kunit_register_params_array`.
- Leveraging the Resource API for test resource management.
Signed-off-by: Marie Zhussupova marievic@google.com
Thanks for writing some examples! This is great, and makes the rest of the series much easier to understand.
(It also reminds me how much I hate the verbose parts of the resource API, but it's definitely out of scope to refactor that here. :-))
It does seem like this is a lot of effort to go through for one shared integer, though. In the real world, I'd suggest using kunit->parent->priv here. As an example, though, it's fine (though maybe using a named resource or even kunit_kzalloc() or similar would give a better example of how convenient this could be.
It's also not entirely clear why we're using kunit_register_params_array() for a static array, when KUNIT_ARRAY_PARAM() exists. (This is clearly because the latter doesn't support init functions; and I see why we don't necessarily want to make the number of macros explode through adding KUNIT_ARRAY_PARAM_WITH_INIT() et al, but maybe we should note that in the commit description, either here or before.)
Actual test looks fine, though:
Reviewed-by: David Gow davidgow@google.com
Cheers, -- David
lib/kunit/kunit-example-test.c | 112 +++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 3056d6bc705d..5bf559e243f6 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -277,6 +277,116 @@ static void example_slow_test(struct kunit *test) KUNIT_EXPECT_EQ(test, 1 + 1, 2); }
+/*
- This custom function allocates memory for the kunit_resource data field.
- The function is passed to kunit_alloc_resource() and executed once
- by the internal helper __kunit_add_resource().
- */
+static int example_resource_init(struct kunit_resource *res, void *context) +{
int *info = kmalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
*info = *(int *)context;
res->data = info;
return 0;
+}
+/*
- This function deallocates memory for the 'kunit_resource' data field.
- The function is passed to kunit_alloc_resource() and automatically
- executes within kunit_release_resource() when the resource's reference
- count, via kunit_put_resource(), drops to zero. KUnit uses reference
- counting to ensure that resources are not freed prematurely.
- */
+static void example_resource_free(struct kunit_resource *res) +{
kfree(res->data);
+}
+/*
- This match function is invoked by kunit_find_resource() to locate
- a test resource based on defined criteria. The current example
- uniquely identifies the resource by its free function; however,
- alternative custom criteria can be implemented. Refer to
- lib/kunit/platform.c and lib/kunit/static_stub.c for further examples.
- */
+static bool example_resource_alloc_match(struct kunit *test,
struct kunit_resource *res,
void *match_data)
+{
return res->data && res->free == example_resource_free;
+}
+/*
- This is an example of a function that provides a description for each of the
- parameters.
- */
+static void example_param_array_get_desc(const void *p, char *desc) +{
const struct example_param *param = p;
snprintf(desc, KUNIT_PARAM_DESC_SIZE,
"example check if %d is less than or equal to 3", param->value);
+}
+/*
- Initializes the parent kunit struct for parameterized KUnit tests.
- This function enables sharing resources across all parameterized
- tests by adding them to the `parent` kunit test struct. It also supports
- registering either static or dynamic arrays of test parameters.
- */
+static int example_param_init(struct kunit *test) +{
int ctx = 3; /* Data to be stored. */
int arr_size = ARRAY_SIZE(example_params_array);
/*
* This allocates a struct kunit_resource, sets its data field to
* ctx, and adds it to the kunit struct's resources list. Note that
* this is test managed so we don't need to have a custom exit function
* to free it.
*/
void *data = kunit_alloc_resource(test, example_resource_init, example_resource_free,
GFP_KERNEL, &ctx);
if (!data)
return -ENOMEM;
/* Pass the static param array information to the parent struct kunit. */
kunit_register_params_array(test, example_params_array, arr_size,
example_param_array_get_desc);
return 0;
+}
+/*
- This is an example of a parameterized test that uses shared resources
- available from the struct kunit parent field of the kunit struct.
- */
+static void example_params_test_with_init(struct kunit *test) +{
int threshold;
struct kunit_resource *res;
const struct example_param *param = test->param_value;
/* By design, param pointer will not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, param);
/* Here we access the parent pointer of the test to find the shared resource. */
res = kunit_find_resource(test->parent, example_resource_alloc_match, NULL);
KUNIT_ASSERT_NOT_NULL(test, res);
/* Since the data field in kunit_resource is a void pointer we need to typecast it. */
threshold = *((int *)res->data);
/* Assert that the parameter is less than or equal to a certain threshold. */
KUNIT_ASSERT_LE(test, param->value, threshold);
/* This decreases the reference count after calling kunit_find_resource(). */
kunit_put_resource(res);
+}
/*
- Here we make a list of all the test cases we want to add to the test suite
- below.
@@ -296,6 +406,8 @@ static struct kunit_case example_test_cases[] = { KUNIT_CASE(example_static_stub_using_fn_ptr_test), KUNIT_CASE(example_priv_test), KUNIT_CASE_PARAM(example_params_test, example_gen_params),
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, NULL,
example_param_init, NULL), KUNIT_CASE_SLOW(example_slow_test), {}
};
2.50.1.552.g942d659e1b-goog
Introduce `example_params_test_with_init_dynamic_arr`. This new KUnit test demonstrates directly assigning a dynamic parameter array using the `kunit_register_params_array` macro. It highlights the use of `param_init` and `param_exit` for proper initialization and cleanup, and their registration to the test with `KUNIT_CASE_PARAM_WITH_INIT`.
Signed-off-by: Marie Zhussupova marievic@google.com --- lib/kunit/kunit-example-test.c | 95 ++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+)
diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 5bf559e243f6..3ab121d81bf6 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -387,6 +387,98 @@ static void example_params_test_with_init(struct kunit *test) kunit_put_resource(res); }
+/* + * Helper function to create a parameter array of Fibonacci numbers. This example + * highlights a parameter generation scenario that is: + * 1. Not feasible to fully pre-generate at compile time. + * 2. Challenging to implement with a standard 'generate_params' function, + * as it typically only provides the immediately 'prev' parameter, while + * Fibonacci requires access to two preceding values for calculation. + */ +static void *make_fibonacci_params(int seq_size) +{ + int *seq; + + if (seq_size <= 0) + return NULL; + + seq = kmalloc_array(seq_size, sizeof(int), GFP_KERNEL); + + if (!seq) + return NULL; + + if (seq_size >= 1) + seq[0] = 0; + if (seq_size >= 2) + seq[1] = 1; + for (int i = 2; i < seq_size; i++) + seq[i] = seq[i - 1] + seq[i - 2]; + return seq; +} + +/* + * This is an example of a function that provides a description for each of the + * parameters. + */ +static void example_param_dynamic_arr_get_desc(const void *p, char *desc) +{ + const int *fib_num = p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "fibonacci param: %d", *fib_num); +} + +/* + * Example of a parameterized test init function that registers a dynamic array. + */ +static int example_param_init_dynamic_arr(struct kunit *test) +{ + int seq_size = 6; + int *fibonacci_params = make_fibonacci_params(seq_size); + + if (!fibonacci_params) + return -ENOMEM; + + /* + * Passes the dynamic parameter array information to the parent struct kunit. + * The array and its metadata will be stored in test->parent->params_data. + * 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; +} + +/** + * Function to clean up the parameterized test's parent kunit struct if + * there were custom allocations. + */ +static void example_param_exit_dynamic_arr(struct kunit *test) +{ + /* + * We allocated this array, so we need to free it. + * Since the parent parameter instance is passed here, + * we can directly access the array via `test->params_data.params` + * instead of `test->parent->params_data.params`. + */ + kfree(test->params_data.params); +} + +/* + * Example of test that uses the registered dynamic array to perform assertions + * and expectations. + */ +static void example_params_test_with_init_dynamic_arr(struct kunit *test) +{ + const int *param = test->param_value; + int param_val; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + param_val = *param; + KUNIT_EXPECT_EQ(test, param_val - param_val, 0); +} + /* * Here we make a list of all the test cases we want to add to the test suite * below. @@ -408,6 +500,9 @@ static struct kunit_case example_test_cases[] = { KUNIT_CASE_PARAM(example_params_test, example_gen_params), KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, NULL, example_param_init, NULL), + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr, NULL, + example_param_init_dynamic_arr, + example_param_exit_dynamic_arr), KUNIT_CASE_SLOW(example_slow_test), {} };
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
Introduce `example_params_test_with_init_dynamic_arr`. This new KUnit test demonstrates directly assigning a dynamic parameter array using the `kunit_register_params_array` macro. It highlights the use of `param_init` and `param_exit` for proper initialization and cleanup, and their registration to the test with `KUNIT_CASE_PARAM_WITH_INIT`.
Signed-off-by: Marie Zhussupova marievic@google.com
This is an excellent example, thanks. (I much prefer it to the previous one. In fact, if we could use some shared resource in this, we could probably get rid of the previous one entirely.)
Reviewed-by: David Gow davidgow@google.com
Cheers, -- David
lib/kunit/kunit-example-test.c | 95 ++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+)
diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 5bf559e243f6..3ab121d81bf6 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -387,6 +387,98 @@ static void example_params_test_with_init(struct kunit *test) kunit_put_resource(res); }
+/*
- Helper function to create a parameter array of Fibonacci numbers. This example
- highlights a parameter generation scenario that is:
- Not feasible to fully pre-generate at compile time.
- Challenging to implement with a standard 'generate_params' function,
- as it typically only provides the immediately 'prev' parameter, while
- Fibonacci requires access to two preceding values for calculation.
- */
+static void *make_fibonacci_params(int seq_size) +{
int *seq;
if (seq_size <= 0)
return NULL;
seq = kmalloc_array(seq_size, sizeof(int), GFP_KERNEL);
If we used kunit_kmalloc_array here (we'd need to pass test through somehow, though), we could have a good example of a shared resource here.
if (!seq)
return NULL;
if (seq_size >= 1)
seq[0] = 0;
if (seq_size >= 2)
seq[1] = 1;
for (int i = 2; i < seq_size; i++)
seq[i] = seq[i - 1] + seq[i - 2];
return seq;
+}
+/*
- This is an example of a function that provides a description for each of the
- parameters.
- */
+static void example_param_dynamic_arr_get_desc(const void *p, char *desc)
Seeing this makes me wonder whether we should pass struct *kunit to the get_desc function, too.
Thoughts?
+{
const int *fib_num = p;
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "fibonacci param: %d", *fib_num);
+}
+/*
- Example of a parameterized test init function that registers a dynamic array.
- */
+static int example_param_init_dynamic_arr(struct kunit *test) +{
int seq_size = 6;
int *fibonacci_params = make_fibonacci_params(seq_size);
if (!fibonacci_params)
return -ENOMEM;
/*
* Passes the dynamic parameter array information to the parent struct kunit.
* The array and its metadata will be stored in test->parent->params_data.
* 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;
+}
+/**
- Function to clean up the parameterized test's parent kunit struct if
- there were custom allocations.
- */
+static void example_param_exit_dynamic_arr(struct kunit *test) +{
/*
* We allocated this array, so we need to free it.
* Since the parent parameter instance is passed here,
* we can directly access the array via `test->params_data.params`
* instead of `test->parent->params_data.params`.
*/
kfree(test->params_data.params);
If we used kunit_kmalloc_array above, though, we'd miss this good example. So I'm torn...
(I suppose we could use kunit_kfree() anyway, though, and just rely on the shared resource management for early aborts.)
+}
+/*
- Example of test that uses the registered dynamic array to perform assertions
- and expectations.
- */
+static void example_params_test_with_init_dynamic_arr(struct kunit *test) +{
const int *param = test->param_value;
int param_val;
/* By design, param pointer will not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, param);
param_val = *param;
KUNIT_EXPECT_EQ(test, param_val - param_val, 0);
+}
/*
- Here we make a list of all the test cases we want to add to the test suite
- below.
@@ -408,6 +500,9 @@ static struct kunit_case example_test_cases[] = { KUNIT_CASE_PARAM(example_params_test, example_gen_params), KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, NULL, example_param_init, NULL),
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr, NULL,
example_param_init_dynamic_arr,
example_param_exit_dynamic_arr), KUNIT_CASE_SLOW(example_slow_test), {}
};
2.50.1.552.g942d659e1b-goog
-Update the KUnit documentation to explain the concept of a parent parameterized test. -Add examples demonstrating different ways of passing parameters to parameterized tests and how to manage shared resources between them.
Signed-off-by: Marie Zhussupova marievic@google.com --- Documentation/dev-tools/kunit/usage.rst | 455 +++++++++++++++++++++++- 1 file changed, 449 insertions(+), 6 deletions(-)
diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst index 066ecda1dd98..be1d656053cf 100644 --- a/Documentation/dev-tools/kunit/usage.rst +++ b/Documentation/dev-tools/kunit/usage.rst @@ -542,11 +542,21 @@ 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 efficiently and elegantly validate a test case against a variety of inputs, +KUnit also provides a parameterized testing framework. This feature formalizes +and extends the concept of table-driven tests discussed previously, offering +a more integrated and flexible way to handle multiple test scenarios with +minimal code duplication. + +Passing Parameters to the Test Cases +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +There are three main ways to provide the parameters to a test case: + +Array Parameter Macros (``KUNIT_ARRAY_PARAM`` or ``KUNIT_ARRAY_PARAM_DESC``): + 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 +565,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 +600,439 @@ By reusing the same ``cases`` array from above, we can write the test as a {} };
+Custom Parameter Generator (``generate_params``): + You can pass your own ``generate_params`` function to the ``KUNIT_CASE_PARAM`` + or ``KUNIT_CASE_PARAM_WITH_INIT`` macros. This function is responsible for + generating parameters one by one. It receives the previously generated parameter + as the ``prev`` argument (which is ``NULL`` on the first call) and can also + access any context available from the parent ``struct kunit`` passed as the + ``test`` argument. KUnit calls this function repeatedly until it returns + ``NULL``. 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 managed," meaning it's automatically cleaned up at + // the end of each parameter execution. + 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), + {} + }; + +Direct Registration in Parameter Init Function (using ``kunit_register_params_array``): + For more complex scenarios, you can directly register a parameter array with + a test case instead of using a ``generate_params`` function. This is done by + passing the array to the ``kunit_register_params_array`` macro within an + initialization function for the parameterized test series + (i.e., a function named ``param_init``). To better understand this mechanism + please refer to the "Adding Shared Resources" section below. + + This method supports both dynamically built and static arrays. + + As the following code shows, the ``example_param_init_dynamic_arr`` function + utilizes ``make_fibonacci_params`` to create a dynamic array, which is then + registered using ``kunit_register_params_array``. The corresponding exit + function, ``example_param_exit``, is responsible for freeing this dynamically + allocated params array after the parameterized test series ends. + +.. code-block:: c + + /* + * Helper function to create a parameter array of Fibonacci numbers. This example + * highlights a parameter generation scenario that is: + * 1. Not feasible to fully pre-generate at compile time. + * 2. Challenging to implement with a standard 'generate_params' function, + * as it typically only provides the immediately 'prev' parameter, while + * Fibonacci requires access to two preceding values for calculation. + */ + static void *make_fibonacci_params(int seq_size) + { + int *seq; + + if (seq_size <= 0) + return NULL; + + seq = kmalloc_array(seq_size, sizeof(int), GFP_KERNEL); + + if (!seq) + return NULL; + + if (seq_size >= 1) + seq[0] = 0; + if (seq_size >= 2) + seq[1] = 1; + for (int i = 2; i < seq_size; i++) + seq[i] = seq[i - 1] + seq[i - 2]; + return seq; + } + + // This is an example of a function that provides a description for each of the + // parameters. + static void example_param_dynamic_arr_get_desc(const void *p, char *desc) + { + const int *fib_num = p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "fibonacci param: %d", *fib_num); + } + + // Example of a parameterized test init function that registers a dynamic array. + static int example_param_init_dynamic_arr(struct kunit *test) + { + int seq_size = 6; + int *fibonacci_params = make_fibonacci_params(seq_size); + + if (!fibonacci_params) + return -ENOMEM; + + /* + * Passes the dynamic parameter array information to the parent struct kunit. + * The array and its metadata will be stored in test->parent->params_data. + * 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; + } + + // Function to clean up the parameterized test's parent kunit struct if + // there were custom allocations. + static void example_param_exit_dynamic_arr(struct kunit *test) + { + /* + * We allocated this array, so we need to free it. + * Since the parent parameter instance is passed here, + * we can directly access the array via `test->params_data.params` + * instead of `test->parent->params_data.params`. + */ + kfree(test->params_data.params); + } + + /* + * Example of test that uses the registered dynamic array to perform assertions + * and expectations. + */ + static void example_params_test_with_init_dynamic_arr(struct kunit *test) + { + const int *param = test->param_value; + int param_val; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + param_val = *param; + KUNIT_EXPECT_EQ(test, param_val - param_val, 0); + } + + static struct kunit_case example_tests[] = { + // The NULL here stands in for the generate_params function + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr, NULL, + example_param_init_dynamic_arr, + example_param_exit_dynamic_arr), + {} + }; + + +Adding Shared Resources +^^^^^^^^^^^^^^^^^^^^^^^ +All parameterized test executions in this framework have a parent test of type +``struct kunit``. This parent is not used to execute any test logic itself; +instead, it serves as a container for shared context that can be accessed by +all its individual test executions (or parameters). Therefore, each individual +test execution holds a pointer to this parent, accessible via a field named +``parent``. + +It's possible to add resources to share between the individual test executions +within a parameterized test series by using the ``KUNIT_CASE_PARAM_WITH_INIT`` +macro, to which you pass custom ``param_init`` and ``param_exit`` functions. +These functions run once before and once after the entire parameterized test +series, respectively. The ``param_init`` function can be used for adding any +resources to the resources field of a parent test and also provide an additional +way of setting the parameter array. The ``param_exit`` function can be used +release any resources that were not test managed i.e. not automatically cleaned +up after the test ends. + +.. note:: + If both a ``generate_params`` function is passed to ``KUNIT_CASE_PARAM_WITH_INIT`` + and an array is registered via ``kunit_register_params_array`` in + ``param_init``, the ``generate_params`` function will be used to get + the parameters. + +Both ``param_init`` and ``param_exit`` are passed the parent instance of a test +(parent ``struct kunit``) behind the scenes. However, the test case function +receives the individual instance of a test for each parameter. Therefore, to +manage and access shared resources from within a test case function, you must use +``test->parent``. + +.. note:: + The ``suite->init()`` function, which runs before each parameter execution, + receives the individual instance of a test for each parameter. Therefore, + resources set up in ``suite->init()`` are reset for each individual + parameterized test execution and are only visible within that specific test. + +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). + +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. + +.. code-block:: c + + /* An example parameter array. */ + static const struct example_param { + int value; + } example_params_array[] = { + { .value = 3, }, + { .value = 2, }, + { .value = 1, }, + { .value = 0, }, + }; + + /* + * This custom function allocates memory for the kunit_resource data field. + * The function is passed to kunit_alloc_resource() and executed once + * by the internal helper __kunit_add_resource(). + */ + static int example_resource_init(struct kunit_resource *res, void *context) + { + int *info = kmalloc(sizeof(*info), GFP_KERNEL); + + if (!info) + return -ENOMEM; + *info = *(int *)context; + res->data = info; + return 0; + } + + /* + * This function deallocates memory for the 'kunit_resource' data field. + * The function is passed to kunit_alloc_resource() and automatically + * executes within kunit_release_resource() when the resource's reference + * count, via kunit_put_resource(), drops to zero. KUnit uses reference + * counting to ensure that resources are not freed prematurely. + */ + static void example_resource_free(struct kunit_resource *res) + { + kfree(res->data); + } + + /* + * This match function is invoked by kunit_find_resource() to locate + * a test resource based on defined criteria. The current example + * uniquely identifies the resource by its free function; however, + * alternative custom criteria can be implemented. Refer to + * lib/kunit/platform.c and lib/kunit/static_stub.c for further examples. + */ + static bool example_resource_alloc_match(struct kunit *test, + struct kunit_resource *res, + void *match_data) + { + return res->data && res->free == example_resource_free; + } + + /* + * This is an example of a function that provides a description for each of the + * parameters. + */ + static void example_param_array_get_desc(const void *p, char *desc) + { + const struct example_param *param = p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "example check if %d is less than or equal to 3", param->value); + } + + /* + * Initializes the parent kunit struct for parameterized KUnit tests. + * This function enables sharing resources across all parameterized + * tests by adding them to the `parent` kunit test struct. It also supports + * registering either static or dynamic arrays of test parameters. + */ + static int example_param_init(struct kunit *test) + { + int ctx = 3; /* Data to be stored. */ + int arr_size = ARRAY_SIZE(example_params_array); + + /* + * This allocates a struct kunit_resource, sets its data field to + * ctx, and adds it to the kunit struct's resources list. Note that + * this is test managed so we don't need to have a custom exit function + * to free it. + */ + void *data = kunit_alloc_resource(test, example_resource_init, example_resource_free, + GFP_KERNEL, &ctx); + + if (!data) + return -ENOMEM; + /* Pass the static param array information to the parent struct kunit. */ + kunit_register_params_array(test, example_params_array, arr_size, + example_param_array_get_desc); + return 0; + } + + /* + * This is an example of a parameterized test that uses shared resources + * available from the struct kunit parent field of the kunit struct. + */ + static void example_params_test_with_init(struct kunit *test) + { + int threshold; + struct kunit_resource *res; + const struct example_param *param = test->param_value; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + /* Here we need to access the parent pointer of the test to find the shared resource. */ + res = kunit_find_resource(test->parent, example_resource_alloc_match, NULL); + + KUNIT_ASSERT_NOT_NULL(test, res); + + /* Since the data field in kunit_resource is a void pointer we need to typecast it. */ + threshold = *((int *)res->data); + + /* Assert that the parameter is less than or equal to a certain threshold. */ + KUNIT_ASSERT_LE(test, param->value, threshold); + + /* This decreases the reference count after calling kunit_find_resource(). */ + kunit_put_resource(res); + } + + + static struct kunit_case example_tests[] = { + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, NULL, + example_param_init, NULL), + {} + }; + +As an alternative to using the KUnit Resource API for shared resources, you can +place them in ``test->parent->priv``. It can store data that needs to persist +and be accessible across all executions within a parameterized test series. + +As stated previously ``param_init`` and ``param_exit`` receive the parent +``struct kunit`` instance. So, you can directly use ``test->priv`` within them +to manage shared resources. However, from within the test case function, you must +navigate up to the parent i.e. use ``test->parent->priv`` to access those same +resources. + +The resources placed in ``test->parent-priv`` will also need to be allocated in +memory to persist across the parameterized tests executions. If memory is +allocated using the memory allocation APIs provided by KUnit (described more in +the section below), you will not need to worry about deallocating them as they +will be managed by the parent parameterized test that gets automatically cleaned +up upon the end of the parameterized test series. + +The code below demonstrates example usage of the ``priv`` field for shared +resources: + +.. code-block:: c + + /* An example parameter array. */ + static const struct example_param { + int value; + } example_params_array[] = { + { .value = 3, }, + { .value = 2, }, + { .value = 1, }, + { .value = 0, }, + }; + + /* + * Initializes the parent kunit struct for parameterized KUnit tests. + * This function enables sharing resources across all parameterized + * tests. + */ + 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 parent instance of test, this memory + * allocation will be scoped to the lifetime of the whole + * parameterized test series. + */ + test->priv = kunit_kzalloc(test, sizeof(int), GFP_KERNEL); + + /* Assign the context value to test->priv.*/ + *((int *)test->priv) = ctx; + + /* Pass the static param array information to the parent struct kunit. */ + kunit_register_params_array(test, example_params_array, arr_size, NULL); + return 0; + } + + /* + * This is an example of a parameterized test that uses shared resources + * available from the struct kunit parent field of the kunit struct. + */ + static void example_params_test_with_init_priv(struct kunit *test) + { + int threshold; + const struct example_param *param = test->param_value; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + /* By design, test->parent will also not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, test->parent); + + /* Assert that test->parent->priv has data. */ + KUNIT_ASSERT_NOT_NULL(test, test->parent->priv); + + /* Here we need to use test->parent->priv to access the shared resource. */ + threshold = *(int *)test->parent->priv; + + /* Assert that the parameter is less than or equal to a certain threshold. */ + KUNIT_ASSERT_LE(test, param->value, threshold); + } + + + static struct kunit_case example_tests[] = { + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_priv, NULL, + example_param_init_priv, NULL), + {} + }; + Allocating Memory -----------------
On Wed, 30 Jul 2025 at 03:37, Marie Zhussupova marievic@google.com wrote:
-Update the KUnit documentation to explain the concept of a parent parameterized test. -Add examples demonstrating different ways of passing parameters to parameterized tests and how to manage shared resources between them.
Nit: We don't need the dot points ('-') here. Just make them paragraphs.
Signed-off-by: Marie Zhussupova marievic@google.com
Thanks very, very much for including such detailed documentation.
I do think some of the examples could be trimmed / left in the kunit-example-test.c file and referenced, as they're long enough that it's difficult to focus on the essentials. But otherwise, this looks great.
A few small notes below, but otherwise:
Reviewed-by: David Gow davidgow@google.com
Cheers, -- David
Documentation/dev-tools/kunit/usage.rst | 455 +++++++++++++++++++++++- 1 file changed, 449 insertions(+), 6 deletions(-)
diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst index 066ecda1dd98..be1d656053cf 100644 --- a/Documentation/dev-tools/kunit/usage.rst +++ b/Documentation/dev-tools/kunit/usage.rst @@ -542,11 +542,21 @@ 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 efficiently and elegantly validate a test case against a variety of inputs, +KUnit also provides a parameterized testing framework. This feature formalizes +and extends the concept of table-driven tests discussed previously, offering +a more integrated and flexible way to handle multiple test scenarios with +minimal code duplication.
Nit: maybe we can tone down the adjectives slightly here. I do like parameterised testing a lot, but it probably doesn't need to be "efficient", "elegant", "integrated", and "flexible".
+Passing Parameters to the Test Cases +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +There are three main ways to provide the parameters to a test case:
+Array Parameter Macros (``KUNIT_ARRAY_PARAM`` or ``KUNIT_ARRAY_PARAM_DESC``):
- 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 +565,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 +600,439 @@ By reusing the same ``cases`` array from above, we can write the test as a {} };
+Custom Parameter Generator (``generate_params``):
- You can pass your own ``generate_params`` function to the ``KUNIT_CASE_PARAM``
- or ``KUNIT_CASE_PARAM_WITH_INIT`` macros. This function is responsible for
- generating parameters one by one. It receives the previously generated parameter
- as the ``prev`` argument (which is ``NULL`` on the first call) and can also
- access any context available from the parent ``struct kunit`` passed as the
- ``test`` argument. KUnit calls this function repeatedly until it returns
- ``NULL``. 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 managed," meaning it's automatically cleaned up at
// the end of each parameter execution.
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),
{}
};
+Direct Registration in Parameter Init Function (using ``kunit_register_params_array``):
Maybe we should highlight this as being array-based more explicitly. "Runtime Array Registration in the Init function" or similar?
- For more complex scenarios, you can directly register a parameter array with
- a test case instead of using a ``generate_params`` function. This is done by
- passing the array to the ``kunit_register_params_array`` macro within an
- initialization function for the parameterized test series
- (i.e., a function named ``param_init``). To better understand this mechanism
- please refer to the "Adding Shared Resources" section below.
- This method supports both dynamically built and static arrays.
- As the following code shows, the ``example_param_init_dynamic_arr`` function
- utilizes ``make_fibonacci_params`` to create a dynamic array, which is then
- registered using ``kunit_register_params_array``. The corresponding exit
- function, ``example_param_exit``, is responsible for freeing this dynamically
- allocated params array after the parameterized test series ends.
+.. code-block:: c
/*
* Helper function to create a parameter array of Fibonacci numbers. This example
* highlights a parameter generation scenario that is:
* 1. Not feasible to fully pre-generate at compile time.
* 2. Challenging to implement with a standard 'generate_params' function,
* as it typically only provides the immediately 'prev' parameter, while
* Fibonacci requires access to two preceding values for calculation.
*/
static void *make_fibonacci_params(int seq_size)
{
int *seq;
if (seq_size <= 0)
return NULL;
seq = kmalloc_array(seq_size, sizeof(int), GFP_KERNEL);
if (!seq)
return NULL;
if (seq_size >= 1)
seq[0] = 0;
if (seq_size >= 2)
seq[1] = 1;
for (int i = 2; i < seq_size; i++)
seq[i] = seq[i - 1] + seq[i - 2];
return seq;
}
// This is an example of a function that provides a description for each of the
// parameters.
static void example_param_dynamic_arr_get_desc(const void *p, char *desc)
{
const int *fib_num = p;
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "fibonacci param: %d", *fib_num);
}
// Example of a parameterized test init function that registers a dynamic array.
static int example_param_init_dynamic_arr(struct kunit *test)
{
int seq_size = 6;
int *fibonacci_params = make_fibonacci_params(seq_size);
if (!fibonacci_params)
return -ENOMEM;
/*
* Passes the dynamic parameter array information to the parent struct kunit.
* The array and its metadata will be stored in test->parent->params_data.
* 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;
}
// Function to clean up the parameterized test's parent kunit struct if
// there were custom allocations.
static void example_param_exit_dynamic_arr(struct kunit *test)
{
/*
* We allocated this array, so we need to free it.
* Since the parent parameter instance is passed here,
* we can directly access the array via `test->params_data.params`
* instead of `test->parent->params_data.params`.
*/
kfree(test->params_data.params);
}
/*
* Example of test that uses the registered dynamic array to perform assertions
* and expectations.
*/
static void example_params_test_with_init_dynamic_arr(struct kunit *test)
{
const int *param = test->param_value;
int param_val;
/* By design, param pointer will not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, param);
param_val = *param;
KUNIT_EXPECT_EQ(test, param_val - param_val, 0);
}
static struct kunit_case example_tests[] = {
// The NULL here stands in for the generate_params function
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr, NULL,
example_param_init_dynamic_arr,
example_param_exit_dynamic_arr),
{}
};
This is a long example, which already exists in the source code (kunit-example-test.c). Could we just include some highlights (e.g., the init function and the KUNIT_CASE_PARAM_WITH_INIT call), and link to the source code for the rest?
+Adding Shared Resources +^^^^^^^^^^^^^^^^^^^^^^^ +All parameterized test executions in this framework have a parent test of type +``struct kunit``. This parent is not used to execute any test logic itself; +instead, it serves as a container for shared context that can be accessed by +all its individual test executions (or parameters). Therefore, each individual +test execution holds a pointer to this parent, accessible via a field named +``parent``.
+It's possible to add resources to share between the individual test executions +within a parameterized test series by using the ``KUNIT_CASE_PARAM_WITH_INIT`` +macro, to which you pass custom ``param_init`` and ``param_exit`` functions. +These functions run once before and once after the entire parameterized test +series, respectively. The ``param_init`` function can be used for adding any +resources to the resources field of a parent test and also provide an additional +way of setting the parameter array. The ``param_exit`` function can be used +release any resources that were not test managed i.e. not automatically cleaned +up after the test ends.
+.. note::
- If both a ``generate_params`` function is passed to ``KUNIT_CASE_PARAM_WITH_INIT``
- and an array is registered via ``kunit_register_params_array`` in
- ``param_init``, the ``generate_params`` function will be used to get
- the parameters.
Maybe note that the ``generate_params`` function can use the array passed, though?
+Both ``param_init`` and ``param_exit`` are passed the parent instance of a test +(parent ``struct kunit``) behind the scenes. However, the test case function +receives the individual instance of a test for each parameter. Therefore, to +manage and access shared resources from within a test case function, you must use +``test->parent``.
+.. note::
- The ``suite->init()`` function, which runs before each parameter execution,
- receives the individual instance of a test for each parameter. Therefore,
- resources set up in ``suite->init()`` are reset for each individual
- parameterized test execution and are only visible within that specific test.
+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).
+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.
+.. code-block:: c
/* An example parameter array. */
static const struct example_param {
int value;
} example_params_array[] = {
{ .value = 3, },
{ .value = 2, },
{ .value = 1, },
{ .value = 0, },
};
/*
* This custom function allocates memory for the kunit_resource data field.
* The function is passed to kunit_alloc_resource() and executed once
* by the internal helper __kunit_add_resource().
*/
static int example_resource_init(struct kunit_resource *res, void *context)
{
int *info = kmalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
*info = *(int *)context;
res->data = info;
return 0;
}
/*
* This function deallocates memory for the 'kunit_resource' data field.
* The function is passed to kunit_alloc_resource() and automatically
* executes within kunit_release_resource() when the resource's reference
* count, via kunit_put_resource(), drops to zero. KUnit uses reference
* counting to ensure that resources are not freed prematurely.
*/
static void example_resource_free(struct kunit_resource *res)
{
kfree(res->data);
}
/*
* This match function is invoked by kunit_find_resource() to locate
* a test resource based on defined criteria. The current example
* uniquely identifies the resource by its free function; however,
* alternative custom criteria can be implemented. Refer to
* lib/kunit/platform.c and lib/kunit/static_stub.c for further examples.
*/
static bool example_resource_alloc_match(struct kunit *test,
struct kunit_resource *res,
void *match_data)
{
return res->data && res->free == example_resource_free;
}
/*
* This is an example of a function that provides a description for each of the
* parameters.
*/
static void example_param_array_get_desc(const void *p, char *desc)
{
const struct example_param *param = p;
snprintf(desc, KUNIT_PARAM_DESC_SIZE,
"example check if %d is less than or equal to 3", param->value);
}
/*
* Initializes the parent kunit struct for parameterized KUnit tests.
* This function enables sharing resources across all parameterized
* tests by adding them to the `parent` kunit test struct. It also supports
* registering either static or dynamic arrays of test parameters.
*/
static int example_param_init(struct kunit *test)
{
int ctx = 3; /* Data to be stored. */
int arr_size = ARRAY_SIZE(example_params_array);
/*
* This allocates a struct kunit_resource, sets its data field to
* ctx, and adds it to the kunit struct's resources list. Note that
* this is test managed so we don't need to have a custom exit function
* to free it.
*/
void *data = kunit_alloc_resource(test, example_resource_init, example_resource_free,
GFP_KERNEL, &ctx);
if (!data)
return -ENOMEM;
/* Pass the static param array information to the parent struct kunit. */
kunit_register_params_array(test, example_params_array, arr_size,
example_param_array_get_desc);
return 0;
}
/*
* This is an example of a parameterized test that uses shared resources
* available from the struct kunit parent field of the kunit struct.
*/
static void example_params_test_with_init(struct kunit *test)
{
int threshold;
struct kunit_resource *res;
const struct example_param *param = test->param_value;
/* By design, param pointer will not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, param);
/* Here we need to access the parent pointer of the test to find the shared resource. */
res = kunit_find_resource(test->parent, example_resource_alloc_match, NULL);
KUNIT_ASSERT_NOT_NULL(test, res);
/* Since the data field in kunit_resource is a void pointer we need to typecast it. */
threshold = *((int *)res->data);
/* Assert that the parameter is less than or equal to a certain threshold. */
KUNIT_ASSERT_LE(test, param->value, threshold);
/* This decreases the reference count after calling kunit_find_resource(). */
kunit_put_resource(res);
}
static struct kunit_case example_tests[] = {
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, NULL,
example_param_init, NULL),
{}
};
This is a really long example, which already exists in kunit-example-test.c. Can we either link to it there (and just include the most critical lines here), or have a smaller, less-complete example inline here?
+As an alternative to using the KUnit Resource API for shared resources, you can +place them in ``test->parent->priv``. It can store data that needs to persist +and be accessible across all executions within a parameterized test series.
+As stated previously ``param_init`` and ``param_exit`` receive the parent +``struct kunit`` instance. So, you can directly use ``test->priv`` within them +to manage shared resources. However, from within the test case function, you must +navigate up to the parent i.e. use ``test->parent->priv`` to access those same +resources.
+The resources placed in ``test->parent-priv`` will also need to be allocated in +memory to persist across the parameterized tests executions. If memory is
Nit: 'parameterized test executions' singular?
+allocated using the memory allocation APIs provided by KUnit (described more in +the section below), you will not need to worry about deallocating them as they +will be managed by the parent parameterized test that gets automatically cleaned +up upon the end of the parameterized test series.
+The code below demonstrates example usage of the ``priv`` field for shared +resources:
+.. code-block:: c
/* An example parameter array. */
static const struct example_param {
int value;
} example_params_array[] = {
{ .value = 3, },
{ .value = 2, },
{ .value = 1, },
{ .value = 0, },
};
/*
* Initializes the parent kunit struct for parameterized KUnit tests.
* This function enables sharing resources across all parameterized
* tests.
*/
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 parent instance of test, this memory
* allocation will be scoped to the lifetime of the whole
* parameterized test series.
*/
test->priv = kunit_kzalloc(test, sizeof(int), GFP_KERNEL);
/* Assign the context value to test->priv.*/
*((int *)test->priv) = ctx;
/* Pass the static param array information to the parent struct kunit. */
kunit_register_params_array(test, example_params_array, arr_size, NULL);
return 0;
}
/*
* This is an example of a parameterized test that uses shared resources
* available from the struct kunit parent field of the kunit struct.
*/
static void example_params_test_with_init_priv(struct kunit *test)
{
int threshold;
const struct example_param *param = test->param_value;
/* By design, param pointer will not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, param);
/* By design, test->parent will also not be NULL. */
KUNIT_ASSERT_NOT_NULL(test, test->parent);
/* Assert that test->parent->priv has data. */
KUNIT_ASSERT_NOT_NULL(test, test->parent->priv);
/* Here we need to use test->parent->priv to access the shared resource. */
threshold = *(int *)test->parent->priv;
/* Assert that the parameter is less than or equal to a certain threshold. */
KUNIT_ASSERT_LE(test, param->value, threshold);
}
static struct kunit_case example_tests[] = {
KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_priv, NULL,
example_param_init_priv, NULL),
{}
};
Again, this is a little long, but it's not as bad as the others, and isn't in the example tests, so I'm okay with leaving it. Though maybe we could get rid of some of the asserts for the purpose of keeping the documentation focused and readable.
Allocating Memory
-- 2.50.1.552.g942d659e1b-goog
linux-kselftest-mirror@lists.linaro.org