--- a/DebugClients/Python/coverage/xmlreport.py Sat Oct 10 12:06:10 2015 +0200 +++ b/DebugClients/Python/coverage/xmlreport.py Sat Oct 10 12:44:52 2015 +0200 @@ -1,15 +1,29 @@ +# 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, sys, time +import os +import sys +import time import xml.dom.minidom -from . import __url__, __version__ -from .backward import sorted, rpartition # pylint: disable=W0622 -from .report import Reporter +from coverage import __url__, __version__, files +from coverage.report import Reporter + +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.""" - return "%.4g" % (float(hit) / (num or 1.0)) + if num == 0: + return "1" + else: + return "%.4g" % (float(hit) / num) class XmlReporter(Reporter): @@ -18,14 +32,15 @@ def __init__(self, coverage, config): super(XmlReporter, self).__init__(coverage, config) - self.packages = None + self.source_paths = set() + 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. @@ -35,11 +50,7 @@ # 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 @@ -48,16 +59,27 @@ xcoverage.appendChild(self.xml_out.createComment( " Generated by coverage.py: %s " % __url__ )) - xpackages = self.xml_out.createElement("packages") - xcoverage.appendChild(xpackages) + xcoverage.appendChild(self.xml_out.createComment(" Based on %s " % DTD_URL)) # Call xml_file for each file in the data. - self.packages = {} self.report_files(self.xml_file, morfs) + xsources = self.xml_out.createElement("sources") + xcoverage.appendChild(xsources) + + # Populate the XML DOM with the source info. + for path in sorted(self.source_paths): + xsource = self.xml_out.createElement("source") + xsources.appendChild(xsource) + txt = self.xml_out.createTextNode(path) + xsource.appendChild(txt) + lnum_tot, lhits_tot = 0, 0 bnum_tot, bhits_tot = 0, 0 + xpackages = self.xml_out.createElement("packages") + xcoverage.appendChild(xpackages) + # Populate the XML DOM with the package info. for pkg_name in sorted(self.packages.keys()): pkg_data = self.packages[pkg_name] @@ -70,7 +92,11 @@ xclasses.appendChild(class_elts[class_name]) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) xpackage.setAttribute("line-rate", rate(lhits, lnum)) - xpackage.setAttribute("branch-rate", rate(bhits, bnum)) + if self.has_arcs: + branch_rate = rate(bhits, bnum) + else: + branch_rate = "0" + xpackage.setAttribute("branch-rate", branch_rate) xpackage.setAttribute("complexity", "0") lnum_tot += lnum @@ -79,7 +105,11 @@ bhits_tot += bhits xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot)) - xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot)) + 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()) @@ -92,14 +122,20 @@ pct = 100.0 * (lhits_tot + bhits_tot) / denom return pct - def xml_file(self, cu, analysis): + def xml_file(self, fr, analysis): """Add to the XML report for a single file.""" # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. - package_name = rpartition(cu.name, ".")[0] - className = cu.name + 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.relative_filename() + self.source_paths.add(files.relative_directory().rstrip('/')) package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) xclass = self.xml_out.createElement("class") @@ -109,12 +145,12 @@ xlines = self.xml_out.createElement("lines") xclass.appendChild(xlines) - xclass.setAttribute("name", className) - filename = cu.file_locator.relative_filename(cu.filename) - xclass.setAttribute("filename", filename.replace("\\", "/")) + xclass.setAttribute("name", os.path.relpath(filename, dirname)) + xclass.setAttribute("filename", filename) 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): @@ -125,21 +161,25 @@ # 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") - xline.setAttribute("condition-coverage", + xline.setAttribute( + "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: - class_branches = sum([t for t,k in branch_stats.values()]) - missing_branches = sum([t-k for t,k in branch_stats.values()]) + 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 else: class_branches = 0.0 @@ -147,7 +187,12 @@ # Finalize the statistics that are collected in the XML DOM. xclass.setAttribute("line-rate", rate(class_hits, class_lines)) - xclass.setAttribute("branch-rate", rate(class_br_hits, class_branches)) + 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[1] += class_hits package[2] += class_lines