summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/misc.py25
-rw-r--r--coverage/parser.py24
-rw-r--r--coverage/results.py17
-rw-r--r--tests/coveragetest.py28
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()