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