summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2016-01-18 19:24:50 -0500
committerNed Batchelder <ned@nedbatchelder.com>2016-01-18 19:24:50 -0500
commitf9f70709ef14812aea64cb450c67f99bac6ef6ac (patch)
treeb1fb3204e641f7d217530b80fe2bf3164a3df6b5
parent4b2ba8915687d5eabbb51d921bba169800bd93f3 (diff)
downloadpython-coveragepy-git-f9f70709ef14812aea64cb450c67f99bac6ef6ac.tar.gz
Fix #466: multi-line statements first in decorated functions
Also, leave in the SetSpy tracer we've used before to find things like this.
-rw-r--r--CHANGES.rst21
-rw-r--r--coverage/parser.py32
-rw-r--r--tests/test_arcs.py32
3 files changed, 71 insertions, 14 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 8352fe3a..200308f0 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,15 +9,22 @@ Change history for Coverage.py
Unreleased
----------
-- In 4.1 beta 1, class docstrings were considered executable. Now they no
- longer are.
+- Problems with the new branch measurement in 4.1 beta 1 were fixed:
+
+ - Class docstrings were considered executable. Now they no longer are.
+
+ - ``yield from`` and ``await`` were considered returns from functions, since
+ they could tranfer control to the caller. This produced unhelpful "missing
+ branch" reports in a number of circumstances. Now they no longer are
+ considered returns.
+
+ - In unusual situations, a missing branch to a negative number was reported.
+ This has been fixed, closing `issue 466`_.
+
+- ``coverage report`` won't produce trailing whitespace.
-- In 4.1 beta 1, ``yield from`` and ``await`` were considered returns from
- functions, since they could tranfer control to the caller. This produced
- unhelpful "missing branch" reports in a number of circumstances. Now they no
- longer are considered returns.
-- ``coverage report`` won't produce trailing whitespace.
+.. _issue 466: https://bitbucket.org/ned/coveragepy/issues/466/impossible-missed-branch-to-a-negative
Version 4.1b1 --- 2016-01-10
diff --git a/coverage/parser.py b/coverage/parser.py
index 07cb75d2..2d0bceba 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -321,6 +321,19 @@ class TryBlock(object):
self.raise_from = set()
+class SetSpy(object): # pragma: debugging
+ """A set proxy that shows who is adding things to it."""
+ def __init__(self, the_set):
+ self.the_set = the_set
+
+ def add(self, arc):
+ """set.add, but with a stack trace."""
+ from coverage.debug import short_stack
+ print("\nAdding arc: {}".format(arc))
+ print(short_stack(limit=6))
+ self.the_set.add(arc)
+
+
class AstArcAnalyzer(object):
"""Analyze source text with an AST to find executable code paths."""
@@ -333,11 +346,13 @@ class AstArcAnalyzer(object):
if int(os.environ.get("COVERAGE_ASTDUMP", 0)): # pragma: debugging
# Dump the AST so that failing tests have helpful output.
- print(self.statements)
- print(self.multiline)
+ print("Statements: {}".format(self.statements))
+ print("Multiline map: {}".format(self.multiline))
ast_dump(self.root_node)
- self.arcs = set()
+ self.arcs = self.arcs_to_return = set()
+ if int(os.environ.get("COVERAGE_TRACK_ARCS", 0)): # pragma: debugging
+ self.arcs = SetSpy(self.arcs)
self.block_stack = []
def collect_arcs(self):
@@ -352,7 +367,7 @@ class AstArcAnalyzer(object):
if code_object_handler is not None:
code_object_handler(node)
- return self.arcs
+ return self.arcs_to_return
def nearest_blocks(self):
"""Yield the blocks in nearest-to-farthest order."""
@@ -517,10 +532,13 @@ class AstArcAnalyzer(object):
dec_start = self.line_for_node(dec_node)
if dec_start != last:
self.arcs.add((last, dec_start))
- last = dec_start
- # The definition line may have been missed, but we should have it in
- # `self.statements`.
+ last = dec_start
+ # The definition line may have been missed, but we should have it
+ # in `self.statements`. For some constructs, `line_for_node` is
+ # not what we'd think of as the first line in the statement, so map
+ # it to the first one.
body_start = self.line_for_node(node.body[0])
+ body_start = self.multiline.get(body_start, body_start)
for lineno in range(last+1, body_start):
if lineno in self.statements:
self.arcs.add((last, lineno))
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index 04dbd15a..5c0d1e50 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -1108,6 +1108,38 @@ class DecoratorArcTest(CoverageTest):
".6 D-6 ", # MyObject
)
+ def test_bug_466(self):
+ # A bad interaction between decorators and multi-line list assignments,
+ # believe it or not...!
+ self.check_coverage("""\
+ class Parser(object):
+
+ @classmethod
+ def parse(cls):
+ formats = [ 5 ]
+
+
+ return None
+
+ Parser.parse()
+ """,
+ arcz=".1 1A A. 13 3. .5 58 8-3",
+ )
+ self.check_coverage("""\
+ class Parser(object):
+
+ @classmethod
+ def parse(cls):
+ formats = [
+ 6,
+ ]
+ return None
+
+ Parser.parse()
+ """,
+ arcz=".1 1A A. 13 3. .5 58 8-3",
+ )
+
class LambdaArcTest(CoverageTest):
"""Tests of lambdas"""