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