#!/usr/bin/python #Copyright (c) 2005 Drew Smathers # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies # of the Software, and to permit persons to whom the Software is furnished to do # so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. #From http://svn.xix.python-hosting.com/trunk/xix/utils/cover.py """More things for working with coverage.py """ from UserList import UserList import sys, os import glob from StringIO import StringIO import lxml.etree as ET __author__ = 'Drew Smathers' __version__ = '$Revision$'[11:-2] class AnnotationLine: def __init__(self, text, isexec=False, covered=False): self.text = text self.isexec = isexec self.covered = covered # hack if self.text and self.text[-1] == '\n': self.text = self.text[:-1] def __repr__(self): buf = "" buf = buf + self.text + "\n" return buf class Annotation(UserList): pass class AnnotationParser: """Parser for annotation files generated by coverage. """ def parse(self, input): fd = input if not hasattr(input, 'read'): fd = open(input) annotation = Annotation() for line in input: # print line if not line or line[0] not in ('>', '!'): annotation.append(AnnotationLine(line[2:], isexec=False)) elif line[0] == '>': annotation.append(AnnotationLine(line[2:], isexec=True, covered=True)) elif line[0] == '!': annotation.append(AnnotationLine(line[2:], isexec=True, covered=False)) else: print >> sys.stderr, 'invalid annotation line: ' + line return annotation def annotationToXML(annotation, tree=False): """Transform annotation object to XML string or ElementTree instance if tree is True. @param annotation: Annotation instance @param tree: set to true to return ElementTree instance. """ etree = _annotationToXML(annotation) if tree: return etree buffer = StringIO() etree.write(buffer) return buffer.getvalue() def _annotationToXML(annotation): root = ET.Element('coverageAnnotation') for line in annotation: aline = ET.SubElement(root, 'line') aline.attrib['executable'] = str(line.isexec).lower() aline.attrib['covered'] = str(line.covered).lower() aline.text = line.text return ET.ElementTree(root) def annotationToHTML(annotation, xml=None, xslt=None, tree=False): """Transform annotation object to HTML string or ElementTree instance if tree is True. @param annotation: Annotation instance @param xslt: xslt source for transformation @param tree: set to true to return ElementTree instance. """ style = xslt or _XSLT # style = _XSLT if not hasattr(style, 'read'): style = StringIO(style) style = ET.parse(style) etree = _annotationToXML(annotation) html = etree.xslt(style) if tree: return html buffer = StringIO() html.write(buffer) return buffer.getvalue() ########################################### # Coverage reports ########################################### class CoverageReport(UserList): package_name = None summary = None class CoverageReportEntry: def __init__(self, modname, statements, executed, coverage, missing): self.modname = modname self.statements = statements self.executed = executed self.coverage = coverage self.missing = missing class CoverageReportParser: def parse(self, input): fd = input if not hasattr(input, 'read'): fd = open(input) report = CoverageReport() for line in fd.readlines()[2:]: tokens = line.split() try: modname, stmts, execd, coverage = tokens[:4] perc = coverage[:-1] _stmts, _execd = int(stmts), int(execd) except Exception, e: continue if modname == 'TOTAL': report.summary = CoverageReportEntry(modname, stmts, execd, coverage, None) break modname = modname.replace(os.path.sep, '.') missed = [ tk.replace(',','') for tk in tokens[4:] ] report.append(CoverageReportEntry(modname, stmts, execd, coverage, missed)) return report def reportToXML(report, tree=False): """Transform report object to XML representation as string or ElementTree instance if tree arg is set to True. @param report: report instance @param tree: set to true to return ElementTree instance """ etree = _reportToXML(report) if tree: return etree buffer = StringIO() etree.write(buffer) return buffer.getvalue() def _reportToXML(report): root = ET.Element('coverage-report') elm = ET.SubElement(root, 'summary') attr = elm.attrib attr['statements'] = report.summary.statements attr['executed'] = report.summary.executed attr['coverage'] = report.summary.coverage for entry in report: elm = ET.SubElement(root, 'module') attr = elm.attrib attr['name'] = entry.modname attr['statements'] = entry.statements attr['coverage'] = entry.coverage attr['executed'] = entry.executed melm = ET.SubElement(elm, 'missing-ranges') for missed in entry.missing: start_end = missed.split('-') if len(start_end) == 2: start, end = start_end else: start = end = start_end[0] relm = ET.SubElement(melm, 'range') relm.attrib['start'] = start relm.attrib['end'] = end return ET.ElementTree(root) def gen_html(path_to_cover, path_for_html): cover_files = glob.glob("%s/*,cover" % path_to_cover) # write out the css file f = open("coverage.css", "w") f.write(_CSS) f.close() for cf in cover_files: fd = open(cf, "r") ann = AnnotationParser().parse(fd) html = annotationToHTML(ann, xslt=_XSLT) base_name = os.path.basename(cf) source_name = base_name.split(',')[0] html_name = "%s/%s.html" % (path_for_html, source_name) f = open(html_name, "w") f.write(html) f.close() # fd = open("%s/cover.report" % path_to_cover) # crp = CoverageReportParser().parse(fd) _CSS = """ body { margin: 0px; } h2 { padding: 3px; margin: 0px; background-color: #ddd; border-bottom: 2px solid; } th { text-align: left; padding-right: 28px; } .report-column { border-bottom: 1px dashed; padding-right: 28px; } .summary { background-color: #eed; border-bottom: 0px; } a { text-decoration: none; } .annotation-line { font-style: italic; font-weight: 700; font-family: mono,arial; border-left: 8px solid #aaa; padding-left: 5px; } .covered { border-left: 8px solid #3e3; background-color: #cfc; } .uncovered { border-left: 8px solid red; } pre { margin: 1px; display: inline; } .uncovered { background: #ebb; } .non-exec { border-left: 8px solid #aaa; background-color: #eee; } .lineno { margin-right: 5px; background-color: #ef5; } #colophon { border-top: 1px solid; background-color: #eee; display: block; position: relative; bottom: 0px; padding: 8px; float: bottom; width: 100%; margin-top: 20px; font-size: 75%; text-align: center; } """ _XSLT = ''' Coverage Results for <xsl:value-of select="$modname"/>

Coverage Results for

''' if __name__ == "__main__": gen_html(sys.argv[1], sys.argv[2])