1#!/usr/bin/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 os
15
16import kunit_config
17import kunit_parser
18import kunit_kernel
19import kunit
20
21test_tmpdir = ''
22
23def setUpModule():
24	global test_tmpdir
25	test_tmpdir = tempfile.mkdtemp()
26
27def tearDownModule():
28	shutil.rmtree(test_tmpdir)
29
30def get_absolute_path(path):
31	return os.path.join(os.path.dirname(__file__), path)
32
33class KconfigTest(unittest.TestCase):
34
35	def test_is_subset_of(self):
36		kconfig0 = kunit_config.Kconfig()
37		self.assertTrue(kconfig0.is_subset_of(kconfig0))
38
39		kconfig1 = kunit_config.Kconfig()
40		kconfig1.add_entry(kunit_config.KconfigEntry('TEST', 'y'))
41		self.assertTrue(kconfig1.is_subset_of(kconfig1))
42		self.assertTrue(kconfig0.is_subset_of(kconfig1))
43		self.assertFalse(kconfig1.is_subset_of(kconfig0))
44
45	def test_read_from_file(self):
46		kconfig = kunit_config.Kconfig()
47		kconfig_path = get_absolute_path(
48			'test_data/test_read_from_file.kconfig')
49
50		kconfig.read_from_file(kconfig_path)
51
52		expected_kconfig = kunit_config.Kconfig()
53		expected_kconfig.add_entry(
54			kunit_config.KconfigEntry('UML', 'y'))
55		expected_kconfig.add_entry(
56			kunit_config.KconfigEntry('MMU', 'y'))
57		expected_kconfig.add_entry(
58			kunit_config.KconfigEntry('TEST', 'y'))
59		expected_kconfig.add_entry(
60			kunit_config.KconfigEntry('EXAMPLE_TEST', 'y'))
61		expected_kconfig.add_entry(
62			kunit_config.KconfigEntry('MK8', 'n'))
63
64		self.assertEqual(kconfig.entries(), expected_kconfig.entries())
65
66	def test_write_to_file(self):
67		kconfig_path = os.path.join(test_tmpdir, '.config')
68
69		expected_kconfig = kunit_config.Kconfig()
70		expected_kconfig.add_entry(
71			kunit_config.KconfigEntry('UML', 'y'))
72		expected_kconfig.add_entry(
73			kunit_config.KconfigEntry('MMU', 'y'))
74		expected_kconfig.add_entry(
75			kunit_config.KconfigEntry('TEST', 'y'))
76		expected_kconfig.add_entry(
77			kunit_config.KconfigEntry('EXAMPLE_TEST', 'y'))
78		expected_kconfig.add_entry(
79			kunit_config.KconfigEntry('MK8', 'n'))
80
81		expected_kconfig.write_to_file(kconfig_path)
82
83		actual_kconfig = kunit_config.Kconfig()
84		actual_kconfig.read_from_file(kconfig_path)
85
86		self.assertEqual(actual_kconfig.entries(),
87				 expected_kconfig.entries())
88
89class KUnitParserTest(unittest.TestCase):
90
91	def assertContains(self, needle, haystack):
92		for line in haystack:
93			if needle in line:
94				return
95		raise AssertionError('"' +
96			str(needle) + '" not found in "' + str(haystack) + '"!')
97
98	def test_output_isolated_correctly(self):
99		log_path = get_absolute_path(
100			'test_data/test_output_isolated_correctly.log')
101		file = open(log_path)
102		result = kunit_parser.isolate_kunit_output(file.readlines())
103		self.assertContains('TAP version 14\n', result)
104		self.assertContains('	# Subtest: example', result)
105		self.assertContains('	1..2', result)
106		self.assertContains('	ok 1 - example_simple_test', result)
107		self.assertContains('	ok 2 - example_mock_test', result)
108		self.assertContains('ok 1 - example', result)
109		file.close()
110
111	def test_output_with_prefix_isolated_correctly(self):
112		log_path = get_absolute_path(
113			'test_data/test_pound_sign.log')
114		with open(log_path) as file:
115			result = kunit_parser.isolate_kunit_output(file.readlines())
116		self.assertContains('TAP version 14\n', result)
117		self.assertContains('	# Subtest: kunit-resource-test', result)
118		self.assertContains('	1..5', result)
119		self.assertContains('	ok 1 - kunit_resource_test_init_resources', result)
120		self.assertContains('	ok 2 - kunit_resource_test_alloc_resource', result)
121		self.assertContains('	ok 3 - kunit_resource_test_destroy_resource', result)
122		self.assertContains(' foo bar 	#', result)
123		self.assertContains('	ok 4 - kunit_resource_test_cleanup_resources', result)
124		self.assertContains('	ok 5 - kunit_resource_test_proper_free_ordering', result)
125		self.assertContains('ok 1 - kunit-resource-test', result)
126		self.assertContains(' foo bar 	# non-kunit output', result)
127		self.assertContains('	# Subtest: kunit-try-catch-test', result)
128		self.assertContains('	1..2', result)
129		self.assertContains('	ok 1 - kunit_test_try_catch_successful_try_no_catch',
130				    result)
131		self.assertContains('	ok 2 - kunit_test_try_catch_unsuccessful_try_does_catch',
132				    result)
133		self.assertContains('ok 2 - kunit-try-catch-test', result)
134		self.assertContains('	# Subtest: string-stream-test', result)
135		self.assertContains('	1..3', result)
136		self.assertContains('	ok 1 - string_stream_test_empty_on_creation', result)
137		self.assertContains('	ok 2 - string_stream_test_not_empty_after_add', result)
138		self.assertContains('	ok 3 - string_stream_test_get_string', result)
139		self.assertContains('ok 3 - string-stream-test', result)
140
141	def test_parse_successful_test_log(self):
142		all_passed_log = get_absolute_path(
143			'test_data/test_is_test_passed-all_passed.log')
144		file = open(all_passed_log)
145		result = kunit_parser.parse_run_tests(file.readlines())
146		self.assertEqual(
147			kunit_parser.TestStatus.SUCCESS,
148			result.status)
149		file.close()
150
151	def test_parse_failed_test_log(self):
152		failed_log = get_absolute_path(
153			'test_data/test_is_test_passed-failure.log')
154		file = open(failed_log)
155		result = kunit_parser.parse_run_tests(file.readlines())
156		self.assertEqual(
157			kunit_parser.TestStatus.FAILURE,
158			result.status)
159		file.close()
160
161	def test_no_tests(self):
162		empty_log = get_absolute_path(
163			'test_data/test_is_test_passed-no_tests_run.log')
164		file = open(empty_log)
165		result = kunit_parser.parse_run_tests(
166			kunit_parser.isolate_kunit_output(file.readlines()))
167		self.assertEqual(0, len(result.suites))
168		self.assertEqual(
169			kunit_parser.TestStatus.NO_TESTS,
170			result.status)
171		file.close()
172
173	def test_no_kunit_output(self):
174		crash_log = get_absolute_path(
175			'test_data/test_insufficient_memory.log')
176		file = open(crash_log)
177		print_mock = mock.patch('builtins.print').start()
178		result = kunit_parser.parse_run_tests(
179			kunit_parser.isolate_kunit_output(file.readlines()))
180		print_mock.assert_any_call(StrContains("no kunit output detected"))
181		print_mock.stop()
182		file.close()
183
184	def test_crashed_test(self):
185		crashed_log = get_absolute_path(
186			'test_data/test_is_test_passed-crash.log')
187		file = open(crashed_log)
188		result = kunit_parser.parse_run_tests(file.readlines())
189		self.assertEqual(
190			kunit_parser.TestStatus.TEST_CRASHED,
191			result.status)
192		file.close()
193
194	def test_ignores_prefix_printk_time(self):
195		prefix_log = get_absolute_path(
196			'test_data/test_config_printk_time.log')
197		with open(prefix_log) as file:
198			result = kunit_parser.parse_run_tests(file.readlines())
199		self.assertEqual('kunit-resource-test', result.suites[0].name)
200
201	def test_ignores_multiple_prefixes(self):
202		prefix_log = get_absolute_path(
203			'test_data/test_multiple_prefixes.log')
204		with open(prefix_log) as file:
205			result = kunit_parser.parse_run_tests(file.readlines())
206		self.assertEqual('kunit-resource-test', result.suites[0].name)
207
208	def test_prefix_mixed_kernel_output(self):
209		mixed_prefix_log = get_absolute_path(
210			'test_data/test_interrupted_tap_output.log')
211		with open(mixed_prefix_log) as file:
212			result = kunit_parser.parse_run_tests(file.readlines())
213		self.assertEqual('kunit-resource-test', result.suites[0].name)
214
215	def test_prefix_poundsign(self):
216		pound_log = get_absolute_path('test_data/test_pound_sign.log')
217		with open(pound_log) as file:
218			result = kunit_parser.parse_run_tests(file.readlines())
219		self.assertEqual('kunit-resource-test', result.suites[0].name)
220
221	def test_kernel_panic_end(self):
222		panic_log = get_absolute_path('test_data/test_kernel_panic_interrupt.log')
223		with open(panic_log) as file:
224			result = kunit_parser.parse_run_tests(file.readlines())
225		self.assertEqual('kunit-resource-test', result.suites[0].name)
226
227	def test_pound_no_prefix(self):
228		pound_log = get_absolute_path('test_data/test_pound_no_prefix.log')
229		with open(pound_log) as file:
230			result = kunit_parser.parse_run_tests(file.readlines())
231		self.assertEqual('kunit-resource-test', result.suites[0].name)
232
233class StrContains(str):
234	def __eq__(self, other):
235		return self in other
236
237class KUnitMainTest(unittest.TestCase):
238	def setUp(self):
239		path = get_absolute_path('test_data/test_is_test_passed-all_passed.log')
240		file = open(path)
241		all_passed_log = file.readlines()
242		self.print_patch = mock.patch('builtins.print')
243		self.print_mock = self.print_patch.start()
244		self.linux_source_mock = mock.Mock()
245		self.linux_source_mock.build_reconfig = mock.Mock(return_value=True)
246		self.linux_source_mock.build_um_kernel = mock.Mock(return_value=True)
247		self.linux_source_mock.run_kernel = mock.Mock(return_value=all_passed_log)
248
249	def tearDown(self):
250		self.print_patch.stop()
251		pass
252
253	def test_config_passes_args_pass(self):
254		kunit.main(['config', '--build_dir=.kunit'], self.linux_source_mock)
255		assert self.linux_source_mock.build_reconfig.call_count == 1
256		assert self.linux_source_mock.run_kernel.call_count == 0
257
258	def test_build_passes_args_pass(self):
259		kunit.main(['build'], self.linux_source_mock)
260		assert self.linux_source_mock.build_reconfig.call_count == 0
261		self.linux_source_mock.build_um_kernel.assert_called_once_with(False, 8, '.kunit', None)
262		assert self.linux_source_mock.run_kernel.call_count == 0
263
264	def test_exec_passes_args_pass(self):
265		kunit.main(['exec'], self.linux_source_mock)
266		assert self.linux_source_mock.build_reconfig.call_count == 0
267		assert self.linux_source_mock.run_kernel.call_count == 1
268		self.linux_source_mock.run_kernel.assert_called_once_with(build_dir='.kunit', timeout=300)
269		self.print_mock.assert_any_call(StrContains('Testing complete.'))
270
271	def test_run_passes_args_pass(self):
272		kunit.main(['run'], self.linux_source_mock)
273		assert self.linux_source_mock.build_reconfig.call_count == 1
274		assert self.linux_source_mock.run_kernel.call_count == 1
275		self.linux_source_mock.run_kernel.assert_called_once_with(
276			build_dir='.kunit', timeout=300)
277		self.print_mock.assert_any_call(StrContains('Testing complete.'))
278
279	def test_exec_passes_args_fail(self):
280		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
281		with self.assertRaises(SystemExit) as e:
282			kunit.main(['exec'], self.linux_source_mock)
283		assert type(e.exception) == SystemExit
284		assert e.exception.code == 1
285
286	def test_run_passes_args_fail(self):
287		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
288		with self.assertRaises(SystemExit) as e:
289			kunit.main(['run'], self.linux_source_mock)
290		assert type(e.exception) == SystemExit
291		assert e.exception.code == 1
292		assert self.linux_source_mock.build_reconfig.call_count == 1
293		assert self.linux_source_mock.run_kernel.call_count == 1
294		self.print_mock.assert_any_call(StrContains(' 0 tests run'))
295
296	def test_exec_raw_output(self):
297		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
298		kunit.main(['exec', '--raw_output'], self.linux_source_mock)
299		assert self.linux_source_mock.run_kernel.call_count == 1
300		for kall in self.print_mock.call_args_list:
301			assert kall != mock.call(StrContains('Testing complete.'))
302			assert kall != mock.call(StrContains(' 0 tests run'))
303
304	def test_run_raw_output(self):
305		self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
306		kunit.main(['run', '--raw_output'], self.linux_source_mock)
307		assert self.linux_source_mock.build_reconfig.call_count == 1
308		assert self.linux_source_mock.run_kernel.call_count == 1
309		for kall in self.print_mock.call_args_list:
310			assert kall != mock.call(StrContains('Testing complete.'))
311			assert kall != mock.call(StrContains(' 0 tests run'))
312
313	def test_exec_timeout(self):
314		timeout = 3453
315		kunit.main(['exec', '--timeout', str(timeout)], self.linux_source_mock)
316		self.linux_source_mock.run_kernel.assert_called_once_with(build_dir='.kunit', timeout=timeout)
317		self.print_mock.assert_any_call(StrContains('Testing complete.'))
318
319	def test_run_timeout(self):
320		timeout = 3453
321		kunit.main(['run', '--timeout', str(timeout)], self.linux_source_mock)
322		assert self.linux_source_mock.build_reconfig.call_count == 1
323		self.linux_source_mock.run_kernel.assert_called_once_with(
324			build_dir='.kunit', timeout=timeout)
325		self.print_mock.assert_any_call(StrContains('Testing complete.'))
326
327	def test_run_builddir(self):
328		build_dir = '.kunit'
329		kunit.main(['run', '--build_dir=.kunit'], self.linux_source_mock)
330		assert self.linux_source_mock.build_reconfig.call_count == 1
331		self.linux_source_mock.run_kernel.assert_called_once_with(
332			build_dir=build_dir, timeout=300)
333		self.print_mock.assert_any_call(StrContains('Testing complete.'))
334
335	def test_config_builddir(self):
336		build_dir = '.kunit'
337		kunit.main(['config', '--build_dir', build_dir], self.linux_source_mock)
338		assert self.linux_source_mock.build_reconfig.call_count == 1
339
340	def test_build_builddir(self):
341		build_dir = '.kunit'
342		kunit.main(['build', '--build_dir', build_dir], self.linux_source_mock)
343		self.linux_source_mock.build_um_kernel.assert_called_once_with(False, 8, build_dir, None)
344
345	def test_exec_builddir(self):
346		build_dir = '.kunit'
347		kunit.main(['exec', '--build_dir', build_dir], self.linux_source_mock)
348		self.linux_source_mock.run_kernel.assert_called_once_with(build_dir=build_dir, timeout=300)
349		self.print_mock.assert_any_call(StrContains('Testing complete.'))
350
351if __name__ == '__main__':
352	unittest.main()
353