Coverage for coverage / xmlreport.py: 100.000%

165 statements  

« prev     ^ index     » next       coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000

1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 

2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt 

3 

4"""XML reporting for coverage.py""" 

5 

6from __future__ import annotations 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

7 

8import os 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

9import os.path 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

10import sys 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

11import time 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

12import xml.dom.minidom 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

13from collections.abc import Iterable 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

14from dataclasses import dataclass 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

15from typing import IO, TYPE_CHECKING, Any 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

16 

17from coverage import __version__, files 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

18from coverage.misc import human_sorted, human_sorted_items, isolate_module 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

19from coverage.plugin import FileReporter 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

20from coverage.report_core import get_analysis_to_report 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

21from coverage.results import Analysis 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

22from coverage.types import TMorf 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

23from coverage.version import __url__ 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

24 

25if TYPE_CHECKING: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

26 from coverage import Coverage 

27 

28os = isolate_module(os) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

29 

30 

31DTD_URL = "https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd" 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

32 

33 

34def rate(hit: int, num: int) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

35 """Return the fraction of `hit`/`num`, as a string.""" 

36 if num == 0: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

37 return "1" 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

38 else: 

39 return f"{hit / num:.4g}" 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

40 

41 

42@dataclass 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

43class PackageData: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

44 """Data we keep about each "package" (in Java terms).""" 

45 

46 elements: dict[str, xml.dom.minidom.Element] 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

47 hits: int 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

48 lines: int 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

49 br_hits: int 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

50 branches: int 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

51 

52 

53def appendChild(parent: Any, child: Any) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

54 """Append a child to a parent, in a way mypy will shut up about.""" 

55 parent.appendChild(child) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

56 

57 

58class XmlReporter: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

59 """A reporter for writing Cobertura-style XML coverage results.""" 

60 

61 report_type = "XML report" 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

62 

63 def __init__(self, coverage: Coverage) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

64 self.coverage = coverage 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

65 self.config = self.coverage.config 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

66 

67 self.source_paths = set() 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

68 if self.config.source: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

69 for src in self.config.source: 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQRSTUVWXYZ01234

70 if os.path.exists(src): 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQRSTUVWXYZ01234

71 if self.config.relative_files: 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQRSTUVWXYZ01234

72 src = src.rstrip(r"\/") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

73 else: 

74 src = files.canonical_filename(src) 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQRSTUVWXYZ01234

75 self.source_paths.add(src) 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQRSTUVWXYZ01234

76 self.packages: dict[str, PackageData] = {} 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

77 self.xml_out: xml.dom.minidom.Document 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

78 

79 def report(self, morfs: Iterable[TMorf] | None, outfile: IO[str] | None = None) -> float: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

80 """Generate a Cobertura-compatible XML report for `morfs`. 

81 

82 `morfs` is a list of modules or file names. 

83 

84 `outfile` is a file object to write the XML to. 

85 

86 """ 

87 # Initial setup. 

88 outfile = outfile or sys.stdout 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

89 has_arcs = self.coverage.get_data().has_arcs() 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

90 

91 # Create the DOM that will store the data. 

92 impl = xml.dom.minidom.getDOMImplementation() 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

93 assert impl is not None 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

94 self.xml_out = impl.createDocument(None, "coverage", None) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

95 

96 # Write header stuff. 

97 xcoverage = self.xml_out.documentElement 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

98 assert xcoverage is not None 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

99 xcoverage.setAttribute("version", __version__) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

100 xcoverage.setAttribute("timestamp", str(int(time.time() * 1000))) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

101 xcoverage.appendChild( 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

102 self.xml_out.createComment( 

103 f" Generated by coverage.py: {__url__} ", 

104 ) 

105 ) 

106 xcoverage.appendChild(self.xml_out.createComment(f" Based on {DTD_URL} ")) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

107 

108 # Call xml_file for each file in the data. 

109 for fr, analysis in get_analysis_to_report(self.coverage, morfs): 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

110 self.xml_file(fr, analysis, has_arcs) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

111 

112 xsources = self.xml_out.createElement("sources") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

113 xcoverage.appendChild(xsources) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

114 

115 # Populate the XML DOM with the source info. 

116 for path in human_sorted(self.source_paths): 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

117 xsource = self.xml_out.createElement("source") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

118 appendChild(xsources, xsource) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

119 txt = self.xml_out.createTextNode(path) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

120 appendChild(xsource, txt) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

121 

122 lnum_tot, lhits_tot = 0, 0 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

123 bnum_tot, bhits_tot = 0, 0 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

124 

125 xpackages = self.xml_out.createElement("packages") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

126 xcoverage.appendChild(xpackages) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

127 

128 # Populate the XML DOM with the package info. 

129 for pkg_name, pkg_data in human_sorted_items(self.packages.items()): 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

130 xpackage = self.xml_out.createElement("package") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

131 appendChild(xpackages, xpackage) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

132 xclasses = self.xml_out.createElement("classes") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

133 appendChild(xpackage, xclasses) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

134 for _, class_elt in human_sorted_items(pkg_data.elements.items()): 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

135 appendChild(xclasses, class_elt) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

136 xpackage.setAttribute("name", pkg_name.replace(os.sep, ".")) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

