diff options
-rw-r--r-- | coverage/parser.py | 81 | ||||
-rw-r--r-- | tests/test_arcs.py | 19 | ||||
-rw-r--r-- | tests/test_coverage.py | 57 |
3 files changed, 117 insertions, 40 deletions
diff --git a/coverage/parser.py b/coverage/parser.py index 262a78e3..44cb1559 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -540,41 +540,56 @@ class AstArcAnalyzer(object): return set() def handle_Try(self, node): - return self.try_work(node, node.body, node.handlers, node.orelse, node.finalbody) - - def handle_TryExcept(self, node): - return self.try_work(node, node.body, node.handlers, node.orelse, None) - - def handle_TryFinally(self, node): - return self.try_work(node, node.body, None, None, node.finalbody) - - def try_work(self, node, body, handlers, orelse, finalbody): # try/finally is tricky. If there's a finally clause, then we need a # FinallyBlock to track what flows might go through the finally instead # of their normal flow. - if handlers: - handler_start = self.line_for_node(handlers[0]) + if node.handlers: + handler_start = self.line_for_node(node.handlers[0]) else: handler_start = None - if finalbody: - final_start = self.line_for_node(finalbody[0]) + + if node.finalbody: + final_start = self.line_for_node(node.finalbody[0]) else: final_start = None + self.block_stack.append(TryBlock(handler_start=handler_start, final_start=final_start)) + start = self.line_for_node(node) - exits = self.add_body_arcs(body, from_line=start) + exits = self.add_body_arcs(node.body, from_line=start) + try_block = self.block_stack.pop() handler_exits = set() - if handlers: - for handler_node in handlers: + last_handler_start = None + if node.handlers: + for handler_node in node.handlers: handler_start = self.line_for_node(handler_node) - # TODO: handler_node.name and handler_node.type + if last_handler_start is not None: + self.arcs.add((last_handler_start, handler_start)) + last_handler_start = handler_start handler_exits |= self.add_body_arcs(handler_node.body, from_line=handler_start) - # TODO: node.orelse + if handler_node.type is None: + # "except:" doesn't jump to subsequent handlers, or + # "finally:". + last_handler_start = None + # TODO: should we break here? Handlers after "except:" + # won't be run. Should coverage know that code can't be + # run, or should it flag it as not run? + + if node.orelse: + exits = self.add_body_arcs(node.orelse, prev_lines=exits) + exits |= handler_exits - if finalbody: - final_from = exits | try_block.break_from | try_block.continue_from | try_block.raise_from | try_block.return_from - exits = self.add_body_arcs(finalbody, prev_lines=final_from) + if node.finalbody: + final_from = exits | try_block.break_from | try_block.continue_from | try_block.return_from + if node.handlers and last_handler_start is not None: + # If there was an "except X:" clause, then a "raise" in the + # body goes to the "except X:" before the "finally", but the + # "except" go to the finally. + final_from.add(last_handler_start) + else: + final_from |= try_block.raise_from + exits = self.add_body_arcs(node.finalbody, prev_lines=final_from) if try_block.break_from: self.process_break_exits(exits) if try_block.continue_from: @@ -585,6 +600,30 @@ class AstArcAnalyzer(object): self.process_return_exits(exits) return exits + def handle_TryExcept(self, node): + # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get + # TryExcept, it means there was no finally, so fake it, and treat as + # a general Try node. + node.finalbody = [] + return self.handle_Try(node) + + def handle_TryFinally(self, node): + # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get + # TryFinally, see if there's a TryExcept nested inside. If so, merge + # them. Otherwise, fake fields to complete a Try node. + node.handlers = [] + node.orelse = [] + + if node.body: + first = node.body[0] + if first.__class__.__name__ == "TryExcept" and node.lineno == first.lineno: + assert len(node.body) == 1 + node.body = first.body + node.handlers = first.handlers + node.orelse = first.orelse + + return self.handle_Try(node) + def handle_While(self, node): constant_test = self.is_constant_expr(node.test) start = to_top = self.line_for_node(node.test) diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 6ba663bc..cd3aafff 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -627,7 +627,9 @@ class ExceptionArcTest(CoverageTest): c = 9 assert a == 3 and b == 1 and c == 9 """, - arcz=".1 12 23 45 39 59 67 79 9A A.", arcz_missing="45 59 67 79") + arcz=".1 12 23 45 46 39 59 67 79 69 9A A.", + arcz_missing="45 59 46 67 79 69", + ) self.check_coverage("""\ a, b, c = 1, 1, 1 try: @@ -640,8 +642,10 @@ class ExceptionArcTest(CoverageTest): c = 9 assert a == 1 and b == 5 and c == 9 """, - arcz=".1 12 23 45 39 59 67 79 9A A.", arcz_missing="39 67 79", - arcz_unpredicted="34") + arcz=".1 12 23 45 46 69 39 59 67 79 9A A.", + arcz_missing="39 46 67 79 69", + arcz_unpredicted="34", + ) self.check_coverage("""\ a, b, c = 1, 1, 1 try: @@ -654,8 +658,9 @@ class ExceptionArcTest(CoverageTest): c = 9 assert a == 7 and b == 1 and c == 9 """, - arcz=".1 12 23 45 39 59 67 79 9A A.", arcz_missing="39 45 59", - arcz_unpredicted="34 46", # TODO: 46 can be predicted. + arcz=".1 12 23 45 46 39 59 67 79 69 9A A.", + arcz_missing="39 45 59 69", + arcz_unpredicted="34", ) self.check_coverage("""\ a, b, c = 1, 1, 1 @@ -672,9 +677,9 @@ class ExceptionArcTest(CoverageTest): pass assert a == 1 and b == 1 and c == 10 """, - arcz=".1 12 23 34 4A 56 6A 78 8A AD BC CD D.", + arcz=".1 12 23 34 4A 56 6A 57 78 8A 7A AD BC CD D.", arcz_missing="4A 56 6A 78 8A AD", - arcz_unpredicted="45 57 7A AB", # TODO: 57 7A can be predicted. + arcz_unpredicted="45 AB", ) diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 78a5dc86..9bb0f488 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -1021,7 +1021,10 @@ class CompoundStatementTest(CoverageTest): a = 123 assert a == 123 """, - [1,2,3,4,5,7,8], "4-5") + [1,2,3,4,5,7,8], "4-5", + arcz=".1 12 23 45 58 37 78 8.", + arcz_missing="45 58", + ) self.check_coverage("""\ a = 0 try: @@ -1033,7 +1036,10 @@ class CompoundStatementTest(CoverageTest): a = 123 assert a == 99 """, - [1,2,3,4,5,6,8,9], "8") + [1,2,3,4,5,6,8,9], "8", + arcz=".1 12 23 34 45 56 69 89 9.", + arcz_missing="89", + ) def test_try_finally(self): self.check_coverage("""\ @@ -1379,7 +1385,10 @@ class ExcludeTest(CoverageTest): a = 123 assert a == 123 """, - [1,2,3,7,8], "", excludes=['#pragma: NO COVER']) + [1,2,3,7,8], "", excludes=['#pragma: NO COVER'], + arcz=".1 12 23 37 45 58 78 8.", + arcz_missing="45 58", + ) self.check_coverage("""\ a = 0 try: @@ -1391,7 +1400,10 @@ class ExcludeTest(CoverageTest): a = 123 assert a == 99 """, - [1,2,3,4,5,6,9], "", excludes=['#pragma: NO COVER']) + [1,2,3,4,5,6,9], "", excludes=['#pragma: NO COVER'], + arcz=".1 12 23 34 45 56 69 89 9.", + arcz_missing="89", + ) def test_excluding_try_except_pass(self): self.check_coverage("""\ @@ -1425,7 +1437,10 @@ class ExcludeTest(CoverageTest): a = 123 assert a == 123 """, - [1,2,3,7,8], "", excludes=['#pragma: NO COVER']) + [1,2,3,7,8], "", excludes=['#pragma: NO COVER'], + arcz=".1 12 23 37 45 58 78 8.", + arcz_missing="45 58", + ) self.check_coverage("""\ a = 0 try: @@ -1437,7 +1452,10 @@ class ExcludeTest(CoverageTest): x = 2 assert a == 99 """, - [1,2,3,4,5,6,9], "", excludes=['#pragma: NO COVER']) + [1,2,3,4,5,6,9], "", excludes=['#pragma: NO COVER'], + arcz=".1 12 23 34 45 56 69 89 9.", + arcz_missing="89", + ) def test_excluding_if_pass(self): # From a comment on the coverage.py page by Michael McNeil Forbes: @@ -1600,7 +1618,9 @@ class Py25Test(CoverageTest): b = 2 assert a == 1 and b == 2 """, - [1,2,3,4,5,7,8], "4-5") + [1,2,3,4,5,7,8], "4-5", + arcz=".1 12 23 37 45 57 78 8.", arcz_missing="45 57", + ) self.check_coverage("""\ a = 0; b = 0 try: @@ -1612,7 +1632,9 @@ class Py25Test(CoverageTest): b = 2 assert a == 99 and b == 2 """, - [1,2,3,4,5,6,8,9], "") + [1,2,3,4,5,6,8,9], "", + arcz=".1 12 23 34 45 56 68 89 9.", + ) self.check_coverage("""\ a = 0; b = 0 try: @@ -1626,7 +1648,9 @@ class Py25Test(CoverageTest): b = 2 assert a == 123 and b == 2 """, - [1,2,3,4,5,6,7,8,10,11], "6") + [1,2,3,4,5,6,7,8,10,11], "6", + arcz=".1 12 23 34 45 56 57 78 6A 8A AB B.", arcz_missing="56 6A", + ) self.check_coverage("""\ a = 0; b = 0 try: @@ -1642,7 +1666,10 @@ class Py25Test(CoverageTest): b = 2 assert a == 17 and b == 2 """, - [1,2,3,4,5,6,7,8,9,10,12,13], "6, 9-10") + [1,2,3,4,5,6,7,8,9,10,12,13], "6, 9-10", + arcz=".1 12 23 34 45 56 6C 57 78 8C 79 9A AC CD D.", + arcz_missing="56 6C 79 9A AC", + ) self.check_coverage("""\ a = 0; b = 0 try: @@ -1655,7 +1682,10 @@ class Py25Test(CoverageTest): b = 2 assert a == 123 and b == 2 """, - [1,2,3,4,5,7,9,10], "4-5") + [1,2,3,4,5,7,9,10], "4-5", + arcz=".1 12 23 37 45 59 79 9A A.", + arcz_missing="45 59", + ) self.check_coverage("""\ a = 0; b = 0 try: @@ -1669,7 +1699,10 @@ class Py25Test(CoverageTest): b = 2 assert a == 99 and b == 2 """, - [1,2,3,4,5,6,8,10,11], "8") + [1,2,3,4,5,6,8,10,11], "8", + arcz=".1 12 23 34 45 56 6A 8A AB B.", + arcz_missing="8A", + ) class ModuleTest(CoverageTest): |