[PATCH 0/2] kunit: tool: test: Allow running with pytest

1 view
Skip to first unread message

Thomas Weißschuh

unread,
Dec 30, 2025, 7:56:48 AM12/30/25
to Brendan Higgins, David Gow, Rae Moar, linux-k...@vger.kernel.org, kuni...@googlegroups.com, linux-...@vger.kernel.org, Thomas Weißschuh
pytest can run unittest-based testsuites, like kunit_tool_test.py.
It has a more features than the standard runner.
Unfortunately a few minor issues currently break this.

Adapt the testsuite to work with pytest.

Signed-off-by: Thomas Weißschuh <thomas.w...@linutronix.de>
---
Thomas Weißschuh (2):
kunit: tool: test: Rename test_data_path() to _test_data_path()
kunit: tool: test: Don't rely on implicit working directory change

tools/testing/kunit/kunit_tool_test.py | 59 +++++++++++++++++-----------------
1 file changed, 30 insertions(+), 29 deletions(-)
---
base-commit: a77b19b747c8a53ac16289685b0f77085a2ad553
change-id: 20251230-kunit-pytest-259a1eb36a42

Best regards,
--
Thomas Weißschuh <thomas.w...@linutronix.de>

Thomas Weißschuh

unread,
Dec 30, 2025, 7:56:50 AM12/30/25
to Brendan Higgins, David Gow, Rae Moar, linux-k...@vger.kernel.org, kuni...@googlegroups.com, linux-...@vger.kernel.org, Thomas Weißschuh
If no kunitconfig_paths are passed to LinuxSourceTree() it falls back to
DEFAULT_KUNITCONFIG_PATH. This resolution only works when the current
working directory is the root of the source tree. This works by chance
when running the full testsuite through the default unittest runner, as
some tests will change the current working directory as a side-effect of
'kunit.main()'. When running a single testcase or using pytest, which
resets the working directory for each test, this assumption breaks.

Explicitly specify an empty kunitconfig for the affected tests.

Signed-off-by: Thomas Weißschuh <thomas.w...@linutronix.de>
---
tools/testing/kunit/kunit_tool_test.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index fd7ca89596c0..584275ce6ba6 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -466,7 +466,8 @@ class LinuxSourceTreeTest(unittest.TestCase):
want_kconfig = kunit_config.Kconfig()
want_kconfig.add_entry('NOT_REAL', 'y')

- tree = kunit_kernel.LinuxSourceTree('', kconfig_add=['CONFIG_NOT_REAL=y'])
+ tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull],
+ kconfig_add=['CONFIG_NOT_REAL=y'])
self.assertTrue(want_kconfig.is_subset_of(tree._kconfig), msg=tree._kconfig)

def test_invalid_arch(self):
@@ -478,7 +479,7 @@ class LinuxSourceTreeTest(unittest.TestCase):
return subprocess.Popen(['echo "hi\nbye"'], shell=True, text=True, stdout=subprocess.PIPE)

with tempfile.TemporaryDirectory('') as build_dir:
- tree = kunit_kernel.LinuxSourceTree(build_dir)
+ tree = kunit_kernel.LinuxSourceTree(build_dir, kunitconfig_paths=[os.devnull])
mock.patch.object(tree._ops, 'start', side_effect=fake_start).start()

with self.assertRaises(ValueError):

--
2.52.0

David Gow

unread,
Jan 5, 2026, 3:56:24 AM (13 days ago) Jan 5
to Thomas Weißschuh, Brendan Higgins, Rae Moar, linux-k...@vger.kernel.org, kuni...@googlegroups.com, linux-...@vger.kernel.org
On Tue, 30 Dec 2025 at 20:56, Thomas Weißschuh
<thomas.w...@linutronix.de> wrote:
>
> If no kunitconfig_paths are passed to LinuxSourceTree() it falls back to
> DEFAULT_KUNITCONFIG_PATH. This resolution only works when the current
> working directory is the root of the source tree. This works by chance
> when running the full testsuite through the default unittest runner, as
> some tests will change the current working directory as a side-effect of
> 'kunit.main()'. When running a single testcase or using pytest, which
> resets the working directory for each test, this assumption breaks.
>
> Explicitly specify an empty kunitconfig for the affected tests.
>
> Signed-off-by: Thomas Weißschuh <thomas.w...@linutronix.de>
> ---

