xref: /openbmc/linux/tools/testing/kunit/kunit.py (revision 3959d0a6)
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
2021a6d178SHeidi Fahimimport kunit_json
216ebf5866SFelix Guoimport kunit_kernel
226ebf5866SFelix Guoimport kunit_parser
236ebf5866SFelix Guo
2445ba7a89SDavid GowKunitResult = namedtuple('KunitResult', ['status','result','elapsed_time'])
256ebf5866SFelix Guo
2645ba7a89SDavid GowKunitConfigRequest = namedtuple('KunitConfigRequest',
2701397e82SVitor Massaru Iha				['build_dir', 'make_options'])
2845ba7a89SDavid GowKunitBuildRequest = namedtuple('KunitBuildRequest',
2945ba7a89SDavid Gow			       ['jobs', 'build_dir', 'alltests',
3045ba7a89SDavid Gow				'make_options'])
3145ba7a89SDavid GowKunitExecRequest = namedtuple('KunitExecRequest',
3245ba7a89SDavid Gow			      ['timeout', 'build_dir', 'alltests'])
3345ba7a89SDavid GowKunitParseRequest = namedtuple('KunitParseRequest',
3421a6d178SHeidi Fahim			       ['raw_output', 'input_data', 'build_dir', 'json'])
35021ed9f5SHeidi FahimKunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs',
3621a6d178SHeidi Fahim					   'build_dir', 'alltests', 'json',
379bdf64b3SVitor Massaru Iha					   'make_options'])
386ebf5866SFelix Guo
39be886ba9SHeidi FahimKernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0]
40be886ba9SHeidi Fahim
416ebf5866SFelix Guoclass KunitStatus(Enum):
426ebf5866SFelix Guo	SUCCESS = auto()
436ebf5866SFelix Guo	CONFIG_FAILURE = auto()
446ebf5866SFelix Guo	BUILD_FAILURE = auto()
456ebf5866SFelix Guo	TEST_FAILURE = auto()
466ebf5866SFelix Guo
47ff7b437fSBrendan Higginsdef create_default_kunitconfig():
48e3212513SSeongJae Park	if not os.path.exists(kunit_kernel.kunitconfig_path):
49ff7b437fSBrendan Higgins		shutil.copyfile('arch/um/configs/kunit_defconfig',
50e3212513SSeongJae Park				kunit_kernel.kunitconfig_path)
51ff7b437fSBrendan Higgins
52be886ba9SHeidi Fahimdef get_kernel_root_path():
53be886ba9SHeidi Fahim	parts = sys.argv[0] if not __file__ else __file__
54be886ba9SHeidi Fahim	parts = os.path.realpath(parts).split('tools/testing/kunit')
55be886ba9SHeidi Fahim	if len(parts) != 2:
56be886ba9SHeidi Fahim		sys.exit(1)
57be886ba9SHeidi Fahim	return parts[0]
58be886ba9SHeidi Fahim
5945ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree,
6045ba7a89SDavid Gow		 request: KunitConfigRequest) -> KunitResult:
6145ba7a89SDavid Gow	kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...')
6245ba7a89SDavid Gow
636ebf5866SFelix Guo	config_start = time.time()
6445ba7a89SDavid 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:
6845ba7a89SDavid Gow		return KunitResult(KunitStatus.CONFIG_FAILURE,
6945ba7a89SDavid Gow				   'could not configure kernel',
7045ba7a89SDavid Gow				   config_end - config_start)
7145ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS,
7245ba7a89SDavid Gow			   'configured kernel successfully',
7345ba7a89SDavid Gow			   config_end - config_start)
746ebf5866SFelix Guo
7545ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree,
7645ba7a89SDavid 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:
86ee61492aSDavid Gow		return KunitResult(KunitStatus.BUILD_FAILURE,
87ee61492aSDavid Gow				   'could not build kernel',
88ee61492aSDavid Gow				   build_end - build_start)
8945ba7a89SDavid Gow	if not success:
9045ba7a89SDavid Gow		return KunitResult(KunitStatus.BUILD_FAILURE,
9145ba7a89SDavid Gow				   'could not build kernel',
9245ba7a89SDavid Gow				   build_end - build_start)
9345ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS,
9445ba7a89SDavid Gow			   'built kernel successfully',
9545ba7a89SDavid Gow			   build_end - build_start)
966ebf5866SFelix Guo
9745ba7a89SDavid Gowdef exec_tests(linux: kunit_kernel.LinuxSourceTree,
9845ba7a89SDavid Gow	       request: KunitExecRequest) -> KunitResult:
996ebf5866SFelix Guo	kunit_parser.print_with_timestamp('Starting KUnit Kernel ...')
1006ebf5866SFelix Guo	test_start = time.time()
10145ba7a89SDavid Gow	result = linux.run_kernel(
102021ed9f5SHeidi Fahim		timeout=None if request.alltests else request.timeout,
1036ec1b81dSSeongJae Park		build_dir=request.build_dir)
10445ba7a89SDavid Gow
1056ebf5866SFelix Guo	test_end = time.time()
1066ebf5866SFelix Guo
10745ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS,
10845ba7a89SDavid Gow			   result,
10945ba7a89SDavid Gow			   test_end - test_start)
11045ba7a89SDavid Gow
11145ba7a89SDavid Gowdef parse_tests(request: KunitParseRequest) -> KunitResult:
11245ba7a89SDavid Gow	parse_start = time.time()
11345ba7a89SDavid Gow
11445ba7a89SDavid Gow	test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS,
11545ba7a89SDavid Gow					      [],
11645ba7a89SDavid Gow					      'Tests not Parsed.')
11721a6d178SHeidi Fahim
11845ba7a89SDavid Gow	if request.raw_output:
11945ba7a89SDavid Gow		kunit_parser.raw_output(request.input_data)
12045ba7a89SDavid Gow	else:
12145ba7a89SDavid Gow		test_result = kunit_parser.parse_run_tests(request.input_data)
12245ba7a89SDavid Gow	parse_end = time.time()
12345ba7a89SDavid Gow
12421a6d178SHeidi Fahim	if request.json:
12521a6d178SHeidi Fahim		json_obj = kunit_json.get_json_result(
12621a6d178SHeidi Fahim					test_result=test_result,
12721a6d178SHeidi Fahim					def_config='kunit_defconfig',
12821a6d178SHeidi Fahim					build_dir=request.build_dir,
12921a6d178SHeidi Fahim					json_path=request.json)
13021a6d178SHeidi Fahim		if request.json == 'stdout':
13121a6d178SHeidi Fahim			print(json_obj)
13221a6d178SHeidi Fahim
13345ba7a89SDavid Gow	if test_result.status != kunit_parser.TestStatus.SUCCESS:
13445ba7a89SDavid Gow		return KunitResult(KunitStatus.TEST_FAILURE, test_result,
13545ba7a89SDavid Gow				   parse_end - parse_start)
13645ba7a89SDavid Gow
13745ba7a89SDavid Gow	return KunitResult(KunitStatus.SUCCESS, test_result,
13845ba7a89SDavid Gow				parse_end - parse_start)
13945ba7a89SDavid Gow
14045ba7a89SDavid Gow
14145ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree,
14245ba7a89SDavid Gow	      request: KunitRequest) -> KunitResult:
14345ba7a89SDavid Gow	run_start = time.time()
14445ba7a89SDavid Gow
14545ba7a89SDavid Gow	config_request = KunitConfigRequest(request.build_dir,
14645ba7a89SDavid Gow					    request.make_options)
14745ba7a89SDavid Gow	config_result = config_tests(linux, config_request)
14845ba7a89SDavid Gow	if config_result.status != KunitStatus.SUCCESS:
14945ba7a89SDavid Gow		return config_result
15045ba7a89SDavid Gow
15145ba7a89SDavid Gow	build_request = KunitBuildRequest(request.jobs, request.build_dir,
15245ba7a89SDavid Gow					  request.alltests,
15345ba7a89SDavid Gow					  request.make_options)
15445ba7a89SDavid Gow	build_result = build_tests(linux, build_request)
15545ba7a89SDavid Gow	if build_result.status != KunitStatus.SUCCESS:
15645ba7a89SDavid Gow		return build_result
15745ba7a89SDavid Gow
15845ba7a89SDavid Gow	exec_request = KunitExecRequest(request.timeout, request.build_dir,
15945ba7a89SDavid Gow					request.alltests)
16045ba7a89SDavid Gow	exec_result = exec_tests(linux, exec_request)
16145ba7a89SDavid Gow	if exec_result.status != KunitStatus.SUCCESS:
16245ba7a89SDavid Gow		return exec_result
16345ba7a89SDavid Gow
16445ba7a89SDavid Gow	parse_request = KunitParseRequest(request.raw_output,
16521a6d178SHeidi Fahim					  exec_result.result,
16621a6d178SHeidi Fahim					  request.build_dir,
16721a6d178SHeidi Fahim					  request.json)
16845ba7a89SDavid Gow	parse_result = parse_tests(parse_request)
16945ba7a89SDavid Gow
17045ba7a89SDavid Gow	run_end = time.time()
17145ba7a89SDavid Gow
1726ebf5866SFelix Guo	kunit_parser.print_with_timestamp((
1736ebf5866SFelix Guo		'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' +
1746ebf5866SFelix Guo		'building, %.3fs running\n') % (
17545ba7a89SDavid Gow				run_end - run_start,
17645ba7a89SDavid Gow				config_result.elapsed_time,
17745ba7a89SDavid Gow				build_result.elapsed_time,
17845ba7a89SDavid Gow				exec_result.elapsed_time))
17945ba7a89SDavid Gow	return parse_result
1806ebf5866SFelix Guo
18145ba7a89SDavid Gowdef add_common_opts(parser):
18245ba7a89SDavid Gow	parser.add_argument('--build_dir',
18345ba7a89SDavid Gow			    help='As in the make command, it specifies the build '
18445ba7a89SDavid Gow			    'directory.',
185ddbd60c7SVitor Massaru Iha                            type=str, default='.kunit', metavar='build_dir')
18645ba7a89SDavid Gow	parser.add_argument('--make_options',
18745ba7a89SDavid Gow			    help='X=Y make option, can be repeated.',
18845ba7a89SDavid Gow			    action='append')
18945ba7a89SDavid Gow	parser.add_argument('--alltests',
19045ba7a89SDavid Gow			    help='Run all KUnit tests through allyesconfig',
1916ebf5866SFelix Guo			    action='store_true')
1926ebf5866SFelix Guo
19345ba7a89SDavid Gowdef add_build_opts(parser):
19445ba7a89SDavid Gow	parser.add_argument('--jobs',
19545ba7a89SDavid Gow			    help='As in the make command, "Specifies  the number of '
19645ba7a89SDavid Gow			    'jobs (commands) to run simultaneously."',
19745ba7a89SDavid Gow			    type=int, default=8, metavar='jobs')
19845ba7a89SDavid Gow
19945ba7a89SDavid Gowdef add_exec_opts(parser):
20045ba7a89SDavid Gow	parser.add_argument('--timeout',
2016ebf5866SFelix Guo			    help='maximum number of seconds to allow for all tests '
2026ebf5866SFelix Guo			    'to run. This does not include time taken to build the '
2036ebf5866SFelix Guo			    'tests.',
2046ebf5866SFelix Guo			    type=int,
2056ebf5866SFelix Guo			    default=300,
2066ebf5866SFelix Guo			    metavar='timeout')
2076ebf5866SFelix Guo
20845ba7a89SDavid Gowdef add_parse_opts(parser):
20945ba7a89SDavid Gow	parser.add_argument('--raw_output', help='don\'t format output from kernel',
210ff7b437fSBrendan Higgins			    action='store_true')
21121a6d178SHeidi Fahim	parser.add_argument('--json',
21221a6d178SHeidi Fahim			    nargs='?',
21321a6d178SHeidi Fahim			    help='Stores test results in a JSON, and either '
21421a6d178SHeidi Fahim			    'prints to stdout or saves to file if a '
21521a6d178SHeidi Fahim			    'filename is specified',
21621a6d178SHeidi Fahim			    type=str, const='stdout', default=None)
217021ed9f5SHeidi Fahim
21845ba7a89SDavid Gowdef main(argv, linux=None):
21945ba7a89SDavid Gow	parser = argparse.ArgumentParser(
22045ba7a89SDavid Gow			description='Helps writing and running KUnit tests.')
22145ba7a89SDavid Gow	subparser = parser.add_subparsers(dest='subcommand')
22245ba7a89SDavid Gow
22345ba7a89SDavid Gow	# The 'run' command will config, build, exec, and parse in one go.
22445ba7a89SDavid Gow	run_parser = subparser.add_parser('run', help='Runs KUnit tests.')
22545ba7a89SDavid Gow	add_common_opts(run_parser)
22645ba7a89SDavid Gow	add_build_opts(run_parser)
22745ba7a89SDavid Gow	add_exec_opts(run_parser)
22845ba7a89SDavid Gow	add_parse_opts(run_parser)
22945ba7a89SDavid Gow
23045ba7a89SDavid Gow	config_parser = subparser.add_parser('config',
23145ba7a89SDavid Gow						help='Ensures that .config contains all of '
23245ba7a89SDavid Gow						'the options in .kunitconfig')
23345ba7a89SDavid Gow	add_common_opts(config_parser)
23445ba7a89SDavid Gow
23545ba7a89SDavid Gow	build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests')
23645ba7a89SDavid Gow	add_common_opts(build_parser)
23745ba7a89SDavid Gow	add_build_opts(build_parser)
23845ba7a89SDavid Gow
23945ba7a89SDavid Gow	exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests')
24045ba7a89SDavid Gow	add_common_opts(exec_parser)
24145ba7a89SDavid Gow	add_exec_opts(exec_parser)
24245ba7a89SDavid Gow	add_parse_opts(exec_parser)
24345ba7a89SDavid Gow
24445ba7a89SDavid Gow	# The 'parse' option is special, as it doesn't need the kernel source
24545ba7a89SDavid Gow	# (therefore there is no need for a build_dir, hence no add_common_opts)
24645ba7a89SDavid Gow	# and the '--file' argument is not relevant to 'run', so isn't in
24745ba7a89SDavid Gow	# add_parse_opts()
24845ba7a89SDavid Gow	parse_parser = subparser.add_parser('parse',
24945ba7a89SDavid Gow					    help='Parses KUnit results from a file, '
25045ba7a89SDavid Gow					    'and parses formatted results.')
25145ba7a89SDavid Gow	add_parse_opts(parse_parser)
25245ba7a89SDavid Gow	parse_parser.add_argument('file',
25345ba7a89SDavid Gow				  help='Specifies the file to read results from.',
25445ba7a89SDavid Gow				  type=str, nargs='?', metavar='input_file')
2550476e69fSGreg Thelen
2566ebf5866SFelix Guo	cli_args = parser.parse_args(argv)
2576ebf5866SFelix Guo
2585578d008SBrendan Higgins	if get_kernel_root_path():
2595578d008SBrendan Higgins		os.chdir(get_kernel_root_path())
2605578d008SBrendan Higgins
2616ebf5866SFelix Guo	if cli_args.subcommand == 'run':
262e3212513SSeongJae Park		if not os.path.exists(cli_args.build_dir):
263e3212513SSeongJae Park			os.mkdir(cli_args.build_dir)
26482206a0cSBrendan Higgins
26582206a0cSBrendan Higgins		if not os.path.exists(kunit_kernel.kunitconfig_path):
2665578d008SBrendan Higgins			create_default_kunitconfig()
26701397e82SVitor Massaru Iha
268ff7b437fSBrendan Higgins		if not linux:
269ff7b437fSBrendan Higgins			linux = kunit_kernel.LinuxSourceTree()
270ff7b437fSBrendan Higgins
2716ebf5866SFelix Guo		request = KunitRequest(cli_args.raw_output,
2726ebf5866SFelix Guo				       cli_args.timeout,
2736ebf5866SFelix Guo				       cli_args.jobs,
274ff7b437fSBrendan Higgins				       cli_args.build_dir,
2750476e69fSGreg Thelen				       cli_args.alltests,
27621a6d178SHeidi Fahim				       cli_args.json,
2770476e69fSGreg Thelen				       cli_args.make_options)
2786ebf5866SFelix Guo		result = run_tests(linux, request)
2796ebf5866SFelix Guo		if result.status != KunitStatus.SUCCESS:
2806ebf5866SFelix Guo			sys.exit(1)
28145ba7a89SDavid Gow	elif cli_args.subcommand == 'config':
28282206a0cSBrendan Higgins		if cli_args.build_dir and (
28382206a0cSBrendan Higgins				not os.path.exists(cli_args.build_dir)):
28445ba7a89SDavid Gow			os.mkdir(cli_args.build_dir)
28582206a0cSBrendan Higgins
28682206a0cSBrendan Higgins		if not os.path.exists(kunit_kernel.kunitconfig_path):
2875578d008SBrendan Higgins			create_default_kunitconfig()
28801397e82SVitor Massaru Iha
28945ba7a89SDavid Gow		if not linux:
29045ba7a89SDavid Gow			linux = kunit_kernel.LinuxSourceTree()
29145ba7a89SDavid Gow
29245ba7a89SDavid Gow		request = KunitConfigRequest(cli_args.build_dir,
29345ba7a89SDavid Gow					     cli_args.make_options)
29445ba7a89SDavid Gow		result = config_tests(linux, request)
29545ba7a89SDavid Gow		kunit_parser.print_with_timestamp((
29645ba7a89SDavid Gow			'Elapsed time: %.3fs\n') % (
29745ba7a89SDavid Gow				result.elapsed_time))
29845ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
29945ba7a89SDavid Gow			sys.exit(1)
30045ba7a89SDavid Gow	elif cli_args.subcommand == 'build':
30145ba7a89SDavid Gow		if not linux:
30245ba7a89SDavid Gow			linux = kunit_kernel.LinuxSourceTree()
30345ba7a89SDavid Gow
30445ba7a89SDavid Gow		request = KunitBuildRequest(cli_args.jobs,
30545ba7a89SDavid Gow					    cli_args.build_dir,
30645ba7a89SDavid Gow					    cli_args.alltests,
30745ba7a89SDavid Gow					    cli_args.make_options)
30845ba7a89SDavid Gow		result = build_tests(linux, request)
30945ba7a89SDavid Gow		kunit_parser.print_with_timestamp((
31045ba7a89SDavid Gow			'Elapsed time: %.3fs\n') % (
31145ba7a89SDavid Gow				result.elapsed_time))
31245ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
31345ba7a89SDavid Gow			sys.exit(1)
31445ba7a89SDavid Gow	elif cli_args.subcommand == 'exec':
31545ba7a89SDavid Gow		if not linux:
31645ba7a89SDavid Gow			linux = kunit_kernel.LinuxSourceTree()
31745ba7a89SDavid Gow
31845ba7a89SDavid Gow		exec_request = KunitExecRequest(cli_args.timeout,
31945ba7a89SDavid Gow						cli_args.build_dir,
32045ba7a89SDavid Gow						cli_args.alltests)
32145ba7a89SDavid Gow		exec_result = exec_tests(linux, exec_request)
32245ba7a89SDavid Gow		parse_request = KunitParseRequest(cli_args.raw_output,
32321a6d178SHeidi Fahim						  exec_result.result,
32421a6d178SHeidi Fahim						  cli_args.build_dir,
32521a6d178SHeidi Fahim						  cli_args.json)
32645ba7a89SDavid Gow		result = parse_tests(parse_request)
32745ba7a89SDavid Gow		kunit_parser.print_with_timestamp((
32845ba7a89SDavid Gow			'Elapsed time: %.3fs\n') % (
32945ba7a89SDavid Gow				exec_result.elapsed_time))
33045ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
33145ba7a89SDavid Gow			sys.exit(1)
33245ba7a89SDavid Gow	elif cli_args.subcommand == 'parse':
33345ba7a89SDavid Gow		if cli_args.file == None:
33445ba7a89SDavid Gow			kunit_output = sys.stdin
33545ba7a89SDavid Gow		else:
33645ba7a89SDavid Gow			with open(cli_args.file, 'r') as f:
33745ba7a89SDavid Gow				kunit_output = f.read().splitlines()
33845ba7a89SDavid Gow		request = KunitParseRequest(cli_args.raw_output,
33921a6d178SHeidi Fahim					    kunit_output,
3403959d0a6SDavid Gow					    None,
34121a6d178SHeidi Fahim					    cli_args.json)
34245ba7a89SDavid Gow		result = parse_tests(request)
34345ba7a89SDavid Gow		if result.status != KunitStatus.SUCCESS:
34445ba7a89SDavid Gow			sys.exit(1)
3456ebf5866SFelix Guo	else:
3466ebf5866SFelix Guo		parser.print_help()
3476ebf5866SFelix Guo
3486ebf5866SFelix Guoif __name__ == '__main__':
349ff7b437fSBrendan Higgins	main(sys.argv[1:])
350