Merge tag 'x86-fsgsbase-2020-08-04' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-microblaze.git] / tools / testing / kunit / kunit.py
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
14 import shutil
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
23 KunitResult = namedtuple('KunitResult', ['status','result','elapsed_time'])
24
25 KunitConfigRequest = namedtuple('KunitConfigRequest',
26                                 ['build_dir', 'make_options'])
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'])
34 KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs',
35                                            'build_dir', 'alltests',
36                                            'make_options'])
37
38 KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0]
39
40 class KunitStatus(Enum):
41         SUCCESS = auto()
42         CONFIG_FAILURE = auto()
43         BUILD_FAILURE = auto()
44         TEST_FAILURE = auto()
45
46 def create_default_kunitconfig():
47         if not os.path.exists(kunit_kernel.kunitconfig_path):
48                 shutil.copyfile('arch/um/configs/kunit_defconfig',
49                                 kunit_kernel.kunitconfig_path)
50
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
58 def config_tests(linux: kunit_kernel.LinuxSourceTree,
59                  request: KunitConfigRequest) -> KunitResult:
60         kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...')
61
62         config_start = time.time()
63         create_default_kunitconfig()
64         success = linux.build_reconfig(request.build_dir, request.make_options)
65         config_end = time.time()
66         if not success:
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)
73
74 def build_tests(linux: kunit_kernel.LinuxSourceTree,
75                 request: KunitBuildRequest) -> KunitResult:
76         kunit_parser.print_with_timestamp('Building KUnit Kernel ...')
77
78         build_start = time.time()
79         success = linux.build_um_kernel(request.alltests,
80                                         request.jobs,
81                                         request.build_dir,
82                                         request.make_options)
83         build_end = time.time()
84         if not success:
85                 return KunitResult(KunitStatus.BUILD_FAILURE,
86                                    'could not build kernel',
87                                    build_end - build_start)
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)
95
96 def exec_tests(linux: kunit_kernel.LinuxSourceTree,
97                request: KunitExecRequest) -> KunitResult:
98         kunit_parser.print_with_timestamp('Starting KUnit Kernel ...')
99         test_start = time.time()
100         result = linux.run_kernel(
101                 timeout=None if request.alltests else request.timeout,
102                 build_dir=request.build_dir)
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.')
116         if request.raw_output:
117                 kunit_parser.raw_output(request.input_data)
118         else:
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,
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()
158
159         kunit_parser.print_with_timestamp((
160                 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' +
161                 'building, %.3fs running\n') % (
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.',
172                             type=str, default='.kunit', metavar='build_dir')
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
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')
198
199
200 def main(argv, linux=None):
201         parser = argparse.ArgumentParser(
202                         description='Helps writing and running KUnit tests.')
203         subparser = parser.add_subparsers(dest='subcommand')
204
205         # The 'run' command will config, build, exec, and parse in one go.
206         run_parser = subparser.add_parser('run', help='Runs KUnit tests.')
207         add_common_opts(run_parser)
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)
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')
237
238         cli_args = parser.parse_args(argv)
239
240         if cli_args.subcommand == 'run':
241                 if not os.path.exists(cli_args.build_dir):
242                         os.mkdir(cli_args.build_dir)
243                 kunit_kernel.kunitconfig_path = os.path.join(
244                         cli_args.build_dir,
245                         kunit_kernel.kunitconfig_path)
246
247                 if not os.path.exists(kunit_kernel.kunitconfig_path):
248                         create_default_kunitconfig()
249
250                 if not linux:
251                         linux = kunit_kernel.LinuxSourceTree()
252
253                 request = KunitRequest(cli_args.raw_output,
254                                        cli_args.timeout,
255                                        cli_args.jobs,
256                                        cli_args.build_dir,
257                                        cli_args.alltests,
258                                        cli_args.make_options)
259                 result = run_tests(linux, request)
260                 if result.status != KunitStatus.SUCCESS:
261                         sys.exit(1)
262         elif cli_args.subcommand == 'config':
263                 if cli_args.build_dir:
264                         if not os.path.exists(cli_args.build_dir):
265                                 os.mkdir(cli_args.build_dir)
266                         kunit_kernel.kunitconfig_path = os.path.join(
267                                 cli_args.build_dir,
268                                 kunit_kernel.kunitconfig_path)
269
270                 if not os.path.exists(kunit_kernel.kunitconfig_path):
271                         create_default_kunitconfig()
272
273                 if not linux:
274                         linux = kunit_kernel.LinuxSourceTree()
275
276                 request = KunitConfigRequest(cli_args.build_dir,
277                                              cli_args.make_options)
278                 result = config_tests(linux, request)
279                 kunit_parser.print_with_timestamp((
280                         'Elapsed time: %.3fs\n') % (
281                                 result.elapsed_time))
282                 if result.status != KunitStatus.SUCCESS:
283                         sys.exit(1)
284         elif cli_args.subcommand == 'build':
285                 if cli_args.build_dir:
286                         if not os.path.exists(cli_args.build_dir):
287                                 os.mkdir(cli_args.build_dir)
288                         kunit_kernel.kunitconfig_path = os.path.join(
289                                 cli_args.build_dir,
290                                 kunit_kernel.kunitconfig_path)
291
292                 if not os.path.exists(kunit_kernel.kunitconfig_path):
293                         create_default_kunitconfig()
294
295                 if not linux:
296                         linux = kunit_kernel.LinuxSourceTree()
297
298                 request = KunitBuildRequest(cli_args.jobs,
299                                             cli_args.build_dir,
300                                             cli_args.alltests,
301                                             cli_args.make_options)
302                 result = build_tests(linux, request)
303                 kunit_parser.print_with_timestamp((
304                         'Elapsed time: %.3fs\n') % (
305                                 result.elapsed_time))
306                 if result.status != KunitStatus.SUCCESS:
307                         sys.exit(1)
308         elif cli_args.subcommand == 'exec':
309                 if cli_args.build_dir:
310                         if not os.path.exists(cli_args.build_dir):
311                                 os.mkdir(cli_args.build_dir)
312                         kunit_kernel.kunitconfig_path = os.path.join(
313                                 cli_args.build_dir,
314                                 kunit_kernel.kunitconfig_path)
315
316                 if not os.path.exists(kunit_kernel.kunitconfig_path):
317                         create_default_kunitconfig()
318
319                 if not linux:
320                         linux = kunit_kernel.LinuxSourceTree()
321
322                 exec_request = KunitExecRequest(cli_args.timeout,
323                                                 cli_args.build_dir,
324                                                 cli_args.alltests)
325                 exec_result = exec_tests(linux, exec_request)
326                 parse_request = KunitParseRequest(cli_args.raw_output,
327                                                   exec_result.result)
328                 result = parse_tests(parse_request)
329                 kunit_parser.print_with_timestamp((
330                         'Elapsed time: %.3fs\n') % (
331                                 exec_result.elapsed_time))
332                 if result.status != KunitStatus.SUCCESS:
333                         sys.exit(1)
334         elif cli_args.subcommand == 'parse':
335                 if cli_args.file == None:
336                         kunit_output = sys.stdin
337                 else:
338                         with open(cli_args.file, 'r') as f:
339                                 kunit_output = f.read().splitlines()
340                 request = KunitParseRequest(cli_args.raw_output,
341                                             kunit_output)
342                 result = parse_tests(request)
343                 if result.status != KunitStatus.SUCCESS:
344                         sys.exit(1)
345         else:
346                 parser.print_help()
347
348 if __name__ == '__main__':
349         main(sys.argv[1:])