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 13ff9e09a3SDaniel Latypovimport sys 146ebf5866SFelix Guoimport time 156ebf5866SFelix Guo 16df4b0807SSeongJae Parkassert sys.version_info >= (3, 7), "Python version is too old" 17df4b0807SSeongJae Park 18db167981SDaniel Latypovfrom dataclasses import dataclass 196ebf5866SFelix Guofrom enum import Enum, auto 2095dcbc55SDaniel Latypovfrom typing import Iterable, List, Optional, Sequence, Tuple 216ebf5866SFelix Guo 2221a6d178SHeidi Fahimimport kunit_json 236ebf5866SFelix Guoimport kunit_kernel 246ebf5866SFelix Guoimport kunit_parser 256ebf5866SFelix Guo 266ebf5866SFelix Guoclass KunitStatus(Enum): 276ebf5866SFelix Guo SUCCESS = auto() 286ebf5866SFelix Guo CONFIG_FAILURE = auto() 296ebf5866SFelix Guo BUILD_FAILURE = auto() 306ebf5866SFelix Guo TEST_FAILURE = auto() 316ebf5866SFelix Guo 32db167981SDaniel Latypov@dataclass 33db167981SDaniel Latypovclass KunitResult: 34db167981SDaniel Latypov status: KunitStatus 35db167981SDaniel Latypov elapsed_time: float 36db167981SDaniel Latypov 37db167981SDaniel Latypov@dataclass 38db167981SDaniel Latypovclass KunitConfigRequest: 39db167981SDaniel Latypov build_dir: str 40db167981SDaniel Latypov make_options: Optional[List[str]] 41db167981SDaniel Latypov 42db167981SDaniel Latypov@dataclass 43db167981SDaniel Latypovclass KunitBuildRequest(KunitConfigRequest): 44db167981SDaniel Latypov jobs: int 45db167981SDaniel Latypov alltests: bool 46db167981SDaniel Latypov 47db167981SDaniel Latypov@dataclass 48db167981SDaniel Latypovclass KunitParseRequest: 49db167981SDaniel Latypov raw_output: Optional[str] 50db167981SDaniel Latypov json: Optional[str] 51db167981SDaniel Latypov 52db167981SDaniel Latypov@dataclass 53db167981SDaniel Latypovclass KunitExecRequest(KunitParseRequest): 54ee96d25fSDaniel Latypov build_dir: str 55db167981SDaniel Latypov timeout: int 56db167981SDaniel Latypov alltests: bool 57db167981SDaniel Latypov filter_glob: str 58db167981SDaniel Latypov kernel_args: Optional[List[str]] 59db167981SDaniel Latypov run_isolated: Optional[str] 60db167981SDaniel Latypov 61db167981SDaniel Latypov@dataclass 62db167981SDaniel Latypovclass KunitRequest(KunitExecRequest, KunitBuildRequest): 63db167981SDaniel Latypov pass 64db167981SDaniel Latypov 65db167981SDaniel Latypov 6609641f7cSDaniel Latypovdef get_kernel_root_path() -> str: 6709641f7cSDaniel Latypov path = sys.argv[0] if not __file__ else __file__ 6809641f7cSDaniel Latypov parts = os.path.realpath(path).split('tools/testing/kunit') 69be886ba9SHeidi Fahim if len(parts) != 2: 70be886ba9SHeidi Fahim sys.exit(1) 71be886ba9SHeidi Fahim return parts[0] 72be886ba9SHeidi Fahim 7345ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree, 7445ba7a89SDavid Gow request: KunitConfigRequest) -> KunitResult: 7545ba7a89SDavid Gow kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...') 7645ba7a89SDavid Gow 776ebf5866SFelix Guo config_start = time.time() 780476e69fSGreg Thelen success = linux.build_reconfig(request.build_dir, request.make_options) 796ebf5866SFelix Guo config_end = time.time() 806ebf5866SFelix Guo if not success: 8145ba7a89SDavid Gow return KunitResult(KunitStatus.CONFIG_FAILURE, 8245ba7a89SDavid Gow config_end - config_start) 8345ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 8445ba7a89SDavid Gow config_end - config_start) 856ebf5866SFelix Guo 8645ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree, 8745ba7a89SDavid Gow request: KunitBuildRequest) -> KunitResult: 886ebf5866SFelix Guo kunit_parser.print_with_timestamp('Building KUnit Kernel ...') 896ebf5866SFelix Guo 906ebf5866SFelix Guo build_start = time.time() 9187c9c163SBrendan Higgins success = linux.build_kernel(request.alltests, 92021ed9f5SHeidi Fahim request.jobs, 930476e69fSGreg Thelen request.build_dir, 940476e69fSGreg Thelen request.make_options) 956ebf5866SFelix Guo build_end = time.time() 966ebf5866SFelix Guo if not success: 97ee61492aSDavid Gow return KunitResult(KunitStatus.BUILD_FAILURE, 98ee61492aSDavid Gow build_end - build_start) 9945ba7a89SDavid Gow if not success: 10045ba7a89SDavid Gow return KunitResult(KunitStatus.BUILD_FAILURE, 10145ba7a89SDavid Gow build_end - build_start) 10245ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 10345ba7a89SDavid Gow build_end - build_start) 1046ebf5866SFelix Guo 1051ee2ba89SDaniel Latypovdef config_and_build_tests(linux: kunit_kernel.LinuxSourceTree, 1061ee2ba89SDaniel Latypov request: KunitBuildRequest) -> KunitResult: 1071ee2ba89SDaniel Latypov config_result = config_tests(linux, request) 1081ee2ba89SDaniel Latypov if config_result.status != KunitStatus.SUCCESS: 1091ee2ba89SDaniel Latypov return config_result 1101ee2ba89SDaniel Latypov 1111ee2ba89SDaniel Latypov return build_tests(linux, request) 1121ee2ba89SDaniel Latypov 113ff9e09a3SDaniel Latypovdef _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]: 114ff9e09a3SDaniel Latypov args = ['kunit.action=list'] 115ff9e09a3SDaniel Latypov if request.kernel_args: 116ff9e09a3SDaniel Latypov args.extend(request.kernel_args) 117ff9e09a3SDaniel Latypov 118ff9e09a3SDaniel Latypov output = linux.run_kernel(args=args, 119ff9e09a3SDaniel Latypov timeout=None if request.alltests else request.timeout, 120ff9e09a3SDaniel Latypov filter_glob=request.filter_glob, 121ff9e09a3SDaniel Latypov build_dir=request.build_dir) 122ff9e09a3SDaniel Latypov lines = kunit_parser.extract_tap_lines(output) 123ff9e09a3SDaniel Latypov # Hack! Drop the dummy TAP version header that the executor prints out. 124ff9e09a3SDaniel Latypov lines.pop() 125ff9e09a3SDaniel Latypov 126ff9e09a3SDaniel Latypov # Filter out any extraneous non-test output that might have gotten mixed in. 127*0453f984SDaniel Latypov return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)] 128ff9e09a3SDaniel Latypov 129ff9e09a3SDaniel Latypovdef _suites_from_test_list(tests: List[str]) -> List[str]: 130ff9e09a3SDaniel Latypov """Extracts all the suites from an ordered list of tests.""" 131ff9e09a3SDaniel Latypov suites = [] # type: List[str] 132ff9e09a3SDaniel Latypov for t in tests: 133ff9e09a3SDaniel Latypov parts = t.split('.', maxsplit=2) 134ff9e09a3SDaniel Latypov if len(parts) != 2: 135ff9e09a3SDaniel Latypov raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"') 136ff9e09a3SDaniel Latypov suite, case = parts 137ff9e09a3SDaniel Latypov if not suites or suites[-1] != suite: 138ff9e09a3SDaniel Latypov suites.append(suite) 139ff9e09a3SDaniel Latypov return suites 140ff9e09a3SDaniel Latypov 141ff9e09a3SDaniel Latypov 142ff9e09a3SDaniel Latypov 143db167981SDaniel Latypovdef exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult: 144ff9e09a3SDaniel Latypov filter_globs = [request.filter_glob] 145ff9e09a3SDaniel Latypov if request.run_isolated: 146ff9e09a3SDaniel Latypov tests = _list_tests(linux, request) 147ff9e09a3SDaniel Latypov if request.run_isolated == 'test': 148ff9e09a3SDaniel Latypov filter_globs = tests 149ff9e09a3SDaniel Latypov if request.run_isolated == 'suite': 150ff9e09a3SDaniel Latypov filter_globs = _suites_from_test_list(tests) 151ff9e09a3SDaniel Latypov # Apply the test-part of the user's glob, if present. 152ff9e09a3SDaniel Latypov if '.' in request.filter_glob: 153ff9e09a3SDaniel Latypov test_glob = request.filter_glob.split('.', maxsplit=2)[1] 154ff9e09a3SDaniel Latypov filter_globs = [g + '.'+ test_glob for g in filter_globs] 155ff9e09a3SDaniel Latypov 156885210d3SDaniel Latypov metadata = kunit_json.Metadata(arch=linux.arch(), build_dir=request.build_dir, def_config='kunit_defconfig') 157ee96d25fSDaniel Latypov 158d65d07cbSRae Moar test_counts = kunit_parser.TestCounts() 159ff9e09a3SDaniel Latypov exec_time = 0.0 160ff9e09a3SDaniel Latypov for i, filter_glob in enumerate(filter_globs): 161ff9e09a3SDaniel Latypov kunit_parser.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs))) 162ff9e09a3SDaniel Latypov 1636ebf5866SFelix Guo test_start = time.time() 1647ef925eaSDaniel Latypov run_result = linux.run_kernel( 1656cb51a18SDaniel Latypov args=request.kernel_args, 166021ed9f5SHeidi Fahim timeout=None if request.alltests else request.timeout, 167ff9e09a3SDaniel Latypov filter_glob=filter_glob, 1686ec1b81dSSeongJae Park build_dir=request.build_dir) 16945ba7a89SDavid Gow 170ee96d25fSDaniel Latypov _, test_result = parse_tests(request, metadata, run_result) 1715f6aa6d8SDaniel Latypov # run_kernel() doesn't block on the kernel exiting. 1725f6aa6d8SDaniel Latypov # That only happens after we get the last line of output from `run_result`. 1735f6aa6d8SDaniel Latypov # So exec_time here actually contains parsing + execution time, which is fine. 1746ebf5866SFelix Guo test_end = time.time() 175ff9e09a3SDaniel Latypov exec_time += test_end - test_start 176ff9e09a3SDaniel Latypov 17795dcbc55SDaniel Latypov test_counts.add_subtest_counts(test_result.counts) 1786ebf5866SFelix Guo 1797fa7ffcfSDaniel Latypov if len(filter_globs) == 1 and test_counts.crashed > 0: 1807fa7ffcfSDaniel Latypov bd = request.build_dir 1817fa7ffcfSDaniel Latypov print('The kernel seems to have crashed; you can decode the stack traces with:') 1827fa7ffcfSDaniel Latypov print('$ scripts/decode_stacktrace.sh {}/vmlinux {} < {} | tee {}/decoded.log | {} parse'.format( 1837fa7ffcfSDaniel Latypov bd, bd, kunit_kernel.get_outfile_path(bd), bd, sys.argv[0])) 1847fa7ffcfSDaniel Latypov 185d65d07cbSRae Moar kunit_status = _map_to_overall_status(test_counts.get_status()) 18695dcbc55SDaniel Latypov return KunitResult(status=kunit_status, elapsed_time=exec_time) 187d65d07cbSRae Moar 188d65d07cbSRae Moardef _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus: 189d65d07cbSRae Moar if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED): 190d65d07cbSRae Moar return KunitStatus.SUCCESS 191d65d07cbSRae Moar return KunitStatus.TEST_FAILURE 1927ef925eaSDaniel Latypov 193ee96d25fSDaniel Latypovdef parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]: 19445ba7a89SDavid Gow parse_start = time.time() 19545ba7a89SDavid Gow 196e0cc8c05SDaniel Latypov test_result = kunit_parser.Test() 19721a6d178SHeidi Fahim 19845ba7a89SDavid Gow if request.raw_output: 199d65d07cbSRae Moar # Treat unparsed results as one passing test. 200e0cc8c05SDaniel Latypov test_result.status = kunit_parser.TestStatus.SUCCESS 201e0cc8c05SDaniel Latypov test_result.counts.passed = 1 202d65d07cbSRae Moar 2037ef925eaSDaniel Latypov output: Iterable[str] = input_data 2046a499c9cSDaniel Latypov if request.raw_output == 'all': 2056a499c9cSDaniel Latypov pass 2066a499c9cSDaniel Latypov elif request.raw_output == 'kunit': 2076a499c9cSDaniel Latypov output = kunit_parser.extract_tap_lines(output) 2086a499c9cSDaniel Latypov for line in output: 2096a499c9cSDaniel Latypov print(line.rstrip()) 2106a499c9cSDaniel Latypov 21145ba7a89SDavid Gow else: 2127ef925eaSDaniel Latypov test_result = kunit_parser.parse_run_tests(input_data) 21345ba7a89SDavid Gow parse_end = time.time() 21445ba7a89SDavid Gow 21521a6d178SHeidi Fahim if request.json: 21600f75043SDaniel Latypov json_str = kunit_json.get_json_result( 217e0cc8c05SDaniel Latypov test=test_result, 218ee96d25fSDaniel Latypov metadata=metadata) 21921a6d178SHeidi Fahim if request.json == 'stdout': 22000f75043SDaniel Latypov print(json_str) 22100f75043SDaniel Latypov else: 22200f75043SDaniel Latypov with open(request.json, 'w') as f: 22300f75043SDaniel Latypov f.write(json_str) 22400f75043SDaniel Latypov kunit_parser.print_with_timestamp("Test results stored in %s" % 22500f75043SDaniel Latypov os.path.abspath(request.json)) 22621a6d178SHeidi Fahim 22745ba7a89SDavid Gow if test_result.status != kunit_parser.TestStatus.SUCCESS: 22895dcbc55SDaniel Latypov return KunitResult(KunitStatus.TEST_FAILURE, parse_end - parse_start), test_result 22945ba7a89SDavid Gow 23095dcbc55SDaniel Latypov return KunitResult(KunitStatus.SUCCESS, parse_end - parse_start), test_result 23145ba7a89SDavid Gow 23245ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree, 23345ba7a89SDavid Gow request: KunitRequest) -> KunitResult: 23445ba7a89SDavid Gow run_start = time.time() 23545ba7a89SDavid Gow 236db167981SDaniel Latypov config_result = config_tests(linux, request) 23745ba7a89SDavid Gow if config_result.status != KunitStatus.SUCCESS: 23845ba7a89SDavid Gow return config_result 23945ba7a89SDavid Gow 240db167981SDaniel Latypov build_result = build_tests(linux, request) 24145ba7a89SDavid Gow if build_result.status != KunitStatus.SUCCESS: 24245ba7a89SDavid Gow return build_result 24345ba7a89SDavid Gow 244db167981SDaniel Latypov exec_result = exec_tests(linux, request) 24545ba7a89SDavid Gow 24645ba7a89SDavid Gow run_end = time.time() 24745ba7a89SDavid Gow 2486ebf5866SFelix Guo kunit_parser.print_with_timestamp(( 2496ebf5866SFelix Guo 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + 2506ebf5866SFelix Guo 'building, %.3fs running\n') % ( 25145ba7a89SDavid Gow run_end - run_start, 25245ba7a89SDavid Gow config_result.elapsed_time, 25345ba7a89SDavid Gow build_result.elapsed_time, 25445ba7a89SDavid Gow exec_result.elapsed_time)) 2557ef925eaSDaniel Latypov return exec_result 2566ebf5866SFelix Guo 257d8c23eadSDaniel Latypov# Problem: 258d8c23eadSDaniel Latypov# $ kunit.py run --json 259d8c23eadSDaniel Latypov# works as one would expect and prints the parsed test results as JSON. 260d8c23eadSDaniel Latypov# $ kunit.py run --json suite_name 261d8c23eadSDaniel Latypov# would *not* pass suite_name as the filter_glob and print as json. 262d8c23eadSDaniel Latypov# argparse will consider it to be another way of writing 263d8c23eadSDaniel Latypov# $ kunit.py run --json=suite_name 264d8c23eadSDaniel Latypov# i.e. it would run all tests, and dump the json to a `suite_name` file. 265d8c23eadSDaniel Latypov# So we hackily automatically rewrite --json => --json=stdout 266d8c23eadSDaniel Latypovpseudo_bool_flag_defaults = { 267d8c23eadSDaniel Latypov '--json': 'stdout', 268d8c23eadSDaniel Latypov '--raw_output': 'kunit', 269d8c23eadSDaniel Latypov} 270d8c23eadSDaniel Latypovdef massage_argv(argv: Sequence[str]) -> Sequence[str]: 271d8c23eadSDaniel Latypov def massage_arg(arg: str) -> str: 272d8c23eadSDaniel Latypov if arg not in pseudo_bool_flag_defaults: 273d8c23eadSDaniel Latypov return arg 274d8c23eadSDaniel Latypov return f'{arg}={pseudo_bool_flag_defaults[arg]}' 275d8c23eadSDaniel Latypov return list(map(massage_arg, argv)) 276d8c23eadSDaniel Latypov 277ad659ccbSDavid Gowdef get_default_jobs() -> int: 278ad659ccbSDavid Gow return len(os.sched_getaffinity(0)) 279ad659ccbSDavid Gow 28009641f7cSDaniel Latypovdef add_common_opts(parser) -> None: 28145ba7a89SDavid Gow parser.add_argument('--build_dir', 28245ba7a89SDavid Gow help='As in the make command, it specifies the build ' 28345ba7a89SDavid Gow 'directory.', 284baa33315SDaniel Latypov type=str, default='.kunit', metavar='DIR') 28545ba7a89SDavid Gow parser.add_argument('--make_options', 28645ba7a89SDavid Gow help='X=Y make option, can be repeated.', 287baa33315SDaniel Latypov action='append', metavar='X=Y') 28845ba7a89SDavid Gow parser.add_argument('--alltests', 28945ba7a89SDavid Gow help='Run all KUnit tests through allyesconfig', 2906ebf5866SFelix Guo action='store_true') 291243180f5SDaniel Latypov parser.add_argument('--kunitconfig', 2929854781dSDaniel Latypov help='Path to Kconfig fragment that enables KUnit tests.' 2939854781dSDaniel Latypov ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" ' 2949854781dSDaniel Latypov 'will get automatically appended.', 295baa33315SDaniel Latypov metavar='PATH') 2969f57cc76SDaniel Latypov parser.add_argument('--kconfig_add', 2979f57cc76SDaniel Latypov help='Additional Kconfig options to append to the ' 2989f57cc76SDaniel Latypov '.kunitconfig, e.g. CONFIG_KASAN=y. Can be repeated.', 299baa33315SDaniel Latypov action='append', metavar='CONFIG_X=Y') 3006ebf5866SFelix Guo 30187c9c163SBrendan Higgins parser.add_argument('--arch', 30287c9c163SBrendan Higgins help=('Specifies the architecture to run tests under. ' 30387c9c163SBrendan Higgins 'The architecture specified here must match the ' 30487c9c163SBrendan Higgins 'string passed to the ARCH make param, ' 30587c9c163SBrendan Higgins 'e.g. i386, x86_64, arm, um, etc. Non-UML ' 30687c9c163SBrendan Higgins 'architectures run on QEMU.'), 307baa33315SDaniel Latypov type=str, default='um', metavar='ARCH') 30887c9c163SBrendan Higgins 30987c9c163SBrendan Higgins parser.add_argument('--cross_compile', 31087c9c163SBrendan Higgins help=('Sets make\'s CROSS_COMPILE variable; it should ' 31187c9c163SBrendan Higgins 'be set to a toolchain path prefix (the prefix ' 31287c9c163SBrendan Higgins 'of gcc and other tools in your toolchain, for ' 31387c9c163SBrendan Higgins 'example `sparc64-linux-gnu-` if you have the ' 31487c9c163SBrendan Higgins 'sparc toolchain installed on your system, or ' 31587c9c163SBrendan Higgins '`$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux-` ' 31687c9c163SBrendan Higgins 'if you have downloaded the microblaze toolchain ' 31787c9c163SBrendan Higgins 'from the 0-day website to a directory in your ' 31887c9c163SBrendan Higgins 'home directory called `toolchains`).'), 319baa33315SDaniel Latypov metavar='PREFIX') 32087c9c163SBrendan Higgins 32187c9c163SBrendan Higgins parser.add_argument('--qemu_config', 32287c9c163SBrendan Higgins help=('Takes a path to a path to a file containing ' 32387c9c163SBrendan Higgins 'a QemuArchParams object.'), 324baa33315SDaniel Latypov type=str, metavar='FILE') 32587c9c163SBrendan Higgins 32609641f7cSDaniel Latypovdef add_build_opts(parser) -> None: 32745ba7a89SDavid Gow parser.add_argument('--jobs', 32845ba7a89SDavid Gow help='As in the make command, "Specifies the number of ' 32945ba7a89SDavid Gow 'jobs (commands) to run simultaneously."', 330baa33315SDaniel Latypov type=int, default=get_default_jobs(), metavar='N') 33145ba7a89SDavid Gow 33209641f7cSDaniel Latypovdef add_exec_opts(parser) -> None: 33345ba7a89SDavid Gow parser.add_argument('--timeout', 3346ebf5866SFelix Guo help='maximum number of seconds to allow for all tests ' 3356ebf5866SFelix Guo 'to run. This does not include time taken to build the ' 3366ebf5866SFelix Guo 'tests.', 3376ebf5866SFelix Guo type=int, 3386ebf5866SFelix Guo default=300, 339baa33315SDaniel Latypov metavar='SECONDS') 340d992880bSDaniel Latypov parser.add_argument('filter_glob', 341a127b154SDaniel Latypov help='Filter which KUnit test suites/tests run at ' 342a127b154SDaniel Latypov 'boot-time, e.g. list* or list*.*del_test', 343d992880bSDaniel Latypov type=str, 344d992880bSDaniel Latypov nargs='?', 345d992880bSDaniel Latypov default='', 346d992880bSDaniel Latypov metavar='filter_glob') 3476cb51a18SDaniel Latypov parser.add_argument('--kernel_args', 3486cb51a18SDaniel Latypov help='Kernel command-line parameters. Maybe be repeated', 349baa33315SDaniel Latypov action='append', metavar='') 350ff9e09a3SDaniel Latypov parser.add_argument('--run_isolated', help='If set, boot the kernel for each ' 351ff9e09a3SDaniel Latypov 'individual suite/test. This is can be useful for debugging ' 352ff9e09a3SDaniel Latypov 'a non-hermetic test, one that might pass/fail based on ' 353ff9e09a3SDaniel Latypov 'what ran before it.', 354ff9e09a3SDaniel Latypov type=str, 355*0453f984SDaniel Latypov choices=['suite', 'test']) 3566ebf5866SFelix Guo 35709641f7cSDaniel Latypovdef add_parse_opts(parser) -> None: 3586a499c9cSDaniel Latypov parser.add_argument('--raw_output', help='If set don\'t format output from kernel. ' 3596a499c9cSDaniel Latypov 'If set to --raw_output=kunit, filters to just KUnit output.', 360baa33315SDaniel Latypov type=str, nargs='?', const='all', default=None, choices=['all', 'kunit']) 36121a6d178SHeidi Fahim parser.add_argument('--json', 36221a6d178SHeidi Fahim nargs='?', 36321a6d178SHeidi Fahim help='Stores test results in a JSON, and either ' 36421a6d178SHeidi Fahim 'prints to stdout or saves to file if a ' 36521a6d178SHeidi Fahim 'filename is specified', 366baa33315SDaniel Latypov type=str, const='stdout', default=None, metavar='FILE') 367021ed9f5SHeidi Fahim 36845ba7a89SDavid Gowdef main(argv, linux=None): 36945ba7a89SDavid Gow parser = argparse.ArgumentParser( 37045ba7a89SDavid Gow description='Helps writing and running KUnit tests.') 37145ba7a89SDavid Gow subparser = parser.add_subparsers(dest='subcommand') 37245ba7a89SDavid Gow 37345ba7a89SDavid Gow # The 'run' command will config, build, exec, and parse in one go. 37445ba7a89SDavid Gow run_parser = subparser.add_parser('run', help='Runs KUnit tests.') 37545ba7a89SDavid Gow add_common_opts(run_parser) 37645ba7a89SDavid Gow add_build_opts(run_parser) 37745ba7a89SDavid Gow add_exec_opts(run_parser) 37845ba7a89SDavid Gow add_parse_opts(run_parser) 37945ba7a89SDavid Gow 38045ba7a89SDavid Gow config_parser = subparser.add_parser('config', 38145ba7a89SDavid Gow help='Ensures that .config contains all of ' 38245ba7a89SDavid Gow 'the options in .kunitconfig') 38345ba7a89SDavid Gow add_common_opts(config_parser) 38445ba7a89SDavid Gow 38545ba7a89SDavid Gow build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') 38645ba7a89SDavid Gow add_common_opts(build_parser) 38745ba7a89SDavid Gow add_build_opts(build_parser) 38845ba7a89SDavid Gow 38945ba7a89SDavid Gow exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') 39045ba7a89SDavid Gow add_common_opts(exec_parser) 39145ba7a89SDavid Gow add_exec_opts(exec_parser) 39245ba7a89SDavid Gow add_parse_opts(exec_parser) 39345ba7a89SDavid Gow 39445ba7a89SDavid Gow # The 'parse' option is special, as it doesn't need the kernel source 39545ba7a89SDavid Gow # (therefore there is no need for a build_dir, hence no add_common_opts) 39645ba7a89SDavid Gow # and the '--file' argument is not relevant to 'run', so isn't in 39745ba7a89SDavid Gow # add_parse_opts() 39845ba7a89SDavid Gow parse_parser = subparser.add_parser('parse', 39945ba7a89SDavid Gow help='Parses KUnit results from a file, ' 40045ba7a89SDavid Gow 'and parses formatted results.') 40145ba7a89SDavid Gow add_parse_opts(parse_parser) 40245ba7a89SDavid Gow parse_parser.add_argument('file', 40345ba7a89SDavid Gow help='Specifies the file to read results from.', 40445ba7a89SDavid Gow type=str, nargs='?', metavar='input_file') 4050476e69fSGreg Thelen 406d8c23eadSDaniel Latypov cli_args = parser.parse_args(massage_argv(argv)) 4076ebf5866SFelix Guo 4085578d008SBrendan Higgins if get_kernel_root_path(): 4095578d008SBrendan Higgins os.chdir(get_kernel_root_path()) 4105578d008SBrendan Higgins 4116ebf5866SFelix Guo if cli_args.subcommand == 'run': 412e3212513SSeongJae Park if not os.path.exists(cli_args.build_dir): 413e3212513SSeongJae Park os.mkdir(cli_args.build_dir) 41482206a0cSBrendan Higgins 415ff7b437fSBrendan Higgins if not linux: 41687c9c163SBrendan Higgins linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, 41787c9c163SBrendan Higgins kunitconfig_path=cli_args.kunitconfig, 4189f57cc76SDaniel Latypov kconfig_add=cli_args.kconfig_add, 41987c9c163SBrendan Higgins arch=cli_args.arch, 42087c9c163SBrendan Higgins cross_compile=cli_args.cross_compile, 42187c9c163SBrendan Higgins qemu_config_path=cli_args.qemu_config) 422fcdb0bc0SAndy Shevchenko 423db167981SDaniel Latypov request = KunitRequest(build_dir=cli_args.build_dir, 424db167981SDaniel Latypov make_options=cli_args.make_options, 425db167981SDaniel Latypov jobs=cli_args.jobs, 426db167981SDaniel Latypov alltests=cli_args.alltests, 427db167981SDaniel Latypov raw_output=cli_args.raw_output, 428db167981SDaniel Latypov json=cli_args.json, 429db167981SDaniel Latypov timeout=cli_args.timeout, 430db167981SDaniel Latypov filter_glob=cli_args.filter_glob, 431db167981SDaniel Latypov kernel_args=cli_args.kernel_args, 432db167981SDaniel Latypov run_isolated=cli_args.run_isolated) 4336ebf5866SFelix Guo result = run_tests(linux, request) 4346ebf5866SFelix Guo if result.status != KunitStatus.SUCCESS: 4356ebf5866SFelix Guo sys.exit(1) 43645ba7a89SDavid Gow elif cli_args.subcommand == 'config': 43782206a0cSBrendan Higgins if cli_args.build_dir and ( 43882206a0cSBrendan Higgins not os.path.exists(cli_args.build_dir)): 43945ba7a89SDavid Gow os.mkdir(cli_args.build_dir) 44082206a0cSBrendan Higgins 44145ba7a89SDavid Gow if not linux: 44287c9c163SBrendan Higgins linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, 44387c9c163SBrendan Higgins kunitconfig_path=cli_args.kunitconfig, 4449f57cc76SDaniel Latypov kconfig_add=cli_args.kconfig_add, 44587c9c163SBrendan Higgins arch=cli_args.arch, 44687c9c163SBrendan Higgins cross_compile=cli_args.cross_compile, 44787c9c163SBrendan Higgins qemu_config_path=cli_args.qemu_config) 448fcdb0bc0SAndy Shevchenko 449db167981SDaniel Latypov request = KunitConfigRequest(build_dir=cli_args.build_dir, 450db167981SDaniel Latypov make_options=cli_args.make_options) 45145ba7a89SDavid Gow result = config_tests(linux, request) 45245ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 45345ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 45445ba7a89SDavid Gow result.elapsed_time)) 45545ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 45645ba7a89SDavid Gow sys.exit(1) 45745ba7a89SDavid Gow elif cli_args.subcommand == 'build': 45845ba7a89SDavid Gow if not linux: 45987c9c163SBrendan Higgins linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, 46087c9c163SBrendan Higgins kunitconfig_path=cli_args.kunitconfig, 4619f57cc76SDaniel Latypov kconfig_add=cli_args.kconfig_add, 46287c9c163SBrendan Higgins arch=cli_args.arch, 46387c9c163SBrendan Higgins cross_compile=cli_args.cross_compile, 46487c9c163SBrendan Higgins qemu_config_path=cli_args.qemu_config) 465fcdb0bc0SAndy Shevchenko 466db167981SDaniel Latypov request = KunitBuildRequest(build_dir=cli_args.build_dir, 467db167981SDaniel Latypov make_options=cli_args.make_options, 468db167981SDaniel Latypov jobs=cli_args.jobs, 469db167981SDaniel Latypov alltests=cli_args.alltests) 4701ee2ba89SDaniel Latypov result = config_and_build_tests(linux, request) 47145ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 47245ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 47345ba7a89SDavid Gow result.elapsed_time)) 47445ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 47545ba7a89SDavid Gow sys.exit(1) 47645ba7a89SDavid Gow elif cli_args.subcommand == 'exec': 47745ba7a89SDavid Gow if not linux: 47887c9c163SBrendan Higgins linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, 47987c9c163SBrendan Higgins kunitconfig_path=cli_args.kunitconfig, 4809f57cc76SDaniel Latypov kconfig_add=cli_args.kconfig_add, 48187c9c163SBrendan Higgins arch=cli_args.arch, 48287c9c163SBrendan Higgins cross_compile=cli_args.cross_compile, 48387c9c163SBrendan Higgins qemu_config_path=cli_args.qemu_config) 484fcdb0bc0SAndy Shevchenko 485db167981SDaniel Latypov exec_request = KunitExecRequest(raw_output=cli_args.raw_output, 486db167981SDaniel Latypov build_dir=cli_args.build_dir, 487db167981SDaniel Latypov json=cli_args.json, 488db167981SDaniel Latypov timeout=cli_args.timeout, 489db167981SDaniel Latypov alltests=cli_args.alltests, 490db167981SDaniel Latypov filter_glob=cli_args.filter_glob, 491db167981SDaniel Latypov kernel_args=cli_args.kernel_args, 492db167981SDaniel Latypov run_isolated=cli_args.run_isolated) 493db167981SDaniel Latypov result = exec_tests(linux, exec_request) 49445ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 4957ef925eaSDaniel Latypov 'Elapsed time: %.3fs\n') % (result.elapsed_time)) 49645ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 49745ba7a89SDavid Gow sys.exit(1) 49845ba7a89SDavid Gow elif cli_args.subcommand == 'parse': 499*0453f984SDaniel Latypov if cli_args.file is None: 5002ab5d5e6SDaniel Latypov sys.stdin.reconfigure(errors='backslashreplace') # pytype: disable=attribute-error 50145ba7a89SDavid Gow kunit_output = sys.stdin 50245ba7a89SDavid Gow else: 5032ab5d5e6SDaniel Latypov with open(cli_args.file, 'r', errors='backslashreplace') as f: 50445ba7a89SDavid Gow kunit_output = f.read().splitlines() 505ee96d25fSDaniel Latypov # We know nothing about how the result was created! 506ee96d25fSDaniel Latypov metadata = kunit_json.Metadata() 507db167981SDaniel Latypov request = KunitParseRequest(raw_output=cli_args.raw_output, 508db167981SDaniel Latypov json=cli_args.json) 509ee96d25fSDaniel Latypov result, _ = parse_tests(request, metadata, kunit_output) 51045ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 51145ba7a89SDavid Gow sys.exit(1) 5126ebf5866SFelix Guo else: 5136ebf5866SFelix Guo parser.print_help() 5146ebf5866SFelix Guo 5156ebf5866SFelix Guoif __name__ == '__main__': 516ff7b437fSBrendan Higgins main(sys.argv[1:]) 517