Reviewed-by: David Gow <davi...@google.com>

Cheers,
-- David

Thomas Weißschuh

unread,
Jan 7, 2026, 7:52:02 AM (11 days ago) Jan 7
to Brendan Higgins, David Gow, Rae Moar, linux-k...@vger.kernel.org, kuni...@googlegroups.com, linux-...@vger.kernel.org, Thomas Weißschuh
pytest can run unittest-based testsuites, like kunit_tool_test.py.
It has a more features than the standard runner.
Unfortunately a few minor issues currently break this.

Adapt the testsuite to work with pytest.

Signed-off-by: Thomas Weißschuh <thomas.w...@linutronix.de>
---
Changes in v2:
- Rebase onto current kselftest/kunit branch
- Pick up review tags from LKML
- Link to v1: https://lore.kernel.org/r/20251230-kunit-pyte...@linutronix.de

---
Thomas Weißschuh (2):
kunit: tool: test: Rename test_data_path() to _test_data_path()
kunit: tool: test: Don't rely on implicit working directory change

tools/testing/kunit/kunit_tool_test.py | 61 +++++++++++++++++-----------------
1 file changed, 31 insertions(+), 30 deletions(-)
---
base-commit: ab150c2bbafe9425759eca1e45e08d4ad1456818

Thomas Weißschuh

unread,
Jan 7, 2026, 7:52:02 AM (11 days ago) Jan 7
to Brendan Higgins, David Gow, Rae Moar, linux-k...@vger.kernel.org, kuni...@googlegroups.com, linux-...@vger.kernel.org, Thomas Weißschuh
Running the KUnit testsuite through pytest fails, as the function
test_data_path() is recognized as a test function. Its execution fails
as pytest tries to resolve the 'path' argument as a fixture which does
not exist.

Rename the function, so the helper function is not incorrectly
recognized as a test function.

Signed-off-by: Thomas Weißschuh <thomas.w...@linutronix.de>
Reviewed-by: David Gow <davi...@google.com>
---
tools/testing/kunit/kunit_tool_test.py | 56 +++++++++++++++++-----------------
1 file changed, 28 insertions(+), 28 deletions(-)

diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index bdc51b5c7b10..30ac1cb6c8ed 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -36,7 +36,7 @@ def setUpModule():
def tearDownModule():
shutil.rmtree(test_tmpdir)

-def test_data_path(path):
+def _test_data_path(path):
return os.path.join(abs_test_data_dir, path)

class KconfigTest(unittest.TestCase):
@@ -52,7 +52,7 @@ class KconfigTest(unittest.TestCase):
self.assertFalse(kconfig1.is_subset_of(kconfig0))

def test_read_from_file(self):
- kconfig_path = test_data_path('test_read_from_file.kconfig')
+ kconfig_path = _test_data_path('test_read_from_file.kconfig')

kconfig = kunit_config.parse_file(kconfig_path)

@@ -98,7 +98,7 @@ class KUnitParserTest(unittest.TestCase):
raise AssertionError(f'"{needle}" not found in {list(backup)}!')

def test_output_isolated_correctly(self):
- log_path = test_data_path('test_output_isolated_correctly.log')
+ log_path = _test_data_path('test_output_isolated_correctly.log')
with open(log_path) as file:
result = kunit_parser.extract_tap_lines(file.readlines())
self.assertContains('TAP version 14', result)
@@ -109,7 +109,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertContains('ok 1 - example', result)

