diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2016-01-18 19:24:50 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2016-01-18 19:24:50 -0500 |
commit | f9f70709ef14812aea64cb450c67f99bac6ef6ac (patch) | |
tree | b1fb3204e641f7d217530b80fe2bf3164a3df6b5 | |
parent | 4b2ba8915687d5eabbb51d921bba169800bd93f3 (diff) | |
download | python-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.rst | 21 | ||||
-rw-r--r-- | coverage/parser.py | 32 | ||||
-rw-r--r-- | tests/test_arcs.py | 32 |
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""" |