| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 |
|
| 10 | import argparse
|
| 11 | import os
|
| 12 | import re
|
| 13 | import shlex
|
| 14 | import sys
|
| 15 | import time
|
| 16 |
|
| 17 | assert sys.version_info >= (3, 7), "Python version is too old"
|
| 18 |
|
| 19 | from dataclasses import dataclass
|
| 20 | from enum import Enum, auto
|
| 21 | from typing import Iterable, List, Optional, Sequence, Tuple
|
| 22 |
|
| 23 | import kunit_json
|
| 24 | import kunit_junit
|
| 25 | import kunit_kernel
|
| 26 | import kunit_parser
|
| 27 | from kunit_printer import stdout, null_printer
|
| 28 |
|
| 29 | class KunitStatus(Enum):
|
| 30 | SUCCESS = auto()
|
| 31 | CONFIG_FAILURE = auto()
|
| 32 | BUILD_FAILURE = auto()
|
| 33 | TEST_FAILURE = auto()
|
| 34 |
|
| 35 | @dataclass
|
| 36 | class KunitResult:
|
| 37 | status: KunitStatus
|
| 38 | elapsed_time: float
|
| 39 |
|
| 40 | @dataclass
|
| 41 | class KunitConfigRequest:
|
| 42 | build_dir: str
|
| 43 | make_options: Optional[List[str]]
|
| 44 |
|
| 45 | @dataclass
|
| 46 | class KunitBuildRequest(KunitConfigRequest):
|
| 47 | jobs: int
|
| 48 |
|
| 49 | @dataclass
|
| 50 | class KunitParseRequest:
|
| 51 | raw_output: Optional[str]
|
| 52 | json: Optional[str]
|
| 53 | junit: Optional[str]
|
| 54 | summary: bool
|
| 55 | failed: bool
|
| 56 |
|
| 57 | @dataclass
|
| 58 | class KunitExecRequest(KunitParseRequest):
|
| 59 | build_dir: str
|
| 60 | timeout: int
|
| 61 | filter_glob: str
|
| 62 | filter: str
|
| 63 | filter_action: Optional[str]
|
| 64 | kernel_args: Optional[List[str]]
|
| 65 | run_isolated: Optional[str]
|
| 66 | list_tests: bool
|
| 67 | list_tests_attr: bool
|
| 68 | list_suites: bool
|
| 69 |
|
| 70 | @dataclass
|
| 71 | class KunitRequest(KunitExecRequest, KunitBuildRequest):
|
| 72 | pass
|
| 73 |
|
| 74 |
|
| 75 | def get_kernel_root_path() -> str:
|
| 76 | path = sys.argv[0] if not __file__ else __file__
|
| 77 | parts = os.path.realpath(path).split('tools/testing/kunit')
|
| 78 | if len(parts) != 2:
|
| 79 | sys.exit(1)
|
| 80 | return parts[0]
|
| 81 |
|
| 82 | def config_tests(linux: kunit_kernel.LinuxSourceTree,
|
| 83 | request: KunitConfigRequest) -> KunitResult:
|
| 84 | stdout.print_with_timestamp('Configuring KUnit Kernel ...')
|
| 85 |
|
| 86 | config_start = time.time()
|
| 87 | success = linux.build_reconfig(request.build_dir, request.make_options)
|
| 88 | config_end = time.time()
|
| 89 | status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE
|
| 90 | return KunitResult(status, config_end - config_start)
|
| 91 |
|
| 92 | def build_tests(linux: kunit_kernel.LinuxSourceTree,
|
| 93 | request: KunitBuildRequest) -> KunitResult:
|
| 94 | stdout.print_with_timestamp('Building KUnit Kernel ...')
|
| 95 |
|
| 96 | build_start = time.time()
|
| 97 | success = linux.build_kernel(request.jobs,
|
| 98 | request.build_dir,
|
| 99 | request.make_options)
|
| 100 | build_end = time.time()
|
| 101 | status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE
|
| 102 | return KunitResult(status, build_end - build_start)
|
| 103 |
|
| 104 | def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
|
| 105 | request: KunitBuildRequest) -> KunitResult:
|
| 106 | config_result = config_tests(linux, request)
|
| 107 | if config_result.status != KunitStatus.SUCCESS:
|
| 108 | return config_result
|
| 109 |
|
| 110 | return build_tests(linux, request)
|
| 111 |
|
| 112 | def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
|
| 113 | args = ['kunit.action=list']
|
| 114 |
|
| 115 | if request.kernel_args:
|
| 116 | args.extend(request.kernel_args)
|
| 117 |
|
| 118 | output = linux.run_kernel(args=args,
|
| 119 | timeout=request.timeout,
|
| 120 | filter_glob=request.filter_glob,
|
| 121 | filter=request.filter,
|
| 122 | filter_action=request.filter_action,
|
| 123 | build_dir=request.build_dir)
|
| 124 | lines = kunit_parser.extract_tap_lines(output)
|
| 125 | |
| 126 | lines.pop()
|
| 127 |
|
| 128 | |
| 129 | return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
|
| 130 |
|
| 131 | def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]:
|
| 132 | args = ['kunit.action=list_attr']
|
| 133 |
|
| 134 | if request.kernel_args:
|
| 135 | args.extend(request.kernel_args)
|
| 136 |
|
| 137 | output = linux.run_kernel(args=args,
|
| 138 | timeout=request.timeout,
|
| 139 | filter_glob=request.filter_glob,
|
| 140 | filter=request.filter,
|
| 141 | filter_action=request.filter_action,
|
| 142 | build_dir=request.build_dir)
|
| 143 | lines = kunit_parser.extract_tap_lines(output)
|
| 144 | |
| 145 | lines.pop()
|
| 146 |
|
| 147 | |
| 148 | return lines
|
| 149 |
|
| 150 | def _suites_from_test_list(tests: List[str]) -> List[str]:
|
| 151 |
|
| 152 | suites = [] |
| 153 | for t in tests:
|
| 154 | parts = t.split('.', maxsplit=2)
|
| 155 | if len(parts) != 2:
|
| 156 | raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"')
|
| 157 | suite, _ = parts
|
| 158 | if not suites or suites[-1] != suite:
|
| 159 | suites.append(suite)
|
| 160 | return suites
|
| 161 |
|
| 162 | def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult:
|
| 163 | filter_globs = [request.filter_glob]
|
| 164 | if request.list_tests:
|
| 165 | output = _list_tests(linux, request)
|
| 166 | for line in output:
|
| 167 | print(line.rstrip())
|
| 168 | return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
|
| 169 | if request.list_tests_attr:
|
| 170 | attr_output = _list_tests_attr(linux, request)
|
| 171 | for line in attr_output:
|
| 172 | print(line.rstrip())
|
| 173 | return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
|
| 174 | if request.list_suites:
|
| 175 | tests = _list_tests(linux, request)
|
| 176 | output = _suites_from_test_list(tests)
|
| 177 | for line in output:
|
| 178 | print(line.rstrip())
|
| 179 | return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
|
| 180 | if request.run_isolated:
|
| 181 | tests = _list_tests(linux, request)
|
| 182 | if request.run_isolated == 'test':
|
| 183 | filter_globs = tests
|
| 184 | elif request.run_isolated == 'suite':
|
| 185 | filter_globs = _suites_from_test_list(tests)
|
| 186 | |
| 187 | if '.' in request.filter_glob:
|
| 188 | test_glob = request.filter_glob.split('.', maxsplit=2)[1]
|
| 189 | filter_globs = [g + '.'+ test_glob for g in filter_globs]
|
| 190 |
|
| 191 | metadata = kunit_json.Metadata(arch=linux.arch(), build_dir=request.build_dir, def_config='kunit_defconfig')
|
| 192 |
|
| 193 | test_counts = kunit_parser.TestCounts()
|
| 194 | exec_time = 0.0
|
| 195 | for i, filter_glob in enumerate(filter_globs):
|
| 196 | stdout.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs)))
|
| 197 |
|
| 198 | test_start = time.time()
|
| 199 | run_result = linux.run_kernel(
|
| 200 | args=request.kernel_args,
|
| 201 | timeout=request.timeout,
|
| 202 | filter_glob=filter_glob,
|
| 203 | filter=request.filter,
|
| 204 | filter_action=request.filter_action,
|
| 205 | build_dir=request.build_dir)
|
| 206 |
|
| 207 | _, test_result = parse_tests(request, metadata, run_result)
|
| 208 | |
| 209 | |
| 210 | |
| 211 | test_end = time.time()
|
| 212 | exec_time += test_end - test_start
|
| 213 |
|
| 214 | test_counts.add_subtest_counts(test_result.counts)
|
| 215 |
|
| 216 | if len(filter_globs) == 1 and test_counts.crashed > 0:
|
| 217 | bd = request.build_dir
|
| 218 | print('The kernel seems to have crashed; you can decode the stack traces with:')
|
| 219 | print('$ scripts/decode_stacktrace.sh {}/vmlinux {} < {} | tee {}/decoded.log | {} parse'.format(
|
| 220 | bd, bd, kunit_kernel.get_outfile_path(bd), bd, sys.argv[0]))
|
| 221 |
|
| 222 | kunit_status = _map_to_overall_status(test_counts.get_status())
|
| 223 | return KunitResult(status=kunit_status, elapsed_time=exec_time)
|
| 224 |
|
| 225 | def _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus:
|
| 226 | if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED):
|
| 227 | return KunitStatus.SUCCESS
|
| 228 | return KunitStatus.TEST_FAILURE
|
| 229 |
|
| 230 | def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]:
|
| 231 | parse_start = time.time()
|
| 232 |
|
| 233 | if request.raw_output:
|
| 234 | |
| 235 | fake_test = kunit_parser.Test()
|
| 236 | fake_test.status = kunit_parser.TestStatus.SUCCESS
|
| 237 | fake_test.counts.passed = 1
|
| 238 |
|
| 239 | output: Iterable[str] = input_data
|
| 240 | if request.raw_output == 'all' or request.raw_output == 'full':
|
| 241 | pass
|
| 242 | elif request.raw_output == 'kunit':
|
| 243 | output = kunit_parser.extract_tap_lines(output)
|
| 244 | for line in output:
|
| 245 | print(line.rstrip())
|
| 246 | parse_time = time.time() - parse_start
|
| 247 | return KunitResult(KunitStatus.SUCCESS, parse_time), fake_test
|
| 248 |
|
| 249 | default_printer = stdout
|
| 250 | if request.summary or request.failed:
|
| 251 | default_printer = null_printer
|
| 252 |
|
| 253 | |
| 254 | test = kunit_parser.parse_run_tests(input_data, default_printer)
|
| 255 | parse_time = time.time() - parse_start
|
| 256 |
|
| 257 | if request.failed:
|
| 258 | kunit_parser.print_test(test, request.failed, stdout)
|
| 259 | kunit_parser.print_summary_line(test, stdout)
|
| 260 |
|
| 261 | if request.json:
|
| 262 | json_str = kunit_json.get_json_result(
|
| 263 | test=test,
|
| 264 | metadata=metadata)
|
| 265 | if request.json == 'stdout':
|
| 266 | print(json_str)
|
| 267 | else:
|
| 268 | with open(request.json, 'w') as f:
|
| 269 | f.write(json_str)
|
| 270 | stdout.print_with_timestamp("Test results stored in %s" %
|
| 271 | os.path.abspath(request.json))
|
| 272 |
|
| 273 | if request.junit:
|
| 274 | if request.junit == 'stdout':
|
| 275 | kunit_junit.print_junit_result(test=test)
|
| 276 | else:
|
| 277 | kunit_junit.write_junit_result(test=test,filename=request.junit)
|
| 278 | stdout.print_with_timestamp(f"Test results stored in {os.path.abspath(request.junit)}")
|
| 279 |
|
| 280 | if test.status != kunit_parser.TestStatus.SUCCESS:
|
| 281 | return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
|
| 282 |
|
| 283 | return KunitResult(KunitStatus.SUCCESS, parse_time), test
|
| 284 |
|
| 285 | def run_tests(linux: kunit_kernel.LinuxSourceTree,
|
| 286 | request: KunitRequest) -> KunitResult:
|
| 287 | run_start = time.time()
|
| 288 |
|
| 289 | config_result = config_tests(linux, request)
|
| 290 | if config_result.status != KunitStatus.SUCCESS:
|
| 291 | return config_result
|
| 292 |
|
| 293 | build_result = build_tests(linux, request)
|
| 294 | if build_result.status != KunitStatus.SUCCESS:
|
| 295 | return build_result
|
| 296 |
|
| 297 | exec_result = exec_tests(linux, request)
|
| 298 |
|
| 299 | run_end = time.time()
|
| 300 |
|
| 301 | stdout.print_with_timestamp((
|
| 302 | 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' +
|
| 303 | 'building, %.3fs running\n') % (
|
| 304 | run_end - run_start,
|
| 305 | config_result.elapsed_time,
|
| 306 | build_result.elapsed_time,
|
| 307 | exec_result.elapsed_time))
|
| 308 | return exec_result
|
| 309 |
|
| 310 | |
| 311 | |
| 312 | |
| 313 | |
| 314 | |
| 315 | |
| 316 | |
| 317 | |
| 318 | |
| 319 | pseudo_bool_flag_defaults = {
|
| 320 | '--json': 'stdout',
|
| 321 | '--junit': 'stdout',
|
| 322 | '--raw_output': 'kunit',
|
| 323 | }
|
| 324 | def massage_argv(argv: Sequence[str]) -> Sequence[str]:
|
| 325 | def massage_arg(arg: str) -> str:
|
| 326 | if arg not in pseudo_bool_flag_defaults:
|
| 327 | return arg
|
| 328 | return f'{arg}={pseudo_bool_flag_defaults[arg]}'
|
| 329 | return list(map(massage_arg, argv))
|
| 330 |
|
| 331 | def get_default_jobs() -> int:
|
| 332 | if sys.version_info >= (3, 13):
|
| 333 | if (ncpu := os.process_cpu_count()) is not None:
|
| 334 | return ncpu
|
| 335 | raise RuntimeError("os.process_cpu_count() returned None")
|
| 336 | |
| 337 | if sys.platform != "darwin":
|
| 338 | return len(os.sched_getaffinity(0))
|
| 339 | if (ncpu := os.cpu_count()) is not None:
|
| 340 | return ncpu
|
| 341 | raise RuntimeError("os.cpu_count() returned None")
|
| 342 |
|
| 343 | def get_default_build_dir() -> str:
|
| 344 | if 'KBUILD_OUTPUT' in os.environ:
|
| 345 | return os.path.join(os.environ['KBUILD_OUTPUT'], '.kunit')
|
| 346 | return '.kunit'
|
| 347 |
|
| 348 | def add_completion_opts(parser: argparse.ArgumentParser) -> None:
|
| 349 | parser.add_argument('--list-opts',
|
| 350 | help=argparse.SUPPRESS,
|
| 351 | action='store_true')
|
| 352 |
|
| 353 | def add_root_opts(parser: argparse.ArgumentParser) -> None:
|
| 354 | parser.add_argument('--list-cmds',
|
| 355 | help=argparse.SUPPRESS,
|
| 356 | action='store_true')
|
| 357 | add_completion_opts(parser)
|
| 358 |
|
| 359 | def add_common_opts(parser: argparse.ArgumentParser) -> None:
|
| 360 | parser.add_argument('--build_dir',
|
| 361 | help='As in the make command, it specifies the build '
|
| 362 | 'directory.',
|
| 363 | type=str, default=get_default_build_dir(), metavar='DIR')
|
| 364 | parser.add_argument('--make_options',
|
| 365 | help='X=Y make option, can be repeated.',
|
| 366 | action='append', metavar='X=Y')
|
| 367 | parser.add_argument('--alltests',
|
| 368 | help='Run all KUnit tests via tools/testing/kunit/configs/all_tests.config',
|
| 369 | action='store_true')
|
| 370 | parser.add_argument('--kunitconfig',
|
| 371 | help='Path to Kconfig fragment that enables KUnit tests.'
|
| 372 | ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" '
|
| 373 | 'will get automatically appended. If repeated, the files '
|
| 374 | 'blindly concatenated, which might not work in all cases.',
|
| 375 | action='append', metavar='PATHS')
|
| 376 | parser.add_argument('--kconfig_add',
|
| 377 | help='Additional Kconfig options to append to the '
|
| 378 | '.kunitconfig, e.g. CONFIG_KASAN=y. Can be repeated.',
|
| 379 | action='append', metavar='CONFIG_X=Y')
|
| 380 |
|
| 381 | parser.add_argument('--arch',
|
| 382 | help=('Specifies the architecture to run tests under. '
|
| 383 | 'The architecture specified here must match the '
|
| 384 | 'string passed to the ARCH make param, '
|
| 385 | 'e.g. i386, x86_64, arm, um, etc. Non-UML '
|
| 386 | 'architectures run on QEMU.'),
|
| 387 | type=str, default='um', metavar='ARCH')
|
| 388 |
|
| 389 | parser.add_argument('--cross_compile',
|
| 390 | help=('Sets make\'s CROSS_COMPILE variable; it should '
|
| 391 | 'be set to a toolchain path prefix (the prefix '
|
| 392 | 'of gcc and other tools in your toolchain, for '
|
| 393 | 'example `sparc64-linux-gnu-` if you have the '
|
| 394 | 'sparc toolchain installed on your system, or '
|
| 395 | '`$HOME/toolchains/microblaze/gcc-9.2.0-nolibc/microblaze-linux/bin/microblaze-linux-` '
|
| 396 | 'if you have downloaded the microblaze toolchain '
|
| 397 | 'from the 0-day website to a directory in your '
|
| 398 | 'home directory called `toolchains`).'),
|
| 399 | metavar='PREFIX')
|
| 400 |
|
| 401 | parser.add_argument('--qemu_config',
|
| 402 | help=('Takes a path to a path to a file containing '
|
| 403 | 'a QemuArchParams object.'),
|
| 404 | type=str, metavar='FILE')
|
| 405 |
|
| 406 | parser.add_argument('--qemu_args',
|
| 407 | help='Additional QEMU arguments, e.g. "-smp 8"',
|
| 408 | action='append', metavar='')
|
| 409 |
|
| 410 | add_completion_opts(parser)
|
| 411 |
|
| 412 | def add_build_opts(parser: argparse.ArgumentParser) -> None:
|
| 413 | parser.add_argument('--jobs',
|
| 414 | help='As in the make command, "Specifies the number of '
|
| 415 | 'jobs (commands) to run simultaneously."',
|
| 416 | type=int, default=get_default_jobs(), metavar='N')
|
| 417 |
|
| 418 | def add_exec_opts(parser: argparse.ArgumentParser) -> None:
|
| 419 | parser.add_argument('--timeout',
|
| 420 | help='maximum number of seconds to allow for all tests '
|
| 421 | 'to run. This does not include time taken to build the '
|
| 422 | 'tests.',
|
| 423 | type=int,
|
| 424 | default=300,
|
| 425 | metavar='SECONDS')
|
| 426 | parser.add_argument('filter_glob',
|
| 427 | help='Filter which KUnit test suites/tests run at '
|
| 428 | 'boot-time, e.g. list* or list*.*del_test',
|
| 429 | type=str,
|
| 430 | nargs='?',
|
| 431 | default='',
|
| 432 | metavar='filter_glob')
|
| 433 | parser.add_argument('--filter',
|
| 434 | help='Filter KUnit tests with attributes, '
|
| 435 | 'e.g. module=example or speed>slow',
|
| 436 | type=str,
|
| 437 | default='')
|
| 438 | parser.add_argument('--filter_action',
|
| 439 | help='If set to skip, filtered tests will be skipped, '
|
| 440 | 'e.g. --filter_action=skip. Otherwise they will not run.',
|
| 441 | type=str,
|
| 442 | choices=['skip'])
|
| 443 | parser.add_argument('--kernel_args',
|
| 444 | help='Kernel command-line parameters. Maybe be repeated',
|
| 445 | action='append', metavar='')
|
| 446 | parser.add_argument('--run_isolated', help='If set, boot the kernel for each '
|
| 447 | 'individual suite/test. This is can be useful for debugging '
|
| 448 | 'a non-hermetic test, one that might pass/fail based on '
|
| 449 | 'what ran before it.',
|
| 450 | type=str,
|
| 451 | choices=['suite', 'test'])
|
| 452 | parser.add_argument('--list_tests', help='If set, list all tests that will be '
|
| 453 | 'run.',
|
| 454 | action='store_true')
|
| 455 | parser.add_argument('--list_tests_attr', help='If set, list all tests and test '
|
| 456 | 'attributes.',
|
| 457 | action='store_true')
|
| 458 | parser.add_argument('--list_suites', help='If set, list all suites that will be '
|
| 459 | 'run.',
|
| 460 | action='store_true')
|
| 461 |
|
| 462 | def add_parse_opts(parser: argparse.ArgumentParser) -> None:
|
| 463 | parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
|
| 464 | 'By default, filters to just KUnit output. Use '
|
| 465 | '--raw_output=all to show everything',
|
| 466 | type=str, nargs='?', const='all', default=None, choices=['all', 'full', 'kunit'])
|
| 467 | parser.add_argument('--json',
|
| 468 | nargs='?',
|
| 469 | help='Prints parsed test results as JSON to stdout or a file if '
|
| 470 | 'a filename is specified. Does nothing if --raw_output is set.',
|
| 471 | type=str, const='stdout', default=None, metavar='FILE')
|
| 472 | parser.add_argument('--junit',
|
| 473 | nargs='?',
|
| 474 | help='Prints parsed test results as JUnit XML to stdout or a file if '
|
| 475 | 'a filename is specified. Does nothing if --raw_output is set.',
|
| 476 | type=str, const='stdout', default=None, metavar='FILE')
|
| 477 | parser.add_argument('--summary',
|
| 478 | help='Prints only the summary line for parsed test results.'
|
| 479 | 'Does nothing if --raw_output is set.',
|
| 480 | action='store_true')
|
| 481 | parser.add_argument('--failed',
|
| 482 | help='Prints only the failed parsed test results and summary line.'
|
| 483 | 'Does nothing if --raw_output is set.',
|
| 484 | action='store_true')
|
| 485 |
|
| 486 |
|
| 487 | def tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree:
|
| 488 |
|
| 489 | |
| 490 | qemu_args: List[str] = []
|
| 491 | if cli_args.qemu_args:
|
| 492 | for arg in cli_args.qemu_args:
|
| 493 | qemu_args.extend(shlex.split(arg))
|
| 494 |
|
| 495 | kunitconfigs = cli_args.kunitconfig if cli_args.kunitconfig else []
|
| 496 | if cli_args.alltests:
|
| 497 | |
| 498 | |
| 499 | kunitconfigs = [kunit_kernel.ALL_TESTS_CONFIG_PATH] + kunitconfigs
|
| 500 |
|
| 501 | return kunit_kernel.LinuxSourceTree(cli_args.build_dir,
|
| 502 | kunitconfig_paths=kunitconfigs,
|
| 503 | kconfig_add=cli_args.kconfig_add,
|
| 504 | arch=cli_args.arch,
|
| 505 | cross_compile=cli_args.cross_compile,
|
| 506 | qemu_config_path=cli_args.qemu_config,
|
| 507 | extra_qemu_args=qemu_args)
|
| 508 |
|
| 509 |
|
| 510 | def run_handler(cli_args: argparse.Namespace) -> None:
|
| 511 | if not os.path.exists(cli_args.build_dir):
|
| 512 | os.mkdir(cli_args.build_dir)
|
| 513 |
|
| 514 | linux = tree_from_args(cli_args)
|
| 515 | request = KunitRequest(build_dir=cli_args.build_dir,
|
| 516 | make_options=cli_args.make_options,
|
| 517 | jobs=cli_args.jobs,
|
| 518 | raw_output=cli_args.raw_output,
|
| 519 | json=cli_args.json,
|
| 520 | junit=cli_args.junit,
|
| 521 | summary=cli_args.summary,
|
| 522 | failed=cli_args.failed,
|
| 523 | timeout=cli_args.timeout,
|
| 524 | filter_glob=cli_args.filter_glob,
|
| 525 | filter=cli_args.filter,
|
| 526 | filter_action=cli_args.filter_action,
|
| 527 | kernel_args=cli_args.kernel_args,
|
| 528 | run_isolated=cli_args.run_isolated,
|
| 529 | list_tests=cli_args.list_tests,
|
| 530 | list_tests_attr=cli_args.list_tests_attr,
|
| 531 | list_suites=cli_args.list_suites)
|
| 532 | result = run_tests(linux, request)
|
| 533 | if result.status != KunitStatus.SUCCESS:
|
| 534 | sys.exit(1)
|
| 535 |
|
| 536 |
|
| 537 | def config_handler(cli_args: argparse.Namespace) -> None:
|
| 538 | if cli_args.build_dir and (
|
| 539 | not os.path.exists(cli_args.build_dir)):
|
| 540 | os.mkdir(cli_args.build_dir)
|
| 541 |
|
| 542 | linux = tree_from_args(cli_args)
|
| 543 | request = KunitConfigRequest(build_dir=cli_args.build_dir,
|
| 544 | make_options=cli_args.make_options)
|
| 545 | result = config_tests(linux, request)
|
| 546 | stdout.print_with_timestamp((
|
| 547 | 'Elapsed time: %.3fs\n') % (
|
| 548 | result.elapsed_time))
|
| 549 | if result.status != KunitStatus.SUCCESS:
|
| 550 | sys.exit(1)
|
| 551 |
|
| 552 |
|
| 553 | def build_handler(cli_args: argparse.Namespace) -> None:
|
| 554 | linux = tree_from_args(cli_args)
|
| 555 | request = KunitBuildRequest(build_dir=cli_args.build_dir,
|
| 556 | make_options=cli_args.make_options,
|
| 557 | jobs=cli_args.jobs)
|
| 558 | result = config_and_build_tests(linux, request)
|
| 559 | stdout.print_with_timestamp((
|
| 560 | 'Elapsed time: %.3fs\n') % (
|
| 561 | result.elapsed_time))
|
| 562 | if result.status != KunitStatus.SUCCESS:
|
| 563 | sys.exit(1)
|
| 564 |
|
| 565 |
|
| 566 | def exec_handler(cli_args: argparse.Namespace) -> None:
|
| 567 | linux = tree_from_args(cli_args)
|
| 568 | exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
|
| 569 | build_dir=cli_args.build_dir,
|
| 570 | json=cli_args.json,
|
| 571 | junit=cli_args.junit,
|
| 572 | summary=cli_args.summary,
|
| 573 | failed=cli_args.failed,
|
| 574 | timeout=cli_args.timeout,
|
| 575 | filter_glob=cli_args.filter_glob,
|
| 576 | filter=cli_args.filter,
|
| 577 | filter_action=cli_args.filter_action,
|
| 578 | kernel_args=cli_args.kernel_args,
|
| 579 | run_isolated=cli_args.run_isolated,
|
| 580 | list_tests=cli_args.list_tests,
|
| 581 | list_tests_attr=cli_args.list_tests_attr,
|
| 582 | list_suites=cli_args.list_suites)
|
| 583 | result = exec_tests(linux, exec_request)
|
| 584 | stdout.print_with_timestamp((
|
| 585 | 'Elapsed time: %.3fs\n') % (result.elapsed_time))
|
| 586 | if result.status != KunitStatus.SUCCESS:
|
| 587 | sys.exit(1)
|
| 588 |
|
| 589 |
|
| 590 | def parse_handler(cli_args: argparse.Namespace) -> None:
|
| 591 | if cli_args.file is None:
|
| 592 | sys.stdin.reconfigure(errors='backslashreplace') |
| 593 | kunit_output = sys.stdin |
| 594 | else:
|
| 595 | with open(cli_args.file, 'r', errors='backslashreplace') as f:
|
| 596 | kunit_output = f.read().splitlines()
|
| 597 | |
| 598 | metadata = kunit_json.Metadata()
|
| 599 | request = KunitParseRequest(raw_output=cli_args.raw_output,
|
| 600 | json=cli_args.json,
|
| 601 | junit=cli_args.junit,
|
| 602 | summary=cli_args.summary,
|
| 603 | failed=cli_args.failed)
|
| 604 | result, _ = parse_tests(request, metadata, kunit_output)
|
| 605 | if result.status != KunitStatus.SUCCESS:
|
| 606 | sys.exit(1)
|
| 607 |
|
| 608 |
|
| 609 | subcommand_handlers_map = {
|
| 610 | 'run': run_handler,
|
| 611 | 'config': config_handler,
|
| 612 | 'build': build_handler,
|
| 613 | 'exec': exec_handler,
|
| 614 | 'parse': parse_handler
|
| 615 | }
|
| 616 |
|
| 617 |
|
| 618 | def main(argv: Sequence[str]) -> None:
|
| 619 | parser = argparse.ArgumentParser(
|
| 620 | description='Helps writing and running KUnit tests.')
|
| 621 | add_root_opts(parser)
|
| 622 | subparser = parser.add_subparsers(dest='subcommand')
|
| 623 |
|
| 624 | |
| 625 | run_parser = subparser.add_parser('run', help='Runs KUnit tests.')
|
| 626 | add_common_opts(run_parser)
|
| 627 | add_build_opts(run_parser)
|
| 628 | add_exec_opts(run_parser)
|
| 629 | add_parse_opts(run_parser)
|
| 630 |
|
| 631 | config_parser = subparser.add_parser('config',
|
| 632 | help='Ensures that .config contains all of '
|
| 633 | 'the options in .kunitconfig')
|
| 634 | add_common_opts(config_parser)
|
| 635 |
|
| 636 | build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests')
|
| 637 | add_common_opts(build_parser)
|
| 638 | add_build_opts(build_parser)
|
| 639 |
|
| 640 | exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests')
|
| 641 | add_common_opts(exec_parser)
|
| 642 | add_exec_opts(exec_parser)
|
| 643 | add_parse_opts(exec_parser)
|
| 644 |
|
| 645 | |
| 646 | |
| 647 | |
| 648 | |
| 649 | parse_parser = subparser.add_parser('parse',
|
| 650 | help='Parses KUnit results from a file, '
|
| 651 | 'and parses formatted results.')
|
| 652 | add_parse_opts(parse_parser)
|
| 653 | parse_parser.add_argument('file',
|
| 654 | help='Specifies the file to read results from.',
|
| 655 | type=str, nargs='?', metavar='input_file')
|
| 656 | add_completion_opts(parse_parser)
|
| 657 |
|
| 658 | cli_args = parser.parse_args(massage_argv(argv))
|
| 659 |
|
| 660 | if get_kernel_root_path():
|
| 661 | os.chdir(get_kernel_root_path())
|
| 662 |
|
| 663 | if cli_args.list_cmds:
|
| 664 | print(" ".join(subparser.choices.keys()))
|
| 665 | return
|
| 666 |
|
| 667 | if cli_args.list_opts:
|
| 668 | target_parser = subparser.choices.get(cli_args.subcommand)
|
| 669 | if not target_parser:
|
| 670 | target_parser = parser
|
| 671 |
|
| 672 | |
| 673 | |
| 674 | |
| 675 | print(' '.join(target_parser._option_string_actions.keys()))
|
| 676 | return
|
| 677 |
|
| 678 | subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None)
|
| 679 |
|
| 680 | if subcomand_handler is None:
|
| 681 | parser.print_help()
|
| 682 | return
|
| 683 |
|
| 684 | subcomand_handler(cli_args)
|
| 685 |
|
| 686 |
|
| 687 | if __name__ == '__main__':
|
| 688 | main(sys.argv[1:])
|