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