xref: /linux/tools/testing/kunit/kunit.py (revision 723c8258c8fe167191b53e274dea435c4522e4d7)
1c25ce589SFinn Behrens#!/usr/bin/env 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 os
12ff9e09a3SDaniel Latypovimport re
13a9333bd3SDaniel Latypovimport shlex
14ff9e09a3SDaniel Latypovimport sys
156ebf5866SFelix Guoimport time
166ebf5866SFelix Guo
17df4b0807SSeongJae Parkassert sys.version_info >= (3, 7), "Python version is too old"
18df4b0807SSeongJae Park
19db167981SDaniel Latypovfrom dataclasses import dataclass
206ebf5866SFelix Guofrom enum import Enum, auto
2195dcbc55SDaniel Latypovfrom typing import Iterable, List, Optional, Sequence, Tuple
226ebf5866SFelix Guo
2321a6d178SHeidi Fahimimport kunit_json
246ebf5866SFelix Guoimport kunit_kernel
256ebf5866SFelix Guoimport kunit_parser
26e756dbebSDaniel Latypovfrom kunit_printer import stdout
276ebf5866SFelix Guo
286ebf5866SFelix Guoclass KunitStatus(Enum):
296ebf5866SFelix Guo	SUCCESS = auto()
306ebf5866SFelix Guo	CONFIG_FAILURE = auto()
316ebf5866SFelix Guo	BUILD_FAILURE = auto()
326ebf5866SFelix Guo	TEST_FAILURE = auto()
336ebf5866SFelix Guo
34db167981SDaniel Latypov@dataclass
35db167981SDaniel Latypovclass KunitResult:
36db167981SDaniel Latypov	status: KunitStatus
37db167981SDaniel Latypov	elapsed_time: float
38db167981SDaniel Latypov
39db167981SDaniel Latypov@dataclass
40db167981SDaniel Latypovclass KunitConfigRequest:
41db167981SDaniel Latypov	build_dir: str
42db167981SDaniel Latypov	make_options: Optional[List[str]]
43db167981SDaniel Latypov
44db167981SDaniel Latypov@dataclass
45db167981SDaniel Latypovclass KunitBuildRequest(KunitConfigRequest):
46db167981SDaniel Latypov	jobs: int
47db167981SDaniel Latypov
48db167981SDaniel Latypov@dataclass
49db167981SDaniel Latypovclass KunitParseRequest:
50db167981SDaniel Latypov	raw_output: Optional[str]
51db167981SDaniel Latypov	json: Optional[str]
52db167981SDaniel Latypov
53db167981SDaniel Latypov@dataclass
54db167981SDaniel Latypovclass KunitExecRequest(KunitParseRequest):
55ee96d25fSDaniel Latypov	build_dir: str
56db167981SDaniel Latypov	timeout: int
57db167981SDaniel Latypov	filter_glob: str
58*723c8258SRae Moar	filter: str
59*723c8258SRae Moar	filter_action: Optional[str]
60db167981SDaniel Latypov	kernel_args: Optional[List[str]]
61db167981SDaniel Latypov	run_isolated: Optional[str]
62*723c8258SRae Moar	list_tests: bool
63*723c8258SRae Moar	list_tests_attr: bool
64db167981SDaniel Latypov
65db167981SDaniel Latypov@dataclass
66db167981SDaniel Latypovclass KunitRequest(KunitExecRequest, KunitBuildRequest):
67db167981SDaniel Latypov	pass
68db167981SDaniel Latypov
69db167981SDaniel Latypov
7009641f7cSDaniel Latypovdef get_kernel_root_path() -> str:
7109641f7cSDaniel Latypov	path = sys.argv[0] if not __file__ else __file__
7209641f7cSDaniel Latypov	parts = os.path.realpath(path).split('tools/testing/kunit')
73be886ba9SHeidi Fahim	if len(parts) != 2:
74be886ba9SHeidi Fahim		sys.exit(1)
75be886ba9SHeidi Fahim	return parts[0]
76be886ba9SHeidi Fahim
7745ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree,
7845ba7a89SDavid Gow		 request: KunitConfigRequest) -> KunitResult:
79e756dbebSDaniel Latypov	stdout.print_with_timestamp('Configuring KUnit Kernel ...')
8045ba7a89SDavid Gow
816ebf5866SFelix Guo	config_start = time.time()
820476e69fSGreg Thelen	success = linux.build_reconfig(request.build_dir, request.make_options)
836ebf5866SFelix Guo	config_end = time.time()
841fdc6f4fSAlexander Pantyukhin	status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE
851fdc6f4fSAlexander Pantyukhin	return KunitResult(status, config_end - config_start)
866ebf5866SFelix Guo
8745ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree,
8845ba7a89SDavid Gow		request: KunitBuildRequest) -> KunitResult:
89e756dbebSDaniel Latypov	stdout.print_with_timestamp('Building KUnit Kernel ...')
906ebf5866SFelix Guo
916ebf5866SFelix Guo	build_start = time.time()
92980ac3adSDaniel Latypov	success = linux.build_kernel(request.jobs,
930476e69fSGreg Thelen				     request.build_dir,
940476e69fSGreg Thelen				     request.make_options)
956ebf5866SFelix Guo	build_end = time.time()
961fdc6f4fSAlexander Pantyukhin	status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE
971fdc6f4fSAlexander Pantyukhin	return KunitResult(status, build_end - build_start)
986ebf5866SFelix Guo
991ee2ba89SDaniel Latypovdef config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
1001ee2ba89SDaniel Latypov			   request: KunitBuildRequest) -> KunitResult:
1011ee2ba89SDaniel Latypov	config_result = config_tests(linux, request)
1021ee2ba89SDaniel Latypov	if config_result.status != KunitStatus.SUCCESS:
1031ee2ba89SDaniel Latypov		return config_result
1041ee2ba89SDaniel Latypov
1051ee2ba89SDaniel Latypov	return build_tests(linux, request)
1061ee2ba89SDaniel Latypov
107ff9e09a3SDaniel Latypovdef _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
108ff9e09a3SDaniel Latypov	args = ['kunit.action=list']
109*723c8258SRae Moar
110ff9e09a3SDaniel Latypov	if request.kernel_args:
111ff9e09a3SDaniel Latypov		args.extend(request.kernel_args)
112ff9e09a3SDaniel Latypov
113ff9e09a3SDaniel Latypov	output = linux.run_kernel(args=args,
114980ac3adSDaniel Latypov			   timeout=request.timeout,
115ff9e09a3SDaniel Latypov			   filter_glob=request.filter_glob,
116*723c8258SRae Moar			   filter=request.filter,
117*723c8258SRae Moar			   filter_action=request.filter_action,
118ff9e09a3SDaniel Latypov			   build_dir=request.build_dir)
119ff9e09a3SDaniel Latypov	lines = kunit_parser.extract_tap_lines(output)
120ff9e09a3SDaniel Latypov	# Hack! Drop the dummy TAP version header that the executor prints out.
121ff9e09a3SDaniel Latypov	lines.pop()
122ff9e09a3SDaniel Latypov
123ff9e09a3SDaniel Latypov	# Filter out any extraneous non-test output that might have gotten mixed in.
124*723c8258SRae Moar	return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
125*723c8258SRae Moar
126*723c8258SRae Moardef _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]:
127*723c8258SRae Moar	args = ['kunit.action=list_attr']
128*723c8258SRae Moar
129*723c8258SRae Moar	if request.kernel_args:
130*723c8258SRae Moar		args.extend(request.kernel_args)
131*723c8258SRae Moar
132*723c8258SRae Moar	output = linux.run_kernel(args=args,
133*723c8258SRae Moar			   timeout=request.timeout,
134*723c8258SRae Moar			   filter_glob=request.filter_glob,
135*723c8258SRae Moar			   filter=request.filter,
136*723c8258SRae Moar			   filter_action=request.filter_action,
137*723c8258SRae Moar			   build_dir=request.build_dir)
138*723c8258SRae Moar	lines = kunit_parser.extract_tap_lines(output)
139*723c8258SRae Moar	# Hack! Drop the dummy TAP version header that the executor prints out.
140*723c8258SRae Moar	lines.pop()
141*723c8258SRae Moar
142*723c8258SRae Moar	# Filter out any extraneous non-test output that might have gotten mixed in.
143*723c8258SRae Moar	return lines
144ff9e09a3SDaniel Latypov
145ff9e09a3SDaniel Latypovdef _suites_from_test_list(tests: List[str]) -> List[str]:
146ff9e09a3SDaniel Latypov	"""Extracts all the suites from an ordered list of tests."""
147ff9e09a3SDaniel Latypov	suites = []  # type: List[str]
148ff9e09a3SDaniel Latypov	for t in tests:
149ff9e09a3SDaniel Latypov		parts = t.split('.', maxsplit=2)
150ff9e09a3SDaniel Latypov		if len(parts) != 2:
151ff9e09a3SDaniel Latypov			raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"')
152126901baSDaniel Latypov		suite, _ = parts
153ff9e09a3SDaniel Latypov		if not suites or suites[-1] != suite:
154ff9e09a3SDaniel Latypov			suites.append(suite)
155ff9e09a3SDaniel Latypov	return suites
156ff9e09a3SDaniel Latypov
157db167981SDaniel Latypovdef exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult:
158ff9e09a3SDaniel Latypov	filter_globs = [request.filter_glob]
159*723c8258SRae Moar	if request.list_tests:
160*723c8258SRae Moar		output = _list_tests(linux, request)
161*723c8258SRae Moar		for line in output:
162*723c8258SRae Moar			print(line.rstrip())
163*723c8258SRae Moar		return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
164*723c8258SRae Moar	if request.list_tests_attr:
165*723c8258SRae Moar		attr_output = _list_tests_attr(linux, request)
166*723c8258SRae Moar		for line in attr_output:
167*723c8258SRae Moar			print(line.rstrip())
168*723c8258SRae Moar		return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
169ff9e09a3SDaniel Latypov	if request.run_isolated:
170ff9e09a3SDaniel Latypov		tests = _list_tests(linux, request)
171ff9e09a3SDaniel Latypov		if request.run_isolated == 'test':
172ff9e09a3SDaniel Latypov			filter_globs = tests
1731fdc6f4fSAlexander Pantyukhin		elif request.run_isolated == 'suite':
174ff9e09a3SDaniel Latypov			filter_globs = _suites_from_test_list(tests)
175ff9e09a3SDaniel Latypov			# Apply the test-part of the user's glob, if present.
176ff9e09a3SDaniel Latypov			if '.' in request.filter_glob:
177ff9e09a3SDaniel Latypov				test_glob = request.filter_glob.split('.', maxsplit=2)[1]
178ff9e09a3SDaniel Latypov				filter_globs = [g + '.'+ test_glob for g in filter_globs]
179ff9e09a3SDaniel Latypov
180885210d3SDaniel Latypov	metadata = kunit_json.Metadata(arch=linux.arch(), build_dir=request.build_dir, def_config='kunit_defconfig')
181ee96d25fSDaniel Latypov
182d65d07cbSRae Moar	test_counts = kunit_parser.TestCounts()
183ff9e09a3SDaniel Latypov	exec_time = 0.0
184ff9e09a3SDaniel Latypov	for i, filter_glob in enumerate(filter_globs):
185e756dbebSDaniel Latypov		stdout.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs)))
186ff9e09a3SDaniel Latypov
1876ebf5866SFelix Guo		test_start = time.time()
1887ef925eaSDaniel Latypov		run_result = linux.run_kernel(
1896cb51a18SDaniel Latypov			args=request.kernel_args,
190980ac3adSDaniel Latypov			timeout=request.timeout,
191ff9e09a3SDaniel Latypov			filter_glob=filter_glob,
192*723c8258SRae Moar			filter=request.filter,
193*723c8258SRae Moar			filter_action=request.filter_action,
1946ec1b81dSSeongJae Park			build_dir=request.build_dir)
19545ba7a89SDavid Gow
196ee96d25fSDaniel Latypov		_, test_result = parse_tests(request, metadata, run_result)
1975f6aa6d8SDaniel Latypov		# run_kernel() doesn't block on the kernel exiting.
1985f6aa6d8SDaniel Latypov		# That only happens after we get the last line of output from `run_result`.
1995f6aa6d8SDaniel Latypov		# So exec_time here actually contains parsing + execution time, which is fine.
2006ebf5866SFelix Guo		test_end = time.time()
201ff9e09a3SDaniel Latypov		exec_time += test_end - test_start
202ff9e09a3SDaniel Latypov
20395dcbc55SDaniel Latypov		test_counts.add_subtest_counts(test_result.counts)
2046ebf5866SFelix Guo
2057fa7ffcfSDaniel Latypov	if len(filter_globs) == 1 and test_counts.crashed > 0:
2067fa7ffcfSDaniel Latypov		bd = request.build_dir
2077fa7ffcfSDaniel Latypov		print('The kernel seems to have crashed; you can decode the stack traces with:')
2087fa7ffcfSDaniel Latypov		print('$ scripts/decode_stacktrace.sh {}/vmlinux {} < {} | tee {}/decoded.log | {} parse'.format(
2097fa7ffcfSDaniel Latypov				bd, bd, kunit_kernel.get_outfile_path(bd), bd, sys.argv[0]))
2107fa7ffcfSDaniel Latypov
211d65d07cbSRae Moar	kunit_status = _map_to_overall_status(test_counts.get_status())
21295dcbc55SDaniel Latypov	return KunitResult(status=kunit_status, elapsed_time=exec_time)
213d65d07cbSRae Moar
214d65d07cbSRae Moardef _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus:
215d65d07cbSRae Moar	if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED):
216d65d07cbSRae Moar		return KunitStatus.SUCCESS
217d65d07cbSRae Moar	return KunitStatus.TEST_FAILURE
2187ef925eaSDaniel Latypov
219ee96d25fSDaniel Latypovdef parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]:
22045ba7a89SDavid Gow	parse_start = time.time()
22145ba7a89SDavid Gow
22245ba7a89SDavid Gow	if request.raw_output:
223d65d07cbSRae Moar		# Treat unparsed results as one passing test.
224309e22efSDaniel Latypov		fake_test = kunit_parser.Test()
225309e22efSDaniel Latypov		fake_test.status = kunit_parser.TestStatus.SUCCESS
226309e22efSDaniel Latypov		fake_test.counts.passed = 1
227d65d07cbSRae Moar
2287ef925eaSDaniel Latypov		output: Iterable[str] = input_data
2296a499c9cSDaniel Latypov		if request.raw_output == 'all':
2306a499c9cSDaniel Latypov			pass
2316a499c9cSDaniel Latypov		elif request.raw_output == 'kunit':
232c2bb92bcSDaniel Latypov			output = kunit_parser.extract_tap_lines(output)
2336a499c9cSDaniel Latypov		for line in output:
2346a499c9cSDaniel Latypov			print(line.rstrip())
235309e22efSDaniel Latypov		parse_time = time.time() - parse_start
236309e22efSDaniel Latypov		return KunitResult(KunitStatus.SUCCESS, parse_time), fake_test
2376a499c9cSDaniel Latypov
238309e22efSDaniel Latypov
239309e22efSDaniel Latypov	# Actually parse the test results.
240309e22efSDaniel Latypov	test = kunit_parser.parse_run_tests(input_data)
241309e22efSDaniel Latypov	parse_time = time.time() - parse_start
24245ba7a89SDavid Gow
24321a6d178SHeidi Fahim	if request.json:
24400f75043SDaniel Latypov		json_str = kunit_json.get_json_result(
245309e22efSDaniel Latypov					test=test,
246ee96d25fSDaniel Latypov					metadata=metadata)
24721a6d178SHeidi Fahim		if request.json == 'stdout':
24800f75043SDaniel Latypov			print(json_str)
24900f75043SDaniel Latypov		else:
25000f75043SDaniel Latypov			with open(request.json, 'w') as f:
25100f75043SDaniel Latypov				f.write(json_str)
252e756dbebSDaniel Latypov			stdout.print_with_timestamp("Test results stored in %s" %
25300f75043SDaniel Latypov				os.path.abspath(request.json))
25421a6d178SHeidi Fahim
255309e22efSDaniel Latypov	if test.status != kunit_parser.TestStatus.SUCCESS:
256309e22efSDaniel Latypov		return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
25745ba7a89SDavid Gow
258309e22efSDaniel Latypov	return KunitResult(KunitStatus.SUCCESS, parse_time), test
25945ba7a89SDavid Gow
26045ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree,
26145ba7a89SDavid Gow	      request: KunitRequest) -> KunitResult:
26245ba7a89SDavid Gow	run_start = time.time()
26345ba7a89SDavid Gow
264db167981SDaniel Latypov	config_result = config_tests(linux, request)
26545ba7a89SDavid Gow	if config_result.status != KunitStatus.SUCCESS:
26645ba7a89SDavid Gow		return config_result
26745ba7a89SDavid Gow
268db167981SDaniel Latypov	build_result = build_tests(linux, request)
26945ba7a89SDavid Gow	if build_result.status != KunitStatus.SUCCESS:
27045ba7a89SDavid Gow		return build_result
27145ba7a89SDavid Gow
272db167981SDaniel Latypov	exec_result = exec_tests(linux, request)
27345ba7a89SDavid Gow
27445ba7a89SDavid Gow	run_end = time.time()
27545ba7a89SDavid Gow
276e756dbebSDaniel Latypov	stdout.print_with_timestamp((
2776ebf5866SFelix Guo		'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' +
2786ebf5866SFelix Guo		'building, %.3fs running\n') % (
27945ba7a89SDavid Gow				run_end - run_start,
28045ba7a89SDavid Gow				config_result.elapsed_time,
28145ba7a89SDavid Gow				build_result.elapsed_time,
28245ba7a89SDavid Gow				exec_result.elapsed_time))
2837ef925eaSDaniel Latypov	return exec_result
2846ebf5866SFelix Guo
285d8c23eadSDaniel Latypov# Problem:
286d8c23eadSDaniel Latypov# $ kunit.py run --json
287d8c23eadSDaniel Latypov# works as one would expect and prints the parsed test results as JSON.
288d8c23eadSDaniel Latypov# $ kunit.py run --json suite_name
289d8c23eadSDaniel Latypov# would *not* pass suite_name as the filter_glob and print as json.
290d8c23eadSDaniel Latypov# argparse will consider it to be another way of writing
291d8c23eadSDaniel Latypov# $ kunit.py run --json=suite_name
292d8c23eadSDaniel Latypov# i.e. it would run all tests, and dump the json to a `suite_name` file.
293d8c23eadSDaniel Latypov# So we hackily automatically rewrite --json => --json=stdout
294d8c23eadSDaniel Latypovpseudo_bool_flag_defaults = {
295d8c23eadSDaniel Latypov		'--json': 'stdout',
296d8c23eadSDaniel Latypov		'--raw_output': 'kunit',
297d8c23eadSDaniel Latypov}
298d8c23eadSDaniel Latypovdef massage_argv(argv: Sequence[str]) -> Sequence[str]:
299d8c23eadSDaniel Latypov	def massage_arg(arg: str) -> str:
300d8c23eadSDaniel Latypov		if arg not in pseudo_bool_flag_defaults:
301d8c23eadSDaniel Latypov			return arg
302d8c23eadSDaniel Latypov		return  f'{arg}={pseudo_bool_flag_defaults[arg]}'
303d8c23eadSDaniel Latypov	return list(map(massage_arg, argv))
304d8c23eadSDaniel Latypov
305ad659ccbSDavid Gowdef get_default_jobs() -> int:
306ad659ccbSDavid Gow	return len(os.sched_getaffinity(0))
307ad659ccbSDavid Gow
3081da2e622SDaniel Latypovdef add_common_opts(parser: argparse.ArgumentParser) -> None:
30945ba7a89SDavid Gow	parser.add_argument('--build_dir',
31045ba7a89SDavid Gow			    help='As in the make command, it specifies the build '
31145ba7a89SDavid Gow			    'directory.',
312baa33315SDaniel Latypov			    type=str, default='.kunit', metavar='DIR')
31345ba7a89SDavid Gow	parser.add_argument('--make_options',
31445ba7a89SDavid Gow			    help='X=Y make option, can be repeated.',
315baa33315SDaniel Latypov			    action='append', metavar='X=Y')
31645ba7a89SDavid Gow	parser.add_argument('--alltests',
317980ac3adSDaniel Latypov			    help='Run all KUnit tests via tools/testing/kunit/configs/all_tests.config',
3186ebf5866SFelix Guo			    action='store_true')
319243180f5SDaniel Latypov	parser.add_argument('--kunitconfig',
3209854781dSDaniel Latypov			     help='Path to Kconfig fragment that enables KUnit tests.'
3219854781dSDaniel Latypov			     ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" '
32253b46621SDaniel Latypov			     'will get  automatically appended. If repeated, the files '
32353b46621SDaniel Latypov			     'blindly concatenated, which might not work in all cases.',
32453b46621SDaniel Latypov			     action='append', metavar='PATHS')
3259f57cc76SDaniel Latypov	parser.add_argument('--kconfig_add',
3269f57cc76SDaniel Latypov			     help='Additional Kconfig options to append to the '
3279f57cc76SDaniel Latypov			     '.kunitconfig, e.g. CONFIG_KASAN=y. Can be repeated.',
328baa33315SDaniel Latypov			    action='append', metavar='CONFIG_X=Y')
3296ebf5866SFelix Guo
33087c9c163SBrendan Higgins	parser.add_argument('--arch',
33187c9c163SBrendan Higgins			    help=('Specifies the architecture to run tests under. '
33287c9c163SBrendan Higgins				  'The architecture specified here must match the '
33387c9c163SBrendan Higgins				  'string passed to the ARCH make param, '
33487c9c163SBrendan Higgins				  'e.g. i386, x86_64, arm, um, etc. Non-UML '
33587c9c163SBrendan Higgins				  'architectures run on QEMU.'),
336baa33315SDaniel Latypov			    type=str, default='um', metavar='ARCH')
33787c9c163SBrendan Higgins
33887c9c163SBrendan Higgins	parser.add_argument('--cross_compile',
33987c9c163SBrendan Higgins			    help=('Sets make\'s CROSS_COMPILE variable; it should '
34087c9c163SBrendan Higgins				  'be set to a toolchain path prefix (the prefix '
34187c9c163SBrendan Higgins				  'of gcc and other tools in your toolchain, for '
34287c9c163SBrendan Higgins				  'example `sparc64-linux-gnu-` if you have the '
34387c9c163SBrendan Higgins				  'sparc toolchain installed on your system, or '
34487c9c163SBrendan Higgins				  '`$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux-` '
34587c9c163SBrendan Higgins				  'if you have downloaded the microblaze toolchain '
34687c9c163SBrendan Higgins				  'from the 0-day website to a directory in your '
34787c9c163SBrendan Higgins				  'home directory called `toolchains`).'),
348baa33315SDaniel Latypov			    metavar='PREFIX')
34987c9c163SBrendan Higgins
35087c9c163SBrendan Higgins	parser.add_argument('--qemu_config',
35187c9c163SBrendan Higgins			    help=('Takes a path to a path to a file containing '
35287c9c163SBrendan Higgins				  'a QemuArchParams object.'),
353baa33315SDaniel Latypov			    type=str, metavar='FILE')
35487c9c163SBrendan Higgins
355a9333bd3SDaniel Latypov	parser.add_argument('--qemu_args',
356a9333bd3SDaniel Latypov			    help='Additional QEMU arguments, e.g. "-smp 8"',
357a9333bd3SDaniel Latypov			    action='append', metavar='')
358a9333bd3SDaniel Latypov
3591da2e622SDaniel Latypovdef add_build_opts(parser: argparse.ArgumentParser) -> None:
36045ba7a89SDavid Gow	parser.add_argument('--jobs',
36145ba7a89SDavid Gow			    help='As in the make command, "Specifies  the number of '
36245ba7a89SDavid Gow			    'jobs (commands) to run simultaneously."',
363baa33315SDaniel Latypov			    type=int, default=get_default_jobs(), metavar='N')
36445ba7a89SDavid Gow
3651da2e622SDaniel Latypovdef add_exec_opts(parser: argparse.ArgumentParser) -> None:
36645ba7a89SDavid Gow	parser.add_argument('--timeout',
3676ebf5866SFelix Guo			    help='maximum number of seconds to allow for all tests '
3686ebf5866SFelix Guo			    'to run. This does not include time taken to build the '
3696ebf5866SFelix Guo			    'tests.',
3706ebf5866SFelix Guo			    type=int,
3716ebf5866SFelix Guo			    default=300,
372baa33315SDaniel Latypov			    metavar='SECONDS')
373d992880bSDaniel Latypov	parser.add_argument('filter_glob',
374a127b154SDaniel Latypov			    help='Filter which KUnit test suites/tests run at '
375a127b154SDaniel Latypov			    'boot-time, e.g. list* or list*.*del_test',
376d992880bSDaniel Latypov			    type=str,
377d992880bSDaniel Latypov			    nargs='?',
378d992880bSDaniel Latypov			    default='',
379d992880bSDaniel Latypov			    metavar='filter_glob')
380*723c8258SRae Moar	parser.add_argument('--filter',
381*723c8258SRae Moar			    help='Filter KUnit tests with attributes, '
382*723c8258SRae Moar			    'e.g. module=example or speed>slow',
383*723c8258SRae Moar			    type=str,
384*723c8258SRae Moar				default='')
385*723c8258SRae Moar	parser.add_argument('--filter_action',
386*723c8258SRae Moar			    help='If set to skip, filtered tests will be skipped, '
387*723c8258SRae Moar				'e.g. --filter_action=skip. Otherwise they will not run.',
388*723c8258SRae Moar			    type=str,
389*723c8258SRae Moar				choices=['skip'])
3906cb51a18SDaniel Latypov	parser.add_argument('--kernel_args',
3916cb51a18SDaniel Latypov			    help='Kernel command-line parameters. Maybe be repeated',
392baa33315SDaniel Latypov			     action='append', metavar='')
393ff9e09a3SDaniel Latypov	parser.add_argument('--run_isolated', help='If set, boot the kernel for each '
394ff9e09a3SDaniel Latypov			    'individual suite/test. This is can be useful for debugging '
395ff9e09a3SDaniel Latypov			    'a non-hermetic test, one that might pass/fail based on '
396ff9e09a3SDaniel Latypov			    'what ran before it.',
397ff9e09a3SDaniel Latypov			    type=str,
3980453f984SDaniel Latypov			    choices=['suite', 'test'])
399*723c8258SRae Moar	parser.add_argument('--list_tests', help='If set, list all tests that will be '
400*723c8258SRae Moar			    'run.',
401*723c8258SRae Moar			    action='store_true')
402*723c8258SRae Moar	parser.add_argument('--list_tests_attr', help='If set, list all tests and test '
403*723c8258SRae Moar			    'attributes.',
404*723c8258SRae Moar			    action='store_true')
4056ebf5866SFelix Guo
4061da2e622SDaniel Latypovdef add_parse_opts(parser: argparse.ArgumentParser) -> None:
407309e22efSDaniel Latypov	parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
408309e22efSDaniel Latypov			    'By default, filters to just KUnit output. Use '
409309e22efSDaniel Latypov			    '--raw_output=all to show everything',
410baa33315SDaniel Latypov			     type=str, nargs='?', const='all', default=None, choices=['all', 'kunit'])
41121a6d178SHeidi Fahim	parser.add_argument('--json',
41221a6d178SHeidi Fahim			    nargs='?',
413309e22efSDaniel Latypov			    help='Prints parsed test results as JSON to stdout or a file if '
414309e22efSDaniel Latypov			    'a filename is specified. Does nothing if --raw_output is set.',
415baa33315SDaniel Latypov			    type=str, const='stdout', default=None, metavar='FILE')
416021ed9f5SHeidi Fahim
4178a04930fSDaniel Latypov
4188a04930fSDaniel Latypovdef tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree:
4198a04930fSDaniel Latypov	"""Returns a LinuxSourceTree based on the user's arguments."""
420a9333bd3SDaniel Latypov	# Allow users to specify multiple arguments in one string, e.g. '-smp 8'
421a9333bd3SDaniel Latypov	qemu_args: List[str] = []
422a9333bd3SDaniel Latypov	if cli_args.qemu_args:
423a9333bd3SDaniel Latypov		for arg in cli_args.qemu_args:
424a9333bd3SDaniel Latypov			qemu_args.extend(shlex.split(arg))
425a9333bd3SDaniel Latypov
426980ac3adSDaniel Latypov	kunitconfigs = cli_args.kunitconfig if cli_args.kunitconfig else []
427980ac3adSDaniel Latypov	if cli_args.alltests:
428980ac3adSDaniel Latypov		# Prepend so user-specified options take prio if we ever allow
429980ac3adSDaniel Latypov		# --kunitconfig options to have differing options.
430980ac3adSDaniel Latypov		kunitconfigs = [kunit_kernel.ALL_TESTS_CONFIG_PATH] + kunitconfigs
431980ac3adSDaniel Latypov
4328a04930fSDaniel Latypov	return kunit_kernel.LinuxSourceTree(cli_args.build_dir,
433980ac3adSDaniel Latypov			kunitconfig_paths=kunitconfigs,
4348a04930fSDaniel Latypov			kconfig_add=cli_args.kconfig_add,
4358a04930fSDaniel Latypov			arch=cli_args.arch,
4368a04930fSDaniel Latypov			cross_compile=cli_args.cross_compile,
437a9333bd3SDaniel Latypov			qemu_config_path=cli_args.qemu_config,
438a9333bd3SDaniel Latypov			extra_qemu_args=qemu_args)
4398a04930fSDaniel Latypov
4408a04930fSDaniel Latypov
4411da2e622SDaniel Latypovdef run_handler(cli_args: argparse.Namespace) -> None:
4422dc9d6caSAlexander Pantyukhin	if not os.path.exists(cli_args.build_dir):
4432dc9d6caSAlexander Pantyukhin		os.mkdir(cli_args.build_dir)
4442dc9d6caSAlexander Pantyukhin
4452dc9d6caSAlexander Pantyukhin	linux = tree_from_args(cli_args)
4462dc9d6caSAlexander Pantyukhin	request = KunitRequest(build_dir=cli_args.build_dir,
4472dc9d6caSAlexander Pantyukhin					make_options=cli_args.make_options,
4482dc9d6caSAlexander Pantyukhin					jobs=cli_args.jobs,
4492dc9d6caSAlexander Pantyukhin					raw_output=cli_args.raw_output,
4502dc9d6caSAlexander Pantyukhin					json=cli_args.json,
4512dc9d6caSAlexander Pantyukhin					timeout=cli_args.timeout,
4522dc9d6caSAlexander Pantyukhin					filter_glob=cli_args.filter_glob,
453*723c8258SRae Moar					filter=cli_args.filter,
454*723c8258SRae Moar					filter_action=cli_args.filter_action,
4552dc9d6caSAlexander Pantyukhin					kernel_args=cli_args.kernel_args,
456*723c8258SRae Moar					run_isolated=cli_args.run_isolated,
457*723c8258SRae Moar					list_tests=cli_args.list_tests,
458*723c8258SRae Moar					list_tests_attr=cli_args.list_tests_attr)
4592dc9d6caSAlexander Pantyukhin	result = run_tests(linux, request)
4602dc9d6caSAlexander Pantyukhin	if result.status != KunitStatus.SUCCESS:
4612dc9d6caSAlexander Pantyukhin		sys.exit(1)
4622dc9d6caSAlexander Pantyukhin
4632dc9d6caSAlexander Pantyukhin
4641da2e622SDaniel Latypovdef config_handler(cli_args: argparse.Namespace) -> None:
4652dc9d6caSAlexander Pantyukhin	if cli_args.build_dir and (
4662dc9d6caSAlexander Pantyukhin			not os.path.exists(cli_args.build_dir)):
4672dc9d6caSAlexander Pantyukhin		os.mkdir(cli_args.build_dir)
4682dc9d6caSAlexander Pantyukhin
4692dc9d6caSAlexander Pantyukhin	linux = tree_from_args(cli_args)
4702dc9d6caSAlexander Pantyukhin	request = KunitConfigRequest(build_dir=cli_args.build_dir,
4712dc9d6caSAlexander Pantyukhin						make_options=cli_args.make_options)
4722dc9d6caSAlexander Pantyukhin	result = config_tests(linux, request)
4732dc9d6caSAlexander Pantyukhin	stdout.print_with_timestamp((
4742dc9d6caSAlexander Pantyukhin		'Elapsed time: %.3fs\n') % (
4752dc9d6caSAlexander Pantyukhin			result.elapsed_time))
4762dc9d6caSAlexander Pantyukhin	if result.status != KunitStatus.SUCCESS:
4772dc9d6caSAlexander Pantyukhin		sys.exit(1)
4782dc9d6caSAlexander Pantyukhin
4792dc9d6caSAlexander Pantyukhin
4801da2e622SDaniel Latypovdef build_handler(cli_args: argparse.Namespace) -> None:
4812dc9d6caSAlexander Pantyukhin	linux = tree_from_args(cli_args)
4822dc9d6caSAlexander Pantyukhin	request = KunitBuildRequest(build_dir=cli_args.build_dir,
4832dc9d6caSAlexander Pantyukhin					make_options=cli_args.make_options,
4842dc9d6caSAlexander Pantyukhin					jobs=cli_args.jobs)
4852dc9d6caSAlexander Pantyukhin	result = config_and_build_tests(linux, request)
4862dc9d6caSAlexander Pantyukhin	stdout.print_with_timestamp((
4872dc9d6caSAlexander Pantyukhin		'Elapsed time: %.3fs\n') % (
4882dc9d6caSAlexander Pantyukhin			result.elapsed_time))
4892dc9d6caSAlexander Pantyukhin	if result.status != KunitStatus.SUCCESS:
4902dc9d6caSAlexander Pantyukhin		sys.exit(1)
4912dc9d6caSAlexander Pantyukhin
4922dc9d6caSAlexander Pantyukhin
4931da2e622SDaniel Latypovdef exec_handler(cli_args: argparse.Namespace) -> None:
4942dc9d6caSAlexander Pantyukhin	linux = tree_from_args(cli_args)
4952dc9d6caSAlexander Pantyukhin	exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
4962dc9d6caSAlexander Pantyukhin					build_dir=cli_args.build_dir,
4972dc9d6caSAlexander Pantyukhin					json=cli_args.json,
4982dc9d6caSAlexander Pantyukhin					timeout=cli_args.timeout,
4992dc9d6caSAlexander Pantyukhin					filter_glob=cli_args.filter_glob,
500*723c8258SRae Moar					filter=cli_args.filter,
501*723c8258SRae Moar					filter_action=cli_args.filter_action,
5022dc9d6caSAlexander Pantyukhin					kernel_args=cli_args.kernel_args,
503*723c8258SRae Moar					run_isolated=cli_args.run_isolated,
504*723c8258SRae Moar					list_tests=cli_args.list_tests,
505*723c8258SRae Moar					list_tests_attr=cli_args.list_tests_attr)
5062dc9d6caSAlexander Pantyukhin	result = exec_tests(linux, exec_request)
5072dc9d6caSAlexander Pantyukhin	stdout.print_with_timestamp((
5082dc9d6caSAlexander Pantyukhin		'Elapsed time: %.3fs\n') % (result.elapsed_time))
5092dc9d6caSAlexander Pantyukhin	if result.status != KunitStatus.SUCCESS:
5102dc9d6caSAlexander Pantyukhin		sys.exit(1)
5112dc9d6caSAlexander Pantyukhin
5122dc9d6caSAlexander Pantyukhin
5131da2e622SDaniel Latypovdef parse_handler(cli_args: argparse.Namespace) -> None:
5142dc9d6caSAlexander Pantyukhin	if cli_args.file is None:
5151da2e622SDaniel Latypov		sys.stdin.reconfigure(errors='backslashreplace')  # type: ignore
5161da2e622SDaniel Latypov		kunit_output = sys.stdin  # type: Iterable[str]
5172dc9d6caSAlexander Pantyukhin	else:
5182dc9d6caSAlexander Pantyukhin		with open(cli_args.file, 'r', errors='backslashreplace') as f:
5192dc9d6caSAlexander Pantyukhin			kunit_output = f.read().splitlines()
5202dc9d6caSAlexander Pantyukhin	# We know nothing about how the result was created!
5212dc9d6caSAlexander Pantyukhin	metadata = kunit_json.Metadata()
5222dc9d6caSAlexander Pantyukhin	request = KunitParseRequest(raw_output=cli_args.raw_output,
5232dc9d6caSAlexander Pantyukhin					json=cli_args.json)
5242dc9d6caSAlexander Pantyukhin	result, _ = parse_tests(request, metadata, kunit_output)
5252dc9d6caSAlexander Pantyukhin	if result.status != KunitStatus.SUCCESS:
5262dc9d6caSAlexander Pantyukhin		sys.exit(1)
5272dc9d6caSAlexander Pantyukhin
5282dc9d6caSAlexander Pantyukhin
5292dc9d6caSAlexander Pantyukhinsubcommand_handlers_map = {
5302dc9d6caSAlexander Pantyukhin	'run': run_handler,
5312dc9d6caSAlexander Pantyukhin	'config': config_handler,
5322dc9d6caSAlexander Pantyukhin	'build': build_handler,
5332dc9d6caSAlexander Pantyukhin	'exec': exec_handler,
5342dc9d6caSAlexander Pantyukhin	'parse': parse_handler
5352dc9d6caSAlexander Pantyukhin}
5362dc9d6caSAlexander Pantyukhin
5372dc9d6caSAlexander Pantyukhin
5381da2e622SDaniel Latypovdef main(argv: Sequence[str]) -> None:
53945ba7a89SDavid Gow	parser = argparse.ArgumentParser(
54045ba7a89SDavid Gow			description='Helps writing and running KUnit tests.')
54145ba7a89SDavid Gow	subparser = parser.add_subparsers(dest='subcommand')
54245ba7a89SDavid Gow
54345ba7a89SDavid Gow	# The 'run' command will config, build, exec, and parse in one go.
54445ba7a89SDavid Gow	run_parser = subparser.add_parser('run', help='Runs KUnit tests.')
54545ba7a89SDavid Gow	add_common_opts(run_parser)
54645ba7a89SDavid Gow	add_build_opts(run_parser)
54745ba7a89SDavid Gow	add_exec_opts(run_parser)
54845ba7a89SDavid Gow	add_parse_opts(run_parser)
54945ba7a89SDavid Gow
55045ba7a89SDavid Gow	config_parser = subparser.add_parser('config',
55145ba7a89SDavid Gow						help='Ensures that .config contains all of '
55245ba7a89SDavid Gow						'the options in .kunitconfig')
55345ba7a89SDavid Gow	add_common_opts(config_parser)
55445ba7a89SDavid Gow
55545ba7a89SDavid Gow	build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests')
55645ba7a89SDavid Gow	add_common_opts(build_parser)
55745ba7a89SDavid Gow	add_build_opts(build_parser)
55845ba7a89SDavid Gow
55945ba7a89SDavid Gow	exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests')
56045ba7a89SDavid Gow	add_common_opts(exec_parser)
56145ba7a89SDavid Gow	add_exec_opts(exec_parser)
56245ba7a89SDavid Gow	add_parse_opts(exec_parser)
56345ba7a89SDavid Gow
56445ba7a89SDavid Gow	# The 'parse' option is special, as it doesn't need the kernel source
56545ba7a89SDavid Gow	# (therefore there is no need for a build_dir, hence no add_common_opts)
56645ba7a89SDavid Gow	# and the '--file' argument is not relevant to 'run', so isn't in
56745ba7a89SDavid Gow	# add_parse_opts()
56845ba7a89SDavid Gow	parse_parser = subparser.add_parser('parse',
56945ba7a89SDavid Gow					    help='Parses KUnit results from a file, '
57045ba7a89SDavid Gow					    'and parses formatted results.')
57145ba7a89SDavid Gow	add_parse_opts(parse_parser)
57245ba7a89SDavid Gow	parse_parser.add_argument('file',
57345ba7a89SDavid Gow				  help='Specifies the file to read results from.',
57445ba7a89SDavid Gow				  type=str, nargs='?', metavar='input_file')
5750476e69fSGreg Thelen
576d8c23eadSDaniel Latypov	cli_args = parser.parse_args(massage_argv(argv))
5776ebf5866SFelix Guo
5785578d008SBrendan Higgins	if get_kernel_root_path():
5795578d008SBrendan Higgins		os.chdir(get_kernel_root_path())
5805578d008SBrendan Higgins
5812dc9d6caSAlexander Pantyukhin	subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None)
58282206a0cSBrendan Higgins
5832dc9d6caSAlexander Pantyukhin	if subcomand_handler is None:
5846ebf5866SFelix Guo		parser.print_help()
5852dc9d6caSAlexander Pantyukhin		return
5862dc9d6caSAlexander Pantyukhin
5872dc9d6caSAlexander Pantyukhin	subcomand_handler(cli_args)
5882dc9d6caSAlexander Pantyukhin
5896ebf5866SFelix Guo
5906ebf5866SFelix Guoif __name__ == '__main__':
591ff7b437fSBrendan Higgins	main(sys.argv[1:])
592