summaryrefslogtreecommitdiff
path: root/coverage/xmlreport.py
blob: 0489384d4fdbe9814e3f531e2eb8e8cf3362600f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""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