From 0efe609ef5b6ab286ec296369365efd2b9ce774f Mon Sep 17 00:00:00 2001 From: Vaibhav Jain Date: Fri, 26 Jun 2026 14:28:06 +0530 Subject: [PATCH 1/7] kunit,rust: Add ability to skip entire test suites Currently, KUnit provides mechanisms to skip individual test cases, but there is no way to skip an entire test suite based on runtime conditions checked during suite initialization. This limitation forces test suites to either fail or skip tests individually when certain prerequisites are not available. To address this limitation, the patch adds a 'status' field to struct kunit_suite that allows suite_init callbacks to mark the entire suite as KUNIT_SKIPPED. When a suite is marked as skipped, all test cases within that suite are bypassed without execution. The patch proposed changes to kunit_suite_has_succeeded() to Check suite status before evaluating individual test case results. Also kunit_run_tests() is updated to skip suite execution if kunit_suite's 'status' is KUNIT_SKIPPED, thats either set before suite_init or by the suite_init callback itself. kunit_init_suite() is updated to initialize the 'status' of kunit_suite to KUNIT_SUCCESS so that any skipped suite's can be restarted from debugfs. This enables test suites to perform runtime capability checks in their 'suite_init' callback and gracefully skip all tests when prerequisites are not met, rather than reporting failures or requiring each test case to perform redundant checks. In case a kunit-suite is skipped it can be re-run from the kunit's debugfs interface. Also update debugfs_print_results() to clearly log the kunit-suite as 'SKIP'. kunit_suite_has_succeeded() is also updated on which debugfs_print_results() depends to update 'kunit_suite.status' in case any of the kunit_case has failed. Finally, update KUnit Rust binding macro-rule 'kunit_unsafe_test_suite' to add and initialize the newly introduced 'kunit_suite.status'. Without this 'kunit_suite.status' field is never initialized which is an error for the Rust compiler. Link: https://patchwork.kernel.org/project/linux-kselftest/patch/20260626085811.151133-2-vaibhav@linux.ibm.com/mbox/ Reviewed-by: David Gow Signed-off-by: Vaibhav Jain Signed-off-by: Shuah Khan --- include/kunit/test.h | 1 + lib/kunit/debugfs.c | 28 ++++++++++++++++++++-------- lib/kunit/test.c | 17 ++++++++++++++++- rust/kernel/kunit.rs | 1 + 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/include/kunit/test.h b/include/kunit/test.h index e52452e58305..da5312e0dfa5 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -285,6 +285,7 @@ struct kunit_suite { struct string_stream *log; int suite_init_err; bool is_init; + enum kunit_status status; }; /* Stores an array of suites, end points one past the end */ diff --git a/lib/kunit/debugfs.c b/lib/kunit/debugfs.c index 9c326f1837bd..442b2ceb955b 100644 --- a/lib/kunit/debugfs.c +++ b/lib/kunit/debugfs.c @@ -76,18 +76,30 @@ static int debugfs_print_results(struct seq_file *seq, void *v) seq_puts(seq, "KTAP version 1\n"); seq_puts(seq, "1..1\n"); - /* Print suite header because it is not stored in the test logs. */ - seq_puts(seq, KUNIT_SUBTEST_INDENT "KTAP version 1\n"); - seq_printf(seq, KUNIT_SUBTEST_INDENT "# Subtest: %s\n", suite->name); - seq_printf(seq, KUNIT_SUBTEST_INDENT "1..%zd\n", kunit_suite_num_test_cases(suite)); + if (suite->status != KUNIT_SKIPPED) { + /* Print suite header because it is not stored in the test logs. */ + seq_puts(seq, + KUNIT_SUBTEST_INDENT "KTAP version 1\n"); + seq_printf(seq, + KUNIT_SUBTEST_INDENT "# Subtest: %s\n", + suite->name); + seq_printf(seq, + KUNIT_SUBTEST_INDENT "1..%zd\n", + kunit_suite_num_test_cases(suite)); - kunit_suite_for_each_test_case(suite, test_case) - debugfs_print_result(seq, test_case->log); + kunit_suite_for_each_test_case(suite, test_case) + debugfs_print_result(seq, test_case->log); + } debugfs_print_result(seq, suite->log); - seq_printf(seq, "%s %d %s\n", - kunit_status_to_ok_not_ok(success), 1, suite->name); + if (suite->status != KUNIT_SKIPPED) + seq_printf(seq, "%s %d %s\n", + kunit_status_to_ok_not_ok(success), 1, suite->name); + else + seq_printf(seq, "%s %d %s # SKIP %s\n", + kunit_status_to_ok_not_ok(success), 1, suite->name, + suite->status_comment); return 0; } diff --git a/lib/kunit/test.c b/lib/kunit/test.c index 99773e000e1b..09e3dabfac0c 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -214,12 +214,18 @@ enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite) const struct kunit_case *test_case; enum kunit_status status = KUNIT_SKIPPED; + if (suite->status == KUNIT_SKIPPED) + return KUNIT_SKIPPED; + if (suite->suite_init_err) return KUNIT_FAILURE; kunit_suite_for_each_test_case(suite, test_case) { - if (test_case->status == KUNIT_FAILURE) + if (test_case->status == KUNIT_FAILURE) { + /* Update the kunit_suite status also */ + suite->status = KUNIT_FAILURE; return KUNIT_FAILURE; + } else if (test_case->status == KUNIT_SUCCESS) status = KUNIT_SUCCESS; } @@ -795,12 +801,20 @@ int kunit_run_tests(struct kunit_suite *suite) /* Taint the kernel so we know we've run tests. */ add_taint(TAINT_TEST, LOCKDEP_STILL_OK); + if (suite->status == KUNIT_SKIPPED) + goto suite_end; + if (suite->suite_init) { suite->suite_init_err = suite->suite_init(suite); if (suite->suite_init_err) { + suite->status = KUNIT_FAILURE; kunit_err(suite, KUNIT_SUBTEST_INDENT "# failed to initialize (%d)", suite->suite_init_err); goto suite_end; + + } else if (suite->status == KUNIT_SKIPPED) { + /* Skip this kunit suite */ + goto suite_end; } } @@ -825,6 +839,7 @@ static void kunit_init_suite(struct kunit_suite *suite) kunit_debugfs_create_suite(suite); suite->status_comment[0] = '\0'; suite->suite_init_err = 0; + suite->status = KUNIT_SUCCESS; if (suite->log) string_stream_clear(suite->log); diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs index cdee5f27bd7f..91eaff8c186a 100644 --- a/rust/kernel/kunit.rs +++ b/rust/kernel/kunit.rs @@ -288,6 +288,7 @@ macro_rules! kunit_unsafe_test_suite { log: ::core::ptr::null_mut(), suite_init_err: 0, is_init: false, + status: kernel::bindings::kunit_status_KUNIT_SUCCESS, }; #[used(compiler)] From 643ec8ff7d8ed15881bd05d71de24208ce0dadcb Mon Sep 17 00:00:00 2001 From: Vaibhav Jain Date: Fri, 26 Jun 2026 14:28:07 +0530 Subject: [PATCH 2/7] kunit: Add example of test suite that can be skipped at runtime Add an example test suite name 'example_test_skip_suite' to 'kunit-example-test.c' that shows how to skip an entire test suite based on runtime conditions. The example suite 'example_skip_suite' provides a 'suite_init' callback named example_skip_suite_init() which marks the entire suite as skipped using kunit_mark_skipped(). This demonstrates a way for conditionally skipping test suites when any prerequisites for kunit_suite execution are not met. The 'suite_init' callback can perform any necessary checks and mark the suite as skipped, preventing all test cases from executing while also indicating why the suite was skipped. Link: https://lore.kernel.org/r/20260626085811.151133-3-vaibhav@linux.ibm.com Reviewed-by: David Gow Signed-off-by: Vaibhav Jain Signed-off-by: Shuah Khan --- lib/kunit/kunit-example-test.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 0bae7b7ca0b0..b8ded54fa46d 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -591,5 +591,34 @@ static struct kunit_suite example_init_test_suite = { */ kunit_test_init_section_suites(&example_init_test_suite); +/* + * This test should always be skipped. + */ +static void example_skip_suite_test(struct kunit *test) +{ + /* This line should never be seen */ + KUNIT_FAIL(test, "You should not see a this."); +} + +static struct kunit_case example_skip_suite_test_cases[] = { + KUNIT_CASE(example_skip_suite_test), + {} +}; + +static int example_skip_suite_init(struct kunit_suite *suite) +{ + kunit_mark_skipped(suite, "Test suite expected to be skipped"); + return 0; +} + +static struct kunit_suite example_test_skip_suite = { + .name = "example_skip_suite", + .suite_init = example_skip_suite_init, + .test_cases = example_skip_suite_test_cases, +}; + +/* This registers a test suite that will be skipped */ +kunit_test_suite(example_test_skip_suite); + MODULE_DESCRIPTION("Example KUnit test suite"); MODULE_LICENSE("GPL v2"); From 7eac3330517472ea06590c14e683db694ed7a28e Mon Sep 17 00:00:00 2001 From: David Gow Date: Sat, 27 Jun 2026 16:29:19 +0800 Subject: [PATCH 3/7] Documentation: kunit: Test Kconfig entries shouldn't select other configs Add a note to the Kconfig section of style.rst to use 'depends on' rather than 'selects' for dependencies, as this can cause users of CONFIG_KUNIT_ALL_TESTS to suddenly grow unexpected dependencies. Link: https://lore.kernel.org/r/20260627082921.1709181-1-david@davidgow.net Signed-off-by: David Gow Signed-off-by: Shuah Khan --- Documentation/dev-tools/kunit/style.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/dev-tools/kunit/style.rst b/Documentation/dev-tools/kunit/style.rst index eac81a714a29..449f9f816fc7 100644 --- a/Documentation/dev-tools/kunit/style.rst +++ b/Documentation/dev-tools/kunit/style.rst @@ -164,9 +164,10 @@ This Kconfig entry must: * be visible only if ``CONFIG_KUNIT_ALL_TESTS`` is not enabled. * have a default value of ``CONFIG_KUNIT_ALL_TESTS``. * have a brief description of KUnit in the help text. - -If we are not able to meet above conditions (for example, the test is unable to -be built as a module), Kconfig entries for tests should be tristate. +* depend on the feature being tested, rather than selecting it (so that + enabling ``CONFIG_KUNIT_ALL_TESTS`` does not enable unrelated functionality). +* be ``tristate``, unless there is a specific reason that the test cannot be + built as a module. For example, a Kconfig entry might look like: From 483cd4bdd077e6f5342d32ecc6517b0a39be235f Mon Sep 17 00:00:00 2001 From: David Gow Date: Sat, 27 Jun 2026 16:29:20 +0800 Subject: [PATCH 4/7] Documentation: kunit: Fix outdated FAQ entries The KUnit FAQ was written when KUnit in general, and kunit.py in particular, were very heavily focused on UML. While they were updated slightly when qemu support was added, they've not really kept pace with changes to KUnit or the structure of the rest of the documentation. Update them to describe how to run kunit.py with non-UML architectures, and to point to the run_manual.rst page for further detail on how to run KUnit without kunit.py, as it's the authoratative documentation on that subject. Link: https://lore.kernel.org/r/20260627082921.1709181-2-david@davidgow.net Signed-off-by: David Gow Signed-off-by: Shuah Khan --- Documentation/dev-tools/kunit/faq.rst | 43 +++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Documentation/dev-tools/kunit/faq.rst b/Documentation/dev-tools/kunit/faq.rst index fae426f2634a..b1341c1a62d9 100644 --- a/Documentation/dev-tools/kunit/faq.rst +++ b/Documentation/dev-tools/kunit/faq.rst @@ -25,19 +25,21 @@ disqualifying any of them from being considered unit testing frameworks. Does KUnit support running on architectures other than UML? =========================================================== -Yes, mostly. +Yes. KUnit can run on any architecture, though the kunit.py tool can only +build and run kernels for some architectures (of which UML is the default). -For the most part, the KUnit core framework (what we use to write the tests) -can compile to any architecture. It compiles like just another part of the -kernel and runs when the kernel boots, or when built as a module, when the -module is loaded. However, there is infrastructure, like the KUnit Wrapper -(``tools/testing/kunit/kunit.py``) that might not support some architectures -(see :ref:`kunit-on-qemu`). +You can build and run tests without kunit.py at all on any architecture by +enabling ``CONFIG_KUNIT=y`` and booting the kernel. +See Documentation/dev-tools/kunit/run_manual.rst for more details. -In short, yes, you can run KUnit on other architectures, but it might require -more work than using KUnit on UML. +Alternatively, kunit.py supports many common architectures using +cross-compilers and the qemu emulator. This can be done using the ``--arch`` +parameter when running the tests, and the ``--cross_compile`` parameter +when building (if the architecture is not supported by the host compiler). +See :ref:`kunit-on-qemu` for more details. -For more information, see :ref:`kunit-on-non-uml`. +When writing tests targeting other architectures, it's worth keeping the tips +on the :ref:`kunit-on-non-uml` page in mind. .. _kinds-of-tests: @@ -78,27 +80,30 @@ things to try. down where an issue is occurring. (If you think the parser is at fault, you can run it manually against ``stdin`` or a file with ``kunit.py parse``.) 3. Running the UML kernel directly can often reveal issues or error messages, - ``kunit_tool`` ignores. This should be as simple as running ``./vmlinux`` - after building the UML kernel (for example, by using ``kunit.py build``). + ``kunit_tool`` ignores. This should be as simple as runningi the ``vmlinux`` + binary in the output directory (by default ``./.kunit/vmlinux``) after + building the UML kernel (for example, by using ``kunit.py build``). Note that UML has some unusual requirements (such as the host having a tmpfs filesystem mounted), and has had issues in the past when built statically and the host has KASLR enabled. (On older host kernels, you may need to run ``setarch `uname -m` -R ./vmlinux`` to disable KASLR.) -4. Make sure the kernel .config has ``CONFIG_KUNIT=y`` and at least one test +4. Try running KUnit on a different architecture by using the ``--arch`` + option. On an x86_64 host, using ``--arch=x86_64`` is a good first step. +5. Make sure the kernel .config has ``CONFIG_KUNIT=y`` and at least one test (e.g. ``CONFIG_KUNIT_EXAMPLE_TEST=y``). kunit_tool will keep its .config around, so you can see what config was used after running ``kunit.py run``. It also preserves any config changes you might make, so you can enable/disable things with ``make ARCH=um menuconfig`` or similar, and then re-run kunit_tool. -5. Try to run ``make ARCH=um defconfig`` before running ``kunit.py run``. This +6. Try to run ``make ARCH=um defconfig`` before running ``kunit.py run``. This may help clean up any residual config items which could be causing problems. -6. Finally, try running KUnit outside UML. KUnit and KUnit tests can be - built into any kernel, or can be built as a module and loaded at runtime. - Doing so should allow you to determine if UML is causing the issue you're - seeing. When tests are built-in, they will execute when the kernel boots, and +7. Finally, try running KUnit manually, instead of via ``kunit.py``. KUnit can + be built into any kernel, or can be built as a module and loaded at runtime. + When tests are built-in, they will execute when the kernel boots, and modules will automatically execute associated tests when loaded. Test results can be collected from ``/sys/kernel/debug/kunit//results``, and - can be parsed with ``kunit.py parse``. For more details, see :ref:`kunit-on-qemu`. + can be parsed with ``kunit.py parse``. For more details, see + Documentation/dev-tools/kunit/run_manual.rst If none of the above tricks help, you are always welcome to email any issues to kunit-dev@googlegroups.com. From f47180b0e9cc59e1989adb093a4b94187642b405 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Fri, 3 Jul 2026 07:12:46 -0500 Subject: [PATCH 5/7] kunit: string-stream: Replace strlcat() with strscpy() and seq_buf In preparation for removing the strlcat() API[1], replace its uses in string-stream. string_stream_vadd() appends at most a single newline into space that was explicitly reserved when the fragment was sized, so a bounded copy at the end of the string is enough. The return value of strscpy() keeps the length accounting unchanged. string_stream_get_string() concatenates a variable number of fragments into a buffer sized to hold them all, which is what seq_buf is for. Link: https://lore.kernel.org/r/akenPvVk1xr_-480@dev Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Reviewed-by: David Gow Signed-off-by: Shuah Khan --- lib/kunit/string-stream.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/kunit/string-stream.c b/lib/kunit/string-stream.c index 0d8f1b30559b..51ba40ebf19f 100644 --- a/lib/kunit/string-stream.c +++ b/lib/kunit/string-stream.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "string-stream.h" @@ -74,7 +75,8 @@ int string_stream_vadd(struct string_stream *stream, /* Append newline if necessary. */ if (frag_container->fragment[result_len - 1] != '\n') - result_len = strlcat(frag_container->fragment, "\n", buf_len); + result_len += strscpy(frag_container->fragment + result_len, + "\n", buf_len - result_len); } else { result_len = vsnprintf(frag_container->fragment, buf_len, fmt, args); } @@ -118,15 +120,18 @@ char *string_stream_get_string(struct string_stream *stream) { struct string_stream_fragment *frag_container; size_t buf_len = stream->length + 1; /* +1 for null byte. */ + struct seq_buf sb; char *buf; buf = kzalloc(buf_len, stream->gfp); if (!buf) return NULL; + seq_buf_init(&sb, buf, buf_len); + spin_lock(&stream->lock); list_for_each_entry(frag_container, &stream->fragments, node) - strlcat(buf, frag_container->fragment, buf_len); + seq_buf_puts(&sb, frag_container->fragment); spin_unlock(&stream->lock); return buf; From 34b5c0132952c3d176bf5a169c8f7093ecb8c4e8 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 29 Jun 2026 14:42:45 +0200 Subject: [PATCH 6/7] kunit: configs: enable GPIO kunit test cases in all_tests.config Enable CONFIG_GPIOLIB in all_tests.config to ensure the kunit test cases for GPIO core can be built with this config. Link: https://lore.kernel.org/r/20260629124245.27674-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Reviewed-by: David Gow Signed-off-by: Shuah Khan --- tools/testing/kunit/configs/all_tests.config | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config index bccc2c77196d..6825c2e855a5 100644 --- a/tools/testing/kunit/configs/all_tests.config +++ b/tools/testing/kunit/configs/all_tests.config @@ -21,6 +21,7 @@ CONFIG_VFAT_FS=y CONFIG_PCI=y CONFIG_USB4=y CONFIG_I2C=y +CONFIG_GPIOLIB=y CONFIG_NET=y CONFIG_MCTP=y From dea754ded9518b51740c417d2c1e02ff540784c6 Mon Sep 17 00:00:00 2001 From: Mohammad Abu-Khader Date: Mon, 3 Aug 2026 19:02:17 +0000 Subject: [PATCH 7/7] kunit: tool: fix _list_tests filtering wrong variable when list has TAP prefix `_list_tests()` runs the kernel to list tests, strips printk timestamp lines via `extract_tap_lines()`, then drops the dummy TAP header from the cleaned `lines`. However the subsequent regex filter mistakenly operates on the original `output` instead of the cleaned `lines`. When the kernel output includes timestamp prefixes (common with UML or slower setups), e.g.: [ 0.100000] suite.test1 [ 0.100000] suite.test2 the anchored regex `^[^\s.]+\.[^\s.]+$` rejects them and `--list_tests` returns an empty list. Filter `lines` instead of `output`, matching the behavior of the adjacent `_list_tests_attr()` which already returns the cleaned list. Add a regression test with timestamp-prefixed input to verify the fix. Link: https://lore.kernel.org/r/20260803190059.36491-1-mohammad.abukhader@hotmail.com Fixes: 723c8258c8fe ("kunit: tool: Add command line interface to filter and report attributes") Signed-off-by: Mohammad Abu-Khader Reviewed-by: David Gow Signed-off-by: Shuah Khan --- tools/testing/kunit/kunit.py | 2 +- tools/testing/kunit/kunit_tool_test.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index ac3f7159e67f..91d234ac3b57 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -126,7 +126,7 @@ def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) lines.pop() # Filter out any extraneous non-test output that might have gotten mixed in. - return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)] + return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)] def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]: args = ['kunit.action=list_attr'] diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index da88c3a1651d..85ae21754bdf 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -979,6 +979,18 @@ class KUnitMainTest(unittest.TestCase): self.linux_source_mock.run_kernel.assert_called_once_with( args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', filter='', filter_action=None, timeout=300) + def test_list_tests_with_prefix(self): + want = ['suite.test1', 'suite.test2', 'suite2.test1'] + self.linux_source_mock.run_kernel.return_value = [ + '[ 0.100000] TAP version 14', + '[ 0.200000] suite.test1', + '[ 0.200000] suite.test2', + '[ 0.300000] suite2.test1'] + + got = kunit._list_tests(self.linux_source_mock, + kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False)) + self.assertEqual(got, want) + @mock.patch.object(kunit, '_list_tests') def test_run_isolated_by_suite(self, mock_tests): mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1']