Skip to content

Commit

Permalink
- modified Python 3.11 support to ignore the line with RESUME
Browse files Browse the repository at this point in the history
  indicated by Python, aligning it with the behavior of Python 3.8-3.10;
  • Loading branch information
jaltmayerpizzorno committed Aug 23, 2023
1 parent 150813b commit dad2937
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 47 deletions.
2 changes: 2 additions & 0 deletions src/slipcover/bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ def branch2offset(arg: int) -> int:
op_LOAD_GLOBAL = dis.opmap["LOAD_GLOBAL"]

if PYTHON_VERSION >= (3,11):
op_RESUME = dis.opmap["RESUME"]
op_PUSH_NULL = dis.opmap["PUSH_NULL"]
op_PRECALL = dis.opmap["PRECALL"]
op_CALL = dis.opmap["CALL"]
op_CACHE = dis.opmap["CACHE"]
is_EXTENDED_ARG.append(dis._all_opmap["EXTENDED_ARG_QUICK"])
else:
op_RESUME = None
op_PUSH_NULL = None
op_CALL_FUNCTION = dis.opmap["CALL_FUNCTION"]

Expand Down
8 changes: 5 additions & 3 deletions src/slipcover/slipcover.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ def instrument(self, co: types.CodeType, parent: types.CodeType = 0) -> types.Co
for off_item in off_list:
if len(off_item) == 2: # from findlinestarts
offset, lineno = off_item
if lineno == 0: continue # Python 3.11.0b4 generates a 0th line
if lineno == 0 or co.co_code[offset] == bc.op_RESUME:
continue

# Can't insert between an EXTENDED_ARG and the final opcode
if (offset >= 2 and co.co_code[offset-2] == bc.op_EXTENDED_ARG):
Expand Down Expand Up @@ -160,8 +161,9 @@ def instrument(self, co: types.CodeType, parent: types.CodeType = 0) -> types.Co
index = list(zip(ed.get_inserts(), insert_labels))

with self.lock:
# Python 3.11.0b4 generates a 0th line
self.code_lines[co.co_filename].update(line[1] for line in dis.findlinestarts(co) if line[1] != 0)
# Python 3.11 generates a 0th line; 3.11+ generates a line just for RESUME
self.code_lines[co.co_filename].update(line for off, line in dis.findlinestarts(co) \
if line != 0 and co.co_code[off] != bc.op_RESUME)
self.code_branches[co.co_filename].update(branch_set)

if not parent:
Expand Down
61 changes: 17 additions & 44 deletions tests/slipcover_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def check_line_probes(code):
for (offset, line) in dis.findlinestarts(code):
if line:
print(f"checking {code.co_name} line {line}")
if bc.op_RESUME == code.co_code[offset]:
continue

assert bc.op_NOP == code.co_code[offset], f"NOP missing at offset {offset}"
probe_len = bc.branch2offset(code.co_code[offset+1])
it = iter(bc.unpack_opargs(code.co_code[offset+2:offset+2+probe_len]))
Expand Down Expand Up @@ -146,10 +149,7 @@ def foo(n): #1
assert {simple_current_file()} == cov['files'].keys()

