> -----Original Message----- > From: Brendan Higgins > Sent: Friday, August 7, 2020 7:17 PM > > From: Heidi Fahim <heidifahim@xxxxxxxxxx> > > Add a --json flag, which when specified when kunit_tool is run, > generates JSON formatted test results conforming to the KernelCI API > test_group spec[1]. The user can the new flag to specify a filename as Seems to be missing a word. "The user can ? the new flag" > the value to json in order to store the JSON results under linux/. > > Link[1]: https://api.kernelci.org/schema-test-group.html#post > Signed-off-by: Heidi Fahim <heidifahim@xxxxxxxxxx> > Signed-off-by: Brendan Higgins <brendanhiggins@xxxxxxxxxx> > --- > tools/testing/kunit/kunit.py | 35 +++++++++++--- > tools/testing/kunit/kunit_json.py | 63 ++++++++++++++++++++++++++ > tools/testing/kunit/kunit_tool_test.py | 33 ++++++++++++++ > 3 files changed, 125 insertions(+), 6 deletions(-) > create mode 100644 tools/testing/kunit/kunit_json.py > > diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py > index 96344a11ff1f..485b7c63b967 100755 > --- a/tools/testing/kunit/kunit.py > +++ b/tools/testing/kunit/kunit.py > @@ -17,6 +17,7 @@ from collections import namedtuple > from enum import Enum, auto > > import kunit_config > +import kunit_json > import kunit_kernel > import kunit_parser > > @@ -30,9 +31,9 @@ KunitBuildRequest = namedtuple('KunitBuildRequest', > KunitExecRequest = namedtuple('KunitExecRequest', > ['timeout', 'build_dir', 'alltests']) > KunitParseRequest = namedtuple('KunitParseRequest', > - ['raw_output', 'input_data']) > + ['raw_output', 'input_data', 'build_dir', 'json']) > KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', > - 'build_dir', 'alltests', > + 'build_dir', 'alltests', 'json', > 'make_options']) > > KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] > @@ -113,12 +114,22 @@ def parse_tests(request: KunitParseRequest) -> KunitResult: > test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, > [], > 'Tests not Parsed.') > + > if request.raw_output: > kunit_parser.raw_output(request.input_data) > else: > test_result = kunit_parser.parse_run_tests(request.input_data) > parse_end = time.time() > > + if request.json: > + json_obj = kunit_json.get_json_result( > + test_result=test_result, > + def_config='kunit_defconfig', > + build_dir=request.build_dir, > + json_path=request.json) > + if request.json == 'stdout': > + print(json_obj) > + > if test_result.status != kunit_parser.TestStatus.SUCCESS: > return KunitResult(KunitStatus.TEST_FAILURE, test_result, > parse_end - parse_start) > @@ -151,7 +162,9 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, > return exec_result > > parse_request = KunitParseRequest(request.raw_output, > - exec_result.result) > + exec_result.result, > + request.build_dir, > + request.json) > parse_result = parse_tests(parse_request) > > run_end = time.time() > @@ -195,7 +208,12 @@ def add_exec_opts(parser): > def add_parse_opts(parser): > parser.add_argument('--raw_output', help='don\'t format output from kernel', > action='store_true') > - > + parser.add_argument('--json', > + nargs='?', > + help='Stores test results in a JSON, and either ' > + 'prints to stdout or saves to file if a ' > + 'filename is specified', > + type=str, const='stdout', default=None) > > def main(argv, linux=None): > parser = argparse.ArgumentParser( > @@ -254,6 +272,7 @@ def main(argv, linux=None): > cli_args.jobs, > cli_args.build_dir, > cli_args.alltests, > + cli_args.json, > cli_args.make_options) > result = run_tests(linux, request) > if result.status != KunitStatus.SUCCESS: > @@ -308,7 +327,9 @@ def main(argv, linux=None): > cli_args.alltests) > exec_result = exec_tests(linux, exec_request) > parse_request = KunitParseRequest(cli_args.raw_output, > - exec_result.result) > + exec_result.result, > + cli_args.build_dir, > + cli_args.json) > result = parse_tests(parse_request) > kunit_parser.print_with_timestamp(( > 'Elapsed time: %.3fs\n') % ( > @@ -322,7 +343,9 @@ def main(argv, linux=None): > with open(cli_args.file, 'r') as f: > kunit_output = f.read().splitlines() > request = KunitParseRequest(cli_args.raw_output, > - kunit_output) > + kunit_output, > + cli_args.build_dir, > + cli_args.json) > result = parse_tests(request) > if result.status != KunitStatus.SUCCESS: > sys.exit(1) > diff --git a/tools/testing/kunit/kunit_json.py b/tools/testing/kunit/kunit_json.py > new file mode 100644 > index 000000000000..624b31b2dbd6 > --- /dev/null > +++ b/tools/testing/kunit/kunit_json.py > @@ -0,0 +1,63 @@ > +# SPDX-License-Identifier: GPL-2.0 > +# > +# Generates JSON from KUnit results according to > +# KernelCI spec: https://github.com/kernelci/kernelci-doc/wiki/Test-API > +# > +# Copyright (C) 2020, Google LLC. > +# Author: Heidi Fahim <heidifahim@xxxxxxxxxx> > + > +import json > +import os > + > +import kunit_parser > + > +from kunit_parser import TestStatus > + > +def get_json_result(test_result, def_config, build_dir, json_path): > + sub_groups = [] > + > + # Each test suite is mapped to a KernelCI sub_group > + for test_suite in test_result.suites: > + sub_group = { > + "name": test_suite.name, > + "arch": "UM", > + "defconfig": def_config, > + "build_environment": build_dir, > + "test_cases": [], > + "lab_name": None, > + "kernel": None, > + "job": None, > + "git_branch": "kselftest", > + } > + test_cases = [] > + # TODO: Add attachments attribute in test_case with detailed > + # failure message, see https://api.kernelci.org/schema-test-case.html#get > + for case in test_suite.cases: > + test_case = {"name": case.name, "status": "FAIL"} > + if case.status == TestStatus.SUCCESS: > + test_case["status"] = "PASS" > + elif case.status == TestStatus.TEST_CRASHED: > + test_case["status"] = "ERROR" > + test_cases.append(test_case) > + sub_group["test_cases"] = test_cases > + sub_groups.append(sub_group) > + test_group = { > + "name": "KUnit Test Group", > + "arch": "UM", > + "defconfig": def_config, > + "build_environment": build_dir, > + "sub_groups": sub_groups, > + "lab_name": None, > + "kernel": None, > + "job": None, > + "git_branch": "kselftest", > + } > + json_obj = json.dumps(test_group, indent=4) > + if json_path != 'stdout': > + with open(json_path, 'w') as result_path: > + result_path.write(json_obj) > + root = __file__.split('tools/testing/kunit/')[0] > + kunit_parser.print_with_timestamp( > + "Test results stored in %s" % > + os.path.join(root, result_path.name)) > + return json_obj > diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py > index 287c74d821c3..99c3c5671ea4 100755 > --- a/tools/testing/kunit/kunit_tool_test.py > +++ b/tools/testing/kunit/kunit_tool_test.py > @@ -11,11 +11,13 @@ from unittest import mock > > import tempfile, shutil # Handling test_tmpdir > > +import json > import os > > import kunit_config > import kunit_parser > import kunit_kernel > +import kunit_json > import kunit > > test_tmpdir = '' > @@ -230,6 +232,37 @@ class KUnitParserTest(unittest.TestCase): > result = kunit_parser.parse_run_tests(file.readlines()) > self.assertEqual('kunit-resource-test', result.suites[0].name) > > +class KUnitJsonTest(unittest.TestCase): > + > + def _json_for(self, log_file): > + with(open(get_absolute_path(log_file))) as file: > + test_result = kunit_parser.parse_run_tests(file) > + json_obj = kunit_json.get_json_result( > + test_result=test_result, > + def_config='kunit_defconfig', > + build_dir=None, > + json_path='stdout') > + return json.loads(json_obj) > + > + def test_failed_test_json(self): > + result = self._json_for( > + 'test_data/test_is_test_passed-failure.log') > + self.assertEqual( > + {'name': 'example_simple_test', 'status': 'FAIL'}, > + result["sub_groups"][1]["test_cases"][0]) > + > + def test_crashed_test_json(self): > + result = self._json_for( > + 'test_data/test_is_test_passed-crash.log') > + self.assertEqual( > + {'name': 'example_simple_test', 'status': 'ERROR'}, > + result["sub_groups"][1]["test_cases"][0]) > + > + def test_no_tests_json(self): > + result = self._json_for( > + 'test_data/test_is_test_passed-no_tests_run.log') > + self.assertEqual(0, len(result['sub_groups'])) > + > class StrContains(str): > def __eq__(self, other): > return self in other > -- > 2.28.0.236.gb10cc79966-goog