Received: by 2002:a25:e7d8:0:0:0:0:0 with SMTP id e207csp1000284ybh; Wed, 11 Mar 2020 15:20:53 -0700 (PDT) X-Google-Smtp-Source: ADFU+vuDKBk6/AlSmVNhowz3j7WUQ1L0YDbHen85tQkjZnzENaOjFkx7J6gTHmRUXR/4IcF6ihti X-Received: by 2002:a05:6830:1e0d:: with SMTP id s13mr3920190otr.234.1583965253357; Wed, 11 Mar 2020 15:20:53 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1583965253; cv=none; d=google.com; s=arc-20160816; b=mQ4eH19eY9GgMuIcVUOgYLi5bpnK38aVommqty88B0VSbGXvvoSIT/Srxnp+cjqSjE WAPN026ttA51G7u9acQELDgqYVEAqr6v69S91T1QIpWBBF0rQHBYpxRzZnVnrGISB8AX urkuRdJJjJa6ulCm4c5Q+4RfJR1ADLz/gxRj2LeOcWm8Uvmi8oFSXnoPrmoH6WuluVSR x7PIFM6RIf7uqUau7y8e0vjrlO2/bHObBspykkQjLk6HAh/A9vJGAHF1bZ7c4MKSSp89 I5htV/x3PWO0reNML1jSOfUUCKNFapr+kQU8T935PMRdE5sNJbz1qwKugv77qT7Qp59W CQUw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:from:subject:mime-version :message-id:date:dkim-signature; bh=GPGWW1sqDohcwt84cAP0Nr+Zx9nz2MaTy8N1N1fJuDE=; b=huc0UAKFjFsr0Ad8eVeBJkQSqR1p3OMLo8mIY68nqGrqtujMSZxd8+99U3TB+1fpR0 sDzGi9QdQ9+3TtMaGnAnEk9tslAAfbz+K+HEX95H6Pge2y+fGtfhayGTLWLZrGyXyYU5 9xwsVO60Q53tvKHlmANJY6ymvl0nPOxjYH85H66rdiRe5T+ySikEB2lb1Zrxpwr4McX9 q+FbuHOxNl9HUHnn+OTsNF4FoSdDoPQK66TbcIGqzX3RsSjQyl3KT7HuRo2aghYZDK+q yo2vDmznJ5rQlgxdyE/A01fHtnJsBX4nzRpaHx+zIV+jIBtP40jGmwgBgjDuUAHnBs7W f7Xg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=ZPl15rxz; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id l11si787397oom.36.2020.03.11.15.20.40; Wed, 11 Mar 2020 15:20:53 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=ZPl15rxz; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730084AbgCKWUF (ORCPT + 99 others); Wed, 11 Mar 2020 18:20:05 -0400 Received: from mail-pf1-f202.google.com ([209.85.210.202]:44932 "EHLO mail-pf1-f202.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729506AbgCKWUF (ORCPT ); Wed, 11 Mar 2020 18:20:05 -0400 Received: by mail-pf1-f202.google.com with SMTP id b204so2360015pfb.11 for ; Wed, 11 Mar 2020 15:20:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:message-id:mime-version:subject:from:to:cc; bh=GPGWW1sqDohcwt84cAP0Nr+Zx9nz2MaTy8N1N1fJuDE=; b=ZPl15rxznSlxD5GgkSlOPX9akbIouil42MHvxrlzbWwo1y0qjf69eEFVHyTWebVai0 GdglBmYIfU3ZyR7aHCoMy4YC+UC2QfwCiFZQmu8qBAIhhKGW/WPhcrOzSepX4IhPm1Hx H4AtIimUqq1OtBRQDFjlalItsGhjtiLjIiWJP+aMjQrOuGsyH6owTN4R0p+hUlm9N9Ti kx+gK0OpC2eNOvdMWFn/XAsThdz+OjvLX0ooGOqQyofGb9cvrnegPsshcufaMcsaLIjW y32ICO86i/gO4CKRyi09ApOzkBzljIQUt02XgxgeRjKcwzOILSujeXiv5BGhDnVJlW8h Co7w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:message-id:mime-version:subject:from:to:cc; bh=GPGWW1sqDohcwt84cAP0Nr+Zx9nz2MaTy8N1N1fJuDE=; b=p7GadOe5cfllISIg469dOkxSIyVSCbFf3cUk/liCMkbY8levIX1KFGS/guHWlVKr77 Od0Uq6KHC5kmgpFu8azGuEY0IUgpdHlJktbTkXQicskQ/6p6eY6IhRYn6rKWPxGfp3B3 kAq1jScytAa5J9fxygRC17nHECrTtnPG1s0uP63E71WG50IZ+2nq3rTogdHTaYv8Nb3q /oKYFnKR5KPxNhjCqiaYMGPTKqJQ8rZejR3a+r0D/DmJMrFp9a+1hdqoEzXv2bPq07Nm ZCC/PIw3vZ++wnw5aLPzqWx7gr3NXs6jYxLgCZkJSqBnl2c/VMPhUnV9ofkyKbgPp+57 bSTA== X-Gm-Message-State: ANhLgQ3Iba8VDb5zyA4FPJy2PwmIcIjLxmuZsuMSUSbuifqwRyL/azrX wXIVqpnYTqdAtCeq0ewWefPWf3bLDgLT8A== X-Received: by 2002:a17:90a:ac05:: with SMTP id o5mr898934pjq.143.1583965202377; Wed, 11 Mar 2020 15:20:02 -0700 (PDT) Date: Wed, 11 Mar 2020 15:19:57 -0700 Message-Id: <20200311221957.258154-1-davidgow@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.25.1.481.gfbce0eb801-goog Subject: [RFC PATCH kunit-next] kunit: kunit_tool: Separate out config/build/exec/parse From: David Gow To: Brendan Higgins Cc: linux-kselftest@vger.kernel.org, kunit-dev@googlegroups.com, linux-kernel@vger.kernel.org, David Gow Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Add new subcommands to kunit.py to allow stages of the existing 'run' subcommand to be run independently: - 'config': Verifies that .config is a subset of .kunitconfig - 'build': Compiles a UML kernel for KUnit - 'exec': Runs the kernel, and outputs the test results. - 'parse': Parses test results from a file or stdin Most of these are not hugely useful by themselves yet, and there's room for plenty of bikeshedding on the names, but this hopefully can form a foundation for future improvements. Signed-off-by: David Gow --- As was briefly disccussed in [1], this change is part of a "separation of concerns" in kunit_tool. This should make it easier to integrate kunit_tool into other setups. Of particular intrest is probably 'kunit.py parse', which should allow KUnit results to be parsed from other sources, such as after loading a module, or from a non-UML kernel, or from debugfs when that's supported[2]. [1]: https://lkml.org/lkml/2020/2/5/552 [2]: https://patchwork.kernel.org/patch/11419901/ tools/testing/kunit/kunit.py | 236 ++++++++++++++++++++----- tools/testing/kunit/kunit_tool_test.py | 55 ++++++ 2 files changed, 242 insertions(+), 49 deletions(-) diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 180ad1e1b04f..92a634594cf6 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -20,8 +20,12 @@ import kunit_config import kunit_kernel import kunit_parser -KunitResult = namedtuple('KunitResult', ['status','result']) +KunitResult = namedtuple('KunitResult', ['status','result','elapsed_time']) +KunitConfigRequest = namedtuple('KunitConfigRequest', ['build_dir', 'defconfig']) +KunitBuildRequest = namedtuple('KunitBuildRequest', ['jobs', 'build_dir']) +KunitExecRequest = namedtuple('KunitExecRequest', ['timeout', 'build_dir']) +KunitParseRequest = namedtuple('KunitParseRequest', ['raw_output', 'input_data']) KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 'build_dir', 'defconfig']) KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] @@ -44,14 +48,25 @@ def get_kernel_root_path(): sys.exit(1) return parts[0] -def run_tests(linux: kunit_kernel.LinuxSourceTree, - request: KunitRequest) -> KunitResult: +def config_tests(linux: kunit_kernel.LinuxSourceTree, + request: KunitConfigRequest) -> KunitResult: + kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...') + config_start = time.time() + if request.defconfig: + create_default_kunitconfig() success = linux.build_reconfig(request.build_dir) config_end = time.time() if not success: - return KunitResult(KunitStatus.CONFIG_FAILURE, 'could not configure kernel') + return KunitResult(KunitStatus.CONFIG_FAILURE, + 'could not configure kernel', + config_end - config_start) + return KunitResult(KunitStatus.SUCCESS, + 'configured kernel successfully', + config_end - config_start) +def build_tests(linux: kunit_kernel.LinuxSourceTree, + request: KunitBuildRequest) -> KunitResult: kunit_parser.print_with_timestamp('Building KUnit Kernel ...') build_start = time.time() @@ -59,73 +74,156 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, build_end = time.time() if not success: return KunitResult(KunitStatus.BUILD_FAILURE, 'could not build kernel') + if not success: + return KunitResult(KunitStatus.BUILD_FAILURE, + 'could not build kernel', + build_end - build_start) + return KunitResult(KunitStatus.SUCCESS, + 'built kernel successfully', + build_end - build_start) +def exec_tests(linux: kunit_kernel.LinuxSourceTree, + request: KunitExecRequest) -> KunitResult: kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') test_start = time.time() + result = linux.run_kernel(timeout=request.timeout, + build_dir=request.build_dir) + test_end = time.time() + + return KunitResult(KunitStatus.SUCCESS, + result, + test_end - test_start) + +def parse_tests(request: KunitParseRequest) -> KunitResult: + parse_start = time.time() + test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, [], 'Tests not Parsed.') if request.raw_output: - kunit_parser.raw_output( - linux.run_kernel(timeout=request.timeout, - build_dir=request.build_dir)) + kunit_parser.raw_output(request.input_data) else: - kunit_output = linux.run_kernel(timeout=request.timeout, - build_dir=request.build_dir) - test_result = kunit_parser.parse_run_tests(kunit_output) - test_end = time.time() + test_result = kunit_parser.parse_run_tests(request.input_data) + parse_end = time.time() + + if test_result.status != kunit_parser.TestStatus.SUCCESS: + return KunitResult(KunitStatus.TEST_FAILURE, test_result, + parse_end - parse_start) + + return KunitResult(KunitStatus.SUCCESS, test_result, + parse_end - parse_start) + + +def run_tests(linux: kunit_kernel.LinuxSourceTree, + request: KunitRequest) -> KunitResult: + run_start = time.time() + + config_request = KunitConfigRequest(request.build_dir, request.defconfig) + config_result = config_tests(linux, config_request) + if config_result.status != KunitStatus.SUCCESS: + return config_result + + build_request = KunitBuildRequest(request.jobs, request.build_dir) + build_result = build_tests(linux, build_request) + if build_result.status != KunitStatus.SUCCESS: + return build_result + + exec_request = KunitExecRequest(request.timeout, request.build_dir) + exec_result = exec_tests(linux, exec_request) + if exec_result.status != KunitStatus.SUCCESS: + return exec_result + + parse_request = KunitParseRequest(request.raw_output, exec_result.result) + parse_result = parse_tests(parse_request) + + run_end = time.time() kunit_parser.print_with_timestamp(( 'Elapsed time: %.3fs total, %.3fs configuring, %.3fs ' + 'building, %.3fs running\n') % ( - test_end - config_start, - config_end - config_start, - build_end - build_start, - test_end - test_start)) + run_end - run_start, + config_result.elapsed_time, + build_result.elapsed_time, + exec_result.elapsed_time)) + return parse_result + +def add_common_opts(parser): + parser.add_argument('--build_dir', + help='As in the make command, it specifies the build ' + 'directory.', + type=str, default='', metavar='build_dir') + +def add_config_opts(parser): + parser.add_argument('--defconfig', + help='Uses a default .kunitconfig.', + action='store_true') + +def add_build_opts(parser): + parser.add_argument('--jobs', + help='As in the make command, "Specifies the number of ' + 'jobs (commands) to run simultaneously."', + type=int, default=8, metavar='jobs') + +def add_exec_opts(parser): + parser.add_argument('--timeout', + help='maximum number of seconds to allow for all tests ' + 'to run. This does not include time taken to build the ' + 'tests.', + type=int, + default=300, + metavar='timeout') + +def add_parse_opts(parser): + parser.add_argument('--raw_output', help='don\'t format output from kernel', + action='store_true') - if test_result.status != kunit_parser.TestStatus.SUCCESS: - return KunitResult(KunitStatus.TEST_FAILURE, test_result) - else: - return KunitResult(KunitStatus.SUCCESS, test_result) def main(argv, linux=None): parser = argparse.ArgumentParser( description='Helps writing and running KUnit tests.') subparser = parser.add_subparsers(dest='subcommand') + # The 'run' command will config, build, exec, and parse in one go. run_parser = subparser.add_parser('run', help='Runs KUnit tests.') - run_parser.add_argument('--raw_output', help='don\'t format output from kernel', - action='store_true') + add_common_opts(run_parser) + add_config_opts(run_parser) + add_build_opts(run_parser) + add_exec_opts(run_parser) + add_parse_opts(run_parser) - run_parser.add_argument('--timeout', - help='maximum number of seconds to allow for all tests ' - 'to run. This does not include time taken to build the ' - 'tests.', - type=int, - default=300, - metavar='timeout') - - run_parser.add_argument('--jobs', - help='As in the make command, "Specifies the number of ' - 'jobs (commands) to run simultaneously."', - type=int, default=8, metavar='jobs') - - run_parser.add_argument('--build_dir', - help='As in the make command, it specifies the build ' - 'directory.', - type=str, default='', metavar='build_dir') - - run_parser.add_argument('--defconfig', - help='Uses a default .kunitconfig.', - action='store_true') + config_parser = subparser.add_parser('config', + help='Ensures that .config contains all of ' + 'the options in .kunitconfig') + add_common_opts(config_parser) + add_config_opts(config_parser) - cli_args = parser.parse_args(argv) + build_parser = subparser.add_parser('build', help='Builds a kernel with KUnit tests') + add_common_opts(build_parser) + add_build_opts(build_parser) - if cli_args.subcommand == 'run': - if get_kernel_root_path(): - os.chdir(get_kernel_root_path()) + exec_parser = subparser.add_parser('exec', help='Run a kernel with KUnit tests') + add_common_opts(exec_parser) + add_exec_opts(exec_parser) + add_parse_opts(exec_parser) + + # The 'parse' option is special, as it doesn't need the kernel source + # (therefore there is no need for a build_dir, hence no add_common_opts) + # and the '--file' argument is not relevant to 'run', so isn't in + # add_parse_opts() + parse_parser = subparser.add_parser('parse', + help='Parses KUnit results from a file, ' + 'and parses formatted results.') + add_parse_opts(parse_parser) + parse_parser.add_argument('file', + help='Specifies the file to read results from.', + type=str, default='-', metavar='input_file') + + cli_args = parser.parse_args(argv) + # Set up the build_dir and source tree for commands which use it + # (everything but 'parse') + if cli_args.subcommand != 'parse': if cli_args.build_dir: if not os.path.exists(cli_args.build_dir): os.mkdir(cli_args.build_dir) @@ -133,12 +231,11 @@ def main(argv, linux=None): cli_args.build_dir, kunit_kernel.kunitconfig_path) - if cli_args.defconfig: - create_default_kunitconfig() - if not linux: linux = kunit_kernel.LinuxSourceTree() + + if cli_args.subcommand == 'run': request = KunitRequest(cli_args.raw_output, cli_args.timeout, cli_args.jobs, @@ -147,6 +244,47 @@ def main(argv, linux=None): result = run_tests(linux, request) if result.status != KunitStatus.SUCCESS: sys.exit(1) + elif cli_args.subcommand == 'config': + request = KunitConfigRequest(cli_args.build_dir, + cli_args.defconfig) + result = config_tests(linux, request) + kunit_parser.print_with_timestamp(( + 'Elapsed time: %.3fs\n') % ( + result.elapsed_time)) + if result.status != KunitStatus.SUCCESS: + sys.exit(1) + elif cli_args.subcommand == 'build': + request = KunitBuildRequest(cli_args.jobs, + cli_args.build_dir) + result = build_tests(linux, request) + kunit_parser.print_with_timestamp(( + 'Elapsed time: %.3fs\n') % ( + result.elapsed_time)) + if result.status != KunitStatus.SUCCESS: + sys.exit(1) + elif cli_args.subcommand == 'exec': + exec_request = KunitExecRequest(cli_args.timeout, + cli_args.build_dir) + exec_result = exec_tests(linux, exec_request) + parse_request = KunitParseRequest(cli_args.raw_output, + exec_result.result) + result = parse_tests(parse_request) + kunit_parser.print_with_timestamp(( + 'Elapsed time: %.3fs\n') % ( + exec_result.elapsed_time)) + if result.status != KunitStatus.SUCCESS: + sys.exit(1) + elif cli_args.subcommand == 'parse': + if cli_args.file == '-': + kunit_output = sys.stdin + else: + with open(cli_args.file, 'r') as f: + kunit_output = f.read().splitlines() + request = KunitParseRequest(cli_args.raw_output, + kunit_output) + result = parse_tests(request) + if result.status != KunitStatus.SUCCESS: + sys.exit(1) else: parser.print_help() diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index cba97756ac4a..0d04425ead04 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -170,6 +170,24 @@ class KUnitMainTest(unittest.TestCase): self.print_patch.stop() pass + def test_config_passes_args_pass(self): + kunit.main(['config'], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 1 + assert self.linux_source_mock.run_kernel.call_count == 0 + + def test_build_passes_args_pass(self): + kunit.main(['build'], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 0 + self.linux_source_mock.build_um_kernel.assert_called_once_with(8, '') + assert self.linux_source_mock.run_kernel.call_count == 0 + + def test_exec_passes_args_pass(self): + kunit.main(['exec'], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 0 + assert self.linux_source_mock.run_kernel.call_count == 1 + self.linux_source_mock.run_kernel.assert_called_once_with(build_dir='', timeout=300) + self.print_mock.assert_any_call(StrContains('Testing complete.')) + def test_run_passes_args_pass(self): kunit.main(['run'], self.linux_source_mock) assert self.linux_source_mock.build_reconfig.call_count == 1 @@ -177,6 +195,13 @@ class KUnitMainTest(unittest.TestCase): self.linux_source_mock.run_kernel.assert_called_once_with(build_dir='', timeout=300) self.print_mock.assert_any_call(StrContains('Testing complete.')) + def test_exec_passes_args_fail(self): + self.linux_source_mock.run_kernel = mock.Mock(return_value=[]) + with self.assertRaises(SystemExit) as e: + kunit.main(['exec'], self.linux_source_mock) + assert type(e.exception) == SystemExit + assert e.exception.code == 1 + def test_run_passes_args_fail(self): self.linux_source_mock.run_kernel = mock.Mock(return_value=[]) with self.assertRaises(SystemExit) as e: @@ -187,6 +212,14 @@ class KUnitMainTest(unittest.TestCase): assert self.linux_source_mock.run_kernel.call_count == 1 self.print_mock.assert_any_call(StrContains(' 0 tests run')) + def test_exec_raw_output(self): + self.linux_source_mock.run_kernel = mock.Mock(return_value=[]) + kunit.main(['exec', '--raw_output'], self.linux_source_mock) + assert self.linux_source_mock.run_kernel.call_count == 1 + for kall in self.print_mock.call_args_list: + assert kall != mock.call(StrContains('Testing complete.')) + assert kall != mock.call(StrContains(' 0 tests run')) + def test_run_raw_output(self): self.linux_source_mock.run_kernel = mock.Mock(return_value=[]) kunit.main(['run', '--raw_output'], self.linux_source_mock) @@ -196,6 +229,12 @@ class KUnitMainTest(unittest.TestCase): assert kall != mock.call(StrContains('Testing complete.')) assert kall != mock.call(StrContains(' 0 tests run')) + def test_exec_timeout(self): + timeout = 3453 + kunit.main(['exec', '--timeout', str(timeout)], self.linux_source_mock) + self.linux_source_mock.run_kernel.assert_called_once_with(build_dir='', timeout=timeout) + self.print_mock.assert_any_call(StrContains('Testing complete.')) + def test_run_timeout(self): timeout = 3453 kunit.main(['run', '--timeout', str(timeout)], self.linux_source_mock) @@ -210,5 +249,21 @@ class KUnitMainTest(unittest.TestCase): self.linux_source_mock.run_kernel.assert_called_once_with(build_dir=build_dir, timeout=300) self.print_mock.assert_any_call(StrContains('Testing complete.')) + def test_config_builddir(self): + build_dir = '.kunit' + kunit.main(['config', '--build_dir', build_dir], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 1 + + def test_build_builddir(self): + build_dir = '.kunit' + kunit.main(['build', '--build_dir', build_dir], self.linux_source_mock) + self.linux_source_mock.build_um_kernel.assert_called_once_with(8, build_dir) + + def test_exec_builddir(self): + build_dir = '.kunit' + kunit.main(['exec', '--build_dir', build_dir], self.linux_source_mock) + self.linux_source_mock.run_kernel.assert_called_once_with(build_dir=build_dir, timeout=300) + self.print_mock.assert_any_call(StrContains('Testing complete.')) + if __name__ == '__main__': unittest.main() -- 2.25.1.481.gfbce0eb801-goog