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 10import argparse 11import sys 12import os 13import time 14import shutil 15 16from collections import namedtuple 17from enum import Enum, auto 18 19import kunit_config 20import kunit_json 21import kunit_kernel 22import kunit_parser 23 24KunitResult = namedtuple('KunitResult', ['status','result','elapsed_time']) 25 26KunitConfigRequest = namedtuple('KunitConfigRequest', 27 ['build_dir', 'make_options']) 28KunitBuildRequest = namedtuple('KunitBuildRequest', 29 ['jobs', 'build_dir', 'alltests', 30 'make_options']) 31KunitExecRequest = namedtuple('KunitExecRequest', 32 ['timeout', 'build_dir', 'alltests']) 33KunitParseRequest = namedtuple('KunitParseRequest', 34 ['raw_output', 'input_data', 'build_dir', 'json']) 35KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 36 'build_dir', 'alltests', 'json', 37 'make_options']) 38 39KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] 40 41class KunitStatus(Enum): 42 SUCCESS = auto() 43 CONFIG_FAILURE = auto() 44 BUILD_FAILURE = auto() 45 TEST_FAILURE = auto() 46 47def 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 52def 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 59def 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 75def 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 97def 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 111def 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 141def 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 181def 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 193def 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 199def 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 208def 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 218def 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 348if __name__ == '__main__': 349 main(sys.argv[1:]) 350