xref: /openbmc/openbmc/poky/scripts/lib/resulttool/junit.py (revision 8460358c3d24c71d9d38fd126c745854a6301564)
1# resulttool - report test results in JUnit XML format
2#
3# Copyright (c) 2024, Siemens AG.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8import os
9import re
10import xml.etree.ElementTree as ET
11import resulttool.resultutils as resultutils
12
13def junit(args, logger):
14    testresults = resultutils.load_resultsdata(args.json_file, configmap=resultutils.store_map)
15
16    total_time = 0
17    skipped = 0
18    failures = 0
19    errors = 0
20
21    for tests in testresults.values():
22        results = tests[next(reversed(tests))].get("result", {})
23
24    for result_id, result in results.items():
25        # filter out ptestresult.rawlogs and ptestresult.sections
26        if re.search(r'\.test_', result_id):
27            total_time += result.get("duration", 0)
28
29            if result['status'] == "FAILED":
30                failures += 1
31            elif result['status'] == "ERROR":
32                errors += 1
33            elif result['status'] == "SKIPPED":
34                skipped += 1
35
36    testsuites_node = ET.Element("testsuites")
37    testsuites_node.set("time", "%s" % total_time)
38    testsuite_node = ET.SubElement(testsuites_node, "testsuite")
39    testsuite_node.set("name", "Testimage")
40    testsuite_node.set("time", "%s" % total_time)
41    testsuite_node.set("tests", "%s" % len(results))
42    testsuite_node.set("failures", "%s" % failures)
43    testsuite_node.set("errors", "%s" % errors)
44    testsuite_node.set("skipped", "%s" % skipped)
45
46    for result_id, result in results.items():
47        if re.search(r'\.test_', result_id):
48            testcase_node = ET.SubElement(testsuite_node, "testcase", {
49                "name": result_id,
50                "classname": "Testimage",
51                "time": str(result['duration'])
52            })
53            if result['status'] == "SKIPPED":
54                ET.SubElement(testcase_node, "skipped", message=result['log'])
55            elif result['status'] == "FAILED":
56                ET.SubElement(testcase_node, "failure", message=result['log'])
57            elif result['status'] == "ERROR":
58                ET.SubElement(testcase_node, "error", message=result['log'])
59
60    tree = ET.ElementTree(testsuites_node)
61
62    if args.junit_xml_path is None:
63        args.junit_xml_path = os.environ['BUILDDIR'] + '/tmp/log/oeqa/junit.xml'
64    tree.write(args.junit_xml_path, encoding='UTF-8', xml_declaration=True)
65
66    logger.info('Saved JUnit XML report as %s' % args.junit_xml_path)
67
68def register_commands(subparsers):
69    """Register subcommands from this plugin"""
70    parser_build = subparsers.add_parser('junit', help='create test report in JUnit XML format',
71                                         description='generate unit test report in JUnit XML format based on the latest test results in the testresults.json.',
72                                         group='analysis')
73    parser_build.set_defaults(func=junit)
74    parser_build.add_argument('json_file',
75                              help='json file should point to the testresults.json')
76    parser_build.add_argument('-j', '--junit_xml_path',
77                              help='junit xml path allows setting the path of the generated test report. The default location is <build_dir>/tmp/log/oeqa/junit.xml')
78