diff options
Diffstat (limited to 'coverage/xmlreport.py')
-rw-r--r-- | coverage/xmlreport.py | 63 |
1 files changed, 43 insertions, 20 deletions
diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 996f19a2..50a46841 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -1,13 +1,28 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + """XML reporting for coverage.py""" import os +import os.path import sys import time import xml.dom.minidom -from coverage import __url__, __version__ +from coverage import env +from coverage import __url__, __version__, files +from coverage.misc import isolate_module from coverage.report import Reporter +os = isolate_module(os) + + +DTD_URL = ( + 'https://raw.githubusercontent.com/cobertura/web/' + 'f0366e5e2cf18f111cbd61fc34ef720a6584ba02' + '/htdocs/xml/coverage-03.dtd' +) + def rate(hit, num): """Return the fraction of `hit`/`num`, as a string.""" @@ -20,19 +35,22 @@ def rate(hit, num): class XmlReporter(Reporter): """A reporter for writing Cobertura-style XML coverage results.""" - def __init__(self, coverage, config, file_locator): + def __init__(self, coverage, config): super(XmlReporter, self).__init__(coverage, config) - self.file_locator = file_locator self.source_paths = set() + if config.source: + for src in config.source: + if os.path.exists(src): + self.source_paths.add(files.canonical_filename(src)) self.packages = {} self.xml_out = None - self.arcs = coverage.data.has_arcs() + self.has_arcs = coverage.data.has_arcs() def report(self, morfs, outfile=None): """Generate a Cobertura-compatible XML report for `morfs`. - `morfs` is a list of modules or filenames. + `morfs` is a list of modules or file names. `outfile` is a file object to write the XML to. @@ -42,11 +60,7 @@ class XmlReporter(Reporter): # Create the DOM that will store the data. impl = xml.dom.minidom.getDOMImplementation() - docType = impl.createDocumentType( - "coverage", None, - "http://cobertura.sourceforge.net/xml/coverage-03.dtd" - ) - self.xml_out = impl.createDocument(None, "coverage", docType) + self.xml_out = impl.createDocument(None, "coverage", None) # Write header stuff. xcoverage = self.xml_out.documentElement @@ -55,6 +69,7 @@ class XmlReporter(Reporter): xcoverage.appendChild(self.xml_out.createComment( " Generated by coverage.py: %s " % __url__ )) + xcoverage.appendChild(self.xml_out.createComment(" Based on %s " % DTD_URL)) # Call xml_file for each file in the data. self.report_files(self.xml_file, morfs) @@ -87,7 +102,7 @@ class XmlReporter(Reporter): xclasses.appendChild(class_elts[class_name]) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) xpackage.setAttribute("line-rate", rate(lhits, lnum)) - if self.arcs: + if self.has_arcs: branch_rate = rate(bhits, bnum) else: branch_rate = "0" @@ -100,14 +115,17 @@ class XmlReporter(Reporter): bhits_tot += bhits xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot)) - if self.arcs: + if self.has_arcs: branch_rate = rate(bhits_tot, bnum_tot) else: branch_rate = "0" xcoverage.setAttribute("branch-rate", branch_rate) # Use the DOM to write the output file. - outfile.write(self.xml_out.toprettyxml()) + out = self.xml_out.toprettyxml() + if env.PY2: + out = out.encode("utf8") + outfile.write(out) # Return the total percentage. denom = lnum_tot + bnum_tot @@ -122,15 +140,16 @@ class XmlReporter(Reporter): # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. - filename = self.file_locator.relative_filename(fr.filename) + filename = fr.relative_filename() filename = filename.replace("\\", "/") dirname = os.path.dirname(filename) or "." parts = dirname.split("/") dirname = "/".join(parts[:self.config.xml_package_depth]) package_name = dirname.replace("/", ".") - className = fr.name + rel_name = fr.relative_filename() - self.source_paths.add(self.file_locator.relative_dir.rstrip('/')) + if rel_name != fr.filename: + self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/")) package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) xclass = self.xml_out.createElement("class") @@ -145,6 +164,7 @@ class XmlReporter(Reporter): xclass.setAttribute("complexity", "0") branch_stats = analysis.branch_stats() + missing_branch_arcs = analysis.missing_branch_arcs() # For each statement, create an XML 'line' element. for line in sorted(analysis.statements): @@ -155,7 +175,7 @@ class XmlReporter(Reporter): # executed? If so, that should be recorded here. xline.setAttribute("hits", str(int(line not in analysis.missing))) - if self.arcs: + if self.has_arcs: if line in branch_stats: total, taken = branch_stats[line] xline.setAttribute("branch", "true") @@ -163,12 +183,15 @@ class XmlReporter(Reporter): "condition-coverage", "%d%% (%d/%d)" % (100*taken/total, taken, total) ) + if line in missing_branch_arcs: + annlines = ["exit" if b < 0 else str(b) for b in missing_branch_arcs[line]] + xline.setAttribute("missing-branches", ",".join(annlines)) xlines.appendChild(xline) class_lines = len(analysis.statements) class_hits = class_lines - len(analysis.missing) - if self.arcs: + if self.has_arcs: class_branches = sum(t for t, k in branch_stats.values()) missing_branches = sum(t - k for t, k in branch_stats.values()) class_br_hits = class_branches - missing_branches @@ -178,13 +201,13 @@ class XmlReporter(Reporter): # Finalize the statistics that are collected in the XML DOM. xclass.setAttribute("line-rate", rate(class_hits, class_lines)) - if self.arcs: + if self.has_arcs: branch_rate = rate(class_br_hits, class_branches) else: branch_rate = "0" xclass.setAttribute("branch-rate", branch_rate) - package[0][className] = xclass + package[0][rel_name] = xclass package[1] += class_hits package[2] += class_lines package[3] += class_br_hits |