def test_output_with_prefix_isolated_correctly(self):
- log_path = test_data_path('test_pound_sign.log')
+ log_path = _test_data_path('test_pound_sign.log')
with open(log_path) as file:
result = kunit_parser.extract_tap_lines(file.readlines())
self.assertContains('TAP version 14', result)
@@ -138,35 +138,35 @@ class KUnitParserTest(unittest.TestCase):
self.assertContains('ok 3 - string-stream-test', result)

def test_parse_successful_test_log(self):
- all_passed_log = test_data_path('test_is_test_passed-all_passed.log')
+ all_passed_log = _test_data_path('test_is_test_passed-all_passed.log')
with open(all_passed_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
self.assertEqual(result.counts.errors, 0)

def test_parse_successful_nested_tests_log(self):
- all_passed_log = test_data_path('test_is_test_passed-all_passed_nested.log')
+ all_passed_log = _test_data_path('test_is_test_passed-all_passed_nested.log')
with open(all_passed_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
self.assertEqual(result.counts.errors, 0)

def test_kselftest_nested(self):
- kselftest_log = test_data_path('test_is_test_passed-kselftest.log')
+ kselftest_log = _test_data_path('test_is_test_passed-kselftest.log')
with open(kselftest_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
self.assertEqual(result.counts.errors, 0)

def test_parse_failed_test_log(self):
- failed_log = test_data_path('test_is_test_passed-failure.log')
+ failed_log = _test_data_path('test_is_test_passed-failure.log')
with open(failed_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
self.assertEqual(result.counts.errors, 0)

def test_parse_failed_nested_tests_log(self):
- nested_log = test_data_path('test_is_test_passed-failure-nested.log')
+ nested_log = _test_data_path('test_is_test_passed-failure-nested.log')
with open(nested_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
@@ -177,7 +177,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[1].subtests[0].status)

def test_no_header(self):
- empty_log = test_data_path('test_is_test_passed-no_tests_run_no_header.log')
+ empty_log = _test_data_path('test_is_test_passed-no_tests_run_no_header.log')
with open(empty_log) as file:
result = kunit_parser.parse_run_tests(
kunit_parser.extract_tap_lines(file.readlines()), stdout)
@@ -186,7 +186,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 1)

def test_missing_test_plan(self):
- missing_plan_log = test_data_path('test_is_test_passed-'
+ missing_plan_log = _test_data_path('test_is_test_passed-'
'missing_plan.log')
with open(missing_plan_log) as file:
result = kunit_parser.parse_run_tests(
@@ -197,7 +197,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)

def test_no_tests(self):
- header_log = test_data_path('test_is_test_passed-no_tests_run_with_header.log')
+ header_log = _test_data_path('test_is_test_passed-no_tests_run_with_header.log')
with open(header_log) as file:
result = kunit_parser.parse_run_tests(
kunit_parser.extract_tap_lines(file.readlines()), stdout)
@@ -206,7 +206,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 1)

def test_no_tests_no_plan(self):
- no_plan_log = test_data_path('test_is_test_passed-no_tests_no_plan.log')
+ no_plan_log = _test_data_path('test_is_test_passed-no_tests_no_plan.log')
with open(no_plan_log) as file:
result = kunit_parser.parse_run_tests(
kunit_parser.extract_tap_lines(file.readlines()), stdout)
@@ -218,7 +218,7 @@ class KUnitParserTest(unittest.TestCase):


def test_no_kunit_output(self):
- crash_log = test_data_path('test_insufficient_memory.log')
+ crash_log = _test_data_path('test_insufficient_memory.log')
print_mock = mock.patch('kunit_printer.Printer.print').start()
with open(crash_log) as file:
result = kunit_parser.parse_run_tests(
@@ -229,7 +229,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 1)

def test_skipped_test(self):
- skipped_log = test_data_path('test_skip_tests.log')
+ skipped_log = _test_data_path('test_skip_tests.log')
with open(skipped_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)

@@ -238,7 +238,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=4, skipped=1))

def test_skipped_all_tests(self):
- skipped_log = test_data_path('test_skip_all_tests.log')
+ skipped_log = _test_data_path('test_skip_all_tests.log')
with open(skipped_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)

@@ -246,7 +246,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts, kunit_parser.TestCounts(skipped=5))

