1 """XML reporting for coverage.py"""
6 from coverage
import __url__
, __version__
7 from coverage
.backward
import sorted, rpartition
# pylint: disable=W0622
8 from coverage
.report
import Reporter
11 """Return the fraction of `hit`/`num`, as a string."""
12 return "%.4g" % (float(hit
) / (num
or 1.0))
15 class XmlReporter(Reporter
):
16 """A reporter for writing Cobertura-style XML coverage results."""
18 def __init__(self
, coverage
, config
):
19 super(XmlReporter
, self
).__init
__(coverage
, config
)
23 self
.arcs
= coverage
.data
.has_arcs()
25 def report(self
, morfs
, outfile
=None):
26 """Generate a Cobertura-compatible XML report for `morfs`.
28 `morfs` is a list of modules or filenames.
30 `outfile` is a file object to write the XML to.
34 outfile
= outfile
or sys
.stdout
36 # Create the DOM that will store the data.
37 impl
= xml
.dom
.minidom
.getDOMImplementation()
38 docType
= impl
.createDocumentType(
40 "http://cobertura.sourceforge.net/xml/coverage-03.dtd"
42 self
.xml_out
= impl
.createDocument(None, "coverage", docType
)
45 xcoverage
= self
.xml_out
.documentElement
46 xcoverage
.setAttribute("version", __version__
)
47 xcoverage
.setAttribute("timestamp", str(int(time
.time()*1000)))
48 xcoverage
.appendChild(self
.xml_out
.createComment(
49 " Generated by coverage.py: %s " % __url__
51 xpackages
= self
.xml_out
.createElement("packages")
52 xcoverage
.appendChild(xpackages
)
54 # Call xml_file for each file in the data.
56 self
.report_files(self
.xml_file
, morfs
)
58 lnum_tot
, lhits_tot
= 0, 0
59 bnum_tot
, bhits_tot
= 0, 0
61 # Populate the XML DOM with the package info.
62 for pkg_name
in sorted(self
.packages
.keys()):
63 pkg_data
= self
.packages
[pkg_name
]
64 class_elts
, lhits
, lnum
, bhits
, bnum
= pkg_data
65 xpackage
= self
.xml_out
.createElement("package")
66 xpackages
.appendChild(xpackage
)
67 xclasses
= self
.xml_out
.createElement("classes")
68 xpackage
.appendChild(xclasses
)
69 for class_name
in sorted(class_elts
.keys()):
70 xclasses
.appendChild(class_elts
[class_name
])
71 xpackage
.setAttribute("name", pkg_name
.replace(os
.sep
, '.'))
72 xpackage
.setAttribute("line-rate", rate(lhits
, lnum
))
73 xpackage
.setAttribute("branch-rate", rate(bhits
, bnum
))
74 xpackage
.setAttribute("complexity", "0")
81 xcoverage
.setAttribute("line-rate", rate(lhits_tot
, lnum_tot
))
82 xcoverage
.setAttribute("branch-rate", rate(bhits_tot
, bnum_tot
))
84 # Use the DOM to write the output file.
85 outfile
.write(self
.xml_out
.toprettyxml())
87 # Return the total percentage.
88 denom
= lnum_tot
+ bnum_tot
92 pct
= 100.0 * (lhits_tot
+ bhits_tot
) / denom
95 def xml_file(self
, cu
, analysis
):
96 """Add to the XML report for a single file."""
98 # Create the 'lines' and 'package' XML elements, which
99 # are populated later. Note that a package == a directory.
100 package_name
= rpartition(cu
.name
, ".")[0]
103 package
= self
.packages
.setdefault(package_name
, [{}, 0, 0, 0, 0])
105 xclass
= self
.xml_out
.createElement("class")
107 xclass
.appendChild(self
.xml_out
.createElement("methods"))
109 xlines
= self
.xml_out
.createElement("lines")
110 xclass
.appendChild(xlines
)
112 xclass
.setAttribute("name", className
)
113 filename
= cu
.file_locator
.relative_filename(cu
.filename
)
114 xclass
.setAttribute("filename", filename
.replace("\\", "/"))
115 xclass
.setAttribute("complexity", "0")
117 branch_stats
= analysis
.branch_stats()
119 # For each statement, create an XML 'line' element.
120 for line
in sorted(analysis
.statements
):
121 xline
= self
.xml_out
.createElement("line")
122 xline
.setAttribute("number", str(line
))
124 # Q: can we get info about the number of times a statement is
125 # executed? If so, that should be recorded here.
126 xline
.setAttribute("hits", str(int(line
not in analysis
.missing
)))
129 if line
in branch_stats
:
130 total
, taken
= branch_stats
[line
]
131 xline
.setAttribute("branch", "true")
132 xline
.setAttribute("condition-coverage",
133 "%d%% (%d/%d)" % (100*taken
/total
, taken
, total
)
135 xlines
.appendChild(xline
)
137 class_lines
= len(analysis
.statements
)
138 class_hits
= class_lines
- len(analysis
.missing
)
141 class_branches
= sum([t
for t
,k
in branch_stats
.values()])
142 missing_branches
= sum([t
-k
for t
,k
in branch_stats
.values()])
143 class_br_hits
= class_branches
- missing_branches
148 # Finalize the statistics that are collected in the XML DOM.
149 xclass
.setAttribute("line-rate", rate(class_hits
, class_lines
))
150 xclass
.setAttribute("branch-rate", rate(class_br_hits
, class_branches
))
151 package
[0][className
] = xclass
152 package
[1] += class_hits
153 package
[2] += class_lines
154 package
[3] += class_br_hits
155 package
[4] += class_branches