16ebf5866SFelix Guo#!/usr/bin/python3 26ebf5866SFelix Guo# SPDX-License-Identifier: GPL-2.0 36ebf5866SFelix Guo# 46ebf5866SFelix Guo# A thin wrapper on top of the KUnit Kernel 56ebf5866SFelix Guo# 66ebf5866SFelix Guo# Copyright (C) 2019, Google LLC. 76ebf5866SFelix Guo# Author: Felix Guo <felixguoxiuping@gmail.com> 86ebf5866SFelix Guo# Author: Brendan Higgins <brendanhiggins@google.com> 96ebf5866SFelix Guo 106ebf5866SFelix Guoimport argparse 116ebf5866SFelix Guoimport sys 126ebf5866SFelix Guoimport os 136ebf5866SFelix Guoimport time 14ff7b437fSBrendan Higginsimport shutil 156ebf5866SFelix Guo 166ebf5866SFelix Guofrom collections import namedtuple 176ebf5866SFelix Guofrom enum import Enum, auto 186ebf5866SFelix Guo 196ebf5866SFelix Guoimport kunit_config 20*21a6d178SHeidi Fahimimport kunit_json 216ebf5866SFelix Guoimport kunit_kernel 226ebf5866SFelix Guoimport kunit_parser 236ebf5866SFelix Guo 2445ba7a89SDavid GowKunitResult = namedtuple('KunitResult', ['status','result','elapsed_time']) 256ebf5866SFelix Guo 2645ba7a89SDavid GowKunitConfigRequest = namedtuple('KunitConfigRequest', 2701397e82SVitor Massaru Iha ['build_dir', 'make_options']) 2845ba7a89SDavid GowKunitBuildRequest = namedtuple('KunitBuildRequest', 2945ba7a89SDavid Gow ['jobs', 'build_dir', 'alltests', 3045ba7a89SDavid Gow 'make_options']) 3145ba7a89SDavid GowKunitExecRequest = namedtuple('KunitExecRequest', 3245ba7a89SDavid Gow ['timeout', 'build_dir', 'alltests']) 3345ba7a89SDavid GowKunitParseRequest = namedtuple('KunitParseRequest', 34*21a6d178SHeidi Fahim ['raw_output', 'input_data', 'build_dir', 'json']) 35021ed9f5SHeidi FahimKunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 36*21a6d178SHeidi Fahim 'build_dir', 'alltests', 'json', 379bdf64b3SVitor Massaru Iha 'make_options']) 386ebf5866SFelix Guo 39be886ba9SHeidi FahimKernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] 40be886ba9SHeidi Fahim 416ebf5866SFelix Guoclass KunitStatus(Enum): 426ebf5866SFelix Guo SUCCESS = auto() 436ebf5866SFelix Guo CONFIG_FAILURE = auto() 446ebf5866SFelix Guo BUILD_FAILURE = auto() 456ebf5866SFelix Guo TEST_FAILURE = auto() 466ebf5866SFelix Guo 47ff7b437fSBrendan Higginsdef create_default_kunitconfig(): 48e3212513SSeongJae Park if not os.path.exists(kunit_kernel.kunitconfig_path): 49ff7b437fSBrendan Higgins shutil.copyfile('arch/um/configs/kunit_defconfig', 50e3212513SSeongJae Park kunit_kernel.kunitconfig_path) 51ff7b437fSBrendan Higgins 52be886ba9SHeidi Fahimdef get_kernel_root_path(): 53be886ba9SHeidi Fahim parts = sys.argv[0] if not __file__ else __file__ 54be886ba9SHeidi Fahim parts = os.path.realpath(parts).split('tools/testing/kunit') 55be886ba9SHeidi Fahim if len(parts) != 2: 56be886ba9SHeidi Fahim sys.exit(1) 57be886ba9SHeidi Fahim return parts[0] 58be886ba9SHeidi Fahim 5945ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree, 6045ba7a89SDavid Gow request: KunitConfigRequest) -> KunitResult: 6145ba7a89SDavid Gow kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...') 6245ba7a89SDavid Gow 636ebf5866SFelix Guo config_start = time.time() 6445ba7a89SDavid Gow create_default_kunitconfig() 650476e69fSGreg Thelen success = linux.build_reconfig(request.build_dir, request.make_options) 666ebf5866SFelix Guo config_end = time.time() 676ebf5866SFelix Guo if not success: 6845ba7a89SDavid Gow return KunitResult(KunitStatus.CONFIG_FAILURE, 6945ba7a89SDavid Gow 'could not configure kernel', 7045ba7a89SDavid Gow config_end - config_start) 7145ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 7245ba7a89SDavid Gow 'configured kernel successfully', 7345ba7a89SDavid Gow config_end - config_start) 746ebf5866SFelix Guo 7545ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree, 7645ba7a89SDavid Gow request: KunitBuildRequest) -> KunitResult: 776ebf5866SFelix Guo kunit_parser.print_with_timestamp('Building KUnit Kernel ...') 786ebf5866SFelix Guo 796ebf5866SFelix Guo build_start = time.time() 80021ed9f5SHeidi Fahim success = linux.build_um_kernel(request.alltests, 81021ed9f5SHeidi Fahim request.jobs, 820476e69fSGreg Thelen request.build_dir, 830476e69fSGreg Thelen request.make_options) 846ebf5866SFelix Guo build_end = time.time() 856ebf5866SFelix Guo if not success: 86ee61492aSDavid Gow return KunitResult(KunitStatus.BUILD_FAILURE, 87ee61492aSDavid Gow 'could not build kernel', 88ee61492aSDavid Gow build_end - build_start) 8945ba7a89SDavid Gow if not success: 9045ba7a89SDavid Gow return KunitResult(KunitStatus.BUILD_FAILURE, 9145ba7a89SDavid Gow 'could not build kernel', 9245ba7a89SDavid Gow build_end - build_start) 9345ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 9445ba7a89SDavid Gow 'built kernel successfully', 9545ba7a89SDavid Gow build_end - build_start) 966ebf5866SFelix Guo 9745ba7a89SDavid Gowdef exec_tests(linux: kunit_kernel.LinuxSourceTree, 9845ba7a89SDavid Gow request: KunitExecRequest) -> KunitResult: 996ebf5866SFelix Guo kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') 1006ebf5866SFelix Guo test_start = time.time() 10145ba7a89SDavid Gow result = linux.run_kernel( 102021ed9f5SHeidi Fahim timeout=None if request.alltests else request.timeout, 1036ec1b81dSSeongJae Park build_dir=request.build_dir) 10445ba7a89SDavid Gow 1056ebf5866SFelix Guo test_end = time.time() 1066ebf5866SFelix Guo 10745ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 10845ba7a89SDavid Gow result, 10945ba7a89SDavid Gow test_end - test_start) 11045ba7a89SDavid Gow 11145ba7a89SDavid Gowdef parse_tests(request: KunitParseRequest) -> KunitResult: 11245ba7a89SDavid Gow parse_start = time.time() 11345ba7a89SDavid Gow 11445ba7a89SDavid Gow test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, 11545ba7a89SDavid Gow [], 11645ba7a89SDavid Gow 'Tests not Parsed.') 117*21a6d178SHeidi Fahim 11845ba7a89SDavid Gow if request.raw_output: 11945ba7a89SDavid Gow kunit_parser.raw_output(request.input_data) 12045ba7a89SDavid Gow else: 12145ba7a89SDavid Gow test_result = kunit_parser.parse_run_tests(request.input_data) 12245ba7a89SDavid Gow parse_end = time.time() 12345ba7a89SDavid Gow 124*21a6d178SHeidi Fahim if request.json: 125*21a6d178SHeidi Fahim json_obj = kunit_json.get_json_result( 126*21a6d178SHeidi Fahim test_result=test_result, 127*21a6d178SHeidi Fahim def_config='kunit_defconfig', 128*21a6d178SHeidi Fahim build_dir=request.build_dir, 129*21a6d178SHeidi Fahim json_path=request.json) 130*21a6d178SHeidi Fahim if request.json == 'stdout': 131*21a6d178SHeidi Fahim print(json_obj) 132*21a6d178SHeidi Fahim 13345ba7a89SDavid Gow if test_result.status != kunit_parser.TestStatus.SUCCESS: 13445ba7a89SDavid Gow return KunitResult(KunitStatus.TEST_FAILURE, test_result, 13545ba7a89SDavid Gow parse_end - parse_start) 13645ba7a89SDavid Gow 13745ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, test_result, 13845ba7a89SDavid Gow parse_end - parse_start) 13945ba7a89SDavid Gow 14045ba7a89SDavid Gow 14145ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree, 14245ba7a89SDavid Gow request: KunitRequest) -> KunitResult: 14345ba7a89SDavid Gow run_start = time.time() 14445ba7a89SDavid Gow 14545ba7a89SDavid Gow config_request = KunitConfigRequest(request.build_dir, 14645ba7a89SDavid Gow request.make_options) 14745ba7a89SDavid Gow config_result = config_tests(linux, config_request) 14845ba7a89SDavid Gow if config_result.status != KunitStatus.SUCCESS: 14945ba7a89SDavid Gow return config_result 15045ba7a89SDavid Gow 15145ba7a89SDavid Gow build_request = KunitBuildRequest(request.jobs, request.build_dir, 15245ba7a89SDavid Gow request.alltests, 15345ba7a89SDavid Gow request.make_options) 15445ba7a89SDavid Gow build_result = build_tests(linux, build_request) 15545ba7a89SDavid Gow if build_result.status != KunitStatus.SUCCESS: 15645ba7a89SDavid Gow return build_result 15745ba7a89SDavid Gow 15845ba7a89SDavid Gow exec_request = KunitExecRequest(request.timeout, request.build_dir, 15945ba7a89SDavid Gow request.alltests) 16045ba7a89SDavid Gow exec_result = exec_tests(linux, exec_request) 16145ba7a89SDavid Gow if exec_result.status != KunitStatus.SUCCESS: 16245ba7a89SDavid Gow return exec_result 16345ba7a89SDavid Gow 16445ba7a89SDavid Gow parse_request = KunitParseRequest(request.raw_output, 165*21a6d178SHeidi Fahim exec_result.result, 166*21a6d178SHeidi Fahim request.build_dir, 167*21a6d178SHeidi Fahim request.json) 16845ba7a89SDavid Gow parse_result = parse_tests(parse_request) 16945ba7a89SDavid Gow 17045ba7a89SDavid Gow run_end = time.time() 17145ba7a89SDavid Gow 1726ebf5866SFelix Guo kunit_parser.print_with_timestamp(( 1736ebf5866SFelix Guo 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + 1746ebf5866SFelix Guo 'building, %.3fs running\n') % ( 17545ba7a89SDavid Gow run_end - run_start, 17645ba7a89SDavid Gow config_result.elapsed_time, 17745ba7a89SDavid Gow build_result.elapsed_time, 17845ba7a89SDavid Gow exec_result.elapsed_time)) 17945ba7a89SDavid Gow return parse_result 1806ebf5866SFelix Guo 18145ba7a89SDavid Gowdef add_common_opts(parser): 18245ba7a89SDavid Gow parser.add_argument('--build_dir', 18345ba7a89SDavid Gow help='As in the make command, it specifies the build ' 18445ba7a89SDavid Gow 'directory.', 185ddbd60c7SVitor Massaru Iha type=str, default='.kunit', metavar='build_dir') 18645ba7a89SDavid Gow parser.add_argument('--make_options', 18745ba7a89SDavid Gow help='X=Y make option, can be repeated.', 18845ba7a89SDavid Gow action='append') 18945ba7a89SDavid Gow parser.add_argument('--alltests', 19045ba7a89SDavid Gow help='Run all KUnit tests through allyesconfig', 1916ebf5866SFelix Guo action='store_true') 1926ebf5866SFelix Guo 19345ba7a89SDavid Gowdef add_build_opts(parser): 19445ba7a89SDavid Gow parser.add_argument('--jobs', 19545ba7a89SDavid Gow help='As in the make command, "Specifies the number of ' 19645ba7a89SDavid Gow 'jobs (commands) to run simultaneously."', 19745ba7a89SDavid Gow type=int, default=8, metavar='jobs') 19845ba7a89SDavid Gow 19945ba7a89SDavid Gowdef add_exec_opts(parser): 20045ba7a89SDavid Gow parser.add_argument('--timeout', 2016ebf5866SFelix Guo help='maximum number of seconds to allow for all tests ' 2026ebf5866SFelix Guo 'to run. This does not include time taken to build the ' 2036ebf5866SFelix Guo 'tests.', 2046ebf5866SFelix Guo type=int, 2056ebf5866SFelix Guo default=300, 2066ebf5866SFelix Guo metavar='timeout') 2076ebf5866SFelix Guo 20845ba7a89SDavid Gowdef add_parse_opts(parser): 20945ba7a89SDavid Gow parser.add_argument('--raw_output', help='don\'t format output from kernel', 210ff7b437fSBrendan Higgins action='store_true') 211*21a6d178SHeidi Fahim parser.add_argument('--json', 212*21a6d178SHeidi Fahim nargs='?', 213*21a6d178SHeidi Fahim help='Stores test results in a JSON, and either ' 214*21a6d178SHeidi Fahim 'prints to stdout or saves to file if a ' 215*21a6d178SHeidi Fahim 'filename is specified', 216*21a6d178SHeidi Fahim type=str, const='stdout', default=None) 217021ed9f5SHeidi Fahim 21845ba7a89SDavid Gowdef main(argv, linux=None): 21945ba7a89SDavid Gow parser = argparse.ArgumentParser( 22045ba7a89SDavid Gow description='Helps writing and running KUnit tests.') 22145ba7a89SDavid Gow subparser = parser.add_subparsers(dest='subcommand') 22245ba7a89SDavid Gow 22345ba7a89SDavid Gow # The 'run' command will config, build, exec, and parse in one go. 22445ba7a89SDavid Gow run_parser = subparser.add_parser('run', help='Runs KUnit tests.') 22545ba7a89SDavid Gow add_common_opts(run_parser) 22645ba7a89SDavid Gow add_build_opts(run_parser) 22745ba7a89SDavid Gow add_exec_opts(run_parser) 22845ba7a89SDavid Gow add_parse_opts(run_parser) 22945ba7a89SDavid Gow 23045ba7a89SDavid Gow config_parser = subparser.add_parser('config', 23145ba7a89SDavid Gow help='Ensures that .config contains all of ' 23245ba7a89SDavid Gow 'the options in .kunitconfig') 23345ba7a89SDavid Gow add_common_opts(config_parser) 23445ba7a89SDavid Gow 23545ba7a89SDavid Gow build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') 23645ba7a89SDavid Gow add_common_opts(build_parser) 23745ba7a89SDavid Gow add_build_opts(build_parser) 23845ba7a89SDavid Gow 23945ba7a89SDavid Gow exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') 24045ba7a89SDavid Gow add_common_opts(exec_parser) 24145ba7a89SDavid Gow add_exec_opts(exec_parser) 24245ba7a89SDavid Gow add_parse_opts(exec_parser) 24345ba7a89SDavid Gow 24445ba7a89SDavid Gow # The 'parse' option is special, as it doesn't need the kernel source 24545ba7a89SDavid Gow # (therefore there is no need for a build_dir, hence no add_common_opts) 24645ba7a89SDavid Gow # and the '--file' argument is not relevant to 'run', so isn't in 24745ba7a89SDavid Gow # add_parse_opts() 24845ba7a89SDavid Gow parse_parser = subparser.add_parser('parse', 24945ba7a89SDavid Gow help='Parses KUnit results from a file, ' 25045ba7a89SDavid Gow 'and parses formatted results.') 25145ba7a89SDavid Gow add_parse_opts(parse_parser) 25245ba7a89SDavid Gow parse_parser.add_argument('file', 25345ba7a89SDavid Gow help='Specifies the file to read results from.', 25445ba7a89SDavid Gow type=str, nargs='?', metavar='input_file') 2550476e69fSGreg Thelen 2566ebf5866SFelix Guo cli_args = parser.parse_args(argv) 2576ebf5866SFelix Guo 2585578d008SBrendan Higgins if get_kernel_root_path(): 2595578d008SBrendan Higgins os.chdir(get_kernel_root_path()) 2605578d008SBrendan Higgins 2616ebf5866SFelix Guo if cli_args.subcommand == 'run': 262e3212513SSeongJae Park if not os.path.exists(cli_args.build_dir): 263e3212513SSeongJae Park os.mkdir(cli_args.build_dir) 2645578d008SBrendan Higgins create_default_kunitconfig() 26501397e82SVitor Massaru Iha 266ff7b437fSBrendan Higgins if not linux: 267ff7b437fSBrendan Higgins linux = kunit_kernel.LinuxSourceTree() 268ff7b437fSBrendan Higgins 2696ebf5866SFelix Guo request = KunitRequest(cli_args.raw_output, 2706ebf5866SFelix Guo cli_args.timeout, 2716ebf5866SFelix Guo cli_args.jobs, 272ff7b437fSBrendan Higgins cli_args.build_dir, 2730476e69fSGreg Thelen cli_args.alltests, 274*21a6d178SHeidi Fahim cli_args.json, 2750476e69fSGreg Thelen cli_args.make_options) 2766ebf5866SFelix Guo result = run_tests(linux, request) 2776ebf5866SFelix Guo if result.status != KunitStatus.SUCCESS: 2786ebf5866SFelix Guo sys.exit(1) 27945ba7a89SDavid Gow elif cli_args.subcommand == 'config': 28045ba7a89SDavid Gow if cli_args.build_dir: 28145ba7a89SDavid Gow if not os.path.exists(cli_args.build_dir): 28245ba7a89SDavid Gow os.mkdir(cli_args.build_dir) 2835578d008SBrendan Higgins create_default_kunitconfig() 28401397e82SVitor Massaru Iha 28545ba7a89SDavid Gow if not linux: 28645ba7a89SDavid Gow linux = kunit_kernel.LinuxSourceTree() 28745ba7a89SDavid Gow 28845ba7a89SDavid Gow request = KunitConfigRequest(cli_args.build_dir, 28945ba7a89SDavid Gow cli_args.make_options) 29045ba7a89SDavid Gow result = config_tests(linux, request) 29145ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 29245ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 29345ba7a89SDavid Gow result.elapsed_time)) 29445ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 29545ba7a89SDavid Gow sys.exit(1) 29645ba7a89SDavid Gow elif cli_args.subcommand == 'build': 29745ba7a89SDavid Gow if not linux: 29845ba7a89SDavid Gow linux = kunit_kernel.LinuxSourceTree() 29945ba7a89SDavid Gow 30045ba7a89SDavid Gow request = KunitBuildRequest(cli_args.jobs, 30145ba7a89SDavid Gow cli_args.build_dir, 30245ba7a89SDavid Gow cli_args.alltests, 30345ba7a89SDavid Gow cli_args.make_options) 30445ba7a89SDavid Gow result = build_tests(linux, request) 30545ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 30645ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 30745ba7a89SDavid Gow result.elapsed_time)) 30845ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 30945ba7a89SDavid Gow sys.exit(1) 31045ba7a89SDavid Gow elif cli_args.subcommand == 'exec': 31145ba7a89SDavid Gow if not linux: 31245ba7a89SDavid Gow linux = kunit_kernel.LinuxSourceTree() 31345ba7a89SDavid Gow 31445ba7a89SDavid Gow exec_request = KunitExecRequest(cli_args.timeout, 31545ba7a89SDavid Gow cli_args.build_dir, 31645ba7a89SDavid Gow cli_args.alltests) 31745ba7a89SDavid Gow exec_result = exec_tests(linux, exec_request) 31845ba7a89SDavid Gow parse_request = KunitParseRequest(cli_args.raw_output, 319*21a6d178SHeidi Fahim exec_result.result, 320*21a6d178SHeidi Fahim cli_args.build_dir, 321*21a6d178SHeidi Fahim cli_args.json) 32245ba7a89SDavid Gow result = parse_tests(parse_request) 32345ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 32445ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 32545ba7a89SDavid Gow exec_result.elapsed_time)) 32645ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 32745ba7a89SDavid Gow sys.exit(1) 32845ba7a89SDavid Gow elif cli_args.subcommand == 'parse': 32945ba7a89SDavid Gow if cli_args.file == None: 33045ba7a89SDavid Gow kunit_output = sys.stdin 33145ba7a89SDavid Gow else: 33245ba7a89SDavid Gow with open(cli_args.file, 'r') as f: 33345ba7a89SDavid Gow kunit_output = f.read().splitlines() 33445ba7a89SDavid Gow request = KunitParseRequest(cli_args.raw_output, 335*21a6d178SHeidi Fahim kunit_output, 336*21a6d178SHeidi Fahim cli_args.build_dir, 337*21a6d178SHeidi Fahim cli_args.json) 33845ba7a89SDavid Gow result = parse_tests(request) 33945ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 34045ba7a89SDavid Gow sys.exit(1) 3416ebf5866SFelix Guo else: 3426ebf5866SFelix Guo parser.print_help() 3436ebf5866SFelix Guo 3446ebf5866SFelix Guoif __name__ == '__main__': 345ff7b437fSBrendan Higgins main(sys.argv[1:]) 346