"""XML reporting for coverage.py""" import os, sys import xml.dom.minidom from coverage import __url__, __version__ from coverage.backward import sorted # pylint: disable-msg=W0622 from coverage.report import Reporter class XmlReporter(Reporter): """A reporter for writing Cobertura-style XML coverage results.""" def __init__(self, coverage, ignore_errors=False): super(XmlReporter, self).__init__(coverage, ignore_errors) self.packages = None self.xml_out = None def report(self, morfs, omit_prefixes=None, outfile=None): """Generate a Cobertura-compatible XML report for `morfs`. `morfs` is a list of modules or filenames. `omit_prefixes` is a list of strings, prefixes of modules to omit from the report. """ # Initial setup. outfile = outfile or sys.stdout self.find_code_units(morfs, omit_prefixes) # 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) root = self.xml_out.documentElement root.appendChild(self.xml_out.createComment( " Generated by coverage.py %s: %s " % (__version__, __url__) )) packageXml = self.xml_out.createElement("packages") root.appendChild(packageXml) self.packages = {} errors = False self.report_files(self.xml_file, morfs, omit_prefixes=omit_prefixes) # Don't write the XML data if we've encountered errors. if errors: return # Populate the XML DOM with the package info. for packageName, packageData in self.packages.items(): package = self.xml_out.createElement("package") packageXml.appendChild(package) classes = self.xml_out.createElement("classes") package.appendChild(classes) for className in sorted(packageData[0].keys()): classes.appendChild(packageData[0][className]) package.setAttribute("name", packageName.replace(os.sep, '.')) package.setAttribute("line-rate", str(packageData[1]/(packageData[2] or 1.0))) package.setAttribute("branch-rate", str(packageData[3]/(packageData[4] or 1.0))) package.setAttribute("complexity", "0.0") # Use the DOM to write the output file. outfile.write(self.xml_out.toprettyxml()) def xml_file(self, cu, statements, excluded, missing): """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. dirname, fname = os.path.split(cu.name) dirname = dirname or '.' package = self.packages.setdefault(dirname, [ {}, 0, 0, 0, 0 ]) c = self.xml_out.createElement("class") lines = self.xml_out.createElement("lines") c.appendChild(lines) className = fname.replace('.', '_') c.setAttribute("name", className) c.setAttribute("filename", cu.filename) c.setAttribute("complexity", "0.0") # For each statement, create an XML 'line' element. for line in statements: l = self.xml_out.createElement("line") l.setAttribute("number", str(line)) # Q: can we get info about the number of times # a statement is executed? If so, that should be # recorded here. l.setAttribute("hits", str(int(not line in missing))) # Q: can we get info about whether this statement # is a branch? If so, that data should be # used here. l.setAttribute("branch", "false") lines.appendChild(l) class_lines = 1.0 * len(statements) class_hits = class_lines - len(missing) class_branches = 0.0 class_branch_hits = 0.0 # Finalize the statistics that are collected in the XML DOM. line_rate = class_hits / (class_lines or 1.0) branch_rate = class_branch_hits / (class_branches or 1.0) c.setAttribute("line-rate", str(line_rate)) c.setAttribute("branch-rate", str(branch_rate)) package[0][className] = c package[1] += class_hits package[2] += class_lines package[3] += class_branch_hits package[4] += class_branches