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 <[email protected]> 86ebf5866SFelix Guo# Author: Brendan Higgins <[email protected]> 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 26062a9dd9SDavid Gowfrom kunit_printer import stdout, null_printer 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] 52062a9dd9SDavid Gow summary: bool 533c67a2c0SRae Moar failed: bool 54db167981SDaniel Latypov 55db167981SDaniel Latypov@dataclass 56db167981SDaniel Latypovclass KunitExecRequest(KunitParseRequest): 57ee96d25fSDaniel Latypov build_dir: str 58db167981SDaniel Latypov timeout: int 59db167981SDaniel Latypov filter_glob: str 60723c8258SRae Moar filter: str 61723c8258SRae Moar filter_action: Optional[str] 62db167981SDaniel Latypov kernel_args: Optional[List[str]] 63db167981SDaniel Latypov run_isolated: Optional[str] 64723c8258SRae Moar list_tests: bool 65723c8258SRae Moar list_tests_attr: bool 66db167981SDaniel Latypov 67db167981SDaniel Latypov@dataclass 68db167981SDaniel Latypovclass KunitRequest(KunitExecRequest, KunitBuildRequest): 69db167981SDaniel Latypov pass 70db167981SDaniel Latypov 71db167981SDaniel Latypov 7209641f7cSDaniel Latypovdef get_kernel_root_path() -> str: 7309641f7cSDaniel Latypov path = sys.argv[0] if not __file__ else __file__ 7409641f7cSDaniel Latypov parts = os.path.realpath(path).split('tools/testing/kunit') 75be886ba9SHeidi Fahim if len(parts) != 2: 76be886ba9SHeidi Fahim sys.exit(1) 77be886ba9SHeidi Fahim return parts[0] 78be886ba9SHeidi Fahim 7945ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree, 8045ba7a89SDavid Gow request: KunitConfigRequest) -> KunitResult: 81e756dbebSDaniel Latypov stdout.print_with_timestamp('Configuring KUnit Kernel ...') 8245ba7a89SDavid Gow 836ebf5866SFelix Guo config_start = time.time() 840476e69fSGreg Thelen success = linux.build_reconfig(request.build_dir, request.make_options) 856ebf5866SFelix Guo config_end = time.time() 861fdc6f4fSAlexander Pantyukhin status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE 871fdc6f4fSAlexander Pantyukhin return KunitResult(status, config_end - config_start) 886ebf5866SFelix Guo 8945ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree, 9045ba7a89SDavid Gow request: KunitBuildRequest) -> KunitResult: 91e756dbebSDaniel Latypov stdout.print_with_timestamp('Building KUnit Kernel ...') 926ebf5866SFelix Guo 936ebf5866SFelix Guo build_start = time.time() 94980ac3adSDaniel Latypov success = linux.build_kernel(request.jobs, 950476e69fSGreg Thelen request.build_dir, 960476e69fSGreg Thelen request.make_options) 976ebf5866SFelix Guo build_end = time.time() 981fdc6f4fSAlexander Pantyukhin status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE 991fdc6f4fSAlexander Pantyukhin return KunitResult(status, build_end - build_start) 1006ebf5866SFelix Guo 1011ee2ba89SDaniel Latypovdef config_and_build_tests(linux: kunit_kernel.LinuxSourceTree, 1021ee2ba89SDaniel Latypov request: KunitBuildRequest) -> KunitResult: 1031ee2ba89SDaniel Latypov config_result = config_tests(linux, request) 1041ee2ba89SDaniel Latypov if config_result.status != KunitStatus.SUCCESS: 1051ee2ba89SDaniel Latypov return config_result 1061ee2ba89SDaniel Latypov 1071ee2ba89SDaniel Latypov return build_tests(linux, request) 1081ee2ba89SDaniel Latypov 109ff9e09a3SDaniel Latypovdef _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]: 110ff9e09a3SDaniel Latypov args = ['kunit.action=list'] 111723c8258SRae Moar 112ff9e09a3SDaniel Latypov if request.kernel_args: 113ff9e09a3SDaniel Latypov args.extend(request.kernel_args) 114ff9e09a3SDaniel Latypov 115ff9e09a3SDaniel Latypov output = linux.run_kernel(args=args, 116980ac3adSDaniel Latypov timeout=request.timeout, 117ff9e09a3SDaniel Latypov filter_glob=request.filter_glob, 118723c8258SRae Moar filter=request.filter, 119723c8258SRae Moar filter_action=request.filter_action, 120ff9e09a3SDaniel Latypov build_dir=request.build_dir) 121ff9e09a3SDaniel Latypov lines = kunit_parser.extract_tap_lines(output) 122ff9e09a3SDaniel Latypov # Hack! Drop the dummy TAP version header that the executor prints out. 123ff9e09a3SDaniel Latypov lines.pop() 124ff9e09a3SDaniel Latypov 125ff9e09a3SDaniel Latypov # Filter out any extraneous non-test output that might have gotten mixed in. 126723c8258SRae Moar return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)] 127723c8258SRae Moar 128723c8258SRae Moardef _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]: 129723c8258SRae Moar args = ['kunit.action=list_attr'] 130723c8258SRae Moar 131723c8258SRae Moar if request.kernel_args: 132723c8258SRae Moar args.extend(request.kernel_args) 133723c8258SRae Moar 134723c8258SRae Moar output = linux.run_kernel(args=args, 135723c8258SRae Moar timeout=request.timeout, 136723c8258SRae Moar filter_glob=request.filter_glob, 137723c8258SRae Moar filter=request.filter, 138723c8258SRae Moar filter_action=request.filter_action, 139723c8258SRae Moar build_dir=request.build_dir) 140723c8258SRae Moar lines = kunit_parser.extract_tap_lines(output) 141723c8258SRae Moar # Hack! Drop the dummy TAP version header that the executor prints out. 142723c8258SRae Moar lines.pop() 143723c8258SRae Moar 144723c8258SRae Moar # Filter out any extraneous non-test output that might have gotten mixed in. 145723c8258SRae Moar return lines 146ff9e09a3SDaniel Latypov 147ff9e09a3SDaniel Latypovdef _suites_from_test_list(tests: List[str]) -> List[str]: 148ff9e09a3SDaniel Latypov """Extracts all the suites from an ordered list of tests.""" 149ff9e09a3SDaniel Latypov suites = [] # type: List[str] 150ff9e09a3SDaniel Latypov for t in tests: 151ff9e09a3SDaniel Latypov parts = t.split('.', maxsplit=2) 152ff9e09a3SDaniel Latypov if len(parts) != 2: 153ff9e09a3SDaniel Latypov raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"') 154126901baSDaniel Latypov suite, _ = parts 155ff9e09a3SDaniel Latypov if not suites or suites[-1] != suite: 156ff9e09a3SDaniel Latypov suites.append(suite) 157ff9e09a3SDaniel Latypov return suites 158ff9e09a3SDaniel Latypov 159db167981SDaniel Latypovdef exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult: 160ff9e09a3SDaniel Latypov filter_globs = [request.filter_glob] 161723c8258SRae Moar if request.list_tests: 162723c8258SRae Moar output = _list_tests(linux, request) 163723c8258SRae Moar for line in output: 164723c8258SRae Moar print(line.rstrip()) 165723c8258SRae Moar return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0) 166723c8258SRae Moar if request.list_tests_attr: 167723c8258SRae Moar attr_output = _list_tests_attr(linux, request) 168723c8258SRae Moar for line in attr_output: 169723c8258SRae Moar print(line.rstrip()) 170723c8258SRae Moar return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0) 171ff9e09a3SDaniel Latypov if request.run_isolated: 172ff9e09a3SDaniel Latypov tests = _list_tests(linux, request) 173ff9e09a3SDaniel Latypov if request.run_isolated == 'test': 174ff9e09a3SDaniel Latypov filter_globs = tests 1751fdc6f4fSAlexander Pantyukhin elif request.run_isolated == 'suite': 176ff9e09a3SDaniel Latypov filter_globs = _suites_from_test_list(tests) 177ff9e09a3SDaniel Latypov # Apply the test-part of the user's glob, if present. 178ff9e09a3SDaniel Latypov if '.' in request.filter_glob: 179ff9e09a3SDaniel Latypov test_glob = request.filter_glob.split('.', maxsplit=2)[1] 180ff9e09a3SDaniel Latypov filter_globs = [g + '.'+ test_glob for g in filter_globs] 181ff9e09a3SDaniel Latypov 182885210d3SDaniel Latypov metadata = kunit_json.Metadata(arch=linux.arch(), build_dir=request.build_dir, def_config='kunit_defconfig') 183ee96d25fSDaniel Latypov 184d65d07cbSRae Moar test_counts = kunit_parser.TestCounts() 185ff9e09a3SDaniel Latypov exec_time = 0.0 186ff9e09a3SDaniel Latypov for i, filter_glob in enumerate(filter_globs): 187e756dbebSDaniel Latypov stdout.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs))) 188ff9e09a3SDaniel Latypov 1896ebf5866SFelix Guo test_start = time.time() 1907ef925eaSDaniel Latypov run_result = linux.run_kernel( 1916cb51a18SDaniel Latypov args=request.kernel_args, 192980ac3adSDaniel Latypov timeout=request.timeout, 193ff9e09a3SDaniel Latypov filter_glob=filter_glob, 194723c8258SRae Moar filter=request.filter, 195723c8258SRae Moar filter_action=request.filter_action, 1966ec1b81dSSeongJae Park build_dir=request.build_dir) 19745ba7a89SDavid Gow 198ee96d25fSDaniel Latypov _, test_result = parse_tests(request, metadata, run_result) 1995f6aa6d8SDaniel Latypov # run_kernel() doesn't block on the kernel exiting. 2005f6aa6d8SDaniel Latypov # That only happens after we get the last line of output from `run_result`. 2015f6aa6d8SDaniel Latypov # So exec_time here actually contains parsing + execution time, which is fine. 2026ebf5866SFelix Guo test_end = time.time() 203ff9e09a3SDaniel Latypov exec_time += test_end - test_start 204ff9e09a3SDaniel Latypov 20595dcbc55SDaniel Latypov test_counts.add_subtest_counts(test_result.counts) 2066ebf5866SFelix Guo 2077fa7ffcfSDaniel Latypov if len(filter_globs) == 1 and test_counts.crashed > 0: 2087fa7ffcfSDaniel Latypov bd = request.build_dir 2097fa7ffcfSDaniel Latypov print('The kernel seems to have crashed; you can decode the stack traces with:') 2107fa7ffcfSDaniel Latypov print('$ scripts/decode_stacktrace.sh {}/vmlinux {} < {} | tee {}/decoded.log | {} parse'.format( 2117fa7ffcfSDaniel Latypov bd, bd, kunit_kernel.get_outfile_path(bd), bd, sys.argv[0])) 2127fa7ffcfSDaniel Latypov 213d65d07cbSRae Moar kunit_status = _map_to_overall_status(test_counts.get_status()) 21495dcbc55SDaniel Latypov return KunitResult(status=kunit_status, elapsed_time=exec_time) 215d65d07cbSRae Moar 216d65d07cbSRae Moardef _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus: 217d65d07cbSRae Moar if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED): 218d65d07cbSRae Moar return KunitStatus.SUCCESS 219d65d07cbSRae Moar return KunitStatus.TEST_FAILURE 2207ef925eaSDaniel Latypov 221ee96d25fSDaniel Latypovdef parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]: 22245ba7a89SDavid Gow parse_start = time.time() 22345ba7a89SDavid Gow 22445ba7a89SDavid Gow if request.raw_output: 225d65d07cbSRae Moar # Treat unparsed results as one passing test. 226309e22efSDaniel Latypov fake_test = kunit_parser.Test() 227309e22efSDaniel Latypov fake_test.status = kunit_parser.TestStatus.SUCCESS 228309e22efSDaniel Latypov fake_test.counts.passed = 1 229d65d07cbSRae Moar 2307ef925eaSDaniel Latypov output: Iterable[str] = input_data 2316a499c9cSDaniel Latypov if request.raw_output == 'all': 2326a499c9cSDaniel Latypov pass 2336a499c9cSDaniel Latypov elif request.raw_output == 'kunit': 234c2bb92bcSDaniel Latypov output = kunit_parser.extract_tap_lines(output) 2356a499c9cSDaniel Latypov for line in output: 2366a499c9cSDaniel Latypov print(line.rstrip()) 237309e22efSDaniel Latypov parse_time = time.time() - parse_start 238309e22efSDaniel Latypov return KunitResult(KunitStatus.SUCCESS, parse_time), fake_test 2396a499c9cSDaniel Latypov 240062a9dd9SDavid Gow default_printer = stdout 2413c67a2c0SRae Moar if request.summary or request.failed: 242062a9dd9SDavid Gow default_printer = null_printer 243309e22efSDaniel Latypov 244309e22efSDaniel Latypov # Actually parse the test results. 245062a9dd9SDavid Gow test = kunit_parser.parse_run_tests(input_data, default_printer) 246309e22efSDaniel Latypov parse_time = time.time() - parse_start 24745ba7a89SDavid Gow 2483c67a2c0SRae Moar if request.failed: 2493c67a2c0SRae Moar kunit_parser.print_test(test, request.failed, stdout) 250062a9dd9SDavid Gow kunit_parser.print_summary_line(test, stdout) 251062a9dd9SDavid Gow 25221a6d178SHeidi Fahim if request.json: 25300f75043SDaniel Latypov json_str = kunit_json.get_json_result( 254309e22efSDaniel Latypov test=test, 255ee96d25fSDaniel Latypov metadata=metadata) 25621a6d178SHeidi Fahim if request.json == 'stdout': 25700f75043SDaniel Latypov print(json_str) 25800f75043SDaniel Latypov else: 25900f75043SDaniel Latypov with open(request.json, 'w') as f: 26000f75043SDaniel Latypov f.write(json_str) 261e756dbebSDaniel Latypov stdout.print_with_timestamp("Test results stored in %s" % 26200f75043SDaniel Latypov os.path.abspath(request.json)) 26321a6d178SHeidi Fahim 264309e22efSDaniel Latypov if test.status != kunit_parser.TestStatus.SUCCESS: 265309e22efSDaniel Latypov return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test 26645ba7a89SDavid Gow 267309e22efSDaniel Latypov return KunitResult(KunitStatus.SUCCESS, parse_time), test 26845ba7a89SDavid Gow 26945ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree, 27045ba7a89SDavid Gow request: KunitRequest) -> KunitResult: 27145ba7a89SDavid Gow run_start = time.time() 27245ba7a89SDavid Gow 273db167981SDaniel Latypov config_result = config_tests(linux, request) 27445ba7a89SDavid Gow if config_result.status != KunitStatus.SUCCESS: 27545ba7a89SDavid Gow return config_result 27645ba7a89SDavid Gow 277db167981SDaniel Latypov build_result = build_tests(linux, request) 27845ba7a89SDavid Gow if build_result.status != KunitStatus.SUCCESS: 27945ba7a89SDavid Gow return build_result 28045ba7a89SDavid Gow 281db167981SDaniel Latypov exec_result = exec_tests(linux, request) 28245ba7a89SDavid Gow 28345ba7a89SDavid Gow run_end = time.time() 28445ba7a89SDavid Gow 285e756dbebSDaniel Latypov stdout.print_with_timestamp(( 2866ebf5866SFelix Guo 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + 2876ebf5866SFelix Guo 'building, %.3fs running\n') % ( 28845ba7a89SDavid Gow run_end - run_start, 28945ba7a89SDavid Gow config_result.elapsed_time, 29045ba7a89SDavid Gow build_result.elapsed_time, 29145ba7a89SDavid Gow exec_result.elapsed_time)) 2927ef925eaSDaniel Latypov return exec_result 2936ebf5866SFelix Guo 294d8c23eadSDaniel Latypov# Problem: 295d8c23eadSDaniel Latypov# $ kunit.py run --json 296d8c23eadSDaniel Latypov# works as one would expect and prints the parsed test results as JSON. 297d8c23eadSDaniel Latypov# $ kunit.py run --json suite_name 298d8c23eadSDaniel Latypov# would *not* pass suite_name as the filter_glob and print as json. 299d8c23eadSDaniel Latypov# argparse will consider it to be another way of writing 300d8c23eadSDaniel Latypov# $ kunit.py run --json=suite_name 301d8c23eadSDaniel Latypov# i.e. it would run all tests, and dump the json to a `suite_name` file. 302d8c23eadSDaniel Latypov# So we hackily automatically rewrite --json => --json=stdout 303d8c23eadSDaniel Latypovpseudo_bool_flag_defaults = { 304d8c23eadSDaniel Latypov '--json': 'stdout', 305d8c23eadSDaniel Latypov '--raw_output': 'kunit', 306d8c23eadSDaniel Latypov} 307d8c23eadSDaniel Latypovdef massage_argv(argv: Sequence[str]) -> Sequence[str]: 308d8c23eadSDaniel Latypov def massage_arg(arg: str) -> str: 309d8c23eadSDaniel Latypov if arg not in pseudo_bool_flag_defaults: 310d8c23eadSDaniel Latypov return arg 311d8c23eadSDaniel Latypov return f'{arg}={pseudo_bool_flag_defaults[arg]}' 312d8c23eadSDaniel Latypov return list(map(massage_arg, argv)) 313d8c23eadSDaniel Latypov 314ad659ccbSDavid Gowdef get_default_jobs() -> int: 315*56530007STamir Duberstein if sys.version_info >= (3, 13): 316*56530007STamir Duberstein if (ncpu := os.process_cpu_count()) is not None: 317*56530007STamir Duberstein return ncpu 318*56530007STamir Duberstein raise RuntimeError("os.process_cpu_count() returned None") 319*56530007STamir Duberstein # See https://github.com/python/cpython/blob/b61fece/Lib/os.py#L1175-L1186. 320*56530007STamir Duberstein if sys.platform != "darwin": 321ad659ccbSDavid Gow return len(os.sched_getaffinity(0)) 322*56530007STamir Duberstein if (ncpu := os.cpu_count()) is not None: 323*56530007STamir Duberstein return ncpu 324*56530007STamir Duberstein raise RuntimeError("os.cpu_count() returned None") 325ad659ccbSDavid Gow 3261da2e622SDaniel Latypovdef add_common_opts(parser: argparse.ArgumentParser) -> None: 32745ba7a89SDavid Gow parser.add_argument('--build_dir', 32845ba7a89SDavid Gow help='As in the make command, it specifies the build ' 32945ba7a89SDavid Gow 'directory.', 330baa33315SDaniel Latypov type=str, default='.kunit', metavar='DIR') 33145ba7a89SDavid Gow parser.add_argument('--make_options', 33245ba7a89SDavid Gow help='X=Y make option, can be repeated.', 333baa33315SDaniel Latypov action='append', metavar='X=Y') 33445ba7a89SDavid Gow parser.add_argument('--alltests', 335980ac3adSDaniel Latypov help='Run all KUnit tests via tools/testing/kunit/configs/all_tests.config', 3366ebf5866SFelix Guo action='store_true') 337243180f5SDaniel Latypov parser.add_argument('--kunitconfig', 3389854781dSDaniel Latypov help='Path to Kconfig fragment that enables KUnit tests.' 3399854781dSDaniel Latypov ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" ' 34053b46621SDaniel Latypov 'will get automatically appended. If repeated, the files ' 34153b46621SDaniel Latypov 'blindly concatenated, which might not work in all cases.', 34253b46621SDaniel Latypov action='append', metavar='PATHS') 3439f57cc76SDaniel Latypov parser.add_argument('--kconfig_add', 3449f57cc76SDaniel Latypov help='Additional Kconfig options to append to the ' 3459f57cc76SDaniel Latypov '.kunitconfig, e.g. CONFIG_KASAN=y. Can be repeated.', 346baa33315SDaniel Latypov action='append', metavar='CONFIG_X=Y') 3476ebf5866SFelix Guo 34887c9c163SBrendan Higgins parser.add_argument('--arch', 34987c9c163SBrendan Higgins help=('Specifies the architecture to run tests under. ' 35087c9c163SBrendan Higgins 'The architecture specified here must match the ' 35187c9c163SBrendan Higgins 'string passed to the ARCH make param, ' 35287c9c163SBrendan Higgins 'e.g. i386, x86_64, arm, um, etc. Non-UML ' 35387c9c163SBrendan Higgins 'architectures run on QEMU.'), 354baa33315SDaniel Latypov type=str, default='um', metavar='ARCH') 35587c9c163SBrendan Higgins 35687c9c163SBrendan Higgins parser.add_argument('--cross_compile', 35787c9c163SBrendan Higgins help=('Sets make\'s CROSS_COMPILE variable; it should ' 35887c9c163SBrendan Higgins 'be set to a toolchain path prefix (the prefix ' 35987c9c163SBrendan Higgins 'of gcc and other tools in your toolchain, for ' 36087c9c163SBrendan Higgins 'example `sparc64-linux-gnu-` if you have the ' 36187c9c163SBrendan Higgins 'sparc toolchain installed on your system, or ' 36287c9c163SBrendan Higgins '`$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux-` ' 36387c9c163SBrendan Higgins 'if you have downloaded the microblaze toolchain ' 36487c9c163SBrendan Higgins 'from the 0-day website to a directory in your ' 36587c9c163SBrendan Higgins 'home directory called `toolchains`).'), 366baa33315SDaniel Latypov metavar='PREFIX') 36787c9c163SBrendan Higgins 36887c9c163SBrendan Higgins parser.add_argument('--qemu_config', 36987c9c163SBrendan Higgins help=('Takes a path to a path to a file containing ' 37087c9c163SBrendan Higgins 'a QemuArchParams object.'), 371baa33315SDaniel Latypov type=str, metavar='FILE') 37287c9c163SBrendan Higgins 373a9333bd3SDaniel Latypov parser.add_argument('--qemu_args', 374a9333bd3SDaniel Latypov help='Additional QEMU arguments, e.g. "-smp 8"', 375a9333bd3SDaniel Latypov action='append', metavar='') 376a9333bd3SDaniel Latypov 3771da2e622SDaniel Latypovdef add_build_opts(parser: argparse.ArgumentParser) -> None: 37845ba7a89SDavid Gow parser.add_argument('--jobs', 37945ba7a89SDavid Gow help='As in the make command, "Specifies the number of ' 38045ba7a89SDavid Gow 'jobs (commands) to run simultaneously."', 381baa33315SDaniel Latypov type=int, default=get_default_jobs(), metavar='N') 38245ba7a89SDavid Gow 3831da2e622SDaniel Latypovdef add_exec_opts(parser: argparse.ArgumentParser) -> None: 38445ba7a89SDavid Gow parser.add_argument('--timeout', 3856ebf5866SFelix Guo help='maximum number of seconds to allow for all tests ' 3866ebf5866SFelix Guo 'to run. This does not include time taken to build the ' 3876ebf5866SFelix Guo 'tests.', 3886ebf5866SFelix Guo type=int, 3896ebf5866SFelix Guo default=300, 390baa33315SDaniel Latypov metavar='SECONDS') 391d992880bSDaniel Latypov parser.add_argument('filter_glob', 392a127b154SDaniel Latypov help='Filter which KUnit test suites/tests run at ' 393a127b154SDaniel Latypov 'boot-time, e.g. list* or list*.*del_test', 394d992880bSDaniel Latypov type=str, 395d992880bSDaniel Latypov nargs='?', 396d992880bSDaniel Latypov default='', 397d992880bSDaniel Latypov metavar='filter_glob') 398723c8258SRae Moar parser.add_argument('--filter', 399723c8258SRae Moar help='Filter KUnit tests with attributes, ' 400723c8258SRae Moar 'e.g. module=example or speed>slow', 401723c8258SRae Moar type=str, 402723c8258SRae Moar default='') 403723c8258SRae Moar parser.add_argument('--filter_action', 404723c8258SRae Moar help='If set to skip, filtered tests will be skipped, ' 405723c8258SRae Moar 'e.g. --filter_action=skip. Otherwise they will not run.', 406723c8258SRae Moar type=str, 407723c8258SRae Moar choices=['skip']) 4086cb51a18SDaniel Latypov parser.add_argument('--kernel_args', 4096cb51a18SDaniel Latypov help='Kernel command-line parameters. Maybe be repeated', 410baa33315SDaniel Latypov action='append', metavar='') 411ff9e09a3SDaniel Latypov parser.add_argument('--run_isolated', help='If set, boot the kernel for each ' 412ff9e09a3SDaniel Latypov 'individual suite/test. This is can be useful for debugging ' 413ff9e09a3SDaniel Latypov 'a non-hermetic test, one that might pass/fail based on ' 414ff9e09a3SDaniel Latypov 'what ran before it.', 415ff9e09a3SDaniel Latypov type=str, 4160453f984SDaniel Latypov choices=['suite', 'test']) 417723c8258SRae Moar parser.add_argument('--list_tests', help='If set, list all tests that will be ' 418723c8258SRae Moar 'run.', 419723c8258SRae Moar action='store_true') 420723c8258SRae Moar parser.add_argument('--list_tests_attr', help='If set, list all tests and test ' 421723c8258SRae Moar 'attributes.', 422723c8258SRae Moar action='store_true') 4236ebf5866SFelix Guo 4241da2e622SDaniel Latypovdef add_parse_opts(parser: argparse.ArgumentParser) -> None: 425309e22efSDaniel Latypov parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. ' 426309e22efSDaniel Latypov 'By default, filters to just KUnit output. Use ' 427309e22efSDaniel Latypov '--raw_output=all to show everything', 428baa33315SDaniel Latypov type=str, nargs='?', const='all', default=None, choices=['all', 'kunit']) 42921a6d178SHeidi Fahim parser.add_argument('--json', 43021a6d178SHeidi Fahim nargs='?', 431309e22efSDaniel Latypov help='Prints parsed test results as JSON to stdout or a file if ' 432309e22efSDaniel Latypov 'a filename is specified. Does nothing if --raw_output is set.', 433baa33315SDaniel Latypov type=str, const='stdout', default=None, metavar='FILE') 434062a9dd9SDavid Gow parser.add_argument('--summary', 435062a9dd9SDavid Gow help='Prints only the summary line for parsed test results.' 436062a9dd9SDavid Gow 'Does nothing if --raw_output is set.', 437062a9dd9SDavid Gow action='store_true') 4383c67a2c0SRae Moar parser.add_argument('--failed', 4393c67a2c0SRae Moar help='Prints only the failed parsed test results and summary line.' 4403c67a2c0SRae Moar 'Does nothing if --raw_output is set.', 4413c67a2c0SRae Moar action='store_true') 442021ed9f5SHeidi Fahim 4438a04930fSDaniel Latypov 4448a04930fSDaniel Latypovdef tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree: 4458a04930fSDaniel Latypov """Returns a LinuxSourceTree based on the user's arguments.""" 446a9333bd3SDaniel Latypov # Allow users to specify multiple arguments in one string, e.g. '-smp 8' 447a9333bd3SDaniel Latypov qemu_args: List[str] = [] 448a9333bd3SDaniel Latypov if cli_args.qemu_args: 449a9333bd3SDaniel Latypov for arg in cli_args.qemu_args: 450a9333bd3SDaniel Latypov qemu_args.extend(shlex.split(arg)) 451a9333bd3SDaniel Latypov 452980ac3adSDaniel Latypov kunitconfigs = cli_args.kunitconfig if cli_args.kunitconfig else [] 453980ac3adSDaniel Latypov if cli_args.alltests: 454980ac3adSDaniel Latypov # Prepend so user-specified options take prio if we ever allow 455980ac3adSDaniel Latypov # --kunitconfig options to have differing options. 456980ac3adSDaniel Latypov kunitconfigs = [kunit_kernel.ALL_TESTS_CONFIG_PATH] + kunitconfigs 457980ac3adSDaniel Latypov 4588a04930fSDaniel Latypov return kunit_kernel.LinuxSourceTree(cli_args.build_dir, 459980ac3adSDaniel Latypov kunitconfig_paths=kunitconfigs, 4608a04930fSDaniel Latypov kconfig_add=cli_args.kconfig_add, 4618a04930fSDaniel Latypov arch=cli_args.arch, 4628a04930fSDaniel Latypov cross_compile=cli_args.cross_compile, 463a9333bd3SDaniel Latypov qemu_config_path=cli_args.qemu_config, 464a9333bd3SDaniel Latypov extra_qemu_args=qemu_args) 4658a04930fSDaniel Latypov 4668a04930fSDaniel Latypov 4671da2e622SDaniel Latypovdef run_handler(cli_args: argparse.Namespace) -> None: 4682dc9d6caSAlexander Pantyukhin if not os.path.exists(cli_args.build_dir): 4692dc9d6caSAlexander Pantyukhin os.mkdir(cli_args.build_dir) 4702dc9d6caSAlexander Pantyukhin 4712dc9d6caSAlexander Pantyukhin linux = tree_from_args(cli_args) 4722dc9d6caSAlexander Pantyukhin request = KunitRequest(build_dir=cli_args.build_dir, 4732dc9d6caSAlexander Pantyukhin make_options=cli_args.make_options, 4742dc9d6caSAlexander Pantyukhin jobs=cli_args.jobs, 4752dc9d6caSAlexander Pantyukhin raw_output=cli_args.raw_output, 4762dc9d6caSAlexander Pantyukhin json=cli_args.json, 477062a9dd9SDavid Gow summary=cli_args.summary, 4783c67a2c0SRae Moar failed=cli_args.failed, 4792dc9d6caSAlexander Pantyukhin timeout=cli_args.timeout, 4802dc9d6caSAlexander Pantyukhin filter_glob=cli_args.filter_glob, 481723c8258SRae Moar filter=cli_args.filter, 482723c8258SRae Moar filter_action=cli_args.filter_action, 4832dc9d6caSAlexander Pantyukhin kernel_args=cli_args.kernel_args, 484723c8258SRae Moar run_isolated=cli_args.run_isolated, 485723c8258SRae Moar list_tests=cli_args.list_tests, 486723c8258SRae Moar list_tests_attr=cli_args.list_tests_attr) 4872dc9d6caSAlexander Pantyukhin result = run_tests(linux, request) 4882dc9d6caSAlexander Pantyukhin if result.status != KunitStatus.SUCCESS: 4892dc9d6caSAlexander Pantyukhin sys.exit(1) 4902dc9d6caSAlexander Pantyukhin 4912dc9d6caSAlexander Pantyukhin 4921da2e622SDaniel Latypovdef config_handler(cli_args: argparse.Namespace) -> None: 4932dc9d6caSAlexander Pantyukhin if cli_args.build_dir and ( 4942dc9d6caSAlexander Pantyukhin not os.path.exists(cli_args.build_dir)): 4952dc9d6caSAlexander Pantyukhin os.mkdir(cli_args.build_dir) 4962dc9d6caSAlexander Pantyukhin 4972dc9d6caSAlexander Pantyukhin linux = tree_from_args(cli_args) 4982dc9d6caSAlexander Pantyukhin request = KunitConfigRequest(build_dir=cli_args.build_dir, 4992dc9d6caSAlexander Pantyukhin make_options=cli_args.make_options) 5002dc9d6caSAlexander Pantyukhin result = config_tests(linux, request) 5012dc9d6caSAlexander Pantyukhin stdout.print_with_timestamp(( 5022dc9d6caSAlexander Pantyukhin 'Elapsed time: %.3fs\n') % ( 5032dc9d6caSAlexander Pantyukhin result.elapsed_time)) 5042dc9d6caSAlexander Pantyukhin if result.status != KunitStatus.SUCCESS: 5052dc9d6caSAlexander Pantyukhin sys.exit(1) 5062dc9d6caSAlexander Pantyukhin 5072dc9d6caSAlexander Pantyukhin 5081da2e622SDaniel Latypovdef build_handler(cli_args: argparse.Namespace) -> None: 5092dc9d6caSAlexander Pantyukhin linux = tree_from_args(cli_args) 5102dc9d6caSAlexander Pantyukhin request = KunitBuildRequest(build_dir=cli_args.build_dir, 5112dc9d6caSAlexander Pantyukhin make_options=cli_args.make_options, 5122dc9d6caSAlexander Pantyukhin jobs=cli_args.jobs) 5132dc9d6caSAlexander Pantyukhin result = config_and_build_tests(linux, request) 5142dc9d6caSAlexander Pantyukhin stdout.print_with_timestamp(( 5152dc9d6caSAlexander Pantyukhin 'Elapsed time: %.3fs\n') % ( 5162dc9d6caSAlexander Pantyukhin result.elapsed_time)) 5172dc9d6caSAlexander Pantyukhin if result.status != KunitStatus.SUCCESS: 5182dc9d6caSAlexander Pantyukhin sys.exit(1) 5192dc9d6caSAlexander Pantyukhin 5202dc9d6caSAlexander Pantyukhin 5211da2e622SDaniel Latypovdef exec_handler(cli_args: argparse.Namespace) -> None: 5222dc9d6caSAlexander Pantyukhin linux = tree_from_args(cli_args) 5232dc9d6caSAlexander Pantyukhin exec_request = KunitExecRequest(raw_output=cli_args.raw_output, 5242dc9d6caSAlexander Pantyukhin build_dir=cli_args.build_dir, 5252dc9d6caSAlexander Pantyukhin json=cli_args.json, 526062a9dd9SDavid Gow summary=cli_args.summary, 5273c67a2c0SRae Moar failed=cli_args.failed, 5282dc9d6caSAlexander Pantyukhin timeout=cli_args.timeout, 5292dc9d6caSAlexander Pantyukhin filter_glob=cli_args.filter_glob, 530723c8258SRae Moar filter=cli_args.filter, 531723c8258SRae Moar filter_action=cli_args.filter_action, 5322dc9d6caSAlexander Pantyukhin kernel_args=cli_args.kernel_args, 533723c8258SRae Moar run_isolated=cli_args.run_isolated, 534723c8258SRae Moar list_tests=cli_args.list_tests, 535723c8258SRae Moar list_tests_attr=cli_args.list_tests_attr) 5362dc9d6caSAlexander Pantyukhin result = exec_tests(linux, exec_request) 5372dc9d6caSAlexander Pantyukhin stdout.print_with_timestamp(( 5382dc9d6caSAlexander Pantyukhin 'Elapsed time: %.3fs\n') % (result.elapsed_time)) 5392dc9d6caSAlexander Pantyukhin if result.status != KunitStatus.SUCCESS: 5402dc9d6caSAlexander Pantyukhin sys.exit(1) 5412dc9d6caSAlexander Pantyukhin 5422dc9d6caSAlexander Pantyukhin 5431da2e622SDaniel Latypovdef parse_handler(cli_args: argparse.Namespace) -> None: 5442dc9d6caSAlexander Pantyukhin if cli_args.file is None: 5451da2e622SDaniel Latypov sys.stdin.reconfigure(errors='backslashreplace') # type: ignore 5461da2e622SDaniel Latypov kunit_output = sys.stdin # type: Iterable[str] 5472dc9d6caSAlexander Pantyukhin else: 5482dc9d6caSAlexander Pantyukhin with open(cli_args.file, 'r', errors='backslashreplace') as f: 5492dc9d6caSAlexander Pantyukhin kunit_output = f.read().splitlines() 5502dc9d6caSAlexander Pantyukhin # We know nothing about how the result was created! 5512dc9d6caSAlexander Pantyukhin metadata = kunit_json.Metadata() 5522dc9d6caSAlexander Pantyukhin request = KunitParseRequest(raw_output=cli_args.raw_output, 5533c67a2c0SRae Moar json=cli_args.json, summary=cli_args.summary, 5543c67a2c0SRae Moar failed=cli_args.failed) 5552dc9d6caSAlexander Pantyukhin result, _ = parse_tests(request, metadata, kunit_output) 5562dc9d6caSAlexander Pantyukhin if result.status != KunitStatus.SUCCESS: 5572dc9d6caSAlexander Pantyukhin sys.exit(1) 5582dc9d6caSAlexander Pantyukhin 5592dc9d6caSAlexander Pantyukhin 5602dc9d6caSAlexander Pantyukhinsubcommand_handlers_map = { 5612dc9d6caSAlexander Pantyukhin 'run': run_handler, 5622dc9d6caSAlexander Pantyukhin 'config': config_handler, 5632dc9d6caSAlexander Pantyukhin 'build': build_handler, 5642dc9d6caSAlexander Pantyukhin 'exec': exec_handler, 5652dc9d6caSAlexander Pantyukhin 'parse': parse_handler 5662dc9d6caSAlexander Pantyukhin} 5672dc9d6caSAlexander Pantyukhin 5682dc9d6caSAlexander Pantyukhin 5691da2e622SDaniel Latypovdef main(argv: Sequence[str]) -> None: 57045ba7a89SDavid Gow parser = argparse.ArgumentParser( 57145ba7a89SDavid Gow description='Helps writing and running KUnit tests.') 57245ba7a89SDavid Gow subparser = parser.add_subparsers(dest='subcommand') 57345ba7a89SDavid Gow 57445ba7a89SDavid Gow # The 'run' command will config, build, exec, and parse in one go. 57545ba7a89SDavid Gow run_parser = subparser.add_parser('run', help='Runs KUnit tests.') 57645ba7a89SDavid Gow add_common_opts(run_parser) 57745ba7a89SDavid Gow add_build_opts(run_parser) 57845ba7a89SDavid Gow add_exec_opts(run_parser) 57945ba7a89SDavid Gow add_parse_opts(run_parser) 58045ba7a89SDavid Gow 58145ba7a89SDavid Gow config_parser = subparser.add_parser('config', 58245ba7a89SDavid Gow help='Ensures that .config contains all of ' 58345ba7a89SDavid Gow 'the options in .kunitconfig') 58445ba7a89SDavid Gow add_common_opts(config_parser) 58545ba7a89SDavid Gow 58645ba7a89SDavid Gow build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') 58745ba7a89SDavid Gow add_common_opts(build_parser) 58845ba7a89SDavid Gow add_build_opts(build_parser) 58945ba7a89SDavid Gow 59045ba7a89SDavid Gow exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') 59145ba7a89SDavid Gow add_common_opts(exec_parser) 59245ba7a89SDavid Gow add_exec_opts(exec_parser) 59345ba7a89SDavid Gow add_parse_opts(exec_parser) 59445ba7a89SDavid Gow 59545ba7a89SDavid Gow # The 'parse' option is special, as it doesn't need the kernel source 59645ba7a89SDavid Gow # (therefore there is no need for a build_dir, hence no add_common_opts) 59745ba7a89SDavid Gow # and the '--file' argument is not relevant to 'run', so isn't in 59845ba7a89SDavid Gow # add_parse_opts() 59945ba7a89SDavid Gow parse_parser = subparser.add_parser('parse', 60045ba7a89SDavid Gow help='Parses KUnit results from a file, ' 60145ba7a89SDavid Gow 'and parses formatted results.') 60245ba7a89SDavid Gow add_parse_opts(parse_parser) 60345ba7a89SDavid Gow parse_parser.add_argument('file', 60445ba7a89SDavid Gow help='Specifies the file to read results from.', 60545ba7a89SDavid Gow type=str, nargs='?', metavar='input_file') 6060476e69fSGreg Thelen 607d8c23eadSDaniel Latypov cli_args = parser.parse_args(massage_argv(argv)) 6086ebf5866SFelix Guo 6095578d008SBrendan Higgins if get_kernel_root_path(): 6105578d008SBrendan Higgins os.chdir(get_kernel_root_path()) 6115578d008SBrendan Higgins 6122dc9d6caSAlexander Pantyukhin subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None) 61382206a0cSBrendan Higgins 6142dc9d6caSAlexander Pantyukhin if subcomand_handler is None: 6156ebf5866SFelix Guo parser.print_help() 6162dc9d6caSAlexander Pantyukhin return 6172dc9d6caSAlexander Pantyukhin 6182dc9d6caSAlexander Pantyukhin subcomand_handler(cli_args) 6192dc9d6caSAlexander Pantyukhin 6206ebf5866SFelix Guo 6216ebf5866SFelix Guoif __name__ == '__main__': 622ff7b437fSBrendan Higgins main(sys.argv[1:]) 623