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_kernel 21import kunit_parser 22 23KunitResult = namedtuple('KunitResult', ['status','result','elapsed_time']) 24 25KunitConfigRequest = namedtuple('KunitConfigRequest', 26 ['build_dir', 'make_options']) 27KunitBuildRequest = namedtuple('KunitBuildRequest', 28 ['jobs', 'build_dir', 'alltests', 29 'make_options']) 30KunitExecRequest = namedtuple('KunitExecRequest', 31 ['timeout', 'build_dir', 'alltests']) 32KunitParseRequest = namedtuple('KunitParseRequest', 33 ['raw_output', 'input_data']) 34KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 35 'build_dir', 'alltests', 36 'make_options']) 37 38KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] 39 40class KunitStatus(Enum): 41 SUCCESS = auto() 42 CONFIG_FAILURE = auto() 43 BUILD_FAILURE = auto() 44 TEST_FAILURE = auto() 45 46def 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 51def 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 58def 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 74def 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 96def 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 110def 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 130def 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 168def 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 180def 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 186def 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 195def add_parse_opts(parser): 196 parser.add_argument('--raw_output', help='don\'t format output from kernel', 197 action='store_true') 198 199 200def 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 348if __name__ == '__main__': 349 main(sys.argv[1:]) 350