xref: /linux/tools/testing/kunit/kunit.py (revision 45ba7a893ad89114e773b3dc32f6431354c465d6)
16ebf5866SFelix Guo#!/usr/bin/python3
26ebf5866SFelix Guo# SPDX-License-Identifier: GPL-2.0
36ebf5866SFelix Guo#
46ebf5866SFelix Guo# A thin wrapper on top of the KUnit Kernel
56ebf5866SFelix Guo#
66ebf5866SFelix Guo# Copyright (C) 2019, Google LLC.
76ebf5866SFelix Guo# Author: Felix Guo <felixguoxiuping@gmail.com>
86ebf5866SFelix Guo# Author: Brendan Higgins <brendanhiggins@google.com>
96ebf5866SFelix Guo
106ebf5866SFelix Guoimport argparse
116ebf5866SFelix Guoimport sys
126ebf5866SFelix Guoimport os
136ebf5866SFelix Guoimport time
14ff7b437fSBrendan Higginsimport shutil
156ebf5866SFelix Guo
166ebf5866SFelix Guofrom collections import namedtuple
176ebf5866SFelix Guofrom enum import Enum, auto
186ebf5866SFelix Guo
196ebf5866SFelix Guoimport kunit_config
206ebf5866SFelix Guoimport kunit_kernel
216ebf5866SFelix Guoimport kunit_parser
226ebf5866SFelix Guo
23*45ba7a89SDavid GowKunitResult = namedtuple('KunitResult', ['status','result','elapsed_time'])
246ebf5866SFelix Guo
25*45ba7a89SDavid GowKunitConfigRequest = namedtuple('KunitConfigRequest',
26*45ba7a89SDavid Gow				['build_dir', 'defconfig', 'make_options'])
27*45ba7a89SDavid GowKunitBuildRequest = namedtuple('KunitBuildRequest',
28*45ba7a89SDavid Gow			       ['jobs', 'build_dir', 'alltests',
29*45ba7a89SDavid Gow				'make_options'])
30*45ba7a89SDavid GowKunitExecRequest = namedtuple('KunitExecRequest',
31*45ba7a89SDavid Gow			      ['timeout', 'build_dir', 'alltests'])
32*45ba7a89SDavid GowKunitParseRequest = namedtuple('KunitParseRequest',
33*45ba7a89SDavid Gow			       ['raw_output', 'input_data'])
34021ed9f5SHeidi FahimKunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs',
35021ed9f5SHeidi Fahim					   'build_dir', 'defconfig',
360476e69fSGreg Thelen					   'alltests', 'make_options'])
376ebf5866SFelix Guo
38be886ba9SHeidi FahimKernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0]
39be886ba9SHeidi Fahim
406ebf5866SFelix Guoclass KunitStatus(Enum):
416ebf5866SFelix Guo	SUCCESS = auto()
426ebf5866SFelix Guo	CONFIG_FAILURE = auto()
436ebf5866SFelix Guo	BUILD_FAILURE = auto()
446ebf5866SFelix Guo	TEST_FAILURE = auto()
456ebf5866SFelix Guo
46ff7b437fSBrendan Higginsdef create_default_kunitconfig():
47e3212513SSeongJae Park	if not os.path.exists(kunit_kernel.kunitconfig_path):
48ff7b437fSBrendan Higgins		shutil.copyfile('arch/um/configs/kunit_defconfig',
49e3212513SSeongJae Park				kunit_kernel.kunitconfig_path)
50ff7b437fSBrendan Higgins
51be886ba9SHeidi Fahimdef get_kernel_root_path():
52be886ba9SHeidi Fahim	parts = sys.argv[0] if not __file__ else __file__
53be886ba9SHeidi Fahim	parts = os.path.realpath(parts).split('tools/testing/kunit')
54be886ba9SHeidi Fahim	if len(parts) != 2:
55be886ba9SHeidi Fahim		sys.exit(1)
56be886ba9SHeidi Fahim	return parts[0]
57be886ba9SHeidi Fahim
58*45ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree,
59*45ba7a89SDavid Gow		 request: KunitConfigRequest) -> KunitResult:
60*45ba7a89SDavid Gow	kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...')
61*45ba7a89SDavid Gow
626ebf5866SFelix Guo	config_start = time.time()
63*45ba7a89SDavid Gow	if request.defconfig:
64*45ba7a89SDavid Gow		create_default_kunitconfig()
650476e69fSGreg Thelen	success = linux.build_reconfig(request.build_dir, request.make_options)
666ebf5866SFelix Guo	config_end = time.time()
676ebf5866SFelix Guo	if not success:
68*45ba7a89SDavid Gow		return KunitResult(KunitStatus.CONFIG_FAILURE,
69*45ba7a89SDavid Gow				   'could not configure kernel',
70*45ba7a89SDavid Gow				   config_end - config_start)
71*45ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS,
72*45ba7a89SDavid Gow			   'configured kernel successfully',
73*45ba7a89SDavid Gow			   config_end - config_start)
746ebf5866SFelix Guo
75*45ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree,
76*45ba7a89SDavid Gow		request: KunitBuildRequest) -> KunitResult:
776ebf5866SFelix Guo	kunit_parser.print_with_timestamp('Building KUnit Kernel ...')
786ebf5866SFelix Guo
796ebf5866SFelix Guo	build_start = time.time()
80021ed9f5SHeidi Fahim	success = linux.build_um_kernel(request.alltests,
81021ed9f5SHeidi Fahim					request.jobs,
820476e69fSGreg Thelen					request.build_dir,
830476e69fSGreg Thelen					request.make_options)
846ebf5866SFelix Guo	build_end = time.time()
856ebf5866SFelix Guo	if not success:
866ebf5866SFelix Guo		return KunitResult(KunitStatus.BUILD_FAILURE, 'could not build kernel')
87*45ba7a89SDavid Gow	if not success:
88*45ba7a89SDavid Gow		return KunitResult(KunitStatus.BUILD_FAILURE,
89*45ba7a89SDavid Gow				   'could not build kernel',
90*45ba7a89SDavid Gow				   build_end - build_start)
91*45ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS,
92*45ba7a89SDavid Gow			   'built kernel successfully',
93*45ba7a89SDavid Gow			   build_end - build_start)
946ebf5866SFelix Guo
95*45ba7a89SDavid Gowdef exec_tests(linux: kunit_kernel.LinuxSourceTree,
96*45ba7a89SDavid Gow	       request: KunitExecRequest) -> KunitResult:
976ebf5866SFelix Guo	kunit_parser.print_with_timestamp('Starting KUnit Kernel ...')
986ebf5866SFelix Guo	test_start = time.time()
99*45ba7a89SDavid Gow	result = linux.run_kernel(
100021ed9f5SHeidi Fahim		timeout=None if request.alltests else request.timeout,
1016ec1b81dSSeongJae Park		build_dir=request.build_dir)
102*45ba7a89SDavid Gow
1036ebf5866SFelix Guo	test_end = time.time()
1046ebf5866SFelix Guo
105*45ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS,
106*45ba7a89SDavid Gow			   result,
107*45ba7a89SDavid Gow			   test_end - test_start)
108*45ba7a89SDavid Gow
109*45ba7a89SDavid Gowdef parse_tests(request: KunitParseRequest) -> KunitResult:
110*45ba7a89SDavid Gow	parse_start = time.time()
111*45ba7a89SDavid Gow
112*45ba7a89SDavid Gow	test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS,
113*45ba7a89SDavid Gow					      [],
114*45ba7a89SDavid Gow					      'Tests not Parsed.')
115*45ba7a89SDavid Gow	if request.raw_output:
116*45ba7a89SDavid Gow		kunit_parser.raw_output(request.input_data)
117*45ba7a89SDavid Gow	else:
118*45ba7a89SDavid Gow		test_result = kunit_parser.parse_run_tests(request.input_data)
119*45ba7a89SDavid Gow	parse_end = time.time()
120*45ba7a89SDavid Gow
121*45ba7a89SDavid Gow	if test_result.status != kunit_parser.TestStatus.SUCCESS:
122*45ba7a89SDavid Gow		return KunitResult(KunitStatus.TEST_FAILURE, test_result,
123*45ba7a89SDavid Gow				   parse_end - parse_start)
124*45ba7a89SDavid Gow
125*45ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS, test_result,
126*45ba7a89SDavid Gow				parse_end - parse_start)
127*45ba7a89SDavid Gow
128*45ba7a89SDavid Gow
129*45ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree,
130*45ba7a89SDavid Gow	      request: KunitRequest) -> KunitResult:
131*45ba7a89SDavid Gow	run_start = time.time()
132*45ba7a89SDavid Gow
133*45ba7a89SDavid Gow	config_request = KunitConfigRequest(request.build_dir,
134*45ba7a89SDavid Gow					    request.defconfig,
135*45ba7a89SDavid Gow					    request.make_options)
136*45ba7a89SDavid Gow	config_result = config_tests(linux, config_request)
137*45ba7a89SDavid Gow	if config_result.status != KunitStatus.SUCCESS:
138*45ba7a89SDavid Gow		return config_result
139*45ba7a89SDavid Gow
140*45ba7a89SDavid Gow	build_request = KunitBuildRequest(request.jobs, request.build_dir,
141*45ba7a89SDavid Gow					  request.alltests,
142*45ba7a89SDavid Gow					  request.make_options)
143*45ba7a89SDavid Gow	build_result = build_tests(linux, build_request)
144*45ba7a89SDavid Gow	if build_result.status != KunitStatus.SUCCESS:
145*45ba7a89SDavid Gow		return build_result
146*45ba7a89SDavid Gow
147*45ba7a89SDavid Gow	exec_request = KunitExecRequest(request.timeout, request.build_dir,
148*45ba7a89SDavid Gow					request.alltests)
149*45ba7a89SDavid Gow	exec_result = exec_tests(linux, exec_request)
150*45ba7a89SDavid Gow	if exec_result.status != KunitStatus.SUCCESS:
151*45ba7a89SDavid Gow		return exec_result
152*45ba7a89SDavid Gow
153*45ba7a89SDavid Gow	parse_request = KunitParseRequest(request.raw_output,
154*45ba7a89SDavid Gow					  exec_result.result)
155*45ba7a89SDavid Gow	parse_result = parse_tests(parse_request)
156*45ba7a89SDavid Gow
157*45ba7a89SDavid Gow	run_end = time.time()
158*45ba7a89SDavid Gow
1596ebf5866SFelix Guo	kunit_parser.print_with_timestamp((
1606ebf5866SFelix Guo		'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' +
1616ebf5866SFelix Guo		'building, %.3fs running\n') % (
162*45ba7a89SDavid Gow				run_end - run_start,
163*45ba7a89SDavid Gow				config_result.elapsed_time,
164*45ba7a89SDavid Gow				build_result.elapsed_time,
165*45ba7a89SDavid Gow				exec_result.elapsed_time))
166*45ba7a89SDavid Gow	return parse_result
1676ebf5866SFelix Guo
168*45ba7a89SDavid Gowdef add_common_opts(parser):
169*45ba7a89SDavid Gow	parser.add_argument('--build_dir',
170*45ba7a89SDavid Gow			    help='As in the make command, it specifies the build '
171*45ba7a89SDavid Gow			    'directory.',
172*45ba7a89SDavid Gow			    type=str, default='', metavar='build_dir')
173*45ba7a89SDavid Gow	parser.add_argument('--make_options',
174*45ba7a89SDavid Gow			    help='X=Y make option, can be repeated.',
175*45ba7a89SDavid Gow			    action='append')
176*45ba7a89SDavid Gow	parser.add_argument('--alltests',
177*45ba7a89SDavid Gow			    help='Run all KUnit tests through allyesconfig',
1786ebf5866SFelix Guo			    action='store_true')
1796ebf5866SFelix Guo
180*45ba7a89SDavid Gowdef add_config_opts(parser):
181*45ba7a89SDavid Gow	parser.add_argument('--defconfig',
182*45ba7a89SDavid Gow			    help='Uses a default .kunitconfig.',
183*45ba7a89SDavid Gow			    action='store_true')
184*45ba7a89SDavid Gow
185*45ba7a89SDavid Gowdef add_build_opts(parser):
186*45ba7a89SDavid Gow	parser.add_argument('--jobs',
187*45ba7a89SDavid Gow			    help='As in the make command, "Specifies  the number of '
188*45ba7a89SDavid Gow			    'jobs (commands) to run simultaneously."',
189*45ba7a89SDavid Gow			    type=int, default=8, metavar='jobs')
190*45ba7a89SDavid Gow
191*45ba7a89SDavid Gowdef add_exec_opts(parser):
192*45ba7a89SDavid Gow	parser.add_argument('--timeout',
1936ebf5866SFelix Guo			    help='maximum number of seconds to allow for all tests '
1946ebf5866SFelix Guo			    'to run. This does not include time taken to build the '
1956ebf5866SFelix Guo			    'tests.',
1966ebf5866SFelix Guo			    type=int,
1976ebf5866SFelix Guo			    default=300,
1986ebf5866SFelix Guo			    metavar='timeout')
1996ebf5866SFelix Guo
200*45ba7a89SDavid Gowdef add_parse_opts(parser):
201*45ba7a89SDavid Gow	parser.add_argument('--raw_output', help='don\'t format output from kernel',
202ff7b437fSBrendan Higgins			    action='store_true')
203ff7b437fSBrendan Higgins
204021ed9f5SHeidi Fahim
205*45ba7a89SDavid Gowdef main(argv, linux=None):
206*45ba7a89SDavid Gow	parser = argparse.ArgumentParser(
207*45ba7a89SDavid Gow			description='Helps writing and running KUnit tests.')
208*45ba7a89SDavid Gow	subparser = parser.add_subparsers(dest='subcommand')
209*45ba7a89SDavid Gow
210*45ba7a89SDavid Gow	# The 'run' command will config, build, exec, and parse in one go.
211*45ba7a89SDavid Gow	run_parser = subparser.add_parser('run', help='Runs KUnit tests.')
212*45ba7a89SDavid Gow	add_common_opts(run_parser)
213*45ba7a89SDavid Gow	add_config_opts(run_parser)
214*45ba7a89SDavid Gow	add_build_opts(run_parser)
215*45ba7a89SDavid Gow	add_exec_opts(run_parser)
216*45ba7a89SDavid Gow	add_parse_opts(run_parser)
217*45ba7a89SDavid Gow
218*45ba7a89SDavid Gow	config_parser = subparser.add_parser('config',
219*45ba7a89SDavid Gow						help='Ensures that .config contains all of '
220*45ba7a89SDavid Gow						'the options in .kunitconfig')
221*45ba7a89SDavid Gow	add_common_opts(config_parser)
222*45ba7a89SDavid Gow	add_config_opts(config_parser)
223*45ba7a89SDavid Gow
224*45ba7a89SDavid Gow	build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests')
225*45ba7a89SDavid Gow	add_common_opts(build_parser)
226*45ba7a89SDavid Gow	add_build_opts(build_parser)
227*45ba7a89SDavid Gow
228*45ba7a89SDavid Gow	exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests')
229*45ba7a89SDavid Gow	add_common_opts(exec_parser)
230*45ba7a89SDavid Gow	add_exec_opts(exec_parser)
231*45ba7a89SDavid Gow	add_parse_opts(exec_parser)
232*45ba7a89SDavid Gow
233*45ba7a89SDavid Gow	# The 'parse' option is special, as it doesn't need the kernel source
234*45ba7a89SDavid Gow	# (therefore there is no need for a build_dir, hence no add_common_opts)
235*45ba7a89SDavid Gow	# and the '--file' argument is not relevant to 'run', so isn't in
236*45ba7a89SDavid Gow	# add_parse_opts()
237*45ba7a89SDavid Gow	parse_parser = subparser.add_parser('parse',
238*45ba7a89SDavid Gow					    help='Parses KUnit results from a file, '
239*45ba7a89SDavid Gow					    'and parses formatted results.')
240*45ba7a89SDavid Gow	add_parse_opts(parse_parser)
241*45ba7a89SDavid Gow	parse_parser.add_argument('file',
242*45ba7a89SDavid Gow				  help='Specifies the file to read results from.',
243*45ba7a89SDavid Gow				  type=str, nargs='?', metavar='input_file')
2440476e69fSGreg Thelen
2456ebf5866SFelix Guo	cli_args = parser.parse_args(argv)
2466ebf5866SFelix Guo
2476ebf5866SFelix Guo	if cli_args.subcommand == 'run':
248e3212513SSeongJae Park		if cli_args.build_dir:
249e3212513SSeongJae Park			if not os.path.exists(cli_args.build_dir):
250e3212513SSeongJae Park				os.mkdir(cli_args.build_dir)
251e3212513SSeongJae Park			kunit_kernel.kunitconfig_path = os.path.join(
252e3212513SSeongJae Park				cli_args.build_dir,
253e3212513SSeongJae Park				kunit_kernel.kunitconfig_path)
254e3212513SSeongJae Park
255ff7b437fSBrendan Higgins		if not linux:
256ff7b437fSBrendan Higgins			linux = kunit_kernel.LinuxSourceTree()
257ff7b437fSBrendan Higgins
2586ebf5866SFelix Guo		request = KunitRequest(cli_args.raw_output,
2596ebf5866SFelix Guo				       cli_args.timeout,
2606ebf5866SFelix Guo				       cli_args.jobs,
261ff7b437fSBrendan Higgins				       cli_args.build_dir,
262021ed9f5SHeidi Fahim				       cli_args.defconfig,
2630476e69fSGreg Thelen				       cli_args.alltests,
2640476e69fSGreg Thelen				       cli_args.make_options)
2656ebf5866SFelix Guo		result = run_tests(linux, request)
2666ebf5866SFelix Guo		if result.status != KunitStatus.SUCCESS:
2676ebf5866SFelix Guo			sys.exit(1)
268*45ba7a89SDavid Gow	elif cli_args.subcommand == 'config':
269*45ba7a89SDavid Gow		if cli_args.build_dir:
270*45ba7a89SDavid Gow			if not os.path.exists(cli_args.build_dir):
271*45ba7a89SDavid Gow				os.mkdir(cli_args.build_dir)
272*45ba7a89SDavid Gow			kunit_kernel.kunitconfig_path = os.path.join(
273*45ba7a89SDavid Gow				cli_args.build_dir,
274*45ba7a89SDavid Gow				kunit_kernel.kunitconfig_path)
275*45ba7a89SDavid Gow
276*45ba7a89SDavid Gow		if not linux:
277*45ba7a89SDavid Gow			linux = kunit_kernel.LinuxSourceTree()
278*45ba7a89SDavid Gow
279*45ba7a89SDavid Gow		request = KunitConfigRequest(cli_args.build_dir,
280*45ba7a89SDavid Gow					     cli_args.defconfig,
281*45ba7a89SDavid Gow					     cli_args.make_options)
282*45ba7a89SDavid Gow		result = config_tests(linux, request)
283*45ba7a89SDavid Gow		kunit_parser.print_with_timestamp((
284*45ba7a89SDavid Gow			'Elapsed time: %.3fs\n') % (
285*45ba7a89SDavid Gow				result.elapsed_time))
286*45ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
287*45ba7a89SDavid Gow			sys.exit(1)
288*45ba7a89SDavid Gow	elif cli_args.subcommand == 'build':
289*45ba7a89SDavid Gow		if cli_args.build_dir:
290*45ba7a89SDavid Gow			if not os.path.exists(cli_args.build_dir):
291*45ba7a89SDavid Gow				os.mkdir(cli_args.build_dir)
292*45ba7a89SDavid Gow			kunit_kernel.kunitconfig_path = os.path.join(
293*45ba7a89SDavid Gow				cli_args.build_dir,
294*45ba7a89SDavid Gow				kunit_kernel.kunitconfig_path)
295*45ba7a89SDavid Gow
296*45ba7a89SDavid Gow		if not linux:
297*45ba7a89SDavid Gow			linux = kunit_kernel.LinuxSourceTree()
298*45ba7a89SDavid Gow
299*45ba7a89SDavid Gow		request = KunitBuildRequest(cli_args.jobs,
300*45ba7a89SDavid Gow					    cli_args.build_dir,
301*45ba7a89SDavid Gow					    cli_args.alltests,
302*45ba7a89SDavid Gow					    cli_args.make_options)
303*45ba7a89SDavid Gow		result = build_tests(linux, request)
304*45ba7a89SDavid Gow		kunit_parser.print_with_timestamp((
305*45ba7a89SDavid Gow			'Elapsed time: %.3fs\n') % (
306*45ba7a89SDavid Gow				result.elapsed_time))
307*45ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
308*45ba7a89SDavid Gow			sys.exit(1)
309*45ba7a89SDavid Gow	elif cli_args.subcommand == 'exec':
310*45ba7a89SDavid Gow		if cli_args.build_dir:
311*45ba7a89SDavid Gow			if not os.path.exists(cli_args.build_dir):
312*45ba7a89SDavid Gow				os.mkdir(cli_args.build_dir)
313*45ba7a89SDavid Gow			kunit_kernel.kunitconfig_path = os.path.join(
314*45ba7a89SDavid Gow				cli_args.build_dir,
315*45ba7a89SDavid Gow				kunit_kernel.kunitconfig_path)
316*45ba7a89SDavid Gow
317*45ba7a89SDavid Gow		if not linux:
318*45ba7a89SDavid Gow			linux = kunit_kernel.LinuxSourceTree()
319*45ba7a89SDavid Gow
320*45ba7a89SDavid Gow		exec_request = KunitExecRequest(cli_args.timeout,
321*45ba7a89SDavid Gow						cli_args.build_dir,
322*45ba7a89SDavid Gow						cli_args.alltests)
323*45ba7a89SDavid Gow		exec_result = exec_tests(linux, exec_request)
324*45ba7a89SDavid Gow		parse_request = KunitParseRequest(cli_args.raw_output,
325*45ba7a89SDavid Gow						  exec_result.result)
326*45ba7a89SDavid Gow		result = parse_tests(parse_request)
327*45ba7a89SDavid Gow		kunit_parser.print_with_timestamp((
328*45ba7a89SDavid Gow			'Elapsed time: %.3fs\n') % (
329*45ba7a89SDavid Gow				exec_result.elapsed_time))
330*45ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
331*45ba7a89SDavid Gow			sys.exit(1)
332*45ba7a89SDavid Gow	elif cli_args.subcommand == 'parse':
333*45ba7a89SDavid Gow		if cli_args.file == None:
334*45ba7a89SDavid Gow			kunit_output = sys.stdin
335*45ba7a89SDavid Gow		else:
336*45ba7a89SDavid Gow			with open(cli_args.file, 'r') as f:
337*45ba7a89SDavid Gow				kunit_output = f.read().splitlines()
338*45ba7a89SDavid Gow		request = KunitParseRequest(cli_args.raw_output,
339*45ba7a89SDavid Gow					    kunit_output)
340*45ba7a89SDavid Gow		result = parse_tests(request)
341*45ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
342*45ba7a89SDavid Gow			sys.exit(1)
3436ebf5866SFelix Guo	else:
3446ebf5866SFelix Guo		parser.print_help()
3456ebf5866SFelix Guo
3466ebf5866SFelix Guoif __name__ == '__main__':
347ff7b437fSBrendan Higgins	main(sys.argv[1:])
348