summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/parser.py81
-rw-r--r--tests/test_arcs.py19
-rw-r--r--tests/test_coverage.py57
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):