]>
Commit | Line | Data |
---|---|---|
6ebf5866 FG |
1 | #!/usr/bin/python3 |
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 | |
11 | import sys | |
12 | import os | |
13 | import time | |
ff7b437f | 14 | import shutil |
6ebf5866 FG |
15 | |
16 | from collections import namedtuple | |
17 | from enum import Enum, auto | |
18 | ||
19 | import kunit_config | |
20 | import kunit_kernel | |
21 | import kunit_parser | |
22 | ||
45ba7a89 | 23 | KunitResult = namedtuple('KunitResult', ['status','result','elapsed_time']) |
6ebf5866 | 24 | |
45ba7a89 | 25 | KunitConfigRequest = namedtuple('KunitConfigRequest', |
01397e82 | 26 | ['build_dir', 'make_options']) |
45ba7a89 DG |
27 | KunitBuildRequest = namedtuple('KunitBuildRequest', |
28 | ['jobs', 'build_dir', 'alltests', | |
29 | 'make_options']) | |
30 | KunitExecRequest = namedtuple('KunitExecRequest', | |
31 | ['timeout', 'build_dir', 'alltests']) | |
32 | KunitParseRequest = namedtuple('KunitParseRequest', | |
33 | ['raw_output', 'input_data']) | |
021ed9f5 | 34 | KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', |
9bdf64b3 VMI |
35 | 'build_dir', 'alltests', |
36 | 'make_options']) | |
6ebf5866 | 37 | |
be886ba9 HF |
38 | KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] |
39 | ||
6ebf5866 FG |
40 | class KunitStatus(Enum): |
41 | SUCCESS = auto() | |
42 | CONFIG_FAILURE = auto() | |
43 | BUILD_FAILURE = auto() | |
44 | TEST_FAILURE = auto() | |
45 | ||
ff7b437f | 46 | def create_default_kunitconfig(): |
e3212513 | 47 | if not os.path.exists(kunit_kernel.kunitconfig_path): |
ff7b437f | 48 | shutil.copyfile('arch/um/configs/kunit_defconfig', |
e3212513 | 49 | kunit_kernel.kunitconfig_path) |
ff7b437f | 50 | |
be886ba9 HF |
51 | def get_kernel_root_path(): |
52 | parts = sys.argv[0] if not __file__ else __file__ | |
53 | parts = os.path.realpath(parts).split('tools/testing/kunit') | |
54 | if len(parts) != 2: | |
55 | sys.exit(1) | |
56 | return parts[0] | |
57 | ||
45ba7a89 DG |
58 | def config_tests(linux: kunit_kernel.LinuxSourceTree, |
59 | request: KunitConfigRequest) -> KunitResult: | |
60 | kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...') | |
61 | ||
6ebf5866 | 62 | config_start = time.time() |
9bdf64b3 | 63 | create_default_kunitconfig() |
0476e69f | 64 | success = linux.build_reconfig(request.build_dir, request.make_options) |
6ebf5866 FG |
65 | config_end = time.time() |
66 | if not success: | |
45ba7a89 DG |
67 | return KunitResult(KunitStatus.CONFIG_FAILURE, |
68 | 'could not configure kernel', | |
69 | config_end - config_start) | |
70 | return KunitResult(KunitStatus.SUCCESS, | |
71 | 'configured kernel successfully', | |
72 | config_end - config_start) | |
6ebf5866 | 73 | |
45ba7a89 DG |
74 | def build_tests(linux: kunit_kernel.LinuxSourceTree, |
75 | request: KunitBuildRequest) -> KunitResult: | |
6ebf5866 FG |
76 | kunit_parser.print_with_timestamp('Building KUnit Kernel ...') |
77 | ||
78 | build_start = time.time() | |
021ed9f5 HF |
79 | success = linux.build_um_kernel(request.alltests, |
80 | request.jobs, | |
0476e69f GT |
81 | request.build_dir, |
82 | request.make_options) | |
6ebf5866 FG |
83 | build_end = time.time() |
84 | if not success: | |
ee61492a DG |
85 | return KunitResult(KunitStatus.BUILD_FAILURE, |
86 | 'could not build kernel', | |
87 | build_end - build_start) | |
45ba7a89 DG |
88 | if not success: |
89 | return KunitResult(KunitStatus.BUILD_FAILURE, | |
90 | 'could not build kernel', | |
91 | build_end - build_start) | |
92 | return KunitResult(KunitStatus.SUCCESS, | |
93 | 'built kernel successfully', | |
94 | build_end - build_start) | |
6ebf5866 | 95 | |
45ba7a89 DG |
96 | def exec_tests(linux: kunit_kernel.LinuxSourceTree, |
97 | request: KunitExecRequest) -> KunitResult: | |
6ebf5866 FG |
98 | kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') |
99 | test_start = time.time() | |
45ba7a89 | 100 | result = linux.run_kernel( |
021ed9f5 HF |
101 | timeout=None if request.alltests else request.timeout, |
102 | build_dir=request.build_dir) | |
45ba7a89 DG |
103 | |
104 | test_end = time.time() | |
105 | ||
106 | return KunitResult(KunitStatus.SUCCESS, | |
107 | result, | |
108 | test_end - test_start) | |
109 | ||
110 | def parse_tests(request: KunitParseRequest) -> KunitResult: | |
111 | parse_start = time.time() | |
112 | ||
113 | test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, | |
114 | [], | |
115 | 'Tests not Parsed.') | |
6ebf5866 | 116 | if request.raw_output: |
45ba7a89 | 117 | kunit_parser.raw_output(request.input_data) |
6ebf5866 | 118 | else: |
45ba7a89 DG |
119 | test_result = kunit_parser.parse_run_tests(request.input_data) |
120 | parse_end = time.time() | |
121 | ||
122 | if test_result.status != kunit_parser.TestStatus.SUCCESS: | |
123 | return KunitResult(KunitStatus.TEST_FAILURE, test_result, | |
124 | parse_end - parse_start) | |
125 | ||
126 | return KunitResult(KunitStatus.SUCCESS, test_result, | |
127 | parse_end - parse_start) | |
128 | ||
129 | ||
130 | def run_tests(linux: kunit_kernel.LinuxSourceTree, | |
131 | request: KunitRequest) -> KunitResult: | |
132 | run_start = time.time() | |
133 | ||
134 | config_request = KunitConfigRequest(request.build_dir, | |
45ba7a89 DG |
135 | request.make_options) |
136 | config_result = config_tests(linux, config_request) | |
137 | if config_result.status != KunitStatus.SUCCESS: | |
138 | return config_result | |
139 | ||
140 | build_request = KunitBuildRequest(request.jobs, request.build_dir, | |
141 | request.alltests, | |
142 | request.make_options) | |
143 | build_result = build_tests(linux, build_request) | |
144 | if build_result.status != KunitStatus.SUCCESS: | |
145 | return build_result | |
146 | ||
147 | exec_request = KunitExecRequest(request.timeout, request.build_dir, | |
148 | request.alltests) | |
149 | exec_result = exec_tests(linux, exec_request) | |
150 | if exec_result.status != KunitStatus.SUCCESS: | |
151 | return exec_result | |
152 | ||
153 | parse_request = KunitParseRequest(request.raw_output, | |
154 | exec_result.result) | |
155 | parse_result = parse_tests(parse_request) | |
156 | ||
157 | run_end = time.time() | |
6ebf5866 FG |
158 | |
159 | kunit_parser.print_with_timestamp(( | |
160 | 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + | |
161 | 'building, %.3fs running\n') % ( | |
45ba7a89 DG |
162 | run_end - run_start, |
163 | config_result.elapsed_time, | |
164 | build_result.elapsed_time, | |
165 | exec_result.elapsed_time)) | |
166 | return parse_result | |
167 | ||
168 | def add_common_opts(parser): | |
169 | parser.add_argument('--build_dir', | |
170 | help='As in the make command, it specifies the build ' | |
171 | 'directory.', | |
ddbd60c7 | 172 | type=str, default='.kunit', metavar='build_dir') |
45ba7a89 DG |
173 | parser.add_argument('--make_options', |
174 | help='X=Y make option, can be repeated.', | |
175 | action='append') | |
176 | parser.add_argument('--alltests', | |
177 | help='Run all KUnit tests through allyesconfig', | |
178 | action='store_true') | |
179 | ||
45ba7a89 DG |
180 | def add_build_opts(parser): |
181 | parser.add_argument('--jobs', | |
182 | help='As in the make command, "Specifies the number of ' | |
183 | 'jobs (commands) to run simultaneously."', | |
184 | type=int, default=8, metavar='jobs') | |
185 | ||
186 | def add_exec_opts(parser): | |
187 | parser.add_argument('--timeout', | |
188 | help='maximum number of seconds to allow for all tests ' | |
189 | 'to run. This does not include time taken to build the ' | |
190 | 'tests.', | |
191 | type=int, | |
192 | default=300, | |
193 | metavar='timeout') | |
194 | ||
195 | def add_parse_opts(parser): | |
196 | parser.add_argument('--raw_output', help='don\'t format output from kernel', | |
197 | action='store_true') | |
6ebf5866 | 198 | |
6ebf5866 | 199 | |
ff7b437f | 200 | def main(argv, linux=None): |
6ebf5866 FG |
201 | parser = argparse.ArgumentParser( |
202 | description='Helps writing and running KUnit tests.') | |
203 | subparser = parser.add_subparsers(dest='subcommand') | |
204 | ||
45ba7a89 | 205 | # The 'run' command will config, build, exec, and parse in one go. |
6ebf5866 | 206 | run_parser = subparser.add_parser('run', help='Runs KUnit tests.') |
45ba7a89 | 207 | add_common_opts(run_parser) |
45ba7a89 DG |
208 | add_build_opts(run_parser) |
209 | add_exec_opts(run_parser) | |
210 | add_parse_opts(run_parser) | |
211 | ||
212 | config_parser = subparser.add_parser('config', | |
213 | help='Ensures that .config contains all of ' | |
214 | 'the options in .kunitconfig') | |
215 | add_common_opts(config_parser) | |
45ba7a89 DG |
216 | |
217 | build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') | |
218 | add_common_opts(build_parser) | |
219 | add_build_opts(build_parser) | |
220 | ||
221 | exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') | |
222 | add_common_opts(exec_parser) | |
223 | add_exec_opts(exec_parser) | |
224 | add_parse_opts(exec_parser) | |
225 | ||
226 | # The 'parse' option is special, as it doesn't need the kernel source | |
227 | # (therefore there is no need for a build_dir, hence no add_common_opts) | |
228 | # and the '--file' argument is not relevant to 'run', so isn't in | |
229 | # add_parse_opts() | |
230 | parse_parser = subparser.add_parser('parse', | |
231 | help='Parses KUnit results from a file, ' | |
232 | 'and parses formatted results.') | |
233 | add_parse_opts(parse_parser) | |
234 | parse_parser.add_argument('file', | |
235 | help='Specifies the file to read results from.', | |
236 | type=str, nargs='?', metavar='input_file') | |
0476e69f | 237 | |
6ebf5866 FG |
238 | cli_args = parser.parse_args(argv) |
239 | ||
240 | if cli_args.subcommand == 'run': | |
01397e82 VMI |
241 | if not os.path.exists(cli_args.build_dir): |
242 | os.mkdir(cli_args.build_dir) | |
e3212513 | 243 | |
ff7b437f BH |
244 | if not linux: |
245 | linux = kunit_kernel.LinuxSourceTree() | |
246 | ||
6ebf5866 FG |
247 | request = KunitRequest(cli_args.raw_output, |
248 | cli_args.timeout, | |
249 | cli_args.jobs, | |
ff7b437f | 250 | cli_args.build_dir, |
0476e69f GT |
251 | cli_args.alltests, |
252 | cli_args.make_options) | |
6ebf5866 FG |
253 | result = run_tests(linux, request) |
254 | if result.status != KunitStatus.SUCCESS: | |
255 | sys.exit(1) | |
45ba7a89 DG |
256 | elif cli_args.subcommand == 'config': |
257 | if cli_args.build_dir: | |
258 | if not os.path.exists(cli_args.build_dir): | |
259 | os.mkdir(cli_args.build_dir) | |
01397e82 | 260 | |
45ba7a89 DG |
261 | if not linux: |
262 | linux = kunit_kernel.LinuxSourceTree() | |
263 | ||
264 | request = KunitConfigRequest(cli_args.build_dir, | |
45ba7a89 DG |
265 | cli_args.make_options) |
266 | result = config_tests(linux, request) | |
267 | kunit_parser.print_with_timestamp(( | |
268 | 'Elapsed time: %.3fs\n') % ( | |
269 | result.elapsed_time)) | |
270 | if result.status != KunitStatus.SUCCESS: | |
271 | sys.exit(1) | |
272 | elif cli_args.subcommand == 'build': | |
273 | if cli_args.build_dir: | |
274 | if not os.path.exists(cli_args.build_dir): | |
275 | os.mkdir(cli_args.build_dir) | |
01397e82 | 276 | |
45ba7a89 DG |
277 | if not linux: |
278 | linux = kunit_kernel.LinuxSourceTree() | |
279 | ||
280 | request = KunitBuildRequest(cli_args.jobs, | |
281 | cli_args.build_dir, | |
282 | cli_args.alltests, | |
283 | cli_args.make_options) | |
284 | result = build_tests(linux, request) | |
285 | kunit_parser.print_with_timestamp(( | |
286 | 'Elapsed time: %.3fs\n') % ( | |
287 | result.elapsed_time)) | |
288 | if result.status != KunitStatus.SUCCESS: | |
289 | sys.exit(1) | |
290 | elif cli_args.subcommand == 'exec': | |
291 | if cli_args.build_dir: | |
292 | if not os.path.exists(cli_args.build_dir): | |
293 | os.mkdir(cli_args.build_dir) | |
01397e82 | 294 | |
45ba7a89 DG |
295 | if not linux: |
296 | linux = kunit_kernel.LinuxSourceTree() | |
297 | ||
298 | exec_request = KunitExecRequest(cli_args.timeout, | |
299 | cli_args.build_dir, | |
300 | cli_args.alltests) | |
301 | exec_result = exec_tests(linux, exec_request) | |
302 | parse_request = KunitParseRequest(cli_args.raw_output, | |
303 | exec_result.result) | |
304 | result = parse_tests(parse_request) | |
305 | kunit_parser.print_with_timestamp(( | |
306 | 'Elapsed time: %.3fs\n') % ( | |
307 | exec_result.elapsed_time)) | |
308 | if result.status != KunitStatus.SUCCESS: | |
309 | sys.exit(1) | |
310 | elif cli_args.subcommand == 'parse': | |
311 | if cli_args.file == None: | |
312 | kunit_output = sys.stdin | |
313 | else: | |
314 | with open(cli_args.file, 'r') as f: | |
315 | kunit_output = f.read().splitlines() | |
316 | request = KunitParseRequest(cli_args.raw_output, | |
317 | kunit_output) | |
318 | result = parse_tests(request) | |
319 | if result.status != KunitStatus.SUCCESS: | |
320 | sys.exit(1) | |
6ebf5866 FG |
321 | else: |
322 | parser.print_help() | |
323 | ||
324 | if __name__ == '__main__': | |
ff7b437f | 325 | main(sys.argv[1:]) |