162306a36Sopenharmony_ci#!/usr/bin/env python3 262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 362306a36Sopenharmony_ci# 462306a36Sopenharmony_ci# A thin wrapper on top of the KUnit Kernel 562306a36Sopenharmony_ci# 662306a36Sopenharmony_ci# Copyright (C) 2019, Google LLC. 762306a36Sopenharmony_ci# Author: Felix Guo <felixguoxiuping@gmail.com> 862306a36Sopenharmony_ci# Author: Brendan Higgins <brendanhiggins@google.com> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ciimport argparse 1162306a36Sopenharmony_ciimport os 1262306a36Sopenharmony_ciimport re 1362306a36Sopenharmony_ciimport shlex 1462306a36Sopenharmony_ciimport sys 1562306a36Sopenharmony_ciimport time 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ciassert sys.version_info >= (3, 7), "Python version is too old" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cifrom dataclasses import dataclass 2062306a36Sopenharmony_cifrom enum import Enum, auto 2162306a36Sopenharmony_cifrom typing import Iterable, List, Optional, Sequence, Tuple 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ciimport kunit_json 2462306a36Sopenharmony_ciimport kunit_kernel 2562306a36Sopenharmony_ciimport kunit_parser 2662306a36Sopenharmony_cifrom kunit_printer import stdout 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ciclass KunitStatus(Enum): 2962306a36Sopenharmony_ci SUCCESS = auto() 3062306a36Sopenharmony_ci CONFIG_FAILURE = auto() 3162306a36Sopenharmony_ci BUILD_FAILURE = auto() 3262306a36Sopenharmony_ci TEST_FAILURE = auto() 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci@dataclass 3562306a36Sopenharmony_ciclass KunitResult: 3662306a36Sopenharmony_ci status: KunitStatus 3762306a36Sopenharmony_ci elapsed_time: float 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci@dataclass 4062306a36Sopenharmony_ciclass KunitConfigRequest: 4162306a36Sopenharmony_ci build_dir: str 4262306a36Sopenharmony_ci make_options: Optional[List[str]] 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci@dataclass 4562306a36Sopenharmony_ciclass KunitBuildRequest(KunitConfigRequest): 4662306a36Sopenharmony_ci jobs: int 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci@dataclass 4962306a36Sopenharmony_ciclass KunitParseRequest: 5062306a36Sopenharmony_ci raw_output: Optional[str] 5162306a36Sopenharmony_ci json: Optional[str] 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci@dataclass 5462306a36Sopenharmony_ciclass KunitExecRequest(KunitParseRequest): 5562306a36Sopenharmony_ci build_dir: str 5662306a36Sopenharmony_ci timeout: int 5762306a36Sopenharmony_ci filter_glob: str 5862306a36Sopenharmony_ci filter: str 5962306a36Sopenharmony_ci filter_action: Optional[str] 6062306a36Sopenharmony_ci kernel_args: Optional[List[str]] 6162306a36Sopenharmony_ci run_isolated: Optional[str] 6262306a36Sopenharmony_ci list_tests: bool 6362306a36Sopenharmony_ci list_tests_attr: bool 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci@dataclass 6662306a36Sopenharmony_ciclass KunitRequest(KunitExecRequest, KunitBuildRequest): 6762306a36Sopenharmony_ci pass 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cidef get_kernel_root_path() -> str: 7162306a36Sopenharmony_ci path = sys.argv[0] if not __file__ else __file__ 7262306a36Sopenharmony_ci parts = os.path.realpath(path).split('tools/testing/kunit') 7362306a36Sopenharmony_ci if len(parts) != 2: 7462306a36Sopenharmony_ci sys.exit(1) 7562306a36Sopenharmony_ci return parts[0] 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cidef config_tests(linux: kunit_kernel.LinuxSourceTree, 7862306a36Sopenharmony_ci request: KunitConfigRequest) -> KunitResult: 7962306a36Sopenharmony_ci stdout.print_with_timestamp('Configuring KUnit Kernel ...') 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci config_start = time.time() 8262306a36Sopenharmony_ci success = linux.build_reconfig(request.build_dir, request.make_options) 8362306a36Sopenharmony_ci config_end = time.time() 8462306a36Sopenharmony_ci status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE 8562306a36Sopenharmony_ci return KunitResult(status, config_end - config_start) 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cidef build_tests(linux: kunit_kernel.LinuxSourceTree, 8862306a36Sopenharmony_ci request: KunitBuildRequest) -> KunitResult: 8962306a36Sopenharmony_ci stdout.print_with_timestamp('Building KUnit Kernel ...') 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci build_start = time.time() 9262306a36Sopenharmony_ci success = linux.build_kernel(request.jobs, 9362306a36Sopenharmony_ci request.build_dir, 9462306a36Sopenharmony_ci request.make_options) 9562306a36Sopenharmony_ci build_end = time.time() 9662306a36Sopenharmony_ci status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE 9762306a36Sopenharmony_ci return KunitResult(status, build_end - build_start) 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cidef config_and_build_tests(linux: kunit_kernel.LinuxSourceTree, 10062306a36Sopenharmony_ci request: KunitBuildRequest) -> KunitResult: 10162306a36Sopenharmony_ci config_result = config_tests(linux, request) 10262306a36Sopenharmony_ci if config_result.status != KunitStatus.SUCCESS: 10362306a36Sopenharmony_ci return config_result 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci return build_tests(linux, request) 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cidef _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]: 10862306a36Sopenharmony_ci args = ['kunit.action=list'] 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if request.kernel_args: 11162306a36Sopenharmony_ci args.extend(request.kernel_args) 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci output = linux.run_kernel(args=args, 11462306a36Sopenharmony_ci timeout=request.timeout, 11562306a36Sopenharmony_ci filter_glob=request.filter_glob, 11662306a36Sopenharmony_ci filter=request.filter, 11762306a36Sopenharmony_ci filter_action=request.filter_action, 11862306a36Sopenharmony_ci build_dir=request.build_dir) 11962306a36Sopenharmony_ci lines = kunit_parser.extract_tap_lines(output) 12062306a36Sopenharmony_ci # Hack! Drop the dummy TAP version header that the executor prints out. 12162306a36Sopenharmony_ci lines.pop() 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci # Filter out any extraneous non-test output that might have gotten mixed in. 12462306a36Sopenharmony_ci return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)] 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cidef _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]: 12762306a36Sopenharmony_ci args = ['kunit.action=list_attr'] 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci if request.kernel_args: 13062306a36Sopenharmony_ci args.extend(request.kernel_args) 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci output = linux.run_kernel(args=args, 13362306a36Sopenharmony_ci timeout=request.timeout, 13462306a36Sopenharmony_ci filter_glob=request.filter_glob, 13562306a36Sopenharmony_ci filter=request.filter, 13662306a36Sopenharmony_ci filter_action=request.filter_action, 13762306a36Sopenharmony_ci build_dir=request.build_dir) 13862306a36Sopenharmony_ci lines = kunit_parser.extract_tap_lines(output) 13962306a36Sopenharmony_ci # Hack! Drop the dummy TAP version header that the executor prints out. 14062306a36Sopenharmony_ci lines.pop() 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci # Filter out any extraneous non-test output that might have gotten mixed in. 14362306a36Sopenharmony_ci return lines 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cidef _suites_from_test_list(tests: List[str]) -> List[str]: 14662306a36Sopenharmony_ci """Extracts all the suites from an ordered list of tests.""" 14762306a36Sopenharmony_ci suites = [] # type: List[str] 14862306a36Sopenharmony_ci for t in tests: 14962306a36Sopenharmony_ci parts = t.split('.', maxsplit=2) 15062306a36Sopenharmony_ci if len(parts) != 2: 15162306a36Sopenharmony_ci raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"') 15262306a36Sopenharmony_ci suite, _ = parts 15362306a36Sopenharmony_ci if not suites or suites[-1] != suite: 15462306a36Sopenharmony_ci suites.append(suite) 15562306a36Sopenharmony_ci return suites 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cidef exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult: 15862306a36Sopenharmony_ci filter_globs = [request.filter_glob] 15962306a36Sopenharmony_ci if request.list_tests: 16062306a36Sopenharmony_ci output = _list_tests(linux, request) 16162306a36Sopenharmony_ci for line in output: 16262306a36Sopenharmony_ci print(line.rstrip()) 16362306a36Sopenharmony_ci return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0) 16462306a36Sopenharmony_ci if request.list_tests_attr: 16562306a36Sopenharmony_ci attr_output = _list_tests_attr(linux, request) 16662306a36Sopenharmony_ci for line in attr_output: 16762306a36Sopenharmony_ci print(line.rstrip()) 16862306a36Sopenharmony_ci return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0) 16962306a36Sopenharmony_ci if request.run_isolated: 17062306a36Sopenharmony_ci tests = _list_tests(linux, request) 17162306a36Sopenharmony_ci if request.run_isolated == 'test': 17262306a36Sopenharmony_ci filter_globs = tests 17362306a36Sopenharmony_ci elif request.run_isolated == 'suite': 17462306a36Sopenharmony_ci filter_globs = _suites_from_test_list(tests) 17562306a36Sopenharmony_ci # Apply the test-part of the user's glob, if present. 17662306a36Sopenharmony_ci if '.' in request.filter_glob: 17762306a36Sopenharmony_ci test_glob = request.filter_glob.split('.', maxsplit=2)[1] 17862306a36Sopenharmony_ci filter_globs = [g + '.'+ test_glob for g in filter_globs] 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci metadata = kunit_json.Metadata(arch=linux.arch(), build_dir=request.build_dir, def_config='kunit_defconfig') 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci test_counts = kunit_parser.TestCounts() 18362306a36Sopenharmony_ci exec_time = 0.0 18462306a36Sopenharmony_ci for i, filter_glob in enumerate(filter_globs): 18562306a36Sopenharmony_ci stdout.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs))) 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci test_start = time.time() 18862306a36Sopenharmony_ci run_result = linux.run_kernel( 18962306a36Sopenharmony_ci args=request.kernel_args, 19062306a36Sopenharmony_ci timeout=request.timeout, 19162306a36Sopenharmony_ci filter_glob=filter_glob, 19262306a36Sopenharmony_ci filter=request.filter, 19362306a36Sopenharmony_ci filter_action=request.filter_action, 19462306a36Sopenharmony_ci build_dir=request.build_dir) 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci _, test_result = parse_tests(request, metadata, run_result) 19762306a36Sopenharmony_ci # run_kernel() doesn't block on the kernel exiting. 19862306a36Sopenharmony_ci # That only happens after we get the last line of output from `run_result`. 19962306a36Sopenharmony_ci # So exec_time here actually contains parsing + execution time, which is fine. 20062306a36Sopenharmony_ci test_end = time.time() 20162306a36Sopenharmony_ci exec_time += test_end - test_start 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci test_counts.add_subtest_counts(test_result.counts) 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci if len(filter_globs) == 1 and test_counts.crashed > 0: 20662306a36Sopenharmony_ci bd = request.build_dir 20762306a36Sopenharmony_ci print('The kernel seems to have crashed; you can decode the stack traces with:') 20862306a36Sopenharmony_ci print('$ scripts/decode_stacktrace.sh {}/vmlinux {} < {} | tee {}/decoded.log | {} parse'.format( 20962306a36Sopenharmony_ci bd, bd, kunit_kernel.get_outfile_path(bd), bd, sys.argv[0])) 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci kunit_status = _map_to_overall_status(test_counts.get_status()) 21262306a36Sopenharmony_ci return KunitResult(status=kunit_status, elapsed_time=exec_time) 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cidef _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus: 21562306a36Sopenharmony_ci if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED): 21662306a36Sopenharmony_ci return KunitStatus.SUCCESS 21762306a36Sopenharmony_ci return KunitStatus.TEST_FAILURE 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cidef parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]: 22062306a36Sopenharmony_ci parse_start = time.time() 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if request.raw_output: 22362306a36Sopenharmony_ci # Treat unparsed results as one passing test. 22462306a36Sopenharmony_ci fake_test = kunit_parser.Test() 22562306a36Sopenharmony_ci fake_test.status = kunit_parser.TestStatus.SUCCESS 22662306a36Sopenharmony_ci fake_test.counts.passed = 1 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci output: Iterable[str] = input_data 22962306a36Sopenharmony_ci if request.raw_output == 'all': 23062306a36Sopenharmony_ci pass 23162306a36Sopenharmony_ci elif request.raw_output == 'kunit': 23262306a36Sopenharmony_ci output = kunit_parser.extract_tap_lines(output) 23362306a36Sopenharmony_ci for line in output: 23462306a36Sopenharmony_ci print(line.rstrip()) 23562306a36Sopenharmony_ci parse_time = time.time() - parse_start 23662306a36Sopenharmony_ci return KunitResult(KunitStatus.SUCCESS, parse_time), fake_test 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci # Actually parse the test results. 24062306a36Sopenharmony_ci test = kunit_parser.parse_run_tests(input_data) 24162306a36Sopenharmony_ci parse_time = time.time() - parse_start 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if request.json: 24462306a36Sopenharmony_ci json_str = kunit_json.get_json_result( 24562306a36Sopenharmony_ci test=test, 24662306a36Sopenharmony_ci metadata=metadata) 24762306a36Sopenharmony_ci if request.json == 'stdout': 24862306a36Sopenharmony_ci print(json_str) 24962306a36Sopenharmony_ci else: 25062306a36Sopenharmony_ci with open(request.json, 'w') as f: 25162306a36Sopenharmony_ci f.write(json_str) 25262306a36Sopenharmony_ci stdout.print_with_timestamp("Test results stored in %s" % 25362306a36Sopenharmony_ci os.path.abspath(request.json)) 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if test.status != kunit_parser.TestStatus.SUCCESS: 25662306a36Sopenharmony_ci return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci return KunitResult(KunitStatus.SUCCESS, parse_time), test 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cidef run_tests(linux: kunit_kernel.LinuxSourceTree, 26162306a36Sopenharmony_ci request: KunitRequest) -> KunitResult: 26262306a36Sopenharmony_ci run_start = time.time() 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci config_result = config_tests(linux, request) 26562306a36Sopenharmony_ci if config_result.status != KunitStatus.SUCCESS: 26662306a36Sopenharmony_ci return config_result 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci build_result = build_tests(linux, request) 26962306a36Sopenharmony_ci if build_result.status != KunitStatus.SUCCESS: 27062306a36Sopenharmony_ci return build_result 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci exec_result = exec_tests(linux, request) 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci run_end = time.time() 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci stdout.print_with_timestamp(( 27762306a36Sopenharmony_ci 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + 27862306a36Sopenharmony_ci 'building, %.3fs running\n') % ( 27962306a36Sopenharmony_ci run_end - run_start, 28062306a36Sopenharmony_ci config_result.elapsed_time, 28162306a36Sopenharmony_ci build_result.elapsed_time, 28262306a36Sopenharmony_ci exec_result.elapsed_time)) 28362306a36Sopenharmony_ci return exec_result 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci# Problem: 28662306a36Sopenharmony_ci# $ kunit.py run --json 28762306a36Sopenharmony_ci# works as one would expect and prints the parsed test results as JSON. 28862306a36Sopenharmony_ci# $ kunit.py run --json suite_name 28962306a36Sopenharmony_ci# would *not* pass suite_name as the filter_glob and print as json. 29062306a36Sopenharmony_ci# argparse will consider it to be another way of writing 29162306a36Sopenharmony_ci# $ kunit.py run --json=suite_name 29262306a36Sopenharmony_ci# i.e. it would run all tests, and dump the json to a `suite_name` file. 29362306a36Sopenharmony_ci# So we hackily automatically rewrite --json => --json=stdout 29462306a36Sopenharmony_cipseudo_bool_flag_defaults = { 29562306a36Sopenharmony_ci '--json': 'stdout', 29662306a36Sopenharmony_ci '--raw_output': 'kunit', 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_cidef massage_argv(argv: Sequence[str]) -> Sequence[str]: 29962306a36Sopenharmony_ci def massage_arg(arg: str) -> str: 30062306a36Sopenharmony_ci if arg not in pseudo_bool_flag_defaults: 30162306a36Sopenharmony_ci return arg 30262306a36Sopenharmony_ci return f'{arg}={pseudo_bool_flag_defaults[arg]}' 30362306a36Sopenharmony_ci return list(map(massage_arg, argv)) 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cidef get_default_jobs() -> int: 30662306a36Sopenharmony_ci return len(os.sched_getaffinity(0)) 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cidef add_common_opts(parser: argparse.ArgumentParser) -> None: 30962306a36Sopenharmony_ci parser.add_argument('--build_dir', 31062306a36Sopenharmony_ci help='As in the make command, it specifies the build ' 31162306a36Sopenharmony_ci 'directory.', 31262306a36Sopenharmony_ci type=str, default='.kunit', metavar='DIR') 31362306a36Sopenharmony_ci parser.add_argument('--make_options', 31462306a36Sopenharmony_ci help='X=Y make option, can be repeated.', 31562306a36Sopenharmony_ci action='append', metavar='X=Y') 31662306a36Sopenharmony_ci parser.add_argument('--alltests', 31762306a36Sopenharmony_ci help='Run all KUnit tests via tools/testing/kunit/configs/all_tests.config', 31862306a36Sopenharmony_ci action='store_true') 31962306a36Sopenharmony_ci parser.add_argument('--kunitconfig', 32062306a36Sopenharmony_ci help='Path to Kconfig fragment that enables KUnit tests.' 32162306a36Sopenharmony_ci ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" ' 32262306a36Sopenharmony_ci 'will get automatically appended. If repeated, the files ' 32362306a36Sopenharmony_ci 'blindly concatenated, which might not work in all cases.', 32462306a36Sopenharmony_ci action='append', metavar='PATHS') 32562306a36Sopenharmony_ci parser.add_argument('--kconfig_add', 32662306a36Sopenharmony_ci help='Additional Kconfig options to append to the ' 32762306a36Sopenharmony_ci '.kunitconfig, e.g. CONFIG_KASAN=y. Can be repeated.', 32862306a36Sopenharmony_ci action='append', metavar='CONFIG_X=Y') 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci parser.add_argument('--arch', 33162306a36Sopenharmony_ci help=('Specifies the architecture to run tests under. ' 33262306a36Sopenharmony_ci 'The architecture specified here must match the ' 33362306a36Sopenharmony_ci 'string passed to the ARCH make param, ' 33462306a36Sopenharmony_ci 'e.g. i386, x86_64, arm, um, etc. Non-UML ' 33562306a36Sopenharmony_ci 'architectures run on QEMU.'), 33662306a36Sopenharmony_ci type=str, default='um', metavar='ARCH') 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci parser.add_argument('--cross_compile', 33962306a36Sopenharmony_ci help=('Sets make\'s CROSS_COMPILE variable; it should ' 34062306a36Sopenharmony_ci 'be set to a toolchain path prefix (the prefix ' 34162306a36Sopenharmony_ci 'of gcc and other tools in your toolchain, for ' 34262306a36Sopenharmony_ci 'example `sparc64-linux-gnu-` if you have the ' 34362306a36Sopenharmony_ci 'sparc toolchain installed on your system, or ' 34462306a36Sopenharmony_ci '`$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux-` ' 34562306a36Sopenharmony_ci 'if you have downloaded the microblaze toolchain ' 34662306a36Sopenharmony_ci 'from the 0-day website to a directory in your ' 34762306a36Sopenharmony_ci 'home directory called `toolchains`).'), 34862306a36Sopenharmony_ci metavar='PREFIX') 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci parser.add_argument('--qemu_config', 35162306a36Sopenharmony_ci help=('Takes a path to a path to a file containing ' 35262306a36Sopenharmony_ci 'a QemuArchParams object.'), 35362306a36Sopenharmony_ci type=str, metavar='FILE') 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci parser.add_argument('--qemu_args', 35662306a36Sopenharmony_ci help='Additional QEMU arguments, e.g. "-smp 8"', 35762306a36Sopenharmony_ci action='append', metavar='') 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_cidef add_build_opts(parser: argparse.ArgumentParser) -> None: 36062306a36Sopenharmony_ci parser.add_argument('--jobs', 36162306a36Sopenharmony_ci help='As in the make command, "Specifies the number of ' 36262306a36Sopenharmony_ci 'jobs (commands) to run simultaneously."', 36362306a36Sopenharmony_ci type=int, default=get_default_jobs(), metavar='N') 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cidef add_exec_opts(parser: argparse.ArgumentParser) -> None: 36662306a36Sopenharmony_ci parser.add_argument('--timeout', 36762306a36Sopenharmony_ci help='maximum number of seconds to allow for all tests ' 36862306a36Sopenharmony_ci 'to run. This does not include time taken to build the ' 36962306a36Sopenharmony_ci 'tests.', 37062306a36Sopenharmony_ci type=int, 37162306a36Sopenharmony_ci default=300, 37262306a36Sopenharmony_ci metavar='SECONDS') 37362306a36Sopenharmony_ci parser.add_argument('filter_glob', 37462306a36Sopenharmony_ci help='Filter which KUnit test suites/tests run at ' 37562306a36Sopenharmony_ci 'boot-time, e.g. list* or list*.*del_test', 37662306a36Sopenharmony_ci type=str, 37762306a36Sopenharmony_ci nargs='?', 37862306a36Sopenharmony_ci default='', 37962306a36Sopenharmony_ci metavar='filter_glob') 38062306a36Sopenharmony_ci parser.add_argument('--filter', 38162306a36Sopenharmony_ci help='Filter KUnit tests with attributes, ' 38262306a36Sopenharmony_ci 'e.g. module=example or speed>slow', 38362306a36Sopenharmony_ci type=str, 38462306a36Sopenharmony_ci default='') 38562306a36Sopenharmony_ci parser.add_argument('--filter_action', 38662306a36Sopenharmony_ci help='If set to skip, filtered tests will be skipped, ' 38762306a36Sopenharmony_ci 'e.g. --filter_action=skip. Otherwise they will not run.', 38862306a36Sopenharmony_ci type=str, 38962306a36Sopenharmony_ci choices=['skip']) 39062306a36Sopenharmony_ci parser.add_argument('--kernel_args', 39162306a36Sopenharmony_ci help='Kernel command-line parameters. Maybe be repeated', 39262306a36Sopenharmony_ci action='append', metavar='') 39362306a36Sopenharmony_ci parser.add_argument('--run_isolated', help='If set, boot the kernel for each ' 39462306a36Sopenharmony_ci 'individual suite/test. This is can be useful for debugging ' 39562306a36Sopenharmony_ci 'a non-hermetic test, one that might pass/fail based on ' 39662306a36Sopenharmony_ci 'what ran before it.', 39762306a36Sopenharmony_ci type=str, 39862306a36Sopenharmony_ci choices=['suite', 'test']) 39962306a36Sopenharmony_ci parser.add_argument('--list_tests', help='If set, list all tests that will be ' 40062306a36Sopenharmony_ci 'run.', 40162306a36Sopenharmony_ci action='store_true') 40262306a36Sopenharmony_ci parser.add_argument('--list_tests_attr', help='If set, list all tests and test ' 40362306a36Sopenharmony_ci 'attributes.', 40462306a36Sopenharmony_ci action='store_true') 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cidef add_parse_opts(parser: argparse.ArgumentParser) -> None: 40762306a36Sopenharmony_ci parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. ' 40862306a36Sopenharmony_ci 'By default, filters to just KUnit output. Use ' 40962306a36Sopenharmony_ci '--raw_output=all to show everything', 41062306a36Sopenharmony_ci type=str, nargs='?', const='all', default=None, choices=['all', 'kunit']) 41162306a36Sopenharmony_ci parser.add_argument('--json', 41262306a36Sopenharmony_ci nargs='?', 41362306a36Sopenharmony_ci help='Prints parsed test results as JSON to stdout or a file if ' 41462306a36Sopenharmony_ci 'a filename is specified. Does nothing if --raw_output is set.', 41562306a36Sopenharmony_ci type=str, const='stdout', default=None, metavar='FILE') 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cidef tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree: 41962306a36Sopenharmony_ci """Returns a LinuxSourceTree based on the user's arguments.""" 42062306a36Sopenharmony_ci # Allow users to specify multiple arguments in one string, e.g. '-smp 8' 42162306a36Sopenharmony_ci qemu_args: List[str] = [] 42262306a36Sopenharmony_ci if cli_args.qemu_args: 42362306a36Sopenharmony_ci for arg in cli_args.qemu_args: 42462306a36Sopenharmony_ci qemu_args.extend(shlex.split(arg)) 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci kunitconfigs = cli_args.kunitconfig if cli_args.kunitconfig else [] 42762306a36Sopenharmony_ci if cli_args.alltests: 42862306a36Sopenharmony_ci # Prepend so user-specified options take prio if we ever allow 42962306a36Sopenharmony_ci # --kunitconfig options to have differing options. 43062306a36Sopenharmony_ci kunitconfigs = [kunit_kernel.ALL_TESTS_CONFIG_PATH] + kunitconfigs 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci return kunit_kernel.LinuxSourceTree(cli_args.build_dir, 43362306a36Sopenharmony_ci kunitconfig_paths=kunitconfigs, 43462306a36Sopenharmony_ci kconfig_add=cli_args.kconfig_add, 43562306a36Sopenharmony_ci arch=cli_args.arch, 43662306a36Sopenharmony_ci cross_compile=cli_args.cross_compile, 43762306a36Sopenharmony_ci qemu_config_path=cli_args.qemu_config, 43862306a36Sopenharmony_ci extra_qemu_args=qemu_args) 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cidef run_handler(cli_args: argparse.Namespace) -> None: 44262306a36Sopenharmony_ci if not os.path.exists(cli_args.build_dir): 44362306a36Sopenharmony_ci os.mkdir(cli_args.build_dir) 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci linux = tree_from_args(cli_args) 44662306a36Sopenharmony_ci request = KunitRequest(build_dir=cli_args.build_dir, 44762306a36Sopenharmony_ci make_options=cli_args.make_options, 44862306a36Sopenharmony_ci jobs=cli_args.jobs, 44962306a36Sopenharmony_ci raw_output=cli_args.raw_output, 45062306a36Sopenharmony_ci json=cli_args.json, 45162306a36Sopenharmony_ci timeout=cli_args.timeout, 45262306a36Sopenharmony_ci filter_glob=cli_args.filter_glob, 45362306a36Sopenharmony_ci filter=cli_args.filter, 45462306a36Sopenharmony_ci filter_action=cli_args.filter_action, 45562306a36Sopenharmony_ci kernel_args=cli_args.kernel_args, 45662306a36Sopenharmony_ci run_isolated=cli_args.run_isolated, 45762306a36Sopenharmony_ci list_tests=cli_args.list_tests, 45862306a36Sopenharmony_ci list_tests_attr=cli_args.list_tests_attr) 45962306a36Sopenharmony_ci result = run_tests(linux, request) 46062306a36Sopenharmony_ci if result.status != KunitStatus.SUCCESS: 46162306a36Sopenharmony_ci sys.exit(1) 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_cidef config_handler(cli_args: argparse.Namespace) -> None: 46562306a36Sopenharmony_ci if cli_args.build_dir and ( 46662306a36Sopenharmony_ci not os.path.exists(cli_args.build_dir)): 46762306a36Sopenharmony_ci os.mkdir(cli_args.build_dir) 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci linux = tree_from_args(cli_args) 47062306a36Sopenharmony_ci request = KunitConfigRequest(build_dir=cli_args.build_dir, 47162306a36Sopenharmony_ci make_options=cli_args.make_options) 47262306a36Sopenharmony_ci result = config_tests(linux, request) 47362306a36Sopenharmony_ci stdout.print_with_timestamp(( 47462306a36Sopenharmony_ci 'Elapsed time: %.3fs\n') % ( 47562306a36Sopenharmony_ci result.elapsed_time)) 47662306a36Sopenharmony_ci if result.status != KunitStatus.SUCCESS: 47762306a36Sopenharmony_ci sys.exit(1) 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cidef build_handler(cli_args: argparse.Namespace) -> None: 48162306a36Sopenharmony_ci linux = tree_from_args(cli_args) 48262306a36Sopenharmony_ci request = KunitBuildRequest(build_dir=cli_args.build_dir, 48362306a36Sopenharmony_ci make_options=cli_args.make_options, 48462306a36Sopenharmony_ci jobs=cli_args.jobs) 48562306a36Sopenharmony_ci result = config_and_build_tests(linux, request) 48662306a36Sopenharmony_ci stdout.print_with_timestamp(( 48762306a36Sopenharmony_ci 'Elapsed time: %.3fs\n') % ( 48862306a36Sopenharmony_ci result.elapsed_time)) 48962306a36Sopenharmony_ci if result.status != KunitStatus.SUCCESS: 49062306a36Sopenharmony_ci sys.exit(1) 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_cidef exec_handler(cli_args: argparse.Namespace) -> None: 49462306a36Sopenharmony_ci linux = tree_from_args(cli_args) 49562306a36Sopenharmony_ci exec_request = KunitExecRequest(raw_output=cli_args.raw_output, 49662306a36Sopenharmony_ci build_dir=cli_args.build_dir, 49762306a36Sopenharmony_ci json=cli_args.json, 49862306a36Sopenharmony_ci timeout=cli_args.timeout, 49962306a36Sopenharmony_ci filter_glob=cli_args.filter_glob, 50062306a36Sopenharmony_ci filter=cli_args.filter, 50162306a36Sopenharmony_ci filter_action=cli_args.filter_action, 50262306a36Sopenharmony_ci kernel_args=cli_args.kernel_args, 50362306a36Sopenharmony_ci run_isolated=cli_args.run_isolated, 50462306a36Sopenharmony_ci list_tests=cli_args.list_tests, 50562306a36Sopenharmony_ci list_tests_attr=cli_args.list_tests_attr) 50662306a36Sopenharmony_ci result = exec_tests(linux, exec_request) 50762306a36Sopenharmony_ci stdout.print_with_timestamp(( 50862306a36Sopenharmony_ci 'Elapsed time: %.3fs\n') % (result.elapsed_time)) 50962306a36Sopenharmony_ci if result.status != KunitStatus.SUCCESS: 51062306a36Sopenharmony_ci sys.exit(1) 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_cidef parse_handler(cli_args: argparse.Namespace) -> None: 51462306a36Sopenharmony_ci if cli_args.file is None: 51562306a36Sopenharmony_ci sys.stdin.reconfigure(errors='backslashreplace') # type: ignore 51662306a36Sopenharmony_ci kunit_output = sys.stdin # type: Iterable[str] 51762306a36Sopenharmony_ci else: 51862306a36Sopenharmony_ci with open(cli_args.file, 'r', errors='backslashreplace') as f: 51962306a36Sopenharmony_ci kunit_output = f.read().splitlines() 52062306a36Sopenharmony_ci # We know nothing about how the result was created! 52162306a36Sopenharmony_ci metadata = kunit_json.Metadata() 52262306a36Sopenharmony_ci request = KunitParseRequest(raw_output=cli_args.raw_output, 52362306a36Sopenharmony_ci json=cli_args.json) 52462306a36Sopenharmony_ci result, _ = parse_tests(request, metadata, kunit_output) 52562306a36Sopenharmony_ci if result.status != KunitStatus.SUCCESS: 52662306a36Sopenharmony_ci sys.exit(1) 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_cisubcommand_handlers_map = { 53062306a36Sopenharmony_ci 'run': run_handler, 53162306a36Sopenharmony_ci 'config': config_handler, 53262306a36Sopenharmony_ci 'build': build_handler, 53362306a36Sopenharmony_ci 'exec': exec_handler, 53462306a36Sopenharmony_ci 'parse': parse_handler 53562306a36Sopenharmony_ci} 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cidef main(argv: Sequence[str]) -> None: 53962306a36Sopenharmony_ci parser = argparse.ArgumentParser( 54062306a36Sopenharmony_ci description='Helps writing and running KUnit tests.') 54162306a36Sopenharmony_ci subparser = parser.add_subparsers(dest='subcommand') 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci # The 'run' command will config, build, exec, and parse in one go. 54462306a36Sopenharmony_ci run_parser = subparser.add_parser('run', help='Runs KUnit tests.') 54562306a36Sopenharmony_ci add_common_opts(run_parser) 54662306a36Sopenharmony_ci add_build_opts(run_parser) 54762306a36Sopenharmony_ci add_exec_opts(run_parser) 54862306a36Sopenharmony_ci add_parse_opts(run_parser) 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci config_parser = subparser.add_parser('config', 55162306a36Sopenharmony_ci help='Ensures that .config contains all of ' 55262306a36Sopenharmony_ci 'the options in .kunitconfig') 55362306a36Sopenharmony_ci add_common_opts(config_parser) 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') 55662306a36Sopenharmony_ci add_common_opts(build_parser) 55762306a36Sopenharmony_ci add_build_opts(build_parser) 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') 56062306a36Sopenharmony_ci add_common_opts(exec_parser) 56162306a36Sopenharmony_ci add_exec_opts(exec_parser) 56262306a36Sopenharmony_ci add_parse_opts(exec_parser) 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci # The 'parse' option is special, as it doesn't need the kernel source 56562306a36Sopenharmony_ci # (therefore there is no need for a build_dir, hence no add_common_opts) 56662306a36Sopenharmony_ci # and the '--file' argument is not relevant to 'run', so isn't in 56762306a36Sopenharmony_ci # add_parse_opts() 56862306a36Sopenharmony_ci parse_parser = subparser.add_parser('parse', 56962306a36Sopenharmony_ci help='Parses KUnit results from a file, ' 57062306a36Sopenharmony_ci 'and parses formatted results.') 57162306a36Sopenharmony_ci add_parse_opts(parse_parser) 57262306a36Sopenharmony_ci parse_parser.add_argument('file', 57362306a36Sopenharmony_ci help='Specifies the file to read results from.', 57462306a36Sopenharmony_ci type=str, nargs='?', metavar='input_file') 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci cli_args = parser.parse_args(massage_argv(argv)) 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci if get_kernel_root_path(): 57962306a36Sopenharmony_ci os.chdir(get_kernel_root_path()) 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None) 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if subcomand_handler is None: 58462306a36Sopenharmony_ci parser.print_help() 58562306a36Sopenharmony_ci return 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci subcomand_handler(cli_args) 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ciif __name__ == '__main__': 59162306a36Sopenharmony_ci main(sys.argv[1:]) 592