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