xref: /linux/tools/testing/kunit/kunit_tool_test.py (revision e9e05c72752f9d7044b3c98b863cd04ca828e258)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3#
4# A collection of tests for tools/testing/kunit/kunit.py
5#
6# Copyright (C) 2019, Google LLC.
7# Author: Brendan Higgins <brendanhiggins@google.com>
8
9import unittest
10from unittest import mock
11
12import tempfile, shutil # Handling test_tmpdir
13
14import io
15import itertools
16import json
17import os
18import signal
19import subprocess
20import sys
21from typing import Iterable
22
23import kunit_config
24import kunit_parser
25import kunit_kernel
26import kunit_json
27import kunit_junit
28import kunit
29from kunit_printer import stdout
30
31test_tmpdir = ''
32abs_test_data_dir = ''
33
34def setUpModule():
35	global test_tmpdir, abs_test_data_dir
36	test_tmpdir = tempfile.mkdtemp()
37	abs_test_data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data'))
38
39def tearDownModule():
40	shutil.rmtree(test_tmpdir)
41
42def _test_data_path(path):
43	return os.path.join(abs_test_data_dir, path)
44
45class KconfigTest(unittest.TestCase):
46
47	def test_is_subset_of(self):
48		kconfig0 = kunit_config.Kconfig()
49		self.assertTrue(kconfig0.is_subset_of(kconfig0))
50
51		kconfig1 = kunit_config.Kconfig()
52		kconfig1.add_entry('TEST', 'y')
53		self.assertTrue(kconfig1.is_subset_of(kconfig1))
54		self.assertTrue(kconfig0.is_subset_of(kconfig1))
55		self.assertFalse(kconfig1.is_subset_of(kconfig0))
56
57	def test_read_from_file(self):
58		kconfig_path = _test_data_path('test_read_from_file.kconfig')
59
60		kconfig = kunit_config.parse_file(kconfig_path)
61
62		expected_kconfig = kunit_config.Kconfig()
63		expected_kconfig.add_entry('UML', 'y')
64		expected_kconfig.add_entry('MMU', 'y')
65		expected_kconfig.add_entry('TEST', 'y')
66		expected_kconfig.add_entry('EXAMPLE_TEST', 'y')
67		expected_kconfig.add_entry('MK8', 'n')
68
69		self.assertEqual(kconfig, expected_kconfig)
70
71	def test_write_to_file(self):
72		kconfig_path = os.path.join(test_tmpdir, '.config')
73
74		expected_kconfig = kunit_config.Kconfig()
75		expected_kconfig.add_entry('UML', 'y')
76		expected_kconfig.add_entry('MMU', 'y')
77		expected_kconfig.add_entry('TEST', 'y')
78		expected_kconfig.add_entry('EXAMPLE_TEST', 'y')
79		expected_kconfig.add_entry('MK8', 'n')
80
81		expected_kconfig.write_to_file(kconfig_path)
82
83		actual_kconfig = kunit_config.parse_file(kconfig_path)
84		self.assertEqual(actual_kconfig, expected_kconfig)
85
86class KUnitParserTest(unittest.TestCase):
87	def setUp(self):
88		self.print_mock = mock.patch('kunit_printer.Printer.print').start()
89		self.addCleanup(mock.patch.stopall)
90
91	def noPrintCallContains(self, substr: str):
92		for call in self.print_mock.mock_calls:
93			self.assertNotIn(substr, call.args[0])
94
95	def assertContains(self, needle: str, haystack: kunit_parser.LineStream):
96		# Clone the iterator so we can print the contents on failure.
97		copy, backup = itertools.tee(haystack)
98		for line in copy:
99			if needle in line:
100				return
101		raise AssertionError(f'"{needle}" not found in {list(backup)}!')
102
103	def test_output_isolated_correctly(self):
104		log_path = _test_data_path('test_output_isolated_correctly.log')
105		with open(log_path) as file:
106			result = kunit_parser.extract_tap_lines(file.readlines())
107		self.assertContains('TAP version 14', result)
108		self.assertContains('# Subtest: example', result)
109		self.assertContains('1..2', result)
110		self.assertContains('ok 1 - example_simple_test', result)
111		self.assertContains('ok 2 - example_mock_test', result)
112		self.assertContains('ok 1 - example', result)
113
114	def test_output_with_prefix_isolated_correctly(self):
115		log_path = _test_data_path('test_pound_sign.log')
116		with open(log_path) as file:
117			result = kunit_parser.extract_tap_lines(file.readlines())
118		self.assertContains('TAP version 14', result)
119		self.assertContains('# Subtest: kunit-resource-test', result)
120		self.assertContains('1..5', result)
121		self.assertContains('ok 1 - kunit_resource_test_init_resources', result)
122		self.assertContains('ok 2 - kunit_resource_test_alloc_resource', result)
123		self.assertContains('ok 3 - kunit_resource_test_destroy_resource', result)
124		self.assertContains('foo bar 	#', result)
125		self.assertContains('ok 4 - kunit_resource_test_cleanup_resources', result)
126		self.assertContains('ok 5 - kunit_resource_test_proper_free_ordering', result)
127		self.assertContains('ok 1 - kunit-resource-test', result)
128		self.assertContains('foo bar 	# non-kunit output', result)
129		self.assertContains('# Subtest: kunit-try-catch-test', result)
130		self.assertContains('1..2', result)
131		self.assertContains('ok 1 - kunit_test_try_catch_successful_try_no_catch',
132				    result)
133		self.assertContains('ok 2 - kunit_test_try_catch_unsuccessful_try_does_catch',
134				    result)
135		self.assertContains('ok 2 - kunit-try-catch-test', result)
136		self.assertContains('# Subtest: string-stream-test', result)
137		self.assertContains('1..3', result)
138		self.assertContains('ok 1 - string_stream_test_empty_on_creation', result)
139		self.assertContains('ok 2 - string_stream_test_not_empty_after_add', result)
140		self.assertContains('ok 3 - string_stream_test_get_string', result)
141		self.assertContains('ok 3 - string-stream-test', result)
142
143	def test_parse_successful_test_log(self):
144		all_passed_log = _test_data_path('test_is_test_passed-all_passed.log')
145		with open(all_passed_log) as file:
146			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
147		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
148		self.assertEqual(result.counts.errors, 0)
149
150	def test_parse_successful_nested_tests_log(self):
151		all_passed_log = _test_data_path('test_is_test_passed-all_passed_nested.log')
152		with open(all_passed_log) as file:
153			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
154		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
155		self.assertEqual(result.counts.errors, 0)
156
157	def test_kselftest_nested(self):
158		kselftest_log = _test_data_path('test_is_test_passed-kselftest.log')
159		with open(kselftest_log) as file:
160			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
161		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
162		self.assertEqual(result.counts.errors, 0)
163
164	def test_parse_failed_test_log(self):
165		failed_log = _test_data_path('test_is_test_passed-failure.log')
166		with open(failed_log) as file:
167			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
168		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
169		self.assertEqual(result.counts.errors, 0)
170
171	def test_parse_failed_nested_tests_log(self):
172		nested_log = _test_data_path('test_is_test_passed-failure-nested.log')
173		with open(nested_log) as file:
174			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
175		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
176		self.assertEqual(result.counts.failed, 2)
177		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[0].status)
178		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.subtests[0].subtests[0].status)
179		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[1].status)
180		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.subtests[1].subtests[0].status)
181
182	def test_no_header(self):
183		empty_log = _test_data_path('test_is_test_passed-no_tests_run_no_header.log')
184		with open(empty_log) as file:
185			result = kunit_parser.parse_run_tests(
186				kunit_parser.extract_tap_lines(file.readlines()), stdout)
187		self.assertEqual(0, len(result.subtests))
188		self.assertEqual(kunit_parser.TestStatus.FAILURE_TO_PARSE_TESTS, result.status)
189		self.assertEqual(result.counts.errors, 1)
190
191	def test_missing_test_plan(self):
192		missing_plan_log = _test_data_path('test_is_test_passed-'
193			'missing_plan.log')
194		with open(missing_plan_log) as file:
195			result = kunit_parser.parse_run_tests(
196				kunit_parser.extract_tap_lines(
197				file.readlines()), stdout)
198		# A missing test plan is not an error.
199		self.assertEqual(result.counts, kunit_parser.TestCounts(passed=10, errors=0))
200		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
201
202	def test_no_tests(self):
203		header_log = _test_data_path('test_is_test_passed-no_tests_run_with_header.log')
204		with open(header_log) as file:
205			result = kunit_parser.parse_run_tests(
206				kunit_parser.extract_tap_lines(file.readlines()), stdout)
207		self.assertEqual(0, len(result.subtests))
208		self.assertEqual(kunit_parser.TestStatus.NO_TESTS, result.status)
209		self.assertEqual(result.counts.errors, 1)
210
211	def test_no_tests_no_plan(self):
212		no_plan_log = _test_data_path('test_is_test_passed-no_tests_no_plan.log')
213		with open(no_plan_log) as file:
214			result = kunit_parser.parse_run_tests(
215				kunit_parser.extract_tap_lines(file.readlines()), stdout)
216		self.assertEqual(0, len(result.subtests[0].subtests[0].subtests))
217		self.assertEqual(
218			kunit_parser.TestStatus.NO_TESTS,
219			result.subtests[0].subtests[0].status)
220		self.assertEqual(result.counts, kunit_parser.TestCounts(passed=1, errors=1))
221
222
223	def test_no_kunit_output(self):
224		crash_log = _test_data_path('test_insufficient_memory.log')
225		print_mock = mock.patch('kunit_printer.Printer.print').start()
226		with open(crash_log) as file:
227			result = kunit_parser.parse_run_tests(
228				kunit_parser.extract_tap_lines(file.readlines()), stdout)
229		print_mock.assert_any_call(StrContains('Could not find any KTAP output.'))
230		print_mock.stop()
231		self.assertEqual(0, len(result.subtests))
232		self.assertEqual(result.counts.errors, 1)
233
234	def test_skipped_test(self):
235		skipped_log = _test_data_path('test_skip_tests.log')
236		with open(skipped_log) as file:
237			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
238
239		# The test result is skipped, and the skip reason is valid
240		self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[1].subtests[1].status)
241		self.assertEqual("this test should be skipped", result.subtests[1].subtests[1].skip_reason)
242
243		# A skipped test does not fail the whole suite.
244		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
245		self.assertEqual(result.counts, kunit_parser.TestCounts(passed=4, skipped=1))
246
247	def test_skipped_reason_parse(self):
248		skipped_log = _test_data_path('test_skip_all_tests.log')
249		with open(skipped_log) as file:
250			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
251
252		# The first test is skipped, with the correct reaons
253		self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[0].subtests[0].status)
254		self.assertEqual("all tests skipped", result.subtests[0].subtests[0].skip_reason)
255
256		# The first suite is skipped, with no reason
257		self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[0].status)
258		self.assertEqual("", result.subtests[0].skip_reason)
259
260	def test_skipped_all_tests(self):
261		skipped_log = _test_data_path('test_skip_all_tests.log')
262		with open(skipped_log) as file:
263			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
264
265		self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.status)
266		self.assertEqual(result.counts, kunit_parser.TestCounts(skipped=5))
267
268	def test_ignores_hyphen(self):
269		hyphen_log = _test_data_path('test_strip_hyphen.log')
270		with open(hyphen_log) as file:
271			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
272
273		# A skipped test does not fail the whole suite.
274		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
275		self.assertEqual(
276			"sysctl_test",
277			result.subtests[0].name)
278		self.assertEqual(
279			"example",
280			result.subtests[1].name)
281
282	def test_ignores_prefix_printk_time(self):
283		prefix_log = _test_data_path('test_config_printk_time.log')
284		with open(prefix_log) as file:
285			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
286		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
287		self.assertEqual('kunit-resource-test', result.subtests[0].name)
288		self.assertEqual(result.counts.errors, 0)
289
290	def test_ignores_multiple_prefixes(self):
291		prefix_log = _test_data_path('test_multiple_prefixes.log')
292		with open(prefix_log) as file:
293			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
294		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
295		self.assertEqual('kunit-resource-test', result.subtests[0].name)
296		self.assertEqual(result.counts.errors, 0)
297
298	def test_prefix_mixed_kernel_output(self):
299		mixed_prefix_log = _test_data_path('test_interrupted_tap_output.log')
300		with open(mixed_prefix_log) as file:
301			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
302		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
303		self.assertEqual('kunit-resource-test', result.subtests[0].name)
304		self.assertEqual(result.counts.errors, 0)
305
306	def test_prefix_poundsign(self):
307		pound_log = _test_data_path('test_pound_sign.log')
308		with open(pound_log) as file:
309			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
310		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
311		self.assertEqual('kunit-resource-test', result.subtests[0].name)
312		self.assertEqual(result.counts.errors, 0)
313
314	def test_kernel_panic_end(self):
315		panic_log = _test_data_path('test_kernel_panic_interrupt.log')
316		with open(panic_log) as file:
317			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
318		self.assertEqual(kunit_parser.TestStatus.TEST_CRASHED, result.status)
319		self.assertEqual('kunit-resource-test', result.subtests[0].name)
320		self.assertGreaterEqual(result.counts.errors, 1)
321
322	def test_pound_no_prefix(self):
323		pound_log = _test_data_path('test_pound_no_prefix.log')
324		with open(pound_log) as file:
325			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
326		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
327		self.assertEqual('kunit-resource-test', result.subtests[0].name)
328		self.assertEqual(result.counts.errors, 0)
329
330	def test_summarize_failures(self):
331		output = """
332		KTAP version 1
333		1..2
334			# Subtest: all_failed_suite
335			1..2
336			not ok 1 - test1
337			not ok 2 - test2
338		not ok 1 - all_failed_suite
339			# Subtest: some_failed_suite
340			1..2
341			ok 1 - test1
342			not ok 2 - test2
343		not ok 1 - some_failed_suite
344		"""
345		result = kunit_parser.parse_run_tests(output.splitlines(), stdout)
346		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
347
348		self.assertEqual(kunit_parser._summarize_failed_tests(result),
349			'Failures: all_failed_suite, some_failed_suite.test2')
350
351	def test_ktap_format(self):
352		ktap_log = _test_data_path('test_parse_ktap_output.log')
353		with open(ktap_log) as file:
354			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
355		self.assertEqual(result.counts, kunit_parser.TestCounts(passed=3))
356		self.assertEqual('suite', result.subtests[0].name)
357		self.assertEqual('case_1', result.subtests[0].subtests[0].name)
358		self.assertEqual('case_2', result.subtests[0].subtests[1].name)
359
360	def test_parse_subtest_header(self):
361		ktap_log = _test_data_path('test_parse_subtest_header.log')
362		with open(ktap_log) as file:
363			kunit_parser.parse_run_tests(file.readlines(), stdout)
364		self.print_mock.assert_any_call(StrContains('suite (1 subtest)'))
365
366	def test_parse_attributes(self):
367		ktap_log = _test_data_path('test_parse_attributes.log')
368		with open(ktap_log) as file:
369			result = kunit_parser.parse_run_tests(file.readlines(), stdout)
370
371		# Test should pass with no errors
372		self.assertEqual(result.counts, kunit_parser.TestCounts(passed=1, errors=0))
373		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
374
375		# Ensure suite header is parsed correctly
376		self.print_mock.assert_any_call(StrContains('suite (1 subtest)'))
377
378		# Ensure attributes in correct test log
379		self.assertContains('# module: example', result.subtests[0].log)
380		self.assertContains('# test.speed: slow', result.subtests[0].subtests[0].log)
381
382	def test_show_test_output_on_failure(self):
383		output = """
384		KTAP version 1
385		1..1
386		  Test output.
387		    Indented more.
388		not ok 1 test1
389		"""
390		result = kunit_parser.parse_run_tests(output.splitlines(), stdout)
391		self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
392
393		self.print_mock.assert_any_call(StrContains('Test output.'))
394		self.print_mock.assert_any_call(StrContains('  Indented more.'))
395		self.noPrintCallContains('not ok 1 test1')
396
397	def test_parse_late_test_plan(self):
398		output = """
399		TAP version 13
400		ok 4 test4
401		1..4
402		"""
403		result = kunit_parser.parse_run_tests(output.splitlines(), stdout)
404		# Missing test results after test plan should alert a suspected test crash.
405		self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
406		self.assertEqual(result.counts, kunit_parser.TestCounts(passed=1, errors=2))
407
408def line_stream_from_strs(strs: Iterable[str]) -> kunit_parser.LineStream:
409	return kunit_parser.LineStream(enumerate(strs, start=1))
410
411class LineStreamTest(unittest.TestCase):
412
413	def test_basic(self):
414		stream = line_stream_from_strs(['hello', 'world'])
415
416		self.assertTrue(stream, msg='Should be more input')
417		self.assertEqual(stream.line_number(), 1)
418		self.assertEqual(stream.peek(), 'hello')
419		self.assertEqual(stream.pop(), 'hello')
420
421		self.assertTrue(stream, msg='Should be more input')
422		self.assertEqual(stream.line_number(), 2)
423		self.assertEqual(stream.peek(), 'world')
424		self.assertEqual(stream.pop(), 'world')
425
426		self.assertFalse(stream, msg='Should be no more input')
427		with self.assertRaisesRegex(ValueError, 'LineStream: going past EOF'):
428			stream.pop()
429
430	def test_is_lazy(self):
431		called_times = 0
432		def generator():
433			nonlocal called_times
434			for _ in range(1,5):
435				called_times += 1
436				yield called_times, str(called_times)
437
438		stream = kunit_parser.LineStream(generator())
439		self.assertEqual(called_times, 0)
440
441		self.assertEqual(stream.pop(), '1')
442		self.assertEqual(called_times, 1)
443
444		self.assertEqual(stream.pop(), '2')
445		self.assertEqual(called_times, 2)
446
447class LinuxSourceTreeTest(unittest.TestCase):
448
449	def setUp(self):
450		mock.patch.object(signal, 'signal').start()
451		self.addCleanup(mock.patch.stopall)
452
453	def test_invalid_kunitconfig(self):
454		with self.assertRaisesRegex(kunit_kernel.ConfigError, 'nonexistent.* does not exist'):
455			kunit_kernel.LinuxSourceTree('', kunitconfig_paths=['/nonexistent_file'])
456
457	def test_valid_kunitconfig(self):
458		with tempfile.NamedTemporaryFile('wt') as kunitconfig:
459			kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[kunitconfig.name])
460
461	def test_dir_kunitconfig(self):
462		with tempfile.TemporaryDirectory('') as dir:
463			with open(os.path.join(dir, '.kunitconfig'), 'w'):
464				pass
465			kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[dir])
466
467	def test_multiple_kunitconfig(self):
468		want_kconfig = kunit_config.Kconfig()
469		want_kconfig.add_entry('KUNIT', 'y')
470		want_kconfig.add_entry('KUNIT_TEST', 'm')
471
472		with tempfile.TemporaryDirectory('') as dir:
473			other = os.path.join(dir, 'otherkunitconfig')
474			with open(os.path.join(dir, '.kunitconfig'), 'w') as f:
475				f.write('CONFIG_KUNIT=y')
476			with open(other, 'w') as f:
477				f.write('CONFIG_KUNIT_TEST=m')
478				pass
479
480			tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[dir, other])
481			self.assertTrue(want_kconfig.is_subset_of(tree._kconfig), msg=tree._kconfig)
482
483
484	def test_multiple_kunitconfig_invalid(self):
485		with tempfile.TemporaryDirectory('') as dir:
486			other = os.path.join(dir, 'otherkunitconfig')
487			with open(os.path.join(dir, '.kunitconfig'), 'w') as f:
488				f.write('CONFIG_KUNIT=y')
489			with open(other, 'w') as f:
490				f.write('CONFIG_KUNIT=m')
491
492			with self.assertRaisesRegex(kunit_kernel.ConfigError, '(?s)Multiple values.*CONFIG_KUNIT'):
493				kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[dir, other])
494
495
496	def test_kconfig_add(self):
497		want_kconfig = kunit_config.Kconfig()
498		want_kconfig.add_entry('NOT_REAL', 'y')
499
500		tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull],
501						    kconfig_add=['CONFIG_NOT_REAL=y'])
502		self.assertTrue(want_kconfig.is_subset_of(tree._kconfig), msg=tree._kconfig)
503
504	def test_invalid_arch(self):
505		with self.assertRaisesRegex(kunit_kernel.ConfigError, 'not a valid arch, options are.*x86_64'):
506			kunit_kernel.LinuxSourceTree('', arch='invalid')
507
508	def test_run_kernel_hits_exception(self):
509		def fake_start(unused_args, unused_build_dir):
510			return subprocess.Popen(['echo "hi\nbye"'], shell=True, text=True, stdout=subprocess.PIPE)
511
512		with tempfile.TemporaryDirectory('') as build_dir:
513			tree = kunit_kernel.LinuxSourceTree(build_dir, kunitconfig_paths=[os.devnull])
514			mock.patch.object(tree._ops, 'start', side_effect=fake_start).start()
515
516			with self.assertRaises(ValueError):
517				for line in tree.run_kernel(build_dir=build_dir):
518					self.assertEqual(line, 'hi\n')
519					raise ValueError('uh oh, did not read all output')
520
521			with open(kunit_kernel.get_outfile_path(build_dir), 'rt') as outfile:
522				self.assertEqual(outfile.read(), 'hi\nbye\n', msg='Missing some output')
523
524	def test_run_kernel_args_not_mutated(self):
525		"""Verify run_kernel() copies args so callers can reuse them."""
526		start_calls = []
527
528		def fake_start(start_args, unused_build_dir):
529			start_calls.append(list(start_args))
530			return subprocess.Popen(['printf', 'KTAP version 1\n'],
531						text=True, stdout=subprocess.PIPE)
532
533		with tempfile.TemporaryDirectory('') as build_dir:
534			tree = kunit_kernel.LinuxSourceTree(build_dir,
535					kunitconfig_paths=[os.devnull])
536			with mock.patch.object(tree._ops, 'start', side_effect=fake_start), \
537			     mock.patch.object(kunit_kernel.subprocess, 'call'):
538				kernel_args = ['mem=1G']
539				for _ in tree.run_kernel(args=kernel_args, build_dir=build_dir,
540							 filter_glob='suite.test1'):
541					pass
542				for _ in tree.run_kernel(args=kernel_args, build_dir=build_dir,
543							 filter_glob='suite.test2'):
544					pass
545				self.assertEqual(kernel_args, ['mem=1G'],
546					'run_kernel() should not modify caller args')
547				self.assertIn('kunit.filter_glob=suite.test1', start_calls[0])
548				self.assertIn('kunit.filter_glob=suite.test2', start_calls[1])
549
550	def test_run_kernel_skips_terminal_reset_without_tty(self):
551		def fake_start(unused_args, unused_build_dir):
552			return subprocess.Popen(['printf', 'KTAP version 1\n'],
553						text=True, stdout=subprocess.PIPE)
554
555		non_tty_stdin = mock.Mock()
556		non_tty_stdin.isatty.return_value = False
557
558		with tempfile.TemporaryDirectory('') as build_dir:
559			tree = kunit_kernel.LinuxSourceTree(build_dir, kunitconfig_paths=[os.devnull])
560			with mock.patch.object(tree._ops, 'start', side_effect=fake_start), \
561			     mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
562			     mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call:
563				for _ in tree.run_kernel(build_dir=build_dir):
564					pass
565
566				mock_call.assert_not_called()
567
568	def test_signal_handler_skips_terminal_reset_without_tty(self):
569		non_tty_stdin = mock.Mock()
570		non_tty_stdin.isatty.return_value = False
571		tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])
572
573		with mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
574		     mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
575		     mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
576			tree.signal_handler(signal.SIGINT, None)
577			mock_error.assert_called_once()
578			mock_call.assert_not_called()
579
580	def test_signal_handler_resets_terminal_with_tty(self):
581		tty_stdin = mock.Mock()
582		tty_stdin.isatty.return_value = True
583		tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])
584
585		with mock.patch.object(kunit_kernel.sys, 'stdin', tty_stdin), \
586		     mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
587		     mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
588			tree.signal_handler(signal.SIGINT, None)
589			mock_error.assert_called_once()
590			mock_call.assert_called_once_with(['stty', 'sane'])
591
592	def test_build_reconfig_no_config(self):
593		with tempfile.TemporaryDirectory('') as build_dir:
594			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
595				f.write('CONFIG_KUNIT=y')
596
597			tree = kunit_kernel.LinuxSourceTree(build_dir)
598			# Stub out the source tree operations, so we don't have
599			# the defaults for any given architecture get in the
600			# way.
601			tree._ops = kunit_kernel.LinuxSourceTreeOperations('none', None)
602			mock_build_config = mock.patch.object(tree, 'build_config').start()
603
604			# Should generate the .config
605			self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
606			mock_build_config.assert_called_once_with(build_dir, [])
607
608	def test_build_reconfig_existing_config(self):
609		with tempfile.TemporaryDirectory('') as build_dir:
610			# Existing .config is a superset, should not touch it
611			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
612				f.write('CONFIG_KUNIT=y')
613			with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
614				f.write('CONFIG_KUNIT=y')
615			with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
616				f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
617
618			tree = kunit_kernel.LinuxSourceTree(build_dir)
619			# Stub out the source tree operations, so we don't have
620			# the defaults for any given architecture get in the
621			# way.
622			tree._ops = kunit_kernel.LinuxSourceTreeOperations('none', None)
623			mock_build_config = mock.patch.object(tree, 'build_config').start()
624
625			self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
626			self.assertEqual(mock_build_config.call_count, 0)
627
628	def test_build_reconfig_remove_option(self):
629		with tempfile.TemporaryDirectory('') as build_dir:
630			# We removed CONFIG_KUNIT_TEST=y from our .kunitconfig...
631			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
632				f.write('CONFIG_KUNIT=y')
633			with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
634				f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
635			with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
636				f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
637
638			tree = kunit_kernel.LinuxSourceTree(build_dir)
639			# Stub out the source tree operations, so we don't have
640			# the defaults for any given architecture get in the
641			# way.
642			tree._ops = kunit_kernel.LinuxSourceTreeOperations('none', None)
643			mock_build_config = mock.patch.object(tree, 'build_config').start()
644
645			# ... so we should trigger a call to build_config()
646			self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
647			mock_build_config.assert_called_once_with(build_dir, [])
648
649	# TODO: add more test cases.
650
651
652class KUnitJsonTest(unittest.TestCase):
653	def setUp(self):
654		self.print_mock = mock.patch('kunit_printer.Printer.print').start()
655		self.addCleanup(mock.patch.stopall)
656
657	def _json_for(self, log_file):
658		with open(_test_data_path(log_file)) as file:
659			test_result = kunit_parser.parse_run_tests(file, stdout)
660			json_obj = kunit_json.get_json_result(
661				test=test_result,
662				metadata=kunit_json.Metadata())
663		return json.loads(json_obj)
664
665	def test_failed_test_json(self):
666		result = self._json_for('test_is_test_passed-failure.log')
667		self.assertEqual(
668			{'name': 'example_simple_test', 'status': 'FAIL'},
669			result["sub_groups"][1]["test_cases"][0])
670
671	def test_crashed_test_json(self):
672		result = self._json_for('test_kernel_panic_interrupt.log')
673		self.assertEqual(
674			{'name': '', 'status': 'ERROR'},
675			result["sub_groups"][2]["test_cases"][1])
676
677	def test_skipped_test_json(self):
678		result = self._json_for('test_skip_tests.log')
679		self.assertEqual(
680			{'name': 'example_skip_test', 'status': 'SKIP'},
681			result["sub_groups"][1]["test_cases"][1])
682
683	def test_no_tests_json(self):
684		result = self._json_for('test_is_test_passed-no_tests_run_with_header.log')
685		self.assertEqual(0, len(result['sub_groups']))
686
687	def test_nested_json(self):
688		result = self._json_for('test_is_test_passed-all_passed_nested.log')
689		self.assertEqual(
690			{'name': 'example_simple_test', 'status': 'PASS'},
691			result["sub_groups"][0]["sub_groups"][0]["test_cases"][0])
692
693class StrContains(str):
694	def __eq__(self, other):
695		return self in other
696
697class KUnitJUnitTest(unittest.TestCase):
698	def setUp(self):
699		self.print_mock = mock.patch('kunit_printer.Printer.print').start()
700		self.addCleanup(mock.patch.stopall)
701
702	def _junit_string(self, log_file):
703		with open(_test_data_path(log_file)) as file:
704			test_result = kunit_parser.parse_run_tests(file, stdout)
705			junit_string = kunit_junit.get_junit_result(
706					test=test_result)
707		print(junit_string)
708		return junit_string
709
710	def test_failed_test_junit(self):
711		result = self._junit_string('test_is_test_passed-failure.log')
712		self.assertTrue("<failure>" in result)
713
714	def test_skipped_test_junit(self):
715		result = self._junit_string('test_skip_tests.log')
716		self.assertTrue("<skipped>" in result)
717		self.assertTrue("skipped=\"1\"" in result)
718
719	def test_crashed_test_junit(self):
720		result = self._junit_string('test_kernel_panic_interrupt.log')
721		self.assertTrue("<error>" in result);
722
723	def test_no_tests_junit(self):
724		result = self._junit_string('test_is_test_passed-no_tests_run_with_header.log')
725		self.assertTrue("tests=\"0\"" in result)
726		self.assertFalse("testcase" in result)
727
728
729class KUnitMainTest(unittest.TestCase):
730	def setUp(self):
731		path = _test_data_path('test_is_test_passed-all_passed.log')
732		with open(path) as file:
733			all_passed_log = file.readlines()
734
735		self.print_mock = mock.patch('kunit_printer.Printer.print').start()
736		mock.patch.dict(os.environ, clear=True).start()
737		self.addCleanup(mock.patch.stopall)
738
739		self.mock_linux_init = mock.patch.object(kunit_kernel, 'LinuxSourceTree').start()
740		self.linux_source_mock = self.mock_linux_init.return_value
741		self.linux_source_mock.build_reconfig.return_value = True
742		self.linux_source_mock.build_kernel.return_value = True
743		self.linux_source_mock.run_kernel.return_value = all_passed_log
744
745	def test_config_passes_args_pass(self):
746		kunit.main(['config', '--build_dir=.kunit'])
747		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
748		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 0)
749
750	def test_build_passes_args_pass(self):
751		kunit.main(['build'])
752		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
753		self.linux_source_mock.build_kernel.assert_called_once_with(kunit.get_default_jobs(), '.kunit', None)
754		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 0)
755
756	def test_exec_passes_args_pass(self):
757		kunit.main(['exec'])
758		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 0)
759		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
760		self.linux_source_mock.run_kernel.assert_called_once_with(
761			args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
762		self.print_mock.assert_any_call(StrContains('Testing complete.'))
763
764	def test_run_passes_args_pass(self):
765		kunit.main(['run'])
766		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
767		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
768		self.linux_source_mock.run_kernel.assert_called_once_with(
769			args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
770		self.print_mock.assert_any_call(StrContains('Testing complete.'))
771
772	def test_exec_passes_args_fail(self):
773		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
774		with self.assertRaises(SystemExit) as e:
775			kunit.main(['exec'])
776		self.assertEqual(e.exception.code, 1)
777
778	def test_run_passes_args_fail(self):
779		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
780		with self.assertRaises(SystemExit) as e:
781			kunit.main(['run'])
782		self.assertEqual(e.exception.code, 1)
783		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
784		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
785		self.print_mock.assert_any_call(StrContains('Could not find any KTAP output.'))
786
787	def test_exec_no_tests(self):
788		self.linux_source_mock.run_kernel = mock.Mock(return_value=['TAP version 14', '1..0'])
789		with self.assertRaises(SystemExit) as e:
790			kunit.main(['run'])
791		self.assertEqual(e.exception.code, 1)
792		self.linux_source_mock.run_kernel.assert_called_once_with(
793			args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
794		self.print_mock.assert_any_call(StrContains(' 0 tests run!'))
795
796	def test_exec_raw_output(self):
797		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
798		kunit.main(['exec', '--raw_output'])
799		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
800		for call in self.print_mock.call_args_list:
801			self.assertNotEqual(call, mock.call(StrContains('Testing complete.')))
802			self.assertNotEqual(call, mock.call(StrContains(' 0 tests run!')))
803
804	def test_run_raw_output(self):
805		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
806		kunit.main(['run', '--raw_output'])
807		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
808		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
809		for call in self.print_mock.call_args_list:
810			self.assertNotEqual(call, mock.call(StrContains('Testing complete.')))
811			self.assertNotEqual(call, mock.call(StrContains(' 0 tests run!')))
812
813	def test_run_raw_output_kunit(self):
814		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
815		kunit.main(['run', '--raw_output=kunit'])
816		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
817		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
818		for call in self.print_mock.call_args_list:
819			self.assertNotEqual(call, mock.call(StrContains('Testing complete.')))
820			self.assertNotEqual(call, mock.call(StrContains(' 0 tests run')))
821
822	def test_run_raw_output_invalid(self):
823		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
824		with self.assertRaises(SystemExit) as e:
825			kunit.main(['run', '--raw_output=invalid'])
826		self.assertNotEqual(e.exception.code, 0)
827
828	def test_run_raw_output_does_not_take_positional_args(self):
829		# --raw_output is a string flag, but we don't want it to consume
830		# any positional arguments, only ones after an '='
831		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
832		kunit.main(['run', '--raw_output', 'filter_glob'])
833		self.linux_source_mock.run_kernel.assert_called_once_with(
834			args=None, build_dir='.kunit', filter_glob='filter_glob', filter='', filter_action=None, timeout=300)
835
836	def test_exec_timeout(self):
837		timeout = 3453
838		kunit.main(['exec', '--timeout', str(timeout)])
839		self.linux_source_mock.run_kernel.assert_called_once_with(
840			args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
841		self.print_mock.assert_any_call(StrContains('Testing complete.'))
842
843	def test_run_timeout(self):
844		timeout = 3453
845		kunit.main(['run', '--timeout', str(timeout)])
846		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
847		self.linux_source_mock.run_kernel.assert_called_once_with(
848			args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
849		self.print_mock.assert_any_call(StrContains('Testing complete.'))
850
851	def test_run_builddir(self):
852		build_dir = '.kunit'
853		kunit.main(['run', '--build_dir=.kunit'])
854		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
855		self.linux_source_mock.run_kernel.assert_called_once_with(
856			args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
857		self.print_mock.assert_any_call(StrContains('Testing complete.'))
858
859	@mock.patch.dict(os.environ, {'KBUILD_OUTPUT': '/tmp'})
860	def test_run_builddir_from_env(self):
861		build_dir = '/tmp/.kunit'
862		kunit.main(['run'])
863		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
864		self.linux_source_mock.run_kernel.assert_called_once_with(
865			args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
866		self.print_mock.assert_any_call(StrContains('Testing complete.'))
867
868	@mock.patch.dict(os.environ, {'KBUILD_OUTPUT': '/tmp'})
869	def test_run_builddir_override(self):
870		build_dir = '.kunit'
871		kunit.main(['run', '--build_dir=.kunit'])
872		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
873		self.linux_source_mock.run_kernel.assert_called_once_with(
874			args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
875		self.print_mock.assert_any_call(StrContains('Testing complete.'))
876
877	def test_config_builddir(self):
878		build_dir = '.kunit'
879		kunit.main(['config', '--build_dir', build_dir])
880		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
881
882	def test_build_builddir(self):
883		build_dir = '.kunit'
884		jobs = kunit.get_default_jobs()
885		kunit.main(['build', '--build_dir', build_dir])
886		self.linux_source_mock.build_kernel.assert_called_once_with(jobs, build_dir, None)
887
888	def test_exec_builddir(self):
889		build_dir = '.kunit'
890		kunit.main(['exec', '--build_dir', build_dir])
891		self.linux_source_mock.run_kernel.assert_called_once_with(
892			args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
893		self.print_mock.assert_any_call(StrContains('Testing complete.'))
894
895	def test_run_kunitconfig(self):
896		kunit.main(['run', '--kunitconfig=mykunitconfig'])
897		# Just verify that we parsed and initialized it correctly here.
898		self.mock_linux_init.assert_called_once_with('.kunit',
899						kunitconfig_paths=['mykunitconfig'],
900						kconfig_add=None,
901						arch='um',
902						cross_compile=None,
903						qemu_config_path=None,
904						extra_qemu_args=[])
905
906	def test_config_kunitconfig(self):
907		kunit.main(['config', '--kunitconfig=mykunitconfig'])
908		# Just verify that we parsed and initialized it correctly here.
909		self.mock_linux_init.assert_called_once_with('.kunit',
910						kunitconfig_paths=['mykunitconfig'],
911						kconfig_add=None,
912						arch='um',
913						cross_compile=None,
914						qemu_config_path=None,
915						extra_qemu_args=[])
916
917	def test_config_alltests(self):
918		kunit.main(['config', '--kunitconfig=mykunitconfig', '--alltests'])
919		# Just verify that we parsed and initialized it correctly here.
920		self.mock_linux_init.assert_called_once_with('.kunit',
921						kunitconfig_paths=[kunit_kernel.ALL_TESTS_CONFIG_PATH, 'mykunitconfig'],
922						kconfig_add=None,
923						arch='um',
924						cross_compile=None,
925						qemu_config_path=None,
926						extra_qemu_args=[])
927
928
929	@mock.patch.object(kunit_kernel, 'LinuxSourceTree')
930	def test_run_multiple_kunitconfig(self, mock_linux_init):
931		mock_linux_init.return_value = self.linux_source_mock
932		kunit.main(['run', '--kunitconfig=mykunitconfig', '--kunitconfig=other'])
933		# Just verify that we parsed and initialized it correctly here.
934		mock_linux_init.assert_called_once_with('.kunit',
935							kunitconfig_paths=['mykunitconfig', 'other'],
936							kconfig_add=None,
937							arch='um',
938							cross_compile=None,
939							qemu_config_path=None,
940							extra_qemu_args=[])
941
942	def test_run_kconfig_add(self):
943		kunit.main(['run', '--kconfig_add=CONFIG_KASAN=y', '--kconfig_add=CONFIG_KCSAN=y'])
944		# Just verify that we parsed and initialized it correctly here.
945		self.mock_linux_init.assert_called_once_with('.kunit',
946						kunitconfig_paths=[],
947						kconfig_add=['CONFIG_KASAN=y', 'CONFIG_KCSAN=y'],
948						arch='um',
949						cross_compile=None,
950						qemu_config_path=None,
951						extra_qemu_args=[])
952
953	def test_run_qemu_args(self):
954		kunit.main(['run', '--arch=x86_64', '--qemu_args', '-m 2048'])
955		# Just verify that we parsed and initialized it correctly here.
956		self.mock_linux_init.assert_called_once_with('.kunit',
957						kunitconfig_paths=[],
958						kconfig_add=None,
959						arch='x86_64',
960						cross_compile=None,
961						qemu_config_path=None,
962						extra_qemu_args=['-m', '2048'])
963
964	def test_run_kernel_args(self):
965		kunit.main(['run', '--kernel_args=a=1', '--kernel_args=b=2'])
966		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
967		self.linux_source_mock.run_kernel.assert_called_once_with(
968		      args=['a=1','b=2'], build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
969		self.print_mock.assert_any_call(StrContains('Testing complete.'))
970
971	def test_list_tests(self):
972		want = ['suite.test1', 'suite.test2', 'suite2.test1']
973		self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
974
975		got = kunit._list_tests(self.linux_source_mock,
976				     kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
977		self.assertEqual(got, want)
978		# Should respect the user's filter glob when listing tests.
979		self.linux_source_mock.run_kernel.assert_called_once_with(
980			args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', filter='', filter_action=None, timeout=300)
981
982	@mock.patch.object(kunit, '_list_tests')
983	def test_run_isolated_by_suite(self, mock_tests):
984		mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1']
985		kunit.main(['exec', '--run_isolated=suite', 'suite*.test*'])
986
987		# Should respect the user's filter glob when listing tests.
988		mock_tests.assert_called_once_with(mock.ANY,
989				     kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
990		self.linux_source_mock.run_kernel.assert_has_calls([
991			mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
992			mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
993		])
994
995	@mock.patch.object(kunit, '_list_tests')
996	def test_run_isolated_by_test(self, mock_tests):
997		mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1']
998		kunit.main(['exec', '--run_isolated=test', 'suite*'])
999
1000		# Should respect the user's filter glob when listing tests.
1001		mock_tests.assert_called_once_with(mock.ANY,
1002				     kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
1003		self.linux_source_mock.run_kernel.assert_has_calls([
1004			mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
1005			mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
1006			mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', filter='', filter_action=None, timeout=300),
1007		])
1008
1009	@mock.patch.object(kunit, '_list_tests')
1010	@mock.patch.object(sys, 'stdout', new_callable=io.StringIO)
1011	def test_list_suites(self, mock_stdout, mock_tests):
1012		mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1']
1013		kunit.main(['run', '--list_suites'])
1014
1015		want = ['suite', 'suite2']
1016		output = mock_stdout.getvalue().split()
1017		self.assertEqual(output, want)
1018
1019	@mock.patch.object(sys, 'stdout', new_callable=io.StringIO)
1020	def test_list_cmds(self, mock_stdout):
1021		kunit.main(['--list-cmds'])
1022		output = mock_stdout.getvalue()
1023		output_cmds = sorted(output.split())
1024		expected_cmds = sorted(['build', 'config', 'exec', 'parse', 'run'])
1025		self.assertEqual(output_cmds, expected_cmds)
1026
1027	@mock.patch.object(sys, 'stdout', new_callable=io.StringIO)
1028	def test_run_list_opts(self, mock_stdout):
1029		kunit.main(['run', '--list-opts'])
1030		output = mock_stdout.getvalue()
1031		output_cmds = set(output.split())
1032		self.assertIn('--help', output_cmds)
1033		self.assertIn('--kunitconfig', output_cmds)
1034		self.assertIn('--jobs', output_cmds)
1035		self.assertIn('--kernel_args', output_cmds)
1036		self.assertIn('--raw_output', output_cmds)
1037
1038if __name__ == '__main__':
1039	unittest.main()
1040