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