def test_ignores_hyphen(self):
- hyphen_log = test_data_path('test_strip_hyphen.log')
+ hyphen_log = _test_data_path('test_strip_hyphen.log')
with open(hyphen_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)

@@ -260,7 +260,7 @@ class KUnitParserTest(unittest.TestCase):
result.subtests[1].name)

def test_ignores_prefix_printk_time(self):
- prefix_log = test_data_path('test_config_printk_time.log')
+ prefix_log = _test_data_path('test_config_printk_time.log')
with open(prefix_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
@@ -268,7 +268,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 0)

def test_ignores_multiple_prefixes(self):
- prefix_log = test_data_path('test_multiple_prefixes.log')
+ prefix_log = _test_data_path('test_multiple_prefixes.log')
with open(prefix_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
@@ -276,7 +276,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 0)

def test_prefix_mixed_kernel_output(self):
- mixed_prefix_log = test_data_path('test_interrupted_tap_output.log')
+ mixed_prefix_log = _test_data_path('test_interrupted_tap_output.log')
with open(mixed_prefix_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
@@ -284,7 +284,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 0)

def test_prefix_poundsign(self):
- pound_log = test_data_path('test_pound_sign.log')
+ pound_log = _test_data_path('test_pound_sign.log')
with open(pound_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
@@ -292,7 +292,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual(result.counts.errors, 0)

def test_kernel_panic_end(self):
- panic_log = test_data_path('test_kernel_panic_interrupt.log')
+ panic_log = _test_data_path('test_kernel_panic_interrupt.log')
with open(panic_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.TEST_CRASHED, result.status)
@@ -300,7 +300,7 @@ class KUnitParserTest(unittest.TestCase):
self.assertGreaterEqual(result.counts.errors, 1)

def test_pound_no_prefix(self):
- pound_log = test_data_path('test_pound_no_prefix.log')
+ pound_log = _test_data_path('test_pound_no_prefix.log')
with open(pound_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
@@ -329,7 +329,7 @@ class KUnitParserTest(unittest.TestCase):
'Failures: all_failed_suite, some_failed_suite.test2')

def test_ktap_format(self):
- ktap_log = test_data_path('test_parse_ktap_output.log')
+ ktap_log = _test_data_path('test_parse_ktap_output.log')
with open(ktap_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=3))
@@ -338,13 +338,13 @@ class KUnitParserTest(unittest.TestCase):
self.assertEqual('case_2', result.subtests[0].subtests[1].name)

def test_parse_subtest_header(self):
- ktap_log = test_data_path('test_parse_subtest_header.log')
+ ktap_log = _test_data_path('test_parse_subtest_header.log')
with open(ktap_log) as file:
kunit_parser.parse_run_tests(file.readlines(), stdout)
self.print_mock.assert_any_call(StrContains('suite (1 subtest)'))

def test_parse_attributes(self):
- ktap_log = test_data_path('test_parse_attributes.log')
+ ktap_log = _test_data_path('test_parse_attributes.log')
with open(ktap_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)

@@ -566,7 +566,7 @@ class KUnitJsonTest(unittest.TestCase):
self.addCleanup(mock.patch.stopall)

def _json_for(self, log_file):
- with open(test_data_path(log_file)) as file:
+ with open(_test_data_path(log_file)) as file:
test_result = kunit_parser.parse_run_tests(file, stdout)
json_obj = kunit_json.get_json_result(
test=test_result,
@@ -607,7 +607,7 @@ class StrContains(str):

class KUnitMainTest(unittest.TestCase):
def setUp(self):
- path = test_data_path('test_is_test_passed-all_passed.log')
+ path = _test_data_path('test_is_test_passed-all_passed.log')
with open(path) as file:
all_passed_log = file.readlines()


--
2.52.0

Reply all
Reply to author
Forward
0 new messages