17757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch"""Results of coverage measurement."""
27757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
37757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport os
47757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
57757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.backward import set, sorted           # pylint: disable=W0622
67757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.misc import format_lines, join_regex, NoSource
77757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.parser import CodeParser
87757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
97757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass Analysis(object):
117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """The results of analyzing a code unit."""
127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, cov, code_unit):
147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.coverage = cov
157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.code_unit = code_unit
167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.filename = self.code_unit.filename
187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        ext = os.path.splitext(self.filename)[1]
197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        source = None
207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if ext == '.py':
217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if not os.path.exists(self.filename):
227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                source = self.coverage.file_locator.get_zip_data(self.filename)
237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if not source:
247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    raise NoSource("No source for code: %r" % self.filename)
257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.parser = CodeParser(
277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            text=source, filename=self.filename,
287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            exclude=self.coverage._exclude_regex('exclude')
297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            )
307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.statements, self.excluded = self.parser.parse_source()
317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Identify missing statements.
337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        executed = self.coverage.data.executed_lines(self.filename)
347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        exec1 = self.parser.first_lines(executed)
357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.missing = sorted(set(self.statements) - set(exec1))
367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.coverage.data.has_arcs():
387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.no_branch = self.parser.lines_matching(
397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                join_regex(self.coverage.config.partial_list),
407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                join_regex(self.coverage.config.partial_always_list)
417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                )
427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_branches = self.total_branches()
437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            mba = self.missing_branch_arcs()
447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_missing_branches = sum(
457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                [len(v) for k,v in mba.items() if k not in self.missing]
467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                )
477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_branches = n_missing_branches = 0
497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.no_branch = set()
507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.numbers = Numbers(
527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_files=1,
537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_statements=len(self.statements),
547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_excluded=len(self.excluded),
557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_missing=len(self.missing),
567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_branches=n_branches,
577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            n_missing_branches=n_missing_branches,
587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            )
597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def missing_formatted(self):
617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """The missing line numbers, formatted nicely.
627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns a string like "1-2, 5-11, 13-14".
647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return format_lines(self.statements, self.missing)
677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def has_arcs(self):
697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Were arcs measured in this result?"""
707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.coverage.data.has_arcs()
717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def arc_possibilities(self):
737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns a sorted list of the arcs in the code."""
747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        arcs = self.parser.arcs()
757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return arcs
767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def arcs_executed(self):
787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns a sorted list of the arcs actually executed in the code."""
797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        executed = self.coverage.data.executed_arcs(self.filename)
807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        m2fl = self.parser.first_line
817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        executed = [(m2fl(l1), m2fl(l2)) for (l1,l2) in executed]
827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return sorted(executed)
837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def arcs_missing(self):
857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns a sorted list of the arcs in the code not executed."""
867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        possible = self.arc_possibilities()
877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        executed = self.arcs_executed()
887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        missing = [
897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            p for p in possible
907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if p not in executed
917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    and p[0] not in self.no_branch
927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ]
937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return sorted(missing)
947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def arcs_unpredicted(self):
967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns a sorted list of the executed arcs missing from the code."""
977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        possible = self.arc_possibilities()
987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        executed = self.arcs_executed()
997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Exclude arcs here which connect a line to itself.  They can occur
1007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # in executed data in some cases.  This is where they can cause
1017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # trouble, and here is where it's the least burden to remove them.
1027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        unpredicted = [
1037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            e for e in executed
1047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if e not in possible
1057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    and e[0] != e[1]
1067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ]
1077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return sorted(unpredicted)
1087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def branch_lines(self):
1107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns a list of line numbers that have more than one exit."""
1117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        exit_counts = self.parser.exit_counts()
1127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return [l1 for l1,count in exit_counts.items() if count > 1]
1137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def total_branches(self):
1157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """How many total branches are there?"""
1167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        exit_counts = self.parser.exit_counts()
1177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return sum([count for count in exit_counts.values() if count > 1])
1187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def missing_branch_arcs(self):
1207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return arcs that weren't executed from branch lines.
1217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns {l1:[l2a,l2b,...], ...}
1237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        missing = self.arcs_missing()
1267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        branch_lines = set(self.branch_lines())
1277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        mba = {}
1287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for l1, l2 in missing:
1297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if l1 in branch_lines:
1307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if l1 not in mba:
1317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    mba[l1] = []
1327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                mba[l1].append(l2)
1337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return mba
1347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def branch_stats(self):
1367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get stats about branches.
1377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns a dict mapping line numbers to a tuple:
1397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        (total_exits, taken_exits).
1407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        exit_counts = self.parser.exit_counts()
1437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        missing_arcs = self.missing_branch_arcs()
1447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        stats = {}
1457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for lnum in self.branch_lines():
1467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            exits = exit_counts[lnum]
1477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            try:
1487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                missing = len(missing_arcs[lnum])
1497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            except KeyError:
1507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                missing = 0
1517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            stats[lnum] = (exits, exits - missing)
1527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return stats
1537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass Numbers(object):
1567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """The numerical results of measuring coverage.
1577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    This holds the basic statistics from `Analysis`, and is used to roll
1597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    up statistics across files.
1607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
1627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    # A global to determine the precision on coverage percentages, the number
1637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    # of decimal places.
1647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    _precision = 0
1657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    _near0 = 1.0              # These will change when _precision is changed.
1667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    _near100 = 99.0
1677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0,
1697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    n_branches=0, n_missing_branches=0
1707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    ):
1717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.n_files = n_files
1727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.n_statements = n_statements
1737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.n_excluded = n_excluded
1747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.n_missing = n_missing
1757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.n_branches = n_branches
1767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.n_missing_branches = n_missing_branches
1777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def set_precision(cls, precision):
1797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Set the number of decimal places used to report percentages."""
1807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        assert 0 <= precision < 10
1817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        cls._precision = precision
1827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        cls._near0 = 1.0 / 10**precision
1837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        cls._near100 = 100.0 - cls._near0
1847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    set_precision = classmethod(set_precision)
1857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_n_executed(self):
1877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns the number of executed statements."""
1887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.n_statements - self.n_missing
1897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    n_executed = property(_get_n_executed)
1907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_n_executed_branches(self):
1927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns the number of executed branches."""
1937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.n_branches - self.n_missing_branches
1947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    n_executed_branches = property(_get_n_executed_branches)
1957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_pc_covered(self):
1977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns a single percentage value for coverage."""
1987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.n_statements > 0:
1997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pc_cov = (100.0 * (self.n_executed + self.n_executed_branches) /
2007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        (self.n_statements + self.n_branches))
2017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
2027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pc_cov = 100.0
2037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return pc_cov
2047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pc_covered = property(_get_pc_covered)
2057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_pc_covered_str(self):
2077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Returns the percent covered, as a string, without a percent sign.
2087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Note that "0" is only returned when the value is truly zero, and "100"
2107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        is only returned when the value is truly 100.  Rounding can never
2117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        result in either "0" or "100".
2127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        pc = self.pc_covered
2157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if 0 < pc < self._near0:
2167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pc = self._near0
2177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        elif self._near100 < pc < 100:
2187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pc = self._near100
2197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
2207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pc = round(pc, self._precision)
2217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return "%.*f" % (self._precision, pc)
2227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pc_covered_str = property(_get_pc_covered_str)
2237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def pc_str_width(cls):
2257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """How many characters wide can pc_covered_str be?"""
2267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        width = 3   # "100"
2277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if cls._precision > 0:
2287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            width += 1 + cls._precision
2297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return width
2307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pc_str_width = classmethod(pc_str_width)
2317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __add__(self, other):
2337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums = Numbers()
2347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums.n_files = self.n_files + other.n_files
2357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums.n_statements = self.n_statements + other.n_statements
2367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums.n_excluded = self.n_excluded + other.n_excluded
2377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums.n_missing = self.n_missing + other.n_missing
2387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums.n_branches = self.n_branches + other.n_branches
2397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        nums.n_missing_branches = (self.n_missing_branches +
2407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                                    other.n_missing_branches)
2417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return nums
2427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __radd__(self, other):
2447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Implementing 0+Numbers allows us to sum() a list of Numbers.
2457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if other == 0:
2467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return self
2477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return NotImplemented
248