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