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