137 xpackage.setAttribute("line-rate", rate(pkg_data.hits, pkg_data.lines)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

138 if has_arcs: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

139 branch_rate = rate(pkg_data.br_hits, pkg_data.branches) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

140 else: 

141 branch_rate = "0" 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

142 xpackage.setAttribute("branch-rate", branch_rate) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

143 xpackage.setAttribute("complexity", "0") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

144 

145 lhits_tot += pkg_data.hits 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

146 lnum_tot += pkg_data.lines 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

147 bhits_tot += pkg_data.br_hits 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

148 bnum_tot += pkg_data.branches 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

149 

150 xcoverage.setAttribute("lines-valid", str(lnum_tot)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

151 xcoverage.setAttribute("lines-covered", str(lhits_tot)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

152 xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

153 if has_arcs: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

154 xcoverage.setAttribute("branches-valid", str(bnum_tot)) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

155 xcoverage.setAttribute("branches-covered", str(bhits_tot)) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

156 xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot)) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

157 else: 

158 xcoverage.setAttribute("branches-covered", "0") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

159 xcoverage.setAttribute("branches-valid", "0") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

160 xcoverage.setAttribute("branch-rate", "0") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

161 xcoverage.setAttribute("complexity", "0") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

162 

163 # Write the output file. 

164 outfile.write(serialize_xml(self.xml_out)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

165 

166 # Return the total percentage. 

167 denom = lnum_tot + bnum_tot 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

168 if denom == 0: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

169 pct = 0.0 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

170 else: 

171 pct = 100.0 * (lhits_tot + bhits_tot) / denom 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

172 return pct 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

173 

174 def xml_file(self, fr: FileReporter, analysis: Analysis, has_arcs: bool) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

175 """Add to the XML report for a single file.""" 

176 

177 if self.config.skip_empty: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

178 if analysis.numbers.n_statements == 0: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

179 return 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

180 

181 # Create the "lines" and "package" XML elements, which 

182 # are populated later. Note that a package == a directory. 

183 filename = fr.filename.replace("\\", "/") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

184 for source_path in self.source_paths: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

185 if not self.config.relative_files: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

186 source_path = files.canonical_filename(source_path) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

187 if filename.startswith(source_path.replace("\\", "/") + "/"): 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

188 rel_name = filename[len(source_path) + 1 :] 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQR6ST7UVWXYZ01234

189 break 1abcdefghijklmnopqrstuvwxyzABCDEFGH5IJKLMNOPQR6ST7UVWXYZ01234

190 else: 

191 rel_name = fr.relative_filename().replace("\\", "/") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

192 self.source_paths.add(fr.filename[: -len(rel_name)].rstrip(r"\/")) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

193 

194 dirname = os.path.dirname(rel_name) or "." 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

195 dirname = "/".join(dirname.split("/")[: self.config.xml_package_depth]) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

196 package_name = dirname.replace("/", ".") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

197 

198 package = self.packages.setdefault(package_name, PackageData({}, 0, 0, 0, 0)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

199 

200 xclass: xml.dom.minidom.Element = self.xml_out.createElement("class") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

201 

202 appendChild(xclass, self.xml_out.createElement("methods")) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

203 

204 xlines = self.xml_out.createElement("lines") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

205 appendChild(xclass, xlines) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

206 

207 xclass.setAttribute("name", os.path.relpath(rel_name, dirname)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

208 xclass.setAttribute("filename", rel_name.replace("\\", "/")) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

209 xclass.setAttribute("complexity", "0") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

210 

211 branch_stats = analysis.branch_stats() 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

212 missing_branch_arcs = analysis.missing_branch_arcs() 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

213 

214 # For each statement, create an XML "line" element. 

215 for line in sorted(analysis.statements): 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

216 xline = self.xml_out.createElement("line") 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

217 xline.setAttribute("number", str(line)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

218 

219 # Q: can we get info about the number of times a statement is 

220 # executed? If so, that should be recorded here. 

221 xline.setAttribute("hits", str(int(line not in analysis.missing))) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

222 

223 if has_arcs: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

224 if line in branch_stats: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

225 total, taken = branch_stats[line] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

226 xline.setAttribute("branch", "true") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

227 xline.setAttribute( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

228 "condition-coverage", 

229 f"{100 * taken // total}% ({taken}/{total})", 

230 ) 

231 if line in missing_branch_arcs: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

232 annlines = ["exit" if b < 0 else str(b) for b in missing_branch_arcs[line]] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

233 xline.setAttribute("missing-branches", ",".join(annlines)) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

234 appendChild(xlines, xline) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

235 

236 class_lines = len(analysis.statements) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

237 class_hits = class_lines - len(analysis.missing) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

238 

239 if has_arcs: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

240 class_branches = sum(t for t, k in branch_stats.values()) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

241 missing_branches = sum(t - k for t, k in branch_stats.values()) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

242 class_br_hits = class_branches - missing_branches 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

243 else: 

244 class_branches = 0 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

245 class_br_hits = 0 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

246 

247 # Finalize the statistics that are collected in the XML DOM. 

248 xclass.setAttribute("line-rate", rate(class_hits, class_lines)) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

249 if has_arcs: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

250 branch_rate = rate(class_br_hits, class_branches) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234

251 else: 

252 branch_rate = "0" 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

253 xclass.setAttribute("branch-rate", branch_rate) 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

254 

255 package.elements[rel_name] = xclass 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

256 package.hits += class_hits 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

257 package.lines += class_lines 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

258 package.br_hits += class_br_hits 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

259 package.branches += class_branches 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

260 

261 

262def serialize_xml(dom: xml.dom.minidom.Document) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234

263 """Serialize a minidom node to XML.""" 

264 return dom.toprettyxml() 1abcdefghijklmnopqrstuvwxyzABCDEF8GH5IJ9KL!MN#OP$QR6ST7UV%WX'YZ(01)234