Coverage for /builds/svesch/cikit/src/cikit/xml_tools.py: 96%
44 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-09 14:17 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-09 14:17 +0000
1# SPDX-License-Identifier: GPL-3.0-or-later
2'''
3brief
5detailed
7Author:
8 Sven Schlender
9License:
10 SPDX-License-Identifier: GPL-3.0-or-later
11Date:
12 2026-03-06
13'''
15from pathlib import Path
16import xml.etree.ElementTree as ET
18from cikit.errors import CiToolsError
21def read_junit_totals(test_container: str, xml_file: Path) -> dict:
22 """Read aggregated test statistics from a JUnit-style XML file.
24 Supports pytest and GoogleTest XML formats. All <testsuite> elements
25 are scanned and attributes are summed up. GoogleTest uses "disabled"
26 instead of "skipped", which is mapped accordingly.
28 Args:
29 test_container: Name of the tests container
30 xml_file: Path to the JUnit XML file.
32 Returns:
33 Dict containing tests, failures, errors and skipped counts.
35 Raises:
36 ValueError: If the XML file cannot be parsed.
37 """
38 try:
39 tree = ET.parse(xml_file)
40 except ET.ParseError as exp:
41 raise ValueError(f"Invalid XML file: {xml_file}") from exp
43 root = tree.getroot()
45 totals = {
46 "name": test_container,
47 "tests": 0,
48 "failures": 0,
49 "errors": 0,
50 "skipped": 0,
51 }
53 for suite in root.iter("testsuite"):
54 for key in ("tests", "failures", "errors"):
55 value = suite.get(key)
56 if value: 56 ↛ 54line 56 didn't jump to line 54 because the condition on line 56 was always true
57 totals[key] += int(value)
59 # pytest: skipped
60 skipped = suite.get("skipped")
62 # gtest: disabled
63 disabled = suite.get("disabled")
65 if skipped:
66 totals["skipped"] += int(skipped)
67 elif disabled: 67 ↛ 53line 67 didn't jump to line 53 because the condition on line 67 was always true
68 totals["skipped"] += int(disabled)
70 totals["succeeded"] = (totals["tests"] - totals["failures"] -
71 totals["errors"] - totals["skipped"])
73 return totals
76def read_cobertura_coverage(xml_file: Path) -> dict:
77 """Read coverage statistics from a Cobertura XML file.
79 The Cobertura root element provides the overall coverage metrics as
80 attributes. This function extracts the line coverage ratio as well as
81 the absolute number of covered and valid lines.
83 Args:
84 xml_file: Path to the Cobertura XML file.
86 Returns:
87 Dict containing:
88 coverage: Coverage percentage rounded to one decimal place (0.0 .. 100.0).
89 lines_covered: Number of executed lines.
90 lines_valid: Total number of executable lines.
92 Raises:
93 CiToolsError: If the XML file cannot be parsed or required values
94 are missing or invalid.
95 """
96 try:
97 tree = ET.parse(xml_file)
98 except ET.ParseError as exp:
99 raise CiToolsError(f"Invalid XML file: {xml_file}") from exp
101 root = tree.getroot()
103 raw_line_rate = root.get("line-rate")
104 raw_lines_covered = root.get("lines-covered")
105 raw_lines_valid = root.get("lines-valid")
107 if raw_line_rate is None:
108 raise CiToolsError(f"Missing line-rate in XML file: {xml_file}")
110 try:
111 line_rate = float(raw_line_rate.replace(",", "."))
112 except ValueError as exp:
113 raise CiToolsError(f"Invalid line-rate value {raw_line_rate!r} in XML "
114 f"file: {xml_file}") from exp
116 try:
117 lines_covered = int(raw_lines_covered) if raw_lines_covered else 0
118 lines_valid = int(raw_lines_valid) if raw_lines_valid else 0
119 except ValueError as exp:
120 raise CiToolsError(f"Invalid line counters in XML file: {xml_file}") from exp
122 return {
123 "coverage": round(line_rate * 100, 1),
124 "lines_covered": lines_covered,
125 "lines_valid": lines_valid,
126 }