cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

kunit_tool_test.py (28811B)


      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 itertools
     15import json
     16import os
     17import signal
     18import subprocess
     19from typing import Iterable
     20
     21import kunit_config
     22import kunit_parser
     23import kunit_kernel
     24import kunit_json
     25import kunit
     26
     27test_tmpdir = ''
     28abs_test_data_dir = ''
     29
     30def setUpModule():
     31	global test_tmpdir, abs_test_data_dir
     32	test_tmpdir = tempfile.mkdtemp()
     33	abs_test_data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_data'))
     34
     35def tearDownModule():
     36	shutil.rmtree(test_tmpdir)
     37
     38def test_data_path(path):
     39	return os.path.join(abs_test_data_dir, path)
     40
     41class KconfigTest(unittest.TestCase):
     42
     43	def test_is_subset_of(self):
     44		kconfig0 = kunit_config.Kconfig()
     45		self.assertTrue(kconfig0.is_subset_of(kconfig0))
     46
     47		kconfig1 = kunit_config.Kconfig()
     48		kconfig1.add_entry(kunit_config.KconfigEntry('TEST', 'y'))
     49		self.assertTrue(kconfig1.is_subset_of(kconfig1))
     50		self.assertTrue(kconfig0.is_subset_of(kconfig1))
     51		self.assertFalse(kconfig1.is_subset_of(kconfig0))
     52
     53	def test_read_from_file(self):
     54		kconfig_path = test_data_path('test_read_from_file.kconfig')
     55
     56		kconfig = kunit_config.parse_file(kconfig_path)
     57
     58		expected_kconfig = kunit_config.Kconfig()
     59		expected_kconfig.add_entry(
     60			kunit_config.KconfigEntry('UML', 'y'))
     61		expected_kconfig.add_entry(
     62			kunit_config.KconfigEntry('MMU', 'y'))
     63		expected_kconfig.add_entry(
     64			kunit_config.KconfigEntry('TEST', 'y'))
     65		expected_kconfig.add_entry(
     66			kunit_config.KconfigEntry('EXAMPLE_TEST', 'y'))
     67		expected_kconfig.add_entry(
     68			kunit_config.KconfigEntry('MK8', 'n'))
     69
     70		self.assertEqual(kconfig.entries(), expected_kconfig.entries())
     71
     72	def test_write_to_file(self):
     73		kconfig_path = os.path.join(test_tmpdir, '.config')
     74
     75		expected_kconfig = kunit_config.Kconfig()
     76		expected_kconfig.add_entry(
     77			kunit_config.KconfigEntry('UML', 'y'))
     78		expected_kconfig.add_entry(
     79			kunit_config.KconfigEntry('MMU', 'y'))
     80		expected_kconfig.add_entry(
     81			kunit_config.KconfigEntry('TEST', 'y'))
     82		expected_kconfig.add_entry(
     83			kunit_config.KconfigEntry('EXAMPLE_TEST', 'y'))
     84		expected_kconfig.add_entry(
     85			kunit_config.KconfigEntry('MK8', 'n'))
     86
     87		expected_kconfig.write_to_file(kconfig_path)
     88
     89		actual_kconfig = kunit_config.parse_file(kconfig_path)
     90
     91		self.assertEqual(actual_kconfig.entries(),
     92				 expected_kconfig.entries())
     93
     94class KUnitParserTest(unittest.TestCase):
     95
     96	def assertContains(self, needle: str, haystack: kunit_parser.LineStream):
     97		# Clone the iterator so we can print the contents on failure.
     98		copy, backup = itertools.tee(haystack)
     99		for line in copy:
    100			if needle in line:
    101				return
    102		raise AssertionError(f'"{needle}" not found in {list(backup)}!')
    103
    104	def test_output_isolated_correctly(self):
    105		log_path = test_data_path('test_output_isolated_correctly.log')
    106		with open(log_path) as file:
    107			result = kunit_parser.extract_tap_lines(file.readlines())
    108		self.assertContains('TAP version 14', result)
    109		self.assertContains('# Subtest: example', result)
    110		self.assertContains('1..2', result)
    111		self.assertContains('ok 1 - example_simple_test', result)
    112		self.assertContains('ok 2 - example_mock_test', result)
    113		self.assertContains('ok 1 - example', result)
    114
    115	def test_output_with_prefix_isolated_correctly(self):
    116		log_path = test_data_path('test_pound_sign.log')
    117		with open(log_path) as file:
    118			result = kunit_parser.extract_tap_lines(file.readlines())
    119		self.assertContains('TAP version 14', result)
    120		self.assertContains('# Subtest: kunit-resource-test', result)
    121		self.assertContains('1..5', result)
    122		self.assertContains('ok 1 - kunit_resource_test_init_resources', result)
    123		self.assertContains('ok 2 - kunit_resource_test_alloc_resource', result)
    124		self.assertContains('ok 3 - kunit_resource_test_destroy_resource', result)
    125		self.assertContains('foo bar 	#', result)
    126		self.assertContains('ok 4 - kunit_resource_test_cleanup_resources', result)
    127		self.assertContains('ok 5 - kunit_resource_test_proper_free_ordering', result)
    128		self.assertContains('ok 1 - kunit-resource-test', result)
    129		self.assertContains('foo bar 	# non-kunit output', result)
    130		self.assertContains('# Subtest: kunit-try-catch-test', result)
    131		self.assertContains('1..2', result)
    132		self.assertContains('ok 1 - kunit_test_try_catch_successful_try_no_catch',
    133				    result)
    134		self.assertContains('ok 2 - kunit_test_try_catch_unsuccessful_try_does_catch',
    135				    result)
    136		self.assertContains('ok 2 - kunit-try-catch-test', result)
    137		self.assertContains('# Subtest: string-stream-test', result)
    138		self.assertContains('1..3', result)
    139		self.assertContains('ok 1 - string_stream_test_empty_on_creation', result)
    140		self.assertContains('ok 2 - string_stream_test_not_empty_after_add', result)
    141		self.assertContains('ok 3 - string_stream_test_get_string', result)
    142		self.assertContains('ok 3 - string-stream-test', result)
    143
    144	def test_parse_successful_test_log(self):
    145		all_passed_log = test_data_path('test_is_test_passed-all_passed.log')
    146		with open(all_passed_log) as file:
    147			result = kunit_parser.parse_run_tests(file.readlines())
    148		self.assertEqual(
    149			kunit_parser.TestStatus.SUCCESS,
    150			result.status)
    151
    152	def test_parse_successful_nested_tests_log(self):
    153		all_passed_log = test_data_path('test_is_test_passed-all_passed_nested.log')
    154		with open(all_passed_log) as file:
    155			result = kunit_parser.parse_run_tests(file.readlines())
    156		self.assertEqual(
    157			kunit_parser.TestStatus.SUCCESS,
    158			result.status)
    159
    160	def test_kselftest_nested(self):
    161		kselftest_log = test_data_path('test_is_test_passed-kselftest.log')
    162		with open(kselftest_log) as file:
    163			result = kunit_parser.parse_run_tests(file.readlines())
    164			self.assertEqual(
    165				kunit_parser.TestStatus.SUCCESS,
    166				result.status)
    167
    168	def test_parse_failed_test_log(self):
    169		failed_log = test_data_path('test_is_test_passed-failure.log')
    170		with open(failed_log) as file:
    171			result = kunit_parser.parse_run_tests(file.readlines())
    172		self.assertEqual(
    173			kunit_parser.TestStatus.FAILURE,
    174			result.status)
    175
    176	def test_no_header(self):
    177		empty_log = test_data_path('test_is_test_passed-no_tests_run_no_header.log')
    178		with open(empty_log) as file:
    179			result = kunit_parser.parse_run_tests(
    180				kunit_parser.extract_tap_lines(file.readlines()))
    181		self.assertEqual(0, len(result.subtests))
    182		self.assertEqual(
    183			kunit_parser.TestStatus.FAILURE_TO_PARSE_TESTS,
    184			result.status)
    185
    186	def test_missing_test_plan(self):
    187		missing_plan_log = test_data_path('test_is_test_passed-'
    188			'missing_plan.log')
    189		with open(missing_plan_log) as file:
    190			result = kunit_parser.parse_run_tests(
    191				kunit_parser.extract_tap_lines(
    192				file.readlines()))
    193		# A missing test plan is not an error.
    194		self.assertEqual(0, result.counts.errors)
    195		# All tests should be accounted for.
    196		self.assertEqual(10, result.counts.total())
    197		self.assertEqual(
    198			kunit_parser.TestStatus.SUCCESS,
    199			result.status)
    200
    201	def test_no_tests(self):
    202		header_log = test_data_path('test_is_test_passed-no_tests_run_with_header.log')
    203		with open(header_log) as file:
    204			result = kunit_parser.parse_run_tests(
    205				kunit_parser.extract_tap_lines(file.readlines()))
    206		self.assertEqual(0, len(result.subtests))
    207		self.assertEqual(
    208			kunit_parser.TestStatus.NO_TESTS,
    209			result.status)
    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()))
    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(1, result.counts.errors)
    221
    222
    223	def test_no_kunit_output(self):
    224		crash_log = test_data_path('test_insufficient_memory.log')
    225		print_mock = mock.patch('builtins.print').start()
    226		with open(crash_log) as file:
    227			result = kunit_parser.parse_run_tests(
    228				kunit_parser.extract_tap_lines(file.readlines()))
    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
    233	def test_skipped_test(self):
    234		skipped_log = test_data_path('test_skip_tests.log')
    235		with open(skipped_log) as file:
    236			result = kunit_parser.parse_run_tests(file.readlines())
    237
    238		# A skipped test does not fail the whole suite.
    239		self.assertEqual(
    240			kunit_parser.TestStatus.SUCCESS,
    241			result.status)
    242
    243	def test_skipped_all_tests(self):
    244		skipped_log = test_data_path('test_skip_all_tests.log')
    245		with open(skipped_log) as file:
    246			result = kunit_parser.parse_run_tests(file.readlines())
    247
    248		self.assertEqual(
    249			kunit_parser.TestStatus.SKIPPED,
    250			result.status)
    251
    252	def test_ignores_hyphen(self):
    253		hyphen_log = test_data_path('test_strip_hyphen.log')
    254		with open(hyphen_log) as file:
    255			result = kunit_parser.parse_run_tests(file.readlines())
    256
    257		# A skipped test does not fail the whole suite.
    258		self.assertEqual(
    259			kunit_parser.TestStatus.SUCCESS,
    260			result.status)
    261		self.assertEqual(
    262			"sysctl_test",
    263			result.subtests[0].name)
    264		self.assertEqual(
    265			"example",
    266			result.subtests[1].name)
    267		file.close()
    268
    269
    270	def test_ignores_prefix_printk_time(self):
    271		prefix_log = test_data_path('test_config_printk_time.log')
    272		with open(prefix_log) as file:
    273			result = kunit_parser.parse_run_tests(file.readlines())
    274			self.assertEqual(
    275				kunit_parser.TestStatus.SUCCESS,
    276				result.status)
    277			self.assertEqual('kunit-resource-test', result.subtests[0].name)
    278
    279	def test_ignores_multiple_prefixes(self):
    280		prefix_log = test_data_path('test_multiple_prefixes.log')
    281		with open(prefix_log) as file:
    282			result = kunit_parser.parse_run_tests(file.readlines())
    283			self.assertEqual(
    284				kunit_parser.TestStatus.SUCCESS,
    285				result.status)
    286			self.assertEqual('kunit-resource-test', result.subtests[0].name)
    287
    288	def test_prefix_mixed_kernel_output(self):
    289		mixed_prefix_log = test_data_path('test_interrupted_tap_output.log')
    290		with open(mixed_prefix_log) as file:
    291			result = kunit_parser.parse_run_tests(file.readlines())
    292			self.assertEqual(
    293				kunit_parser.TestStatus.SUCCESS,
    294				result.status)
    295			self.assertEqual('kunit-resource-test', result.subtests[0].name)
    296
    297	def test_prefix_poundsign(self):
    298		pound_log = test_data_path('test_pound_sign.log')
    299		with open(pound_log) as file:
    300			result = kunit_parser.parse_run_tests(file.readlines())
    301			self.assertEqual(
    302				kunit_parser.TestStatus.SUCCESS,
    303				result.status)
    304			self.assertEqual('kunit-resource-test', result.subtests[0].name)
    305
    306	def test_kernel_panic_end(self):
    307		panic_log = test_data_path('test_kernel_panic_interrupt.log')
    308		with open(panic_log) as file:
    309			result = kunit_parser.parse_run_tests(file.readlines())
    310			self.assertEqual(
    311				kunit_parser.TestStatus.TEST_CRASHED,
    312				result.status)
    313			self.assertEqual('kunit-resource-test', result.subtests[0].name)
    314
    315	def test_pound_no_prefix(self):
    316		pound_log = test_data_path('test_pound_no_prefix.log')
    317		with open(pound_log) as file:
    318			result = kunit_parser.parse_run_tests(file.readlines())
    319			self.assertEqual(
    320				kunit_parser.TestStatus.SUCCESS,
    321				result.status)
    322			self.assertEqual('kunit-resource-test', result.subtests[0].name)
    323
    324def line_stream_from_strs(strs: Iterable[str]) -> kunit_parser.LineStream:
    325	return kunit_parser.LineStream(enumerate(strs, start=1))
    326
    327class LineStreamTest(unittest.TestCase):
    328
    329	def test_basic(self):
    330		stream = line_stream_from_strs(['hello', 'world'])
    331
    332		self.assertTrue(stream, msg='Should be more input')
    333		self.assertEqual(stream.line_number(), 1)
    334		self.assertEqual(stream.peek(), 'hello')
    335		self.assertEqual(stream.pop(), 'hello')
    336
    337		self.assertTrue(stream, msg='Should be more input')
    338		self.assertEqual(stream.line_number(), 2)
    339		self.assertEqual(stream.peek(), 'world')
    340		self.assertEqual(stream.pop(), 'world')
    341
    342		self.assertFalse(stream, msg='Should be no more input')
    343		with self.assertRaisesRegex(ValueError, 'LineStream: going past EOF'):
    344			stream.pop()
    345
    346	def test_is_lazy(self):
    347		called_times = 0
    348		def generator():
    349			nonlocal called_times
    350			for _ in range(1,5):
    351				called_times += 1
    352				yield called_times, str(called_times)
    353
    354		stream = kunit_parser.LineStream(generator())
    355		self.assertEqual(called_times, 0)
    356
    357		self.assertEqual(stream.pop(), '1')
    358		self.assertEqual(called_times, 1)
    359
    360		self.assertEqual(stream.pop(), '2')
    361		self.assertEqual(called_times, 2)
    362
    363class LinuxSourceTreeTest(unittest.TestCase):
    364
    365	def setUp(self):
    366		mock.patch.object(signal, 'signal').start()
    367		self.addCleanup(mock.patch.stopall)
    368
    369	def test_invalid_kunitconfig(self):
    370		with self.assertRaisesRegex(kunit_kernel.ConfigError, 'nonexistent.* does not exist'):
    371			kunit_kernel.LinuxSourceTree('', kunitconfig_path='/nonexistent_file')
    372
    373	def test_valid_kunitconfig(self):
    374		with tempfile.NamedTemporaryFile('wt') as kunitconfig:
    375			kunit_kernel.LinuxSourceTree('', kunitconfig_path=kunitconfig.name)
    376
    377	def test_dir_kunitconfig(self):
    378		with tempfile.TemporaryDirectory('') as dir:
    379			with open(os.path.join(dir, '.kunitconfig'), 'w'):
    380				pass
    381			kunit_kernel.LinuxSourceTree('', kunitconfig_path=dir)
    382
    383	def test_kconfig_add(self):
    384		tree = kunit_kernel.LinuxSourceTree('', kconfig_add=['CONFIG_NOT_REAL=y'])
    385		self.assertIn(kunit_config.KconfigEntry('NOT_REAL', 'y'), tree._kconfig.entries())
    386
    387	def test_invalid_arch(self):
    388		with self.assertRaisesRegex(kunit_kernel.ConfigError, 'not a valid arch, options are.*x86_64'):
    389			kunit_kernel.LinuxSourceTree('', arch='invalid')
    390
    391	def test_run_kernel_hits_exception(self):
    392		def fake_start(unused_args, unused_build_dir):
    393			return subprocess.Popen(['echo "hi\nbye"'], shell=True, text=True, stdout=subprocess.PIPE)
    394
    395		with tempfile.TemporaryDirectory('') as build_dir:
    396			tree = kunit_kernel.LinuxSourceTree(build_dir, load_config=False)
    397			mock.patch.object(tree._ops, 'start', side_effect=fake_start).start()
    398
    399			with self.assertRaises(ValueError):
    400				for line in tree.run_kernel(build_dir=build_dir):
    401					self.assertEqual(line, 'hi\n')
    402					raise ValueError('uh oh, did not read all output')
    403
    404			with open(kunit_kernel.get_outfile_path(build_dir), 'rt') as outfile:
    405				self.assertEqual(outfile.read(), 'hi\nbye\n', msg='Missing some output')
    406
    407	def test_build_reconfig_no_config(self):
    408		with tempfile.TemporaryDirectory('') as build_dir:
    409			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
    410				f.write('CONFIG_KUNIT=y')
    411
    412			tree = kunit_kernel.LinuxSourceTree(build_dir)
    413			mock_build_config = mock.patch.object(tree, 'build_config').start()
    414
    415			# Should generate the .config
    416			self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
    417			mock_build_config.assert_called_once_with(build_dir, [])
    418
    419	def test_build_reconfig_existing_config(self):
    420		with tempfile.TemporaryDirectory('') as build_dir:
    421			# Existing .config is a superset, should not touch it
    422			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
    423				f.write('CONFIG_KUNIT=y')
    424			with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
    425				f.write('CONFIG_KUNIT=y')
    426			with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
    427				f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
    428
    429			tree = kunit_kernel.LinuxSourceTree(build_dir)
    430			mock_build_config = mock.patch.object(tree, 'build_config').start()
    431
    432			self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
    433			self.assertEqual(mock_build_config.call_count, 0)
    434
    435	def test_build_reconfig_remove_option(self):
    436		with tempfile.TemporaryDirectory('') as build_dir:
    437			# We removed CONFIG_KUNIT_TEST=y from our .kunitconfig...
    438			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
    439				f.write('CONFIG_KUNIT=y')
    440			with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
    441				f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
    442			with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
    443				f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
    444
    445			tree = kunit_kernel.LinuxSourceTree(build_dir)
    446			mock_build_config = mock.patch.object(tree, 'build_config').start()
    447
    448			# ... so we should trigger a call to build_config()
    449			self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
    450			mock_build_config.assert_called_once_with(build_dir, [])
    451
    452	# TODO: add more test cases.
    453
    454
    455class KUnitJsonTest(unittest.TestCase):
    456
    457	def _json_for(self, log_file):
    458		with open(test_data_path(log_file)) as file:
    459			test_result = kunit_parser.parse_run_tests(file)
    460			json_obj = kunit_json.get_json_result(
    461				test=test_result,
    462				metadata=kunit_json.Metadata())
    463		return json.loads(json_obj)
    464
    465	def test_failed_test_json(self):
    466		result = self._json_for('test_is_test_passed-failure.log')
    467		self.assertEqual(
    468			{'name': 'example_simple_test', 'status': 'FAIL'},
    469			result["sub_groups"][1]["test_cases"][0])
    470
    471	def test_crashed_test_json(self):
    472		result = self._json_for('test_kernel_panic_interrupt.log')
    473		self.assertEqual(
    474			{'name': '', 'status': 'ERROR'},
    475			result["sub_groups"][2]["test_cases"][1])
    476
    477	def test_skipped_test_json(self):
    478		result = self._json_for('test_skip_tests.log')
    479		self.assertEqual(
    480			{'name': 'example_skip_test', 'status': 'SKIP'},
    481			result["sub_groups"][1]["test_cases"][1])
    482
    483	def test_no_tests_json(self):
    484		result = self._json_for('test_is_test_passed-no_tests_run_with_header.log')
    485		self.assertEqual(0, len(result['sub_groups']))
    486
    487	def test_nested_json(self):
    488		result = self._json_for('test_is_test_passed-all_passed_nested.log')
    489		self.assertEqual(
    490			{'name': 'example_simple_test', 'status': 'PASS'},
    491			result["sub_groups"][0]["sub_groups"][0]["test_cases"][0])
    492
    493class StrContains(str):
    494	def __eq__(self, other):
    495		return self in other
    496
    497class KUnitMainTest(unittest.TestCase):
    498	def setUp(self):
    499		path = test_data_path('test_is_test_passed-all_passed.log')
    500		with open(path) as file:
    501			all_passed_log = file.readlines()
    502
    503		self.print_mock = mock.patch('builtins.print').start()
    504		self.addCleanup(mock.patch.stopall)
    505
    506		self.linux_source_mock = mock.Mock()
    507		self.linux_source_mock.build_reconfig = mock.Mock(return_value=True)
    508		self.linux_source_mock.build_kernel = mock.Mock(return_value=True)
    509		self.linux_source_mock.run_kernel = mock.Mock(return_value=all_passed_log)
    510
    511	def test_config_passes_args_pass(self):
    512		kunit.main(['config', '--build_dir=.kunit'], self.linux_source_mock)
    513		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    514		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 0)
    515
    516	def test_build_passes_args_pass(self):
    517		kunit.main(['build'], self.linux_source_mock)
    518		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    519		self.linux_source_mock.build_kernel.assert_called_once_with(False, kunit.get_default_jobs(), '.kunit', None)
    520		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 0)
    521
    522	def test_exec_passes_args_pass(self):
    523		kunit.main(['exec'], self.linux_source_mock)
    524		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 0)
    525		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
    526		self.linux_source_mock.run_kernel.assert_called_once_with(
    527			args=None, build_dir='.kunit', filter_glob='', timeout=300)
    528		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    529
    530	def test_run_passes_args_pass(self):
    531		kunit.main(['run'], self.linux_source_mock)
    532		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    533		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
    534		self.linux_source_mock.run_kernel.assert_called_once_with(
    535			args=None, build_dir='.kunit', filter_glob='', timeout=300)
    536		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    537
    538	def test_exec_passes_args_fail(self):
    539		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    540		with self.assertRaises(SystemExit) as e:
    541			kunit.main(['exec'], self.linux_source_mock)
    542		self.assertEqual(e.exception.code, 1)
    543
    544	def test_run_passes_args_fail(self):
    545		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    546		with self.assertRaises(SystemExit) as e:
    547			kunit.main(['run'], self.linux_source_mock)
    548		self.assertEqual(e.exception.code, 1)
    549		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    550		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
    551		self.print_mock.assert_any_call(StrContains('could not find any KTAP output!'))
    552
    553	def test_exec_no_tests(self):
    554		self.linux_source_mock.run_kernel = mock.Mock(return_value=['TAP version 14', '1..0'])
    555		with self.assertRaises(SystemExit) as e:
    556			kunit.main(['run'], self.linux_source_mock)
    557		self.assertEqual(e.exception.code, 1)
    558		self.linux_source_mock.run_kernel.assert_called_once_with(
    559			args=None, build_dir='.kunit', filter_glob='', timeout=300)
    560		self.print_mock.assert_any_call(StrContains(' 0 tests run!'))
    561
    562	def test_exec_raw_output(self):
    563		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    564		kunit.main(['exec', '--raw_output'], self.linux_source_mock)
    565		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
    566		for call in self.print_mock.call_args_list:
    567			self.assertNotEqual(call, mock.call(StrContains('Testing complete.')))
    568			self.assertNotEqual(call, mock.call(StrContains(' 0 tests run!')))
    569
    570	def test_run_raw_output(self):
    571		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    572		kunit.main(['run', '--raw_output'], self.linux_source_mock)
    573		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    574		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
    575		for call in self.print_mock.call_args_list:
    576			self.assertNotEqual(call, mock.call(StrContains('Testing complete.')))
    577			self.assertNotEqual(call, mock.call(StrContains(' 0 tests run!')))
    578
    579	def test_run_raw_output_kunit(self):
    580		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    581		kunit.main(['run', '--raw_output=kunit'], self.linux_source_mock)
    582		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    583		self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
    584		for call in self.print_mock.call_args_list:
    585			self.assertNotEqual(call, mock.call(StrContains('Testing complete.')))
    586			self.assertNotEqual(call, mock.call(StrContains(' 0 tests run')))
    587
    588	def test_run_raw_output_invalid(self):
    589		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    590		with self.assertRaises(SystemExit) as e:
    591			kunit.main(['run', '--raw_output=invalid'], self.linux_source_mock)
    592		self.assertNotEqual(e.exception.code, 0)
    593
    594	def test_run_raw_output_does_not_take_positional_args(self):
    595		# --raw_output is a string flag, but we don't want it to consume
    596		# any positional arguments, only ones after an '='
    597		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
    598		kunit.main(['run', '--raw_output', 'filter_glob'], self.linux_source_mock)
    599		self.linux_source_mock.run_kernel.assert_called_once_with(
    600			args=None, build_dir='.kunit', filter_glob='filter_glob', timeout=300)
    601
    602	def test_exec_timeout(self):
    603		timeout = 3453
    604		kunit.main(['exec', '--timeout', str(timeout)], self.linux_source_mock)
    605		self.linux_source_mock.run_kernel.assert_called_once_with(
    606			args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
    607		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    608
    609	def test_run_timeout(self):
    610		timeout = 3453
    611		kunit.main(['run', '--timeout', str(timeout)], self.linux_source_mock)
    612		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    613		self.linux_source_mock.run_kernel.assert_called_once_with(
    614			args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
    615		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    616
    617	def test_run_builddir(self):
    618		build_dir = '.kunit'
    619		kunit.main(['run', '--build_dir=.kunit'], self.linux_source_mock)
    620		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    621		self.linux_source_mock.run_kernel.assert_called_once_with(
    622			args=None, build_dir=build_dir, filter_glob='', timeout=300)
    623		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    624
    625	def test_config_builddir(self):
    626		build_dir = '.kunit'
    627		kunit.main(['config', '--build_dir', build_dir], self.linux_source_mock)
    628		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    629
    630	def test_build_builddir(self):
    631		build_dir = '.kunit'
    632		jobs = kunit.get_default_jobs()
    633		kunit.main(['build', '--build_dir', build_dir], self.linux_source_mock)
    634		self.linux_source_mock.build_kernel.assert_called_once_with(False, jobs, build_dir, None)
    635
    636	def test_exec_builddir(self):
    637		build_dir = '.kunit'
    638		kunit.main(['exec', '--build_dir', build_dir], self.linux_source_mock)
    639		self.linux_source_mock.run_kernel.assert_called_once_with(
    640			args=None, build_dir=build_dir, filter_glob='', timeout=300)
    641		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    642
    643	@mock.patch.object(kunit_kernel, 'LinuxSourceTree')
    644	def test_run_kunitconfig(self, mock_linux_init):
    645		mock_linux_init.return_value = self.linux_source_mock
    646		kunit.main(['run', '--kunitconfig=mykunitconfig'])
    647		# Just verify that we parsed and initialized it correctly here.
    648		mock_linux_init.assert_called_once_with('.kunit',
    649							kunitconfig_path='mykunitconfig',
    650							kconfig_add=None,
    651							arch='um',
    652							cross_compile=None,
    653							qemu_config_path=None)
    654
    655	@mock.patch.object(kunit_kernel, 'LinuxSourceTree')
    656	def test_config_kunitconfig(self, mock_linux_init):
    657		mock_linux_init.return_value = self.linux_source_mock
    658		kunit.main(['config', '--kunitconfig=mykunitconfig'])
    659		# Just verify that we parsed and initialized it correctly here.
    660		mock_linux_init.assert_called_once_with('.kunit',
    661							kunitconfig_path='mykunitconfig',
    662							kconfig_add=None,
    663							arch='um',
    664							cross_compile=None,
    665							qemu_config_path=None)
    666
    667	@mock.patch.object(kunit_kernel, 'LinuxSourceTree')
    668	def test_run_kconfig_add(self, mock_linux_init):
    669		mock_linux_init.return_value = self.linux_source_mock
    670		kunit.main(['run', '--kconfig_add=CONFIG_KASAN=y', '--kconfig_add=CONFIG_KCSAN=y'])
    671		# Just verify that we parsed and initialized it correctly here.
    672		mock_linux_init.assert_called_once_with('.kunit',
    673							kunitconfig_path=None,
    674							kconfig_add=['CONFIG_KASAN=y', 'CONFIG_KCSAN=y'],
    675							arch='um',
    676							cross_compile=None,
    677							qemu_config_path=None)
    678
    679	def test_run_kernel_args(self):
    680		kunit.main(['run', '--kernel_args=a=1', '--kernel_args=b=2'], self.linux_source_mock)
    681		self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
    682		self.linux_source_mock.run_kernel.assert_called_once_with(
    683		      args=['a=1','b=2'], build_dir='.kunit', filter_glob='', timeout=300)
    684		self.print_mock.assert_any_call(StrContains('Testing complete.'))
    685
    686	def test_list_tests(self):
    687		want = ['suite.test1', 'suite.test2', 'suite2.test1']
    688		self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
    689
    690		got = kunit._list_tests(self.linux_source_mock,
    691				     kunit.KunitExecRequest(None, None, '.kunit', 300, False, 'suite*', None, 'suite'))
    692
    693		self.assertEqual(got, want)
    694		# Should respect the user's filter glob when listing tests.
    695		self.linux_source_mock.run_kernel.assert_called_once_with(
    696			args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', timeout=300)
    697
    698
    699	@mock.patch.object(kunit, '_list_tests')
    700	def test_run_isolated_by_suite(self, mock_tests):
    701		mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1']
    702		kunit.main(['exec', '--run_isolated=suite', 'suite*.test*'], self.linux_source_mock)
    703
    704		# Should respect the user's filter glob when listing tests.
    705		mock_tests.assert_called_once_with(mock.ANY,
    706				     kunit.KunitExecRequest(None, None, '.kunit', 300, False, 'suite*.test*', None, 'suite'))
    707		self.linux_source_mock.run_kernel.assert_has_calls([
    708			mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', timeout=300),
    709			mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', timeout=300),
    710		])
    711
    712	@mock.patch.object(kunit, '_list_tests')
    713	def test_run_isolated_by_test(self, mock_tests):
    714		mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1']
    715		kunit.main(['exec', '--run_isolated=test', 'suite*'], self.linux_source_mock)
    716
    717		# Should respect the user's filter glob when listing tests.
    718		mock_tests.assert_called_once_with(mock.ANY,
    719				     kunit.KunitExecRequest(None, None, '.kunit', 300, False, 'suite*', None, 'test'))
    720		self.linux_source_mock.run_kernel.assert_has_calls([
    721			mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', timeout=300),
    722			mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', timeout=300),
    723			mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', timeout=300),
    724		])
    725
    726
    727if __name__ == '__main__':
    728	unittest.main()