Sun, 27 Feb 2011 11:29:52 +0100
Prepared release of 5.1.0.
"""Results of coverage measurement.""" import os from .backward import set, sorted # pylint: disable-msg=W0622 from .misc import format_lines, NoSource from .parser import CodeParser class Analysis(object): """The results of analyzing a code unit.""" def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename ext = os.path.splitext(self.filename)[1] source = None if ext == '.py': if not os.path.exists(self.filename): source = self.coverage.file_locator.get_zip_data(self.filename) if not source: raise NoSource("No source for code: %r" % self.filename) self.parser = CodeParser( text=source, filename=self.filename, exclude=self.coverage.exclude_re ) self.statements, self.excluded = self.parser.parse_source() # Identify missing statements. executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): n_branches = self.total_branches() mba = self.missing_branch_arcs() n_missing_branches = sum([len(v) for v in mba.values()]) else: n_branches = n_missing_branches = 0 self.numbers = Numbers( n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_missing_branches=n_missing_branches, ) def missing_formatted(self): """The missing line numbers, formatted nicely. Returns a string like "1-2, 5-11, 13-14". """ return format_lines(self.statements, self.missing) def has_arcs(self): """Were arcs measured in this result?""" return self.coverage.data.has_arcs() def arc_possibilities(self): """Returns a sorted list of the arcs in the code.""" return self.parser.arcs() def arcs_executed(self): """Returns a sorted list of the arcs actually executed in the code.""" executed = self.coverage.data.executed_arcs(self.filename) m2fl = self.parser.first_line executed = [(m2fl(l1), m2fl(l2)) for (l1,l2) in executed] return sorted(executed) def arcs_missing(self): """Returns a sorted list of the arcs in the code not executed.""" possible = self.arc_possibilities() executed = self.arcs_executed() missing = [p for p in possible if p not in executed] return sorted(missing) def arcs_unpredicted(self): """Returns a sorted list of the executed arcs missing from the code.""" possible = self.arc_possibilities() executed = self.arcs_executed() # Exclude arcs here which connect a line to itself. They can occur # in executed data in some cases. This is where they can cause # trouble, and here is where it's the least burden to remove them. unpredicted = [ e for e in executed if e not in possible and e[0] != e[1] ] return sorted(unpredicted) def branch_lines(self): """Returns lines that have more than one exit.""" exit_counts = self.parser.exit_counts() return [l1 for l1,count in exit_counts.items() if count > 1] def total_branches(self): """How many total branches are there?""" exit_counts = self.parser.exit_counts() return sum([count for count in exit_counts.values() if count > 1]) def missing_branch_arcs(self): """Return arcs that weren't executed from branch lines. Returns {l1:[l2a,l2b,...], ...} """ missing = self.arcs_missing() branch_lines = set(self.branch_lines()) mba = {} for l1, l2 in missing: if l1 in branch_lines: if l1 not in mba: mba[l1] = [] mba[l1].append(l2) return mba class Numbers(object): """The numerical results of measuring coverage. This holds the basic statistics from `Analysis`, and is used to roll up statistics across files. """ def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0, n_branches=0, n_missing_branches=0 ): self.n_files = n_files self.n_statements = n_statements self.n_excluded = n_excluded self.n_missing = n_missing self.n_branches = n_branches self.n_missing_branches = n_missing_branches def _get_n_executed(self): """Returns the number of executed statements.""" return self.n_statements - self.n_missing n_executed = property(_get_n_executed) def _get_n_executed_branches(self): """Returns the number of executed branches.""" return self.n_branches - self.n_missing_branches n_executed_branches = property(_get_n_executed_branches) def _get_pc_covered(self): """Returns a single percentage value for coverage.""" if self.n_statements > 0: pc_cov = (100.0 * (self.n_executed + self.n_executed_branches) / (self.n_statements + self.n_branches)) else: pc_cov = 100.0 return pc_cov pc_covered = property(_get_pc_covered) def __add__(self, other): nums = Numbers() nums.n_files = self.n_files + other.n_files nums.n_statements = self.n_statements + other.n_statements nums.n_excluded = self.n_excluded + other.n_excluded nums.n_missing = self.n_missing + other.n_missing nums.n_branches = self.n_branches + other.n_branches nums.n_missing_branches = (self.n_missing_branches + other.n_missing_branches) return nums def __radd__(self, other): # Implementing 0+Numbers allows us to sum() a list of Numbers. if other == 0: return self raise NotImplemented # # eflag: FileType = Python2