1c25ce589SFinn Behrens#!/usr/bin/env 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 146ebf5866SFelix Guo 156ebf5866SFelix Guofrom collections import namedtuple 166ebf5866SFelix Guofrom enum import Enum, auto 176ebf5866SFelix Guo 186ebf5866SFelix Guoimport kunit_config 1921a6d178SHeidi Fahimimport kunit_json 206ebf5866SFelix Guoimport kunit_kernel 216ebf5866SFelix Guoimport kunit_parser 226ebf5866SFelix Guo 2345ba7a89SDavid GowKunitResult = namedtuple('KunitResult', ['status','result','elapsed_time']) 246ebf5866SFelix Guo 2545ba7a89SDavid GowKunitConfigRequest = namedtuple('KunitConfigRequest', 2601397e82SVitor Massaru Iha ['build_dir', 'make_options']) 2745ba7a89SDavid GowKunitBuildRequest = namedtuple('KunitBuildRequest', 2845ba7a89SDavid Gow ['jobs', 'build_dir', 'alltests', 2945ba7a89SDavid Gow 'make_options']) 3045ba7a89SDavid GowKunitExecRequest = namedtuple('KunitExecRequest', 31d992880bSDaniel Latypov ['timeout', 'build_dir', 'alltests', 'filter_glob']) 3245ba7a89SDavid GowKunitParseRequest = namedtuple('KunitParseRequest', 3321a6d178SHeidi Fahim ['raw_output', 'input_data', 'build_dir', 'json']) 34021ed9f5SHeidi FahimKunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 35d992880bSDaniel Latypov 'build_dir', 'alltests', 'filter_glob', 36d992880bSDaniel Latypov 'json', 'make_options']) 376ebf5866SFelix Guo 38be886ba9SHeidi FahimKernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] 39be886ba9SHeidi Fahim 406ebf5866SFelix Guoclass KunitStatus(Enum): 416ebf5866SFelix Guo SUCCESS = auto() 426ebf5866SFelix Guo CONFIG_FAILURE = auto() 436ebf5866SFelix Guo BUILD_FAILURE = auto() 446ebf5866SFelix Guo TEST_FAILURE = auto() 456ebf5866SFelix Guo 4609641f7cSDaniel Latypovdef get_kernel_root_path() -> str: 4709641f7cSDaniel Latypov path = sys.argv[0] if not __file__ else __file__ 4809641f7cSDaniel Latypov parts = os.path.realpath(path).split('tools/testing/kunit') 49be886ba9SHeidi Fahim if len(parts) != 2: 50be886ba9SHeidi Fahim sys.exit(1) 51be886ba9SHeidi Fahim return parts[0] 52be886ba9SHeidi Fahim 5345ba7a89SDavid Gowdef config_tests(linux: kunit_kernel.LinuxSourceTree, 5445ba7a89SDavid Gow request: KunitConfigRequest) -> KunitResult: 5545ba7a89SDavid Gow kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...') 5645ba7a89SDavid Gow 576ebf5866SFelix Guo config_start = time.time() 580476e69fSGreg Thelen success = linux.build_reconfig(request.build_dir, request.make_options) 596ebf5866SFelix Guo config_end = time.time() 606ebf5866SFelix Guo if not success: 6145ba7a89SDavid Gow return KunitResult(KunitStatus.CONFIG_FAILURE, 6245ba7a89SDavid Gow 'could not configure kernel', 6345ba7a89SDavid Gow config_end - config_start) 6445ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 6545ba7a89SDavid Gow 'configured kernel successfully', 6645ba7a89SDavid Gow config_end - config_start) 676ebf5866SFelix Guo 6845ba7a89SDavid Gowdef build_tests(linux: kunit_kernel.LinuxSourceTree, 6945ba7a89SDavid Gow request: KunitBuildRequest) -> KunitResult: 706ebf5866SFelix Guo kunit_parser.print_with_timestamp('Building KUnit Kernel ...') 716ebf5866SFelix Guo 726ebf5866SFelix Guo build_start = time.time() 73021ed9f5SHeidi Fahim success = linux.build_um_kernel(request.alltests, 74021ed9f5SHeidi Fahim request.jobs, 750476e69fSGreg Thelen request.build_dir, 760476e69fSGreg Thelen request.make_options) 776ebf5866SFelix Guo build_end = time.time() 786ebf5866SFelix Guo if not success: 79ee61492aSDavid Gow return KunitResult(KunitStatus.BUILD_FAILURE, 80ee61492aSDavid Gow 'could not build kernel', 81ee61492aSDavid Gow build_end - build_start) 8245ba7a89SDavid Gow if not success: 8345ba7a89SDavid Gow return KunitResult(KunitStatus.BUILD_FAILURE, 8445ba7a89SDavid Gow 'could not build kernel', 8545ba7a89SDavid Gow build_end - build_start) 8645ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 8745ba7a89SDavid Gow 'built kernel successfully', 8845ba7a89SDavid Gow build_end - build_start) 896ebf5866SFelix Guo 9045ba7a89SDavid Gowdef exec_tests(linux: kunit_kernel.LinuxSourceTree, 9145ba7a89SDavid Gow request: KunitExecRequest) -> KunitResult: 926ebf5866SFelix Guo kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') 936ebf5866SFelix Guo test_start = time.time() 9445ba7a89SDavid Gow result = linux.run_kernel( 95021ed9f5SHeidi Fahim timeout=None if request.alltests else request.timeout, 96d992880bSDaniel Latypov filter_glob=request.filter_glob, 976ec1b81dSSeongJae Park build_dir=request.build_dir) 9845ba7a89SDavid Gow 996ebf5866SFelix Guo test_end = time.time() 1006ebf5866SFelix Guo 10145ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, 10245ba7a89SDavid Gow result, 10345ba7a89SDavid Gow test_end - test_start) 10445ba7a89SDavid Gow 10545ba7a89SDavid Gowdef parse_tests(request: KunitParseRequest) -> KunitResult: 10645ba7a89SDavid Gow parse_start = time.time() 10745ba7a89SDavid Gow 10845ba7a89SDavid Gow test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, 10945ba7a89SDavid Gow [], 11045ba7a89SDavid Gow 'Tests not Parsed.') 11121a6d178SHeidi Fahim 11245ba7a89SDavid Gow if request.raw_output: 11345ba7a89SDavid Gow kunit_parser.raw_output(request.input_data) 11445ba7a89SDavid Gow else: 11545ba7a89SDavid Gow test_result = kunit_parser.parse_run_tests(request.input_data) 11645ba7a89SDavid Gow parse_end = time.time() 11745ba7a89SDavid Gow 11821a6d178SHeidi Fahim if request.json: 11921a6d178SHeidi Fahim json_obj = kunit_json.get_json_result( 12021a6d178SHeidi Fahim test_result=test_result, 12121a6d178SHeidi Fahim def_config='kunit_defconfig', 12221a6d178SHeidi Fahim build_dir=request.build_dir, 12321a6d178SHeidi Fahim json_path=request.json) 12421a6d178SHeidi Fahim if request.json == 'stdout': 12521a6d178SHeidi Fahim print(json_obj) 12621a6d178SHeidi Fahim 12745ba7a89SDavid Gow if test_result.status != kunit_parser.TestStatus.SUCCESS: 12845ba7a89SDavid Gow return KunitResult(KunitStatus.TEST_FAILURE, test_result, 12945ba7a89SDavid Gow parse_end - parse_start) 13045ba7a89SDavid Gow 13145ba7a89SDavid Gow return KunitResult(KunitStatus.SUCCESS, test_result, 13245ba7a89SDavid Gow parse_end - parse_start) 13345ba7a89SDavid Gow 13445ba7a89SDavid Gow 13545ba7a89SDavid Gowdef run_tests(linux: kunit_kernel.LinuxSourceTree, 13645ba7a89SDavid Gow request: KunitRequest) -> KunitResult: 13745ba7a89SDavid Gow run_start = time.time() 13845ba7a89SDavid Gow 13945ba7a89SDavid Gow config_request = KunitConfigRequest(request.build_dir, 14045ba7a89SDavid Gow request.make_options) 14145ba7a89SDavid Gow config_result = config_tests(linux, config_request) 14245ba7a89SDavid Gow if config_result.status != KunitStatus.SUCCESS: 14345ba7a89SDavid Gow return config_result 14445ba7a89SDavid Gow 14545ba7a89SDavid Gow build_request = KunitBuildRequest(request.jobs, request.build_dir, 14645ba7a89SDavid Gow request.alltests, 14745ba7a89SDavid Gow request.make_options) 14845ba7a89SDavid Gow build_result = build_tests(linux, build_request) 14945ba7a89SDavid Gow if build_result.status != KunitStatus.SUCCESS: 15045ba7a89SDavid Gow return build_result 15145ba7a89SDavid Gow 15245ba7a89SDavid Gow exec_request = KunitExecRequest(request.timeout, request.build_dir, 153d992880bSDaniel Latypov request.alltests, request.filter_glob) 15445ba7a89SDavid Gow exec_result = exec_tests(linux, exec_request) 15545ba7a89SDavid Gow if exec_result.status != KunitStatus.SUCCESS: 15645ba7a89SDavid Gow return exec_result 15745ba7a89SDavid Gow 15845ba7a89SDavid Gow parse_request = KunitParseRequest(request.raw_output, 15921a6d178SHeidi Fahim exec_result.result, 16021a6d178SHeidi Fahim request.build_dir, 16121a6d178SHeidi Fahim request.json) 16245ba7a89SDavid Gow parse_result = parse_tests(parse_request) 16345ba7a89SDavid Gow 16445ba7a89SDavid Gow run_end = time.time() 16545ba7a89SDavid Gow 1666ebf5866SFelix Guo kunit_parser.print_with_timestamp(( 1676ebf5866SFelix Guo 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + 1686ebf5866SFelix Guo 'building, %.3fs running\n') % ( 16945ba7a89SDavid Gow run_end - run_start, 17045ba7a89SDavid Gow config_result.elapsed_time, 17145ba7a89SDavid Gow build_result.elapsed_time, 17245ba7a89SDavid Gow exec_result.elapsed_time)) 17345ba7a89SDavid Gow return parse_result 1746ebf5866SFelix Guo 17509641f7cSDaniel Latypovdef add_common_opts(parser) -> None: 17645ba7a89SDavid Gow parser.add_argument('--build_dir', 17745ba7a89SDavid Gow help='As in the make command, it specifies the build ' 17845ba7a89SDavid Gow 'directory.', 179ddbd60c7SVitor Massaru Iha type=str, default='.kunit', metavar='build_dir') 18045ba7a89SDavid Gow parser.add_argument('--make_options', 18145ba7a89SDavid Gow help='X=Y make option, can be repeated.', 18245ba7a89SDavid Gow action='append') 18345ba7a89SDavid Gow parser.add_argument('--alltests', 18445ba7a89SDavid Gow help='Run all KUnit tests through allyesconfig', 1856ebf5866SFelix Guo action='store_true') 186243180f5SDaniel Latypov parser.add_argument('--kunitconfig', 187*9854781dSDaniel Latypov help='Path to Kconfig fragment that enables KUnit tests.' 188*9854781dSDaniel Latypov ' If given a directory, (e.g. lib/kunit), "/.kunitconfig" ' 189*9854781dSDaniel Latypov 'will get automatically appended.', 190243180f5SDaniel Latypov metavar='kunitconfig') 1916ebf5866SFelix Guo 19209641f7cSDaniel Latypovdef add_build_opts(parser) -> None: 19345ba7a89SDavid Gow parser.add_argument('--jobs', 19445ba7a89SDavid Gow help='As in the make command, "Specifies the number of ' 19545ba7a89SDavid Gow 'jobs (commands) to run simultaneously."', 19645ba7a89SDavid Gow type=int, default=8, metavar='jobs') 19745ba7a89SDavid Gow 19809641f7cSDaniel Latypovdef add_exec_opts(parser) -> None: 19945ba7a89SDavid Gow parser.add_argument('--timeout', 2006ebf5866SFelix Guo help='maximum number of seconds to allow for all tests ' 2016ebf5866SFelix Guo 'to run. This does not include time taken to build the ' 2026ebf5866SFelix Guo 'tests.', 2036ebf5866SFelix Guo type=int, 2046ebf5866SFelix Guo default=300, 2056ebf5866SFelix Guo metavar='timeout') 206d992880bSDaniel Latypov parser.add_argument('filter_glob', 207d992880bSDaniel Latypov help='maximum number of seconds to allow for all tests ' 208d992880bSDaniel Latypov 'to run. This does not include time taken to build the ' 209d992880bSDaniel Latypov 'tests.', 210d992880bSDaniel Latypov type=str, 211d992880bSDaniel Latypov nargs='?', 212d992880bSDaniel Latypov default='', 213d992880bSDaniel Latypov metavar='filter_glob') 2146ebf5866SFelix Guo 21509641f7cSDaniel Latypovdef add_parse_opts(parser) -> None: 21645ba7a89SDavid Gow parser.add_argument('--raw_output', help='don\'t format output from kernel', 217ff7b437fSBrendan Higgins action='store_true') 21821a6d178SHeidi Fahim parser.add_argument('--json', 21921a6d178SHeidi Fahim nargs='?', 22021a6d178SHeidi Fahim help='Stores test results in a JSON, and either ' 22121a6d178SHeidi Fahim 'prints to stdout or saves to file if a ' 22221a6d178SHeidi Fahim 'filename is specified', 22321a6d178SHeidi Fahim type=str, const='stdout', default=None) 224021ed9f5SHeidi Fahim 22545ba7a89SDavid Gowdef main(argv, linux=None): 22645ba7a89SDavid Gow parser = argparse.ArgumentParser( 22745ba7a89SDavid Gow description='Helps writing and running KUnit tests.') 22845ba7a89SDavid Gow subparser = parser.add_subparsers(dest='subcommand') 22945ba7a89SDavid Gow 23045ba7a89SDavid Gow # The 'run' command will config, build, exec, and parse in one go. 23145ba7a89SDavid Gow run_parser = subparser.add_parser('run', help='Runs KUnit tests.') 23245ba7a89SDavid Gow add_common_opts(run_parser) 23345ba7a89SDavid Gow add_build_opts(run_parser) 23445ba7a89SDavid Gow add_exec_opts(run_parser) 23545ba7a89SDavid Gow add_parse_opts(run_parser) 23645ba7a89SDavid Gow 23745ba7a89SDavid Gow config_parser = subparser.add_parser('config', 23845ba7a89SDavid Gow help='Ensures that .config contains all of ' 23945ba7a89SDavid Gow 'the options in .kunitconfig') 24045ba7a89SDavid Gow add_common_opts(config_parser) 24145ba7a89SDavid Gow 24245ba7a89SDavid Gow build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') 24345ba7a89SDavid Gow add_common_opts(build_parser) 24445ba7a89SDavid Gow add_build_opts(build_parser) 24545ba7a89SDavid Gow 24645ba7a89SDavid Gow exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') 24745ba7a89SDavid Gow add_common_opts(exec_parser) 24845ba7a89SDavid Gow add_exec_opts(exec_parser) 24945ba7a89SDavid Gow add_parse_opts(exec_parser) 25045ba7a89SDavid Gow 25145ba7a89SDavid Gow # The 'parse' option is special, as it doesn't need the kernel source 25245ba7a89SDavid Gow # (therefore there is no need for a build_dir, hence no add_common_opts) 25345ba7a89SDavid Gow # and the '--file' argument is not relevant to 'run', so isn't in 25445ba7a89SDavid Gow # add_parse_opts() 25545ba7a89SDavid Gow parse_parser = subparser.add_parser('parse', 25645ba7a89SDavid Gow help='Parses KUnit results from a file, ' 25745ba7a89SDavid Gow 'and parses formatted results.') 25845ba7a89SDavid Gow add_parse_opts(parse_parser) 25945ba7a89SDavid Gow parse_parser.add_argument('file', 26045ba7a89SDavid Gow help='Specifies the file to read results from.', 26145ba7a89SDavid Gow type=str, nargs='?', metavar='input_file') 2620476e69fSGreg Thelen 2636ebf5866SFelix Guo cli_args = parser.parse_args(argv) 2646ebf5866SFelix Guo 2655578d008SBrendan Higgins if get_kernel_root_path(): 2665578d008SBrendan Higgins os.chdir(get_kernel_root_path()) 2675578d008SBrendan Higgins 2686ebf5866SFelix Guo if cli_args.subcommand == 'run': 269e3212513SSeongJae Park if not os.path.exists(cli_args.build_dir): 270e3212513SSeongJae Park os.mkdir(cli_args.build_dir) 27182206a0cSBrendan Higgins 272ff7b437fSBrendan Higgins if not linux: 273243180f5SDaniel Latypov linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, kunitconfig_path=cli_args.kunitconfig) 274fcdb0bc0SAndy Shevchenko 2756ebf5866SFelix Guo request = KunitRequest(cli_args.raw_output, 2766ebf5866SFelix Guo cli_args.timeout, 2776ebf5866SFelix Guo cli_args.jobs, 278ff7b437fSBrendan Higgins cli_args.build_dir, 2790476e69fSGreg Thelen cli_args.alltests, 280d992880bSDaniel Latypov cli_args.filter_glob, 28121a6d178SHeidi Fahim cli_args.json, 2820476e69fSGreg Thelen cli_args.make_options) 2836ebf5866SFelix Guo result = run_tests(linux, request) 2846ebf5866SFelix Guo if result.status != KunitStatus.SUCCESS: 2856ebf5866SFelix Guo sys.exit(1) 28645ba7a89SDavid Gow elif cli_args.subcommand == 'config': 28782206a0cSBrendan Higgins if cli_args.build_dir and ( 28882206a0cSBrendan Higgins not os.path.exists(cli_args.build_dir)): 28945ba7a89SDavid Gow os.mkdir(cli_args.build_dir) 29082206a0cSBrendan Higgins 29145ba7a89SDavid Gow if not linux: 292243180f5SDaniel Latypov linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, kunitconfig_path=cli_args.kunitconfig) 293fcdb0bc0SAndy Shevchenko 29445ba7a89SDavid Gow request = KunitConfigRequest(cli_args.build_dir, 29545ba7a89SDavid Gow cli_args.make_options) 29645ba7a89SDavid Gow result = config_tests(linux, request) 29745ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 29845ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 29945ba7a89SDavid Gow result.elapsed_time)) 30045ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 30145ba7a89SDavid Gow sys.exit(1) 30245ba7a89SDavid Gow elif cli_args.subcommand == 'build': 30345ba7a89SDavid Gow if not linux: 304243180f5SDaniel Latypov linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir, kunitconfig_path=cli_args.kunitconfig) 305fcdb0bc0SAndy Shevchenko 30645ba7a89SDavid Gow request = KunitBuildRequest(cli_args.jobs, 30745ba7a89SDavid Gow cli_args.build_dir, 30845ba7a89SDavid Gow cli_args.alltests, 30945ba7a89SDavid Gow cli_args.make_options) 31045ba7a89SDavid Gow result = build_tests(linux, request) 31145ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 31245ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 31345ba7a89SDavid Gow result.elapsed_time)) 31445ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 31545ba7a89SDavid Gow sys.exit(1) 31645ba7a89SDavid Gow elif cli_args.subcommand == 'exec': 31745ba7a89SDavid Gow if not linux: 3182b8fdbbfSDaniel Latypov linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir) 319fcdb0bc0SAndy Shevchenko 32045ba7a89SDavid Gow exec_request = KunitExecRequest(cli_args.timeout, 32145ba7a89SDavid Gow cli_args.build_dir, 322d992880bSDaniel Latypov cli_args.alltests, 323d992880bSDaniel Latypov cli_args.filter_glob) 32445ba7a89SDavid Gow exec_result = exec_tests(linux, exec_request) 32545ba7a89SDavid Gow parse_request = KunitParseRequest(cli_args.raw_output, 32621a6d178SHeidi Fahim exec_result.result, 32721a6d178SHeidi Fahim cli_args.build_dir, 32821a6d178SHeidi Fahim cli_args.json) 32945ba7a89SDavid Gow result = parse_tests(parse_request) 33045ba7a89SDavid Gow kunit_parser.print_with_timestamp(( 33145ba7a89SDavid Gow 'Elapsed time: %.3fs\n') % ( 33245ba7a89SDavid Gow exec_result.elapsed_time)) 33345ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 33445ba7a89SDavid Gow sys.exit(1) 33545ba7a89SDavid Gow elif cli_args.subcommand == 'parse': 33645ba7a89SDavid Gow if cli_args.file == None: 33745ba7a89SDavid Gow kunit_output = sys.stdin 33845ba7a89SDavid Gow else: 33945ba7a89SDavid Gow with open(cli_args.file, 'r') as f: 34045ba7a89SDavid Gow kunit_output = f.read().splitlines() 34145ba7a89SDavid Gow request = KunitParseRequest(cli_args.raw_output, 34221a6d178SHeidi Fahim kunit_output, 3433959d0a6SDavid Gow None, 34421a6d178SHeidi Fahim cli_args.json) 34545ba7a89SDavid Gow result = parse_tests(request) 34645ba7a89SDavid Gow if result.status != KunitStatus.SUCCESS: 34745ba7a89SDavid Gow sys.exit(1) 3486ebf5866SFelix Guo else: 3496ebf5866SFelix Guo parser.print_help() 3506ebf5866SFelix Guo 3516ebf5866SFelix Guoif __name__ == '__main__': 352ff7b437fSBrendan Higgins main(sys.argv[1:]) 353