diff options
-rw-r--r-- | coverage/misc.py | 25 | ||||
-rw-r--r-- | coverage/parser.py | 24 | ||||
-rw-r--r-- | coverage/results.py | 17 | ||||
-rw-r--r-- | tests/coveragetest.py | 28 |
4 files changed, 50 insertions, 44 deletions
diff --git a/coverage/misc.py b/coverage/misc.py index 50396d61..44f89772 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -83,19 +83,24 @@ def format_lines(statements, lines): def expensive(fn): - """A decorator to cache the result of an expensive operation. + """A decorator to indicate that a method shouldn't be called more than once. - Only applies to methods with no arguments. + Normally, this does nothing. During testing, this raises an exception if + called more than once. """ - attr = "_cache_" + fn.__name__ - - def _wrapped(self): - """Inner function that checks the cache.""" - if not hasattr(self, attr): - setattr(self, attr, fn(self)) - return getattr(self, attr) - return _wrapped + if env.TESTING: + attr = "_once_" + fn.__name__ + + def _wrapped(self): + """Inner function that checks the cache.""" + if hasattr(self, attr): + raise Exception("Shouldn't have called %s more than once" % fn.__name__) + setattr(self, attr, True) + return fn(self) + return _wrapped + else: + return fn def bool_or_none(b): diff --git a/coverage/parser.py b/coverage/parser.py index bc79a30a..dc067157 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -62,8 +62,9 @@ class PythonParser(object): # The line numbers that start statements. self.statement_starts = set() - # Lazily-created ByteParser + # Lazily-created ByteParser and arc data. self._byte_parser = None + self._all_arcs = None @property def byte_parser(self): @@ -232,13 +233,18 @@ class PythonParser(object): normalized to the first line of multi-line statements. """ - all_arcs = [] - for l1, l2 in self.byte_parser._all_arcs(): - fl1 = self.first_line(l1) - fl2 = self.first_line(l2) - if fl1 != fl2: - all_arcs.append((fl1, fl2)) - return all_arcs + return self.arcs_internal() + + def arcs_internal(self): + """Internal worker to calculate the arcs.""" + if self._all_arcs is None: + self._all_arcs = [] + for l1, l2 in self.byte_parser._all_arcs(): + fl1 = self.first_line(l1) + fl2 = self.first_line(l2) + if fl1 != fl2: + self._all_arcs.append((fl1, fl2)) + return self._all_arcs @expensive def exit_counts(self): @@ -249,7 +255,7 @@ class PythonParser(object): """ excluded_lines = self.first_lines(self.excluded) exit_counts = collections.defaultdict(int) - for l1, l2 in self.arcs(): + for l1, l2 in self.arcs_internal(): if l1 < 0: # Don't ever report -1 as a line number continue diff --git a/coverage/results.py b/coverage/results.py index b2848d02..9627373d 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -25,6 +25,8 @@ class Analysis(object): self.missing = self.statements - executed if self.data.has_arcs(): + self._arc_possibilities = sorted(self.file_reporter.arcs()) + self.exit_counts = self.file_reporter.exit_counts() self.no_branch = self.file_reporter.no_branch_lines() n_branches = self.total_branches() mba = self.missing_branch_arcs() @@ -33,8 +35,10 @@ class Analysis(object): ) n_missing_branches = sum(len(v) for k,v in iitems(mba)) else: - n_branches = n_partial_branches = n_missing_branches = 0 + self._arc_possibilities = [] + self.exit_counts = {} self.no_branch = set() + n_branches = n_partial_branches = n_missing_branches = 0 self.numbers = Numbers( n_files=1, @@ -60,7 +64,7 @@ class Analysis(object): def arc_possibilities(self): """Returns a sorted list of the arcs in the code.""" - return sorted(self.file_reporter.arcs()) + return self._arc_possibilities def arcs_executed(self): """Returns a sorted list of the arcs actually executed in the code.""" @@ -116,13 +120,11 @@ class Analysis(object): def branch_lines(self): """Returns a list of line numbers that have more than one exit.""" - exit_counts = self.file_reporter.exit_counts() - return [l1 for l1,count in iitems(exit_counts) if count > 1] + return [l1 for l1,count in iitems(self.exit_counts) if count > 1] def total_branches(self): """How many total branches are there?""" - exit_counts = self.file_reporter.exit_counts() - return sum(count for count in exit_counts.values() if count > 1) + return sum(count for count in self.exit_counts.values() if count > 1) def missing_branch_arcs(self): """Return arcs that weren't executed from branch lines. @@ -145,11 +147,10 @@ class Analysis(object): (total_exits, taken_exits). """ - exit_counts = self.file_reporter.exit_counts() missing_arcs = self.missing_branch_arcs() stats = {} for lnum in self.branch_lines(): - exits = exit_counts[lnum] + exits = self.exit_counts[lnum] try: missing = len(missing_arcs[lnum]) except KeyError: diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 9e0bb26e..fdb27e6e 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -108,9 +108,9 @@ class CoverageTest( # Map chars to numbers for arcz_to_arcs _arcz_map = {'.': -1} - _arcz_map.update(dict((c, ord(c)-ord('0')) for c in '123456789')) + _arcz_map.update(dict((c, ord(c) - ord('0')) for c in '123456789')) _arcz_map.update(dict( - (c, 10+ord(c)-ord('A')) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + (c, 10 + ord(c) - ord('A')) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' )) def arcz_to_arcs(self, arcz): @@ -141,7 +141,7 @@ class CoverageTest( assert pair[1] == '-' a, _, b = pair bsgn = -1 - arcs.append((asgn*self._arcz_map[a], bsgn*self._arcz_map[b])) + arcs.append((asgn * self._arcz_map[a], bsgn * self._arcz_map[b])) return sorted(arcs) def assert_equal_args(self, a1, a2, msg=None): @@ -178,7 +178,7 @@ class CoverageTest( # Coverage.py wants to deal with things as modules with file names. modname = self.get_module_name() - self.make_file(modname+".py", text) + self.make_file(modname + ".py", text) if arcs is None and arcz is not None: arcs = self.arcz_to_arcs(arcz) @@ -186,9 +186,10 @@ class CoverageTest( arcs_missing = self.arcz_to_arcs(arcz_missing) if arcs_unpredicted is None and arcz_unpredicted is not None: arcs_unpredicted = self.arcz_to_arcs(arcz_unpredicted) + branch = any(x is not None for x in [arcs, arcs_missing, arcs_unpredicted]) # Start up coverage.py. - cov = coverage.Coverage(branch=(arcs_missing is not None)) + cov = coverage.Coverage(branch=branch) cov.erase() for exc in excludes or []: cov.exclude(exc) @@ -215,9 +216,7 @@ class CoverageTest( if statements == line_list: break else: - self.fail( - "None of the lines choices matched %r" % statements - ) + self.fail("None of the lines choices matched %r" % statements) missing_formatted = analysis.missing_formatted() if isinstance(missing, string_class): @@ -227,27 +226,22 @@ class CoverageTest( if missing_formatted == missing_list: break else: - self.fail( - "None of the missing choices matched %r" % - missing_formatted - ) + self.fail("None of the missing choices matched %r" % missing_formatted) if arcs is not None: - self.assert_equal_args( - analysis.arc_possibilities(), arcs, "Possible arcs differ" - ) + self.assert_equal_args(analysis.arc_possibilities(), arcs, "Possible arcs differ") if arcs_missing is not None: self.assert_equal_args( analysis.arcs_missing(), arcs_missing, "Missing arcs differ" - ) + ) if arcs_unpredicted is not None: self.assert_equal_args( analysis.arcs_unpredicted(), arcs_unpredicted, "Unpredicted arcs differ" - ) + ) if report: frep = StringIO() |