xref: /linux/tools/testing/kunit/kunit_junit.py (revision 056a5087d87ead77dedbe9cf5bde53b7cd4b4651)
1# SPDX-License-Identifier: GPL-2.0
2#
3# Generates JUnit XML files from KUnit test results
4#
5# Copyright (C) 2026, Google LLC and David Gow.
6
7from xml.sax.saxutils import quoteattr, XMLGenerator
8import xml.etree.ElementTree as ET
9from kunit_parser import Test, TestStatus
10from typing import Optional
11
12# Get a string representing a tes suite (including subtests) in JUnit XML
13def get_test_suite(test: Test, parent: Optional[ET.Element]) -> ET.Element:
14	suite_attrs = {
15		'name': test.name,
16		'tests': str(test.counts.total()),
17		'failures': str(test.counts.failed),
18		'skipped': str(test.counts.skipped),
19		'errors': str(test.counts.crashed + test.counts.errors),
20	}
21
22	if parent is not None:
23		test_suite_element = ET.SubElement(parent, 'testsuite', suite_attrs)
24	else:
25		test_suite_element = ET.Element('testsuite', suite_attrs)
26
27	for subtest in test.subtests:
28		if subtest.subtests:
29			get_test_suite(subtest, test_suite_element)
30			continue
31		test_case_element = ET.SubElement(test_suite_element, 'testcase', {'name': subtest.name})
32		if subtest.status == TestStatus.FAILURE:
33			ET.SubElement(test_case_element, 'failure', {}).text = 'Test Failed'
34		elif subtest.status == TestStatus.SKIPPED:
35			ET.SubElement(test_case_element, 'skipped', {}).text = subtest.skip_reason
36		elif subtest.status == TestStatus.TEST_CRASHED:
37			ET.SubElement(test_case_element, 'error', {}).text = 'Test Crashed'
38
39		if subtest.log:
40			ET.SubElement(test_case_element, 'system-out', {}).text = "\n".join(subtest.log)
41
42	return test_suite_element
43
44# Get a string for an entire XML file for the test structure starting at test
45def get_junit_result(test: Test) -> str:
46	root_element = get_test_suite(test, None)
47	ET.indent(root_element)
48	return ET.tostring(root_element, encoding="unicode", xml_declaration=True)
49
50# Print a JUnit result to stdout.
51def print_junit_result(test: Test) -> None:
52	root_element = get_test_suite(test, None)
53	ET.indent(root_element)
54	ET.dump(root_element)
55
56# Write an entire XML file for the test structure starting at test
57def write_junit_result(test: Test, filename: str) -> None:
58	root_element = get_test_suite(test, None)
59	ET.indent(root_element)
60	root_et = ET.ElementTree(root_element)
61	root_et.write(filename, encoding='utf-8', xml_declaration=True)
62