cov = cov['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 2, 4, 5, 6, 7] == [l-base_line for l in cov['executed_lines']]
else:
assert [2, 4, 5, 6, 7] == [l-base_line for l in cov['executed_lines']]
assert [2, 4, 5, 6, 7] == [l-base_line for l in cov['executed_lines']]
assert [3] == [l-base_line for l in cov['missing_lines']]


Expand All @@ -174,9 +174,7 @@ def foo(n):
assert foo.__code__.co_stacksize >= bc.calc_max_stack(foo.__code__.co_code)
assert '__slipcover__' in foo.__code__.co_consts

# Are all lines where we expect?
for (offset, _) in dis.findlinestarts(foo.__code__):
assert bc.op_NOP == foo.__code__.co_code[offset]
check_line_probes(foo.__code__)

# dis.dis(foo)
assert X == foo(123)
Expand All @@ -185,10 +183,7 @@ def foo(n):
assert {simple_current_file()} == cov['files'].keys()

cov = cov['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 2, 3, 4, 5, 6, 7, 8] == [l-base_line for l in cov['executed_lines']]
else:
assert [2, 3, 4, 5, 6, 7, 8] == [l-base_line for l in cov['executed_lines']]
assert [2, 3, 4, 5, 6, 7, 8] == [l-base_line for l in cov['executed_lines']]

assert [] == cov['missing_lines']

Expand Down Expand Up @@ -219,9 +214,7 @@ def foo(n): #1
assert foo.__code__.co_stacksize >= orig_code.co_stacksize
assert '__slipcover__' in foo.__code__.co_consts

# Are all lines where we expect?
for (offset, _) in dis.findlinestarts(foo.__code__):
assert bc.op_NOP == foo.__code__.co_code[offset]
check_line_probes(foo.__code__)

dis.dis(foo)
assert X == foo(42)
Expand All @@ -230,10 +223,7 @@ def foo(n): #1
assert {simple_current_file()} == cov['files'].keys()

cov = cov['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 2, 3, 4, 5, 7, 8, 10, 12] == [l-base_line for l in cov['executed_lines']]
else:
assert [2, 3, 4, 5, 7, 8, 10, 12] == [l-base_line for l in cov['executed_lines']]
assert [2, 3, 4, 5, 7, 8, 10, 12] == [l-base_line for l in cov['executed_lines']]

all_lines = {l-base_line for offset, l in dis.findlinestarts(foo.__code__)}

Expand Down Expand Up @@ -304,10 +294,7 @@ def foo(n):
assert {simple_current_file()} == cov['files'].keys()

cov = cov['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 3, 4, 5, 6] == [l-base_line for l in cov['executed_lines']]
else:
assert [3, 4, 5, 6] == [l-base_line for l in cov['executed_lines']]
assert [3, 4, 5, 6] == [l-base_line for l in cov['executed_lines']]
assert [] == cov['missing_lines']


Expand Down Expand Up @@ -526,10 +513,7 @@ def bar(): # 8
assert {simple_current_file()} == cov['files'].keys()

cov = cov['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 6, 8, 9, 12, 13, 15] == [l-base_line for l in cov['missing_lines']]
else:
assert [6, 8, 9, 12, 13, 15] == [l-base_line for l in cov['missing_lines']]
assert [6, 8, 9, 12, 13, 15] == [l-base_line for l in cov['missing_lines']]
assert [] == cov['executed_lines']


Expand Down Expand Up @@ -623,12 +607,13 @@ def bar(n):

sci.instrument(foo)

for off, *_ in dis.findlinestarts(foo.__code__):
assert foo.__code__.co_code[off] == bc.op_NOP
check_line_probes(foo.__code__)

assert 6 == foo(3)

for off, *_ in dis.findlinestarts(foo.__code__):
if bc.op_RESUME == foo.__code__.co_code[off]:
continue
assert foo.__code__.co_code[off] == bc.op_JUMP_FORWARD


Expand Down Expand Up @@ -674,10 +659,7 @@ def foo(n):

assert 6 == foo(3)
cov = sci.get_coverage()['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 2, 5] == [l-base_line for l in cov['executed_lines']]
else:
assert [2, 5] == [l-base_line for l in cov['executed_lines']]
assert [2, 5] == [l-base_line for l in cov['executed_lines']]
assert [3, 4] == [l-base_line for l in cov['missing_lines']]


Expand Down Expand Up @@ -710,10 +692,7 @@ def foo(n):
assert sum(pr.was_removed(t) for t in old_code.co_consts if type(t).__name__ == 'PyCapsule') > 0

cov = sci.get_coverage()['files']['foo']
if PYTHON_VERSION >= (3,11):
assert [1,2,3,4,5] == cov['executed_lines']
else:
assert [2,3,4,5] == cov['executed_lines']
assert [2,3,4,5] == cov['executed_lines']
assert [] == cov['missing_lines']
if do_branch:
assert [(3,4),(3,5)] == cov['executed_branches']
Expand Down Expand Up @@ -749,10 +728,7 @@ class Bar:
foo(1)

cov = sci.get_coverage()['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 2, 3, 5, 6, 7, 8, 9, 10] == [l-base_line for l in cov['executed_lines']]
else:
assert [2, 3, 5, 6, 7, 8, 9, 10] == [l-base_line for l in cov['executed_lines']]
assert [2, 3, 5, 6, 7, 8, 9, 10] == [l-base_line for l in cov['executed_lines']]
assert [4] == [l-base_line for l in cov['missing_lines']]


Expand Down Expand Up @@ -784,10 +760,7 @@ class Bar:
foo(1)

cov = sci.get_coverage()['files'][simple_current_file()]
if PYTHON_VERSION >= (3,11):
assert [1, 2, 3, 5, 6, 7, 8, 9, 10] == [l-base_line for l in cov['executed_lines']]
else:
assert [2, 3, 5, 6, 7, 8, 9, 10] == [l-base_line for l in cov['executed_lines']]
assert [2, 3, 5, 6, 7, 8, 9, 10] == [l-base_line for l in cov['executed_lines']]
assert [4] == [l-base_line for l in cov['missing_lines']]


Expand Down

0 comments on commit dad2937

Please sign in to comment.