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