Merge tag 'sound-fix-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[linux-block.git] / tools / testing / kunit / kunit.py
CommitLineData
c25ce589 1#!/usr/bin/env python3
6ebf5866
FG
2# SPDX-License-Identifier: GPL-2.0
3#
4# A thin wrapper on top of the KUnit Kernel
5#
6# Copyright (C) 2019, Google LLC.
7# Author: Felix Guo <felixguoxiuping@gmail.com>
8# Author: Brendan Higgins <brendanhiggins@google.com>
9
10import argparse
6ebf5866 11import os
ff9e09a3 12import re
a9333bd3 13import shlex
ff9e09a3 14import sys
6ebf5866
FG
15import time
16
df4b0807
SP
17assert sys.version_info >= (3, 7), "Python version is too old"
18
db167981 19from dataclasses import dataclass
6ebf5866 20from enum import Enum, auto
95dcbc55 21from typing import Iterable, List, Optional, Sequence, Tuple
6ebf5866 22
21a6d178 23import kunit_json
6ebf5866
FG
24import kunit_kernel
25import kunit_parser
e756dbeb 26from kunit_printer import stdout
6ebf5866 27
6ebf5866
FG
28class KunitStatus(Enum):
29 SUCCESS = auto()
30 CONFIG_FAILURE = auto()
31 BUILD_FAILURE = auto()
32 TEST_FAILURE = auto()
33
db167981
DL
34@dataclass
35class KunitResult:
36 status: KunitStatus
db167981
DL
37 elapsed_time: float
38
39@dataclass
40class KunitConfigRequest:
41 build_dir: str
42 make_options: Optional[List[str]]
43
44@dataclass
45class KunitBuildRequest(KunitConfigRequest):
46 jobs: int
db167981
DL
47
48@dataclass
49class KunitParseRequest:
50 raw_output: Optional[str]
db167981
DL
51 json: Optional[str]
52
53@dataclass
54class KunitExecRequest(KunitParseRequest):
ee96d25f 55 build_dir: str
db167981 56 timeout: int
db167981 57 filter_glob: str
723c8258
RM
58 filter: str
59 filter_action: Optional[str]
db167981
DL
60 kernel_args: Optional[List[str]]
61 run_isolated: Optional[str]
723c8258
RM
62 list_tests: bool
63 list_tests_attr: bool
db167981
DL
64
65@dataclass
66class KunitRequest(KunitExecRequest, KunitBuildRequest):
67 pass
68
69
09641f7c
DL
70def get_kernel_root_path() -> str:
71 path = sys.argv[0] if not __file__ else __file__
72 parts = os.path.realpath(path).split('tools/testing/kunit')
be886ba9
HF
73 if len(parts) != 2:
74 sys.exit(1)
75 return parts[0]
76
45ba7a89
DG
77def config_tests(linux: kunit_kernel.LinuxSourceTree,
78 request: KunitConfigRequest) -> KunitResult:
e756dbeb 79 stdout.print_with_timestamp('Configuring KUnit Kernel ...')
45ba7a89 80
6ebf5866 81 config_start = time.time()
0476e69f 82 success = linux.build_reconfig(request.build_dir, request.make_options)
6ebf5866 83 config_end = time.time()
1fdc6f4f
AP
84 status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE
85 return KunitResult(status, config_end - config_start)
6ebf5866 86
45ba7a89
DG
87def build_tests(linux: kunit_kernel.LinuxSourceTree,
88 request: KunitBuildRequest) -> KunitResult:
e756dbeb 89 stdout.print_with_timestamp('Building KUnit Kernel ...')
6ebf5866
FG
90
91 build_start = time.time()
980ac3ad 92 success = linux.build_kernel(request.jobs,
87c9c163
BH
93 request.build_dir,
94 request.make_options)
6ebf5866 95 build_end = time.time()
1fdc6f4f
AP
96 status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE
97 return KunitResult(status, build_end - build_start)
6ebf5866 98
1ee2ba89
DL
99def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
100 request: KunitBuildRequest) -> KunitResult:
101 config_result = config_tests(linux, request)
102 if config_result.status != KunitStatus.SUCCESS:
103 return config_result
104
105 return build_tests(linux, request)
106
ff9e09a3
DL
107def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
108 args = ['kunit.action=list']
723c8258
RM
109
110 if request.kernel_args:
111 args.extend(request.kernel_args)
112
113 output = linux.run_kernel(args=args,
114 timeout=request.timeout,
115 filter_glob=request.filter_glob,
116 filter=request.filter,
117 filter_action=request.filter_action,
118 build_dir=request.build_dir)
119 lines = kunit_parser.extract_tap_lines(output)
120 # Hack! Drop the dummy TAP version header that the executor prints out.
121 lines.pop()
122
123 # Filter out any extraneous non-test output that might have gotten mixed in.
124 return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
125
126def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]:
127 args = ['kunit.action=list_attr']
128
ff9e09a3
DL
129 if request.kernel_args:
130 args.extend(request.kernel_args)
131
132 output = linux.run_kernel(args=args,
980ac3ad 133 timeout=request.timeout,
ff9e09a3 134 filter_glob=request.filter_glob,
723c8258
RM
135 filter=request.filter,
136 filter_action=request.filter_action,
ff9e09a3
DL
137 build_dir=request.build_dir)
138 lines = kunit_parser.extract_tap_lines(output)
139 # Hack! Drop the dummy TAP version header that the executor prints out.
140 lines.pop()
141
142 # Filter out any extraneous non-test output that might have gotten mixed in.
723c8258 143 return lines
ff9e09a3
DL
144
145def _suites_from_test_list(tests: List[str]) -> List[str]:
146 """Extracts all the suites from an ordered list of tests."""
147 suites = [] # type: List[str]
148 for t in tests:
149 parts = t.split('.', maxsplit=2)
150 if len(parts) != 2:
151 raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"')
126901ba 152 suite, _ = parts
ff9e09a3
DL
153 if not suites or suites[-1] != suite:
154 suites.append(suite)
155 return suites
156
db167981 157def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult:
ff9e09a3 158 filter_globs = [request.filter_glob]
723c8258
RM
159 if request.list_tests:
160 output = _list_tests(linux, request)
161 for line in output:
162 print(line.rstrip())
163 return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
164 if request.list_tests_attr:
165 attr_output = _list_tests_attr(linux, request)
166 for line in attr_output:
167 print(line.rstrip())
168 return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
ff9e09a3
DL
169 if request.run_isolated:
170 tests = _list_tests(linux, request)
171 if request.run_isolated == 'test':
172 filter_globs = tests
1fdc6f4f 173 elif request.run_isolated == 'suite':
ff9e09a3
DL
174 filter_globs = _suites_from_test_list(tests)
175 # Apply the test-part of the user's glob, if present.
176 if '.' in request.filter_glob:
177 test_glob = request.filter_glob.split('.', maxsplit=2)[1]
178 filter_globs = [g + '.'+ test_glob for g in filter_globs]
179
885210d3 180 metadata = kunit_json.Metadata(arch=linux.arch(), build_dir=request.build_dir, def_config='kunit_defconfig')
ee96d25f 181
d65d07cb 182 test_counts = kunit_parser.TestCounts()
ff9e09a3
DL
183 exec_time = 0.0
184 for i, filter_glob in enumerate(filter_globs):
e756dbeb 185 stdout.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs)))
ff9e09a3
DL
186
187 test_start = time.time()
188 run_result = linux.run_kernel(
189 args=request.kernel_args,
980ac3ad 190 timeout=request.timeout,
ff9e09a3 191 filter_glob=filter_glob,
723c8258
RM
192 filter=request.filter,
193 filter_action=request.filter_action,
ff9e09a3
DL
194 build_dir=request.build_dir)
195
ee96d25f 196 _, test_result = parse_tests(request, metadata, run_result)
ff9e09a3
DL
197 # run_kernel() doesn't block on the kernel exiting.
198 # That only happens after we get the last line of output from `run_result`.
199 # So exec_time here actually contains parsing + execution time, which is fine.
200 test_end = time.time()
201 exec_time += test_end - test_start
202
95dcbc55 203 test_counts.add_subtest_counts(test_result.counts)
45ba7a89 204
7fa7ffcf
DL
205 if len(filter_globs) == 1 and test_counts.crashed > 0:
206 bd = request.build_dir
207 print('The kernel seems to have crashed; you can decode the stack traces with:')
208 print('$ scripts/decode_stacktrace.sh {}/vmlinux {} < {} | tee {}/decoded.log | {} parse'.format(
209 bd, bd, kunit_kernel.get_outfile_path(bd), bd, sys.argv[0]))
210
d65d07cb 211 kunit_status = _map_to_overall_status(test_counts.get_status())
95dcbc55 212 return KunitResult(status=kunit_status, elapsed_time=exec_time)
d65d07cb
RM
213
214def _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus:
215 if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED):
216 return KunitStatus.SUCCESS
0453f984 217 return KunitStatus.TEST_FAILURE
45ba7a89 218
ee96d25f 219def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]:
45ba7a89
DG
220 parse_start = time.time()
221
6ebf5866 222 if request.raw_output:
d65d07cb 223 # Treat unparsed results as one passing test.
309e22ef
DL
224 fake_test = kunit_parser.Test()
225 fake_test.status = kunit_parser.TestStatus.SUCCESS
226 fake_test.counts.passed = 1
d65d07cb 227
7ef925ea 228 output: Iterable[str] = input_data
6a499c9c
DL
229 if request.raw_output == 'all':
230 pass
231 elif request.raw_output == 'kunit':
c2bb92bc 232 output = kunit_parser.extract_tap_lines(output)
6a499c9c
DL
233 for line in output:
234 print(line.rstrip())
309e22ef
DL
235 parse_time = time.time() - parse_start
236 return KunitResult(KunitStatus.SUCCESS, parse_time), fake_test
237
6a499c9c 238
309e22ef
DL
239 # Actually parse the test results.
240 test = kunit_parser.parse_run_tests(input_data)
241 parse_time = time.time() - parse_start
45ba7a89 242
21a6d178 243 if request.json:
00f75043 244 json_str = kunit_json.get_json_result(
309e22ef 245 test=test,
ee96d25f 246 metadata=metadata)
21a6d178 247 if request.json == 'stdout':
00f75043
DL
248 print(json_str)
249 else:
250 with open(request.json, 'w') as f:
251 f.write(json_str)
e756dbeb 252 stdout.print_with_timestamp("Test results stored in %s" %
00f75043 253 os.path.abspath(request.json))
21a6d178 254
309e22ef
DL
255 if test.status != kunit_parser.TestStatus.SUCCESS:
256 return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
45ba7a89 257
309e22ef 258 return KunitResult(KunitStatus.SUCCESS, parse_time), test
45ba7a89 259
45ba7a89
DG
260def run_tests(linux: kunit_kernel.LinuxSourceTree,
261 request: KunitRequest) -> KunitResult:
262 run_start = time.time()
263
db167981 264 config_result = config_tests(linux, request)
45ba7a89
DG
265 if config_result.status != KunitStatus.SUCCESS:
266 return config_result
267
db167981 268 build_result = build_tests(linux, request)
45ba7a89
DG
269 if build_result.status != KunitStatus.SUCCESS:
270 return build_result
271
db167981 272 exec_result = exec_tests(linux, request)
45ba7a89
DG
273
274 run_end = time.time()
6ebf5866 275
e756dbeb 276 stdout.print_with_timestamp((
6ebf5866
FG
277 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' +
278 'building, %.3fs running\n') % (
45ba7a89
DG
279 run_end - run_start,
280 config_result.elapsed_time,
281 build_result.elapsed_time,
282 exec_result.elapsed_time))
7ef925ea 283 return exec_result
45ba7a89 284
d8c23ead
DL
285# Problem:
286# $ kunit.py run --json
287# works as one would expect and prints the parsed test results as JSON.
288# $ kunit.py run --json suite_name
289# would *not* pass suite_name as the filter_glob and print as json.
290# argparse will consider it to be another way of writing
291# $ kunit.py run --json=suite_name
292# i.e. it would run all tests, and dump the json to a `suite_name` file.
293# So we hackily automatically rewrite --json => --json=stdout
294pseudo_bool_flag_defaults = {
295 '--json': 'stdout',
296 '--raw_output': 'kunit',
297}
298def massage_argv(argv: Sequence[str]) -> Sequence[str]:
299 def massage_arg(arg: str) -> str:
300 if arg not in pseudo_bool_flag_defaults:
301 return arg
302 return f'{arg}={pseudo_bool_flag_defaults[arg]}'
303 return list(map(massage_arg, argv))
304
ad659ccb
DG
305def get_default_jobs() -> int:
306 return len(os.sched_getaffinity(0))
307
1da2e622 308def add_common_opts(parser: argparse.ArgumentParser) -> None:
45ba7a89
DG
309 parser.add_argument('--build_dir',
310 help='As in the make command, it specifies the build '
311 'directory.',
baa33315 312 type=str, default='.kunit', metavar='DIR')
45ba7a89
DG
313 parser.add_argument('--make_options',
314 help='X=Y make option, can be repeated.',
baa33315 315 action='append', metavar='X=Y')
45ba7a89 316 parser.add_argument('--alltests',
980ac3ad 317 help='Run all KUnit tests via tools/testing/kunit/configs/all_tests.config',
45ba7a89 318 action='store_true')
243180f5 319 parser.add_argument('--kunitconfig',
9854781d
DL
320 help='Path to Kconfig fragment that enables KUnit tests.'
321 ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" '
53b46621
DL
322 'will get automatically appended. If repeated, the files '
323 'blindly concatenated, which might not work in all cases.',
324 action='append', metavar='PATHS')
9f57cc76
DL
325 parser.add_argument('--kconfig_add',
326 help='Additional Kconfig options to append to the '
327 '.kunitconfig, e.g. CONFIG_KASAN=y. Can be repeated.',
baa33315 328 action='append', metavar='CONFIG_X=Y')
45ba7a89 329
87c9c163
BH
330 parser.add_argument('--arch',
331 help=('Specifies the architecture to run tests under. '
332 'The architecture specified here must match the '
333 'string passed to the ARCH make param, '
334 'e.g. i386, x86_64, arm, um, etc. Non-UML '
335 'architectures run on QEMU.'),
baa33315 336 type=str, default='um', metavar='ARCH')
87c9c163
BH
337
338 parser.add_argument('--cross_compile',
339 help=('Sets make\'s CROSS_COMPILE variable; it should '
340 'be set to a toolchain path prefix (the prefix '
341 'of gcc and other tools in your toolchain, for '
342 'example `sparc64-linux-gnu-` if you have the '
343 'sparc toolchain installed on your system, or '
344 '`$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux-` '
345 'if you have downloaded the microblaze toolchain '
346 'from the 0-day website to a directory in your '
347 'home directory called `toolchains`).'),
baa33315 348 metavar='PREFIX')
87c9c163
BH
349
350 parser.add_argument('--qemu_config',
351 help=('Takes a path to a path to a file containing '
352 'a QemuArchParams object.'),
baa33315 353 type=str, metavar='FILE')
87c9c163 354
a9333bd3
DL
355 parser.add_argument('--qemu_args',
356 help='Additional QEMU arguments, e.g. "-smp 8"',
357 action='append', metavar='')
358
1da2e622 359def add_build_opts(parser: argparse.ArgumentParser) -> None:
45ba7a89
DG
360 parser.add_argument('--jobs',
361 help='As in the make command, "Specifies the number of '
362 'jobs (commands) to run simultaneously."',
baa33315 363 type=int, default=get_default_jobs(), metavar='N')
45ba7a89 364
1da2e622 365def add_exec_opts(parser: argparse.ArgumentParser) -> None:
45ba7a89
DG
366 parser.add_argument('--timeout',
367 help='maximum number of seconds to allow for all tests '
368 'to run. This does not include time taken to build the '
369 'tests.',
370 type=int,
371 default=300,
baa33315 372 metavar='SECONDS')
d992880b 373 parser.add_argument('filter_glob',
a127b154
DL
374 help='Filter which KUnit test suites/tests run at '
375 'boot-time, e.g. list* or list*.*del_test',
d992880b
DL
376 type=str,
377 nargs='?',
378 default='',
379 metavar='filter_glob')
723c8258
RM
380 parser.add_argument('--filter',
381 help='Filter KUnit tests with attributes, '
382 'e.g. module=example or speed>slow',
383 type=str,
384 default='')
385 parser.add_argument('--filter_action',
386 help='If set to skip, filtered tests will be skipped, '
387 'e.g. --filter_action=skip. Otherwise they will not run.',
388 type=str,
389 choices=['skip'])
6cb51a18
DL
390 parser.add_argument('--kernel_args',
391 help='Kernel command-line parameters. Maybe be repeated',
baa33315 392 action='append', metavar='')
ff9e09a3
DL
393 parser.add_argument('--run_isolated', help='If set, boot the kernel for each '
394 'individual suite/test. This is can be useful for debugging '
395 'a non-hermetic test, one that might pass/fail based on '
396 'what ran before it.',
397 type=str,
0453f984 398 choices=['suite', 'test'])
723c8258
RM
399 parser.add_argument('--list_tests', help='If set, list all tests that will be '
400 'run.',
401 action='store_true')
402 parser.add_argument('--list_tests_attr', help='If set, list all tests and test '
403 'attributes.',
404 action='store_true')
45ba7a89 405
1da2e622 406def add_parse_opts(parser: argparse.ArgumentParser) -> None:
309e22ef
DL
407 parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
408 'By default, filters to just KUnit output. Use '
409 '--raw_output=all to show everything',
baa33315 410 type=str, nargs='?', const='all', default=None, choices=['all', 'kunit'])
21a6d178
HF
411 parser.add_argument('--json',
412 nargs='?',
309e22ef
DL
413 help='Prints parsed test results as JSON to stdout or a file if '
414 'a filename is specified. Does nothing if --raw_output is set.',
baa33315 415 type=str, const='stdout', default=None, metavar='FILE')
6ebf5866 416
8a04930f
DL
417
418def tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree:
419 """Returns a LinuxSourceTree based on the user's arguments."""
a9333bd3
DL
420 # Allow users to specify multiple arguments in one string, e.g. '-smp 8'
421 qemu_args: List[str] = []
422 if cli_args.qemu_args:
423 for arg in cli_args.qemu_args:
424 qemu_args.extend(shlex.split(arg))
425
980ac3ad
DL
426 kunitconfigs = cli_args.kunitconfig if cli_args.kunitconfig else []
427 if cli_args.alltests:
428 # Prepend so user-specified options take prio if we ever allow
429 # --kunitconfig options to have differing options.
430 kunitconfigs = [kunit_kernel.ALL_TESTS_CONFIG_PATH] + kunitconfigs
431
8a04930f 432 return kunit_kernel.LinuxSourceTree(cli_args.build_dir,
980ac3ad 433 kunitconfig_paths=kunitconfigs,
8a04930f
DL
434 kconfig_add=cli_args.kconfig_add,
435 arch=cli_args.arch,
436 cross_compile=cli_args.cross_compile,
a9333bd3
DL
437 qemu_config_path=cli_args.qemu_config,
438 extra_qemu_args=qemu_args)
8a04930f
DL
439
440
1da2e622 441def run_handler(cli_args: argparse.Namespace) -> None:
2dc9d6ca
AP
442 if not os.path.exists(cli_args.build_dir):
443 os.mkdir(cli_args.build_dir)
444
445 linux = tree_from_args(cli_args)
446 request = KunitRequest(build_dir=cli_args.build_dir,
447 make_options=cli_args.make_options,
448 jobs=cli_args.jobs,
449 raw_output=cli_args.raw_output,
450 json=cli_args.json,
451 timeout=cli_args.timeout,
452 filter_glob=cli_args.filter_glob,
723c8258
RM
453 filter=cli_args.filter,
454 filter_action=cli_args.filter_action,
2dc9d6ca 455 kernel_args=cli_args.kernel_args,
723c8258
RM
456 run_isolated=cli_args.run_isolated,
457 list_tests=cli_args.list_tests,
458 list_tests_attr=cli_args.list_tests_attr)
2dc9d6ca
AP
459 result = run_tests(linux, request)
460 if result.status != KunitStatus.SUCCESS:
461 sys.exit(1)
462
463
1da2e622 464def config_handler(cli_args: argparse.Namespace) -> None:
2dc9d6ca
AP
465 if cli_args.build_dir and (
466 not os.path.exists(cli_args.build_dir)):
467 os.mkdir(cli_args.build_dir)
468
469 linux = tree_from_args(cli_args)
470 request = KunitConfigRequest(build_dir=cli_args.build_dir,
471 make_options=cli_args.make_options)
472 result = config_tests(linux, request)
473 stdout.print_with_timestamp((
474 'Elapsed time: %.3fs\n') % (
475 result.elapsed_time))
476 if result.status != KunitStatus.SUCCESS:
477 sys.exit(1)
478
479
1da2e622 480def build_handler(cli_args: argparse.Namespace) -> None:
2dc9d6ca
AP
481 linux = tree_from_args(cli_args)
482 request = KunitBuildRequest(build_dir=cli_args.build_dir,
483 make_options=cli_args.make_options,
484 jobs=cli_args.jobs)
485 result = config_and_build_tests(linux, request)
486 stdout.print_with_timestamp((
487 'Elapsed time: %.3fs\n') % (
488 result.elapsed_time))
489 if result.status != KunitStatus.SUCCESS:
490 sys.exit(1)
491
492
1da2e622 493def exec_handler(cli_args: argparse.Namespace) -> None:
2dc9d6ca
AP
494 linux = tree_from_args(cli_args)
495 exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
496 build_dir=cli_args.build_dir,
497 json=cli_args.json,
498 timeout=cli_args.timeout,
499 filter_glob=cli_args.filter_glob,
723c8258
RM
500 filter=cli_args.filter,
501 filter_action=cli_args.filter_action,
2dc9d6ca 502 kernel_args=cli_args.kernel_args,
723c8258
RM
503 run_isolated=cli_args.run_isolated,
504 list_tests=cli_args.list_tests,
505 list_tests_attr=cli_args.list_tests_attr)
2dc9d6ca
AP
506 result = exec_tests(linux, exec_request)
507 stdout.print_with_timestamp((
508 'Elapsed time: %.3fs\n') % (result.elapsed_time))
509 if result.status != KunitStatus.SUCCESS:
510 sys.exit(1)
511
512
1da2e622 513def parse_handler(cli_args: argparse.Namespace) -> None:
2dc9d6ca 514 if cli_args.file is None:
1da2e622
DL
515 sys.stdin.reconfigure(errors='backslashreplace') # type: ignore
516 kunit_output = sys.stdin # type: Iterable[str]
2dc9d6ca
AP
517 else:
518 with open(cli_args.file, 'r', errors='backslashreplace') as f:
519 kunit_output = f.read().splitlines()
520 # We know nothing about how the result was created!
521 metadata = kunit_json.Metadata()
522 request = KunitParseRequest(raw_output=cli_args.raw_output,
523 json=cli_args.json)
524 result, _ = parse_tests(request, metadata, kunit_output)
525 if result.status != KunitStatus.SUCCESS:
526 sys.exit(1)
527
528
529subcommand_handlers_map = {
530 'run': run_handler,
531 'config': config_handler,
532 'build': build_handler,
533 'exec': exec_handler,
534 'parse': parse_handler
535}
536
537
1da2e622 538def main(argv: Sequence[str]) -> None:
6ebf5866
FG
539 parser = argparse.ArgumentParser(
540 description='Helps writing and running KUnit tests.')
541 subparser = parser.add_subparsers(dest='subcommand')
542
45ba7a89 543 # The 'run' command will config, build, exec, and parse in one go.
6ebf5866 544 run_parser = subparser.add_parser('run', help='Runs KUnit tests.')
45ba7a89 545 add_common_opts(run_parser)
45ba7a89
DG
546 add_build_opts(run_parser)
547 add_exec_opts(run_parser)
548 add_parse_opts(run_parser)
549
550 config_parser = subparser.add_parser('config',
551 help='Ensures that .config contains all of '
552 'the options in .kunitconfig')
553 add_common_opts(config_parser)
45ba7a89
DG
554
555 build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests')
556 add_common_opts(build_parser)
557 add_build_opts(build_parser)
558
559 exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests')
560 add_common_opts(exec_parser)
561 add_exec_opts(exec_parser)
562 add_parse_opts(exec_parser)
563
564 # The 'parse' option is special, as it doesn't need the kernel source
565 # (therefore there is no need for a build_dir, hence no add_common_opts)
566 # and the '--file' argument is not relevant to 'run', so isn't in
567 # add_parse_opts()
568 parse_parser = subparser.add_parser('parse',
569 help='Parses KUnit results from a file, '
570 'and parses formatted results.')
571 add_parse_opts(parse_parser)
572 parse_parser.add_argument('file',
573 help='Specifies the file to read results from.',
574 type=str, nargs='?', metavar='input_file')
0476e69f 575
d8c23ead 576 cli_args = parser.parse_args(massage_argv(argv))
6ebf5866 577
5578d008
BH
578 if get_kernel_root_path():
579 os.chdir(get_kernel_root_path())
580
2dc9d6ca
AP
581 subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None)
582
583 if subcomand_handler is None:
6ebf5866 584 parser.print_help()
2dc9d6ca
AP
585 return
586
587 subcomand_handler(cli_args)
588
6ebf5866
FG
589
590if __name__ == '__main__':
ff7b437f 591 main(sys.argv[1:])