Skip to content

Commit e6505f5

Browse files
committed
ByteParser now recognizes synthetic 'return None' blocks and treats them correctly.
1 parent 7dd0e1e commit e6505f5

File tree

3 files changed

+32
-5
lines changed

3 files changed

+32
-5
lines changed

coverage/parser.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ def _opcode_set(*names):
273273
OP_END_FINALLY = _opcode('END_FINALLY')
274274
OP_COMPARE_OP = _opcode('COMPARE_OP')
275275
COMPARE_EXCEPTION = 10 # just have to get this const from the code.
276+
OP_LOAD_CONST = _opcode('LOAD_CONST')
277+
OP_RETURN_VALUE = _opcode('RETURN_VALUE')
276278

277279

278280
class ByteParser(object):
@@ -393,6 +395,9 @@ def _split_into_chunks(self):
393395
# is a count of how many ignores are left.
394396
ignore_branch = 0
395397

398+
# We have to handle the last two bytecodes specially.
399+
ult = penult = None
400+
396401
for bc in ByteCodes(self.code.co_code):
397402
# Maybe have to start a new block
398403
if bc.offset in bytes_lines_map:
@@ -445,8 +450,30 @@ def _split_into_chunks(self):
445450
# This is an except clause. We want to overlook the next
446451
# branch, so that except's don't count as branches.
447452
ignore_branch += 1
453+
454+
penult = ult
455+
ult = bc
456+
448457

449458
if chunks:
459+
# The last two bytecodes could be a dummy "return None" that
460+
# shouldn't be counted as real code. Every Python code object seems
461+
# to end with a return, and a "return None" is inserted if there
462+
# isn't an explicit return in the source.
463+
if ult and penult:
464+
if penult.op == OP_LOAD_CONST and ult.op == OP_RETURN_VALUE:
465+
if self.code.co_consts[penult.arg] is None:
466+
# This is "return None", but is it dummy? A real line
467+
# would be a last chunk all by itself.
468+
if chunks[-1].byte != penult.offset:
469+
last_chunk = chunks[-1]
470+
last_chunk.exits.remove(-1)
471+
last_chunk.exits.add(penult.offset)
472+
chunk = Chunk(penult.offset)
473+
chunk.exits.add(-1)
474+
chunks.append(chunk)
475+
476+
# Give all the chunks a length.
450477
chunks[-1].length = bc.next_offset - chunks[-1].byte
451478
for i in range(len(chunks)-1):
452479
chunks[i].length = chunks[i+1].byte - chunks[i].byte

test/test_arcs.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,15 @@ def if_ret(a):
113113
arcz=".1 16 67 7. .2 23 24 3. 45 5.", arcz_missing=""
114114
)
115115

116-
def XXX_dont_confuse_exit_and_else(self):
116+
def test_dont_confuse_exit_and_else(self):
117117
self.check_coverage("""\
118118
def foo():
119119
if foo:
120120
a = 3
121121
else:
122122
a = 5
123123
return a
124-
assert foo() == 3
124+
assert foo() == 3 # 7
125125
""",
126126
arcz=".1 17 7. .2 23 36 25 56 6.", arcz_missing="25 56"
127127
)
@@ -131,7 +131,7 @@ def foo():
131131
a = 3
132132
else:
133133
a = 5
134-
foo()
134+
foo() # 6
135135
""",
136136
arcz=".1 16 6. .2 23 3. 25 5.", arcz_missing="25 5."
137137
)

test/test_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Bar:
6767
1:0, 2:1, 3:1
6868
})
6969

70-
def XXX_missing_branch_to_excluded_code(self):
70+
def test_missing_branch_to_excluded_code(self):
7171
cp = self.parse_source("""\
7272
if fooey:
7373
a = 2
@@ -93,4 +93,4 @@ def foo():
9393
a = 5
9494
b = 6
9595
""")
96-
self.assertEqual(cp.exit_counts(), { 1:13, 2:1, 6:1 })
96+
self.assertEqual(cp.exit_counts(), { 1:1, 2:1, 3:1, 6:1 })

0 commit comments

Comments
 (0)