~mvo/click/dont-crash-for-empty-db

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
#! /usr/bin/python3

from optparse import OptionParser
import re
import sys
from xml.etree import ElementTree as ET


class CoverageTreeBuilder(ET.TreeBuilder):
    def doctype(self, name, pubid, system):
        self._doctype = (name, pubid, system)


class Coverage:
    def __init__(self):
        self._tree = None
        self._builder = None

    def _merge_children(self, left, right):
        assert left.tag == right.tag
        if left.tag == "coverage":
            for right_child in right:
                left_child = left.find(right_child.tag)
                if left_child is not None:
                    self._merge_children(left_child, right_child)
                else:
                    left.append(right_child)
            # Re-compute rates from scratch.
            lnum, lhits = 0, 0
            bnum, bhits = 0, 0
            condition_re = re.compile(r"\(([0-9]+)/([0-9]+)\)")
            for package in left.find("packages").findall("package"):
                for cclass in package.find("classes").findall("class"):
                    for line in cclass.find("lines").findall("line"):
                        lnum += 1
                        if line.get("hits", "0") != "0":
                            lhits += 1
                        if line.get("branch", "false") == "true":
                            match = condition_re.search(
                                line.get("condition-coverage"))
                            if match is not None:
                                bhits += int(match.group(1))
                                bnum += int(match.group(2))
            left.set("branch-rate", "%.4g" % (float(bhits) / (bnum or 1.0)))
            left.set("line-rate", "%.4g" % (float(lhits) / (lnum or 1.0)))
        elif left.tag == "sources":
            pass  # just ignore this for now
        elif left.tag == "packages":
            left_names = []
            for left_child in left:
                assert left_child.tag == "package"
                left_names.append(left_child.get("name"))
            for right_child in right:
                assert right_child.tag == "package"
                assert right_child.get("name") not in left_names
                left.append(right_child)

    def merge(self, arg):
        builder = CoverageTreeBuilder()
        tree = ET.parse(arg, parser=ET.XMLParser(target=builder))
        if self._tree is None:
            self._tree = tree
            self._builder = builder
        else:
            self._merge_children(self._tree.getroot(), tree.getroot())

    def write(self, outfilename):
        if outfilename is None:
            outfile = sys.stdout
        else:
            outfile = open(outfilename, "w")
        outfile.write('<?xml version="1.0" ?>\n')
        doctype = self._builder._doctype
        outfile.write("<!DOCTYPE %s%s%s>\n" % (
            doctype[0],
            "" if doctype[1] is None else "\n PUBLIC '%s'" % doctype[1],
            "" if doctype[2] is None else "\n SYSTEM '%s'" % doctype[2]))
        try:
            self._tree.write(outfile, encoding="unicode")
        finally:
            outfile.close()


def main():
    parser = OptionParser(usage="%prog FILE [...]")
    parser.add_option(
        "-o", "--output", help="output file name (default: stdout)")
    options, args = parser.parse_args()
    if not args:
        parser.error("need at least one input file")
    coverage = Coverage()
    for arg in args:
        coverage.merge(arg)
    coverage.write(options.output)


if __name__ == "__main__":
    main()