DebugClients/Python/coverage/xmlreport.py

changeset 31
744cd0b4b8cd
child 32
01f04fbc1842
equal deleted inserted replaced
30:9513afbd57f1 31:744cd0b4b8cd
1 """XML reporting for coverage.py"""
2
3 import os, sys, time
4 import xml.dom.minidom
5
6 from coverage import __url__, __version__
7 from coverage.backward import sorted # pylint: disable-msg=W0622
8 from coverage.report import Reporter
9
10 def rate(hit, num):
11 """Return the fraction of `hit`/`num`."""
12 return hit / (num or 1.0)
13
14
15 class XmlReporter(Reporter):
16 """A reporter for writing Cobertura-style XML coverage results."""
17
18 def __init__(self, coverage, ignore_errors=False):
19 super(XmlReporter, self).__init__(coverage, ignore_errors)
20
21 self.packages = None
22 self.xml_out = None
23 self.arcs = coverage.data.has_arcs()
24
25 def report(self, morfs, omit_prefixes=None, outfile=None):
26 """Generate a Cobertura-compatible XML report for `morfs`.
27
28 `morfs` is a list of modules or filenames. `omit_prefixes` is a list
29 of strings, prefixes of modules to omit from the report.
30
31 """
32 # Initial setup.
33 outfile = outfile or sys.stdout
34
35 # Create the DOM that will store the data.
36 impl = xml.dom.minidom.getDOMImplementation()
37 docType = impl.createDocumentType(
38 "coverage", None,
39 "http://cobertura.sourceforge.net/xml/coverage-03.dtd"
40 )
41 self.xml_out = impl.createDocument(None, "coverage", docType)
42
43 # Write header stuff.
44 xcoverage = self.xml_out.documentElement
45 xcoverage.setAttribute("version", __version__)
46 xcoverage.setAttribute("timestamp", str(int(time.time()*1000)))
47 xcoverage.appendChild(self.xml_out.createComment(
48 " Generated by coverage.py: %s " % __url__
49 ))
50 xpackages = self.xml_out.createElement("packages")
51 xcoverage.appendChild(xpackages)
52
53 # Call xml_file for each file in the data.
54 self.packages = {}
55 self.report_files(self.xml_file, morfs, omit_prefixes=omit_prefixes)
56
57 lnum_tot, lhits_tot = 0, 0
58 bnum_tot, bhits_tot = 0, 0
59
60 # Populate the XML DOM with the package info.
61 for pkg_name, pkg_data in self.packages.items():
62 class_elts, lhits, lnum, bhits, bnum = pkg_data
63 xpackage = self.xml_out.createElement("package")
64 xpackages.appendChild(xpackage)
65 xclasses = self.xml_out.createElement("classes")
66 xpackage.appendChild(xclasses)
67 for className in sorted(class_elts.keys()):
68 xclasses.appendChild(class_elts[className])
69 xpackage.setAttribute("name", pkg_name.replace(os.sep, '.'))
70 xpackage.setAttribute("line-rate", str(rate(lhits, lnum)))
71 xpackage.setAttribute("branch-rate", str(rate(bhits, bnum)))
72 xpackage.setAttribute("complexity", "0.0")
73
74 lnum_tot += lnum
75 lhits_tot += lhits
76 bnum_tot += bnum
77 bhits_tot += bhits
78
79 xcoverage.setAttribute("line-rate", str(rate(lhits_tot, lnum_tot)))
80 xcoverage.setAttribute("branch-rate", str(rate(bhits_tot, bnum_tot)))
81
82 # Use the DOM to write the output file.
83 outfile.write(self.xml_out.toprettyxml())
84
85 def xml_file(self, cu, analysis):
86 """Add to the XML report for a single file."""
87
88 # Create the 'lines' and 'package' XML elements, which
89 # are populated later. Note that a package == a directory.
90 dirname, fname = os.path.split(cu.name)
91 dirname = dirname or '.'
92 package = self.packages.setdefault(dirname, [ {}, 0, 0, 0, 0 ])
93
94 xclass = self.xml_out.createElement("class")
95
96 xclass.appendChild(self.xml_out.createElement("methods"))
97
98 xlines = self.xml_out.createElement("lines")
99 xclass.appendChild(xlines)
100 className = fname.replace('.', '_')
101 xclass.setAttribute("name", className)
102 ext = os.path.splitext(cu.filename)[1]
103 xclass.setAttribute("filename", cu.name + ext)
104 xclass.setAttribute("complexity", "0.0")
105
106 branch_lines = analysis.branch_lines()
107
108 # For each statement, create an XML 'line' element.
109 for line in analysis.statements:
110 xline = self.xml_out.createElement("line")
111 xline.setAttribute("number", str(line))
112
113 # Q: can we get info about the number of times a statement is
114 # executed? If so, that should be recorded here.
115 xline.setAttribute("hits", str(int(not line in analysis.missing)))
116
117 if self.arcs:
118 if line in branch_lines:
119 xline.setAttribute("branch", "true")
120 xlines.appendChild(xline)
121
122 class_lines = 1.0 * len(analysis.statements)
123 class_hits = class_lines - len(analysis.missing)
124
125 if self.arcs:
126 # We assume here that every branch line has 2 exits, which is
127 # usually true. In theory, though, we could have a branch line
128 # with more exits..
129 class_branches = 2.0 * len(branch_lines)
130 missed_branch_targets = analysis.missing_branch_arcs().values()
131 missing_branches = sum([len(b) for b in missed_branch_targets])
132 class_branch_hits = class_branches - missing_branches
133 else:
134 class_branches = 0.0
135 class_branch_hits = 0.0
136
137 # Finalize the statistics that are collected in the XML DOM.
138 line_rate = rate(class_hits, class_lines)
139 branch_rate = rate(class_branch_hits, class_branches)
140 xclass.setAttribute("line-rate", str(line_rate))
141 xclass.setAttribute("branch-rate", str(branch_rate))
142 package[0][className] = xclass
143 package[1] += class_hits
144 package[2] += class_lines
145 package[3] += class_branch_hits
146 package[4] += class_branches

eric ide

mercurial