eric6/DebugClients/Python/coverage/xmlreport.py

branch
multi_processing
changeset 7428
27c55a3d0b89
parent 7427
362cd1b6f81a
child 7702
f8b97639deb5
equal deleted inserted replaced
7424:9bb7d8b0f966 7428:27c55a3d0b89
1 # coding: utf-8 1 # coding: utf-8
2 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
3 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt 3 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
4 4
5 """XML reporting for coverage.py""" 5 """XML reporting for coverage.py"""
6 6
7 import os 7 import os
8 import os.path 8 import os.path
9 import re
10 import sys 9 import sys
11 import time 10 import time
12 import xml.dom.minidom 11 import xml.dom.minidom
13 12
14 from coverage import env 13 from coverage import env
15 from coverage import __url__, __version__, files 14 from coverage import __url__, __version__, files
16 from coverage.backward import iitems 15 from coverage.backward import iitems
17 from coverage.misc import isolate_module 16 from coverage.misc import isolate_module
18 from coverage.report import Reporter 17 from coverage.report import get_analysis_to_report
19 18
20 os = isolate_module(os) 19 os = isolate_module(os)
21 20
22 21
23 DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd' 22 DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd'
29 return "1" 28 return "1"
30 else: 29 else:
31 return "%.4g" % (float(hit) / num) 30 return "%.4g" % (float(hit) / num)
32 31
33 32
34 class XmlReporter(Reporter): 33 class XmlReporter(object):
35 """A reporter for writing Cobertura-style XML coverage results.""" 34 """A reporter for writing Cobertura-style XML coverage results."""
36 35
37 def __init__(self, coverage, config): 36 def __init__(self, coverage):
38 super(XmlReporter, self).__init__(coverage, config) 37 self.coverage = coverage
38 self.config = self.coverage.config
39 39
40 self.source_paths = set() 40 self.source_paths = set()
41 if config.source: 41 if self.config.source:
42 for src in config.source: 42 for src in self.config.source:
43 if os.path.exists(src): 43 if os.path.exists(src):
44 self.source_paths.add(files.canonical_filename(src)) 44 self.source_paths.add(files.canonical_filename(src))
45 self.packages = {} 45 self.packages = {}
46 self.xml_out = None 46 self.xml_out = None
47 self.has_arcs = coverage.data.has_arcs()
48 47
49 def report(self, morfs, outfile=None): 48 def report(self, morfs, outfile=None):
50 """Generate a Cobertura-compatible XML report for `morfs`. 49 """Generate a Cobertura-compatible XML report for `morfs`.
51 50
52 `morfs` is a list of modules or file names. 51 `morfs` is a list of modules or file names.
54 `outfile` is a file object to write the XML to. 53 `outfile` is a file object to write the XML to.
55 54
56 """ 55 """
57 # Initial setup. 56 # Initial setup.
58 outfile = outfile or sys.stdout 57 outfile = outfile or sys.stdout
58 has_arcs = self.coverage.get_data().has_arcs()
59 59
60 # Create the DOM that will store the data. 60 # Create the DOM that will store the data.
61 impl = xml.dom.minidom.getDOMImplementation() 61 impl = xml.dom.minidom.getDOMImplementation()
62 self.xml_out = impl.createDocument(None, "coverage", None) 62 self.xml_out = impl.createDocument(None, "coverage", None)
63 63
69 " Generated by coverage.py: %s " % __url__ 69 " Generated by coverage.py: %s " % __url__
70 )) 70 ))
71 xcoverage.appendChild(self.xml_out.createComment(" Based on %s " % DTD_URL)) 71 xcoverage.appendChild(self.xml_out.createComment(" Based on %s " % DTD_URL))
72 72
73 # Call xml_file for each file in the data. 73 # Call xml_file for each file in the data.
74 self.report_files(self.xml_file, morfs) 74 for fr, analysis in get_analysis_to_report(self.coverage, morfs):
75 self.xml_file(fr, analysis, has_arcs)
75 76
76 xsources = self.xml_out.createElement("sources") 77 xsources = self.xml_out.createElement("sources")
77 xcoverage.appendChild(xsources) 78 xcoverage.appendChild(xsources)
78 79
79 # Populate the XML DOM with the source info. 80 # Populate the XML DOM with the source info.
98 xpackage.appendChild(xclasses) 99 xpackage.appendChild(xclasses)
99 for _, class_elt in sorted(iitems(class_elts)): 100 for _, class_elt in sorted(iitems(class_elts)):
100 xclasses.appendChild(class_elt) 101 xclasses.appendChild(class_elt)
101 xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) 102 xpackage.setAttribute("name", pkg_name.replace(os.sep, '.'))
102 xpackage.setAttribute("line-rate", rate(lhits, lnum)) 103 xpackage.setAttribute("line-rate", rate(lhits, lnum))
103 if self.has_arcs: 104 if has_arcs:
104 branch_rate = rate(bhits, bnum) 105 branch_rate = rate(bhits, bnum)
105 else: 106 else:
106 branch_rate = "0" 107 branch_rate = "0"
107 xpackage.setAttribute("branch-rate", branch_rate) 108 xpackage.setAttribute("branch-rate", branch_rate)
108 xpackage.setAttribute("complexity", "0") 109 xpackage.setAttribute("complexity", "0")
113 bhits_tot += bhits 114 bhits_tot += bhits
114 115
115 xcoverage.setAttribute("lines-valid", str(lnum_tot)) 116 xcoverage.setAttribute("lines-valid", str(lnum_tot))
116 xcoverage.setAttribute("lines-covered", str(lhits_tot)) 117 xcoverage.setAttribute("lines-covered", str(lhits_tot))
117 xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot)) 118 xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot))
118 if self.has_arcs: 119 if has_arcs:
119 xcoverage.setAttribute("branches-valid", str(bnum_tot)) 120 xcoverage.setAttribute("branches-valid", str(bnum_tot))
120 xcoverage.setAttribute("branches-covered", str(bhits_tot)) 121 xcoverage.setAttribute("branches-covered", str(bhits_tot))
121 xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot)) 122 xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot))
122 else: 123 else:
123 xcoverage.setAttribute("branches-covered", "0") 124 xcoverage.setAttribute("branches-covered", "0")
134 pct = 0.0 135 pct = 0.0
135 else: 136 else:
136 pct = 100.0 * (lhits_tot + bhits_tot) / denom 137 pct = 100.0 * (lhits_tot + bhits_tot) / denom
137 return pct 138 return pct
138 139
139 def xml_file(self, fr, analysis): 140 def xml_file(self, fr, analysis, has_arcs):
140 """Add to the XML report for a single file.""" 141 """Add to the XML report for a single file."""
141 142
142 # Create the 'lines' and 'package' XML elements, which 143 # Create the 'lines' and 'package' XML elements, which
143 # are populated later. Note that a package == a directory. 144 # are populated later. Note that a package == a directory.
144 filename = fr.filename.replace("\\", "/") 145 filename = fr.filename.replace("\\", "/")
178 179
179 # Q: can we get info about the number of times a statement is 180 # Q: can we get info about the number of times a statement is
180 # executed? If so, that should be recorded here. 181 # executed? If so, that should be recorded here.
181 xline.setAttribute("hits", str(int(line not in analysis.missing))) 182 xline.setAttribute("hits", str(int(line not in analysis.missing)))
182 183
183 if self.has_arcs: 184 if has_arcs:
184 if line in branch_stats: 185 if line in branch_stats:
185 total, taken = branch_stats[line] 186 total, taken = branch_stats[line]
186 xline.setAttribute("branch", "true") 187 xline.setAttribute("branch", "true")
187 xline.setAttribute( 188 xline.setAttribute(
188 "condition-coverage", 189 "condition-coverage",
194 xlines.appendChild(xline) 195 xlines.appendChild(xline)
195 196
196 class_lines = len(analysis.statements) 197 class_lines = len(analysis.statements)
197 class_hits = class_lines - len(analysis.missing) 198 class_hits = class_lines - len(analysis.missing)
198 199
199 if self.has_arcs: 200 if has_arcs:
200 class_branches = sum(t for t, k in branch_stats.values()) 201 class_branches = sum(t for t, k in branch_stats.values())
201 missing_branches = sum(t - k for t, k in branch_stats.values()) 202 missing_branches = sum(t - k for t, k in branch_stats.values())
202 class_br_hits = class_branches - missing_branches 203 class_br_hits = class_branches - missing_branches
203 else: 204 else:
204 class_branches = 0.0 205 class_branches = 0.0
205 class_br_hits = 0.0 206 class_br_hits = 0.0
206 207
207 # Finalize the statistics that are collected in the XML DOM. 208 # Finalize the statistics that are collected in the XML DOM.
208 xclass.setAttribute("line-rate", rate(class_hits, class_lines)) 209 xclass.setAttribute("line-rate", rate(class_hits, class_lines))
209 if self.has_arcs: 210 if has_arcs:
210 branch_rate = rate(class_br_hits, class_branches) 211 branch_rate = rate(class_br_hits, class_branches)
211 else: 212 else:
212 branch_rate = "0" 213 branch_rate = "0"
213 xclass.setAttribute("branch-rate", branch_rate) 214 xclass.setAttribute("branch-rate", branch_rate)
214 215
222 def serialize_xml(dom): 223 def serialize_xml(dom):
223 """Serialize a minidom node to XML.""" 224 """Serialize a minidom node to XML."""
224 out = dom.toprettyxml() 225 out = dom.toprettyxml()
225 if env.PY2: 226 if env.PY2:
226 out = out.encode("utf8") 227 out = out.encode("utf8")
227 # In Python 3.8, minidom lost the sorting of attributes: https://bugs.python.org/issue34160
228 # For the limited kinds of XML we produce, this re-sorts them.
229 if env.PYVERSION >= (3, 8):
230 rx_attr = r' [\w-]+="[^"]*"'
231 rx_attrs = r'(' + rx_attr + ')+'
232 fixed_lines = []
233 for line in out.splitlines(True):
234 hollow_line = re.sub(rx_attrs, u"☺", line)
235 attrs = sorted(re.findall(rx_attr, line))
236 new_line = hollow_line.replace(u"☺", "".join(attrs))
237 fixed_lines.append(new_line)
238 out = "".join(fixed_lines)
239 return out 228 return out

eric ide

mercurial