DRY scanner code more...

Expand 2.6 testing
This commit is contained in:
rocky
2018-04-03 10:35:02 -04:00
parent e2dec73a62
commit 1cd2d1e915
9 changed files with 39 additions and 52 deletions

View File

@@ -28,7 +28,7 @@ check-short: pytest
$(MAKE) -C test check-short $(MAKE) -C test check-short
#: Tests for Python 2.7, 3.3 and 3.4 #: Tests for Python 2.7, 3.3 and 3.4
check-2.7 check-3.3 check-3.4: pytest check-2.6 check-2.7 check-3.3 check-3.4: pytest
$(MAKE) -C test $@ $(MAKE) -C test $@
#: Tests for Python 3.2 and 3.5 - pytest doesn't work here #: Tests for Python 3.2 and 3.5 - pytest doesn't work here

View File

@@ -23,12 +23,7 @@ def test_if_in_for():
code = bug.__code__ code = bug.__code__
scan = get_scanner(PYTHON_VERSION) scan = get_scanner(PYTHON_VERSION)
if 2.7 <= PYTHON_VERSION <= 3.0 and not IS_PYPY: if 2.7 <= PYTHON_VERSION <= 3.0 and not IS_PYPY:
bytecode = scan.build_instructions(code) scan.build_instructions(code)
scan.lines = scan.build_lines_data(code)
scan.insts = list(bytecode)
scan.offset2inst_index = {}
for i, inst in enumerate(scan.insts):
scan.offset2inst_index[inst.offset] = i
fjt = scan.find_jump_targets(False) fjt = scan.find_jump_targets(False)
## FIXME: the data below is wrong. ## FIXME: the data below is wrong.
@@ -43,12 +38,7 @@ def test_if_in_for():
# {'start': 62, 'end': 63, 'type': 'for-else'}] # {'start': 62, 'end': 63, 'type': 'for-else'}]
code = bug_loop.__code__ code = bug_loop.__code__
bytecode = scan.build_instructions(code) scan.build_instructions(code)
scan.lines = scan.build_lines_data(code)
scan.insts = list(bytecode)
scan.offset2inst_index = {}
for i, inst in enumerate(scan.insts):
scan.offset2inst_index[inst.offset] = i
fjt = scan.find_jump_targets(False) fjt = scan.find_jump_targets(False)
assert{64: [42], 67: [42, 42], 42: [16, 41], 19: [6]} == fjt assert{64: [42], 67: [42, 42], 42: [16, 41], 19: [6]} == fjt
assert scan.structs == [ assert scan.structs == [
@@ -62,12 +52,7 @@ def test_if_in_for():
{'start': 48, 'end': 67, 'type': 'while-loop'}] {'start': 48, 'end': 67, 'type': 'while-loop'}]
elif 3.2 < PYTHON_VERSION <= 3.4: elif 3.2 < PYTHON_VERSION <= 3.4:
bytecode = scan.build_instructions(code) scan.build_instructions(code)
scan.lines = scan.build_lines_data(code)
scan.insts = list(bytecode)
scan.offset2inst_index = {}
for i, inst in enumerate(scan.insts):
scan.offset2inst_index[inst.offset] = i
fjt = scan.find_jump_targets(False) fjt = scan.find_jump_targets(False)
assert {69: [66], 63: [18]} == fjt assert {69: [66], 63: [18]} == fjt
assert scan.structs == \ assert scan.structs == \

View File

@@ -6,7 +6,7 @@ import pytest
# uncompyle # uncompyle
from validate import validate_uncompyle from validate import validate_uncompyle
from test_fstring import expressions from test_fstring import expressions
from uncompyle6 import PYTHON_VERSION
alpha = st.sampled_from(string.ascii_lowercase) alpha = st.sampled_from(string.ascii_lowercase)
numbers = st.sampled_from(string.digits) numbers = st.sampled_from(string.digits)
@@ -81,10 +81,11 @@ def function_calls(draw,
def test_function_no_args(): def test_function_no_args():
validate_uncompyle("fn()") validate_uncompyle("fn()")
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
reason="need at least Python 2.7")
def isolated_function_calls(which): def isolated_function_calls(which):
""" """
Returns a strategy for generating function calls, but isolated to Returns a strategy for generating function calls, but isolated to
particular types of arguments, for example only positional arguments. particular types of arguments, for example only positional arguments.
This can help reason about debugging errors in specific types of function This can help reason about debugging errors in specific types of function
@@ -108,21 +109,29 @@ def isolated_function_calls(which):
with settings(max_examples=25): with settings(max_examples=25):
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
reason="need at least Python 2.7")
@given(isolated_function_calls('positional')) @given(isolated_function_calls('positional'))
@example("fn(0)") @example("fn(0)")
def test_function_positional_only(expr): def test_function_positional_only(expr):
validate_uncompyle(expr) validate_uncompyle(expr)
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
reason="need at least Python 2.7")
@given(isolated_function_calls('keyword')) @given(isolated_function_calls('keyword'))
@example("fn(a=0)") @example("fn(a=0)")
def test_function_call_keyword_only(expr): def test_function_call_keyword_only(expr):
validate_uncompyle(expr) validate_uncompyle(expr)
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
reason="need at least Python 2.7")
@given(isolated_function_calls('star')) @given(isolated_function_calls('star'))
@example("fn(*items)") @example("fn(*items)")
def test_function_call_star_only(expr): def test_function_call_star_only(expr):
validate_uncompyle(expr) validate_uncompyle(expr)
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
reason="need at least Python 2.7")
@given(isolated_function_calls('double_star')) @given(isolated_function_calls('double_star'))
@example("fn(**{})") @example("fn(**{})")
def test_function_call_double_star_only(expr): def test_function_call_double_star_only(expr):

View File

@@ -20,13 +20,17 @@ def test_grammar():
# We have custom rules that create the below # We have custom rules that create the below
expect_lhs = set(['pos_arg', 'get_iter', 'attribute']) expect_lhs = set(['pos_arg', 'get_iter', 'attribute'])
unused_rhs = set(['list', 'mkfunc', 'dict', unused_rhs = set(['list', 'mkfunc',
'mklambda', 'mklambda',
'unpack',]) 'unpack',])
expect_right_recursive = set([('designList', expect_right_recursive = set([('designList',
('store', 'DUP_TOP', 'designList'))]) ('store', 'DUP_TOP', 'designList'))])
expect_lhs.add('kvlist')
expect_lhs.add('kv3') if PYTHON_VERSION > 2.6:
expect_lhs.add('kvlist')
expect_lhs.add('kv3')
unused_rhs.add('dict')
if PYTHON3: if PYTHON3:
expect_lhs.add('load_genexpr') expect_lhs.add('load_genexpr')
@@ -85,6 +89,8 @@ def test_grammar():
""".split()) """.split())
if 2.6 <= PYTHON_VERSION <= 2.7: if 2.6 <= PYTHON_VERSION <= 2.7:
opcode_set = set(s.opc.opname).union(ignore_set) opcode_set = set(s.opc.opname).union(ignore_set)
if PYTHON_VERSION == 2.6:
opcode_set.add("THEN")
check_tokens(tokens, opcode_set) check_tokens(tokens, opcode_set)
elif PYTHON_VERSION == 3.4: elif PYTHON_VERSION == 3.4:
ignore_set.add('LOAD_CLASSNAME') ignore_set.add('LOAD_CLASSNAME')

View File

@@ -1,6 +1,8 @@
import pytest import pytest
from uncompyle6 import PYTHON_VERSION, PYTHON3, deparse_code from uncompyle6 import PYTHON_VERSION, deparse_code
@pytest.mark.skip(PYTHON_VERSION < 2.7,
reason="need at least Python 2.7")
def test_single_mode(): def test_single_mode():
single_expressions = ( single_expressions = (
'i = 1', 'i = 1',

View File

@@ -105,6 +105,11 @@ class Scanner(object):
bytecode = Bytecode(co, self.opc) bytecode = Bytecode(co, self.opc)
self.build_prev_op() self.build_prev_op()
self.insts = self.remove_extended_args(list(bytecode)) self.insts = self.remove_extended_args(list(bytecode))
self.lines = self.build_lines_data(co)
self.offset2inst_index = {}
for i, inst in enumerate(self.insts):
self.offset2inst_index[inst.offset] = i
return bytecode return bytecode
def build_lines_data(self, code_obj): def build_lines_data(self, code_obj):
@@ -117,10 +122,6 @@ class Scanner(object):
linestarts = list(self.opc.findlinestarts(code_obj)) linestarts = list(self.opc.findlinestarts(code_obj))
self.linestarts = dict(linestarts) self.linestarts = dict(linestarts)
# Plain set with offsets of first ops on line.
# FIXME: we probably could do without
self.linestart_offsets = set(a for (a, _) in linestarts)
# 'List-map' which shows line number of current op and offset of # 'List-map' which shows line number of current op and offset of
# first op on following line, given offset of op as index # first op on following line, given offset of op as index
lines = [] lines = []
@@ -452,6 +453,9 @@ class Scanner(object):
Go through passed offsets, filtering ifs Go through passed offsets, filtering ifs
located somewhere mid-line. located somewhere mid-line.
""" """
# FIXME: this doesn't work for Python 3.6+
filtered = [] filtered = []
for i in ifs: for i in ifs:
# For each offset, if line number of current and next op # For each offset, if line number of current and next op

View File

@@ -171,11 +171,6 @@ class Scanner2(Scanner):
customize['PyPy'] = 0 customize['PyPy'] = 0
codelen = len(self.code) codelen = len(self.code)
self.lines = self.build_lines_data(co)
self.offset2inst_index = {}
for i, inst in enumerate(self.insts):
self.offset2inst_index[inst.offset] = i
free, names, varnames = self.unmangle_code_names(co, classname) free, names, varnames = self.unmangle_code_names(co, classname)
self.names = names self.names = names
@@ -186,8 +181,6 @@ class Scanner2(Scanner):
self.load_asserts = set() self.load_asserts = set()
for i in self.op_range(0, codelen): for i in self.op_range(0, codelen):
self.offset2inst_index[inst.offset] = i
# We need to detect the difference between: # We need to detect the difference between:
# raise AssertionError # raise AssertionError
# and # and
@@ -358,7 +351,7 @@ class Scanner2(Scanner):
if (offset in self.stmts and if (offset in self.stmts and
self.code[offset+3] not in (self.opc.END_FINALLY, self.code[offset+3] not in (self.opc.END_FINALLY,
self.opc.POP_BLOCK)): self.opc.POP_BLOCK)):
if ((offset in self.linestart_offsets and if ((offset in self.linestarts and
self.code[self.prev[offset]] == self.opc.JUMP_ABSOLUTE) self.code[self.prev[offset]] == self.opc.JUMP_ABSOLUTE)
or self.code[target] == self.opc.FOR_ITER or self.code[target] == self.opc.FOR_ITER
or offset not in self.not_continue): or offset not in self.not_continue):
@@ -956,7 +949,7 @@ class Scanner2(Scanner):
'end': pre_rtarget}) 'end': pre_rtarget})
# FIXME: this is yet another case were we need dominators. # FIXME: this is yet another case were we need dominators.
if (pre_rtarget not in self.linestart_offsets if (pre_rtarget not in self.linestarts
or self.version < 2.7): or self.version < 2.7):
self.not_continue.add(pre_rtarget) self.not_continue.add(pre_rtarget)

View File

@@ -84,13 +84,6 @@ class Scanner26(scan.Scanner2):
codelen = len(self.code) codelen = len(self.code)
self.lines = self.build_lines_data(co)
self.insts = list(bytecode)
self.offset2inst_index = {}
for i, inst in enumerate(self.insts):
self.offset2inst_index[inst.offset] = i
free, names, varnames = self.unmangle_code_names(co, classname) free, names, varnames = self.unmangle_code_names(co, classname)
self.names = names self.names = names
@@ -248,7 +241,7 @@ class Scanner26(scan.Scanner2):
if (offset in self.stmts if (offset in self.stmts
and self.code[offset+3] not in (self.opc.END_FINALLY, and self.code[offset+3] not in (self.opc.END_FINALLY,
self.opc.POP_BLOCK)): self.opc.POP_BLOCK)):
if ((offset in self.linestart_offsets and if ((offset in self.linestarts and
tokens[-1].kind == 'JUMP_BACK') tokens[-1].kind == 'JUMP_BACK')
or offset not in self.not_continue): or offset not in self.not_continue):
op_name = 'CONTINUE' op_name = 'CONTINUE'

View File

@@ -187,19 +187,14 @@ class Scanner3(Scanner):
if self.is_pypy: if self.is_pypy:
customize['PyPy'] = 0 customize['PyPy'] = 0
self.lines = self.build_lines_data(co)
# Scan for assertions. Later we will # Scan for assertions. Later we will
# turn 'LOAD_GLOBAL' to 'LOAD_ASSERT'. # turn 'LOAD_GLOBAL' to 'LOAD_ASSERT'.
# 'LOAD_ASSERT' is used in assert statements. # 'LOAD_ASSERT' is used in assert statements.
self.load_asserts = set() self.load_asserts = set()
self.offset2inst_index = {}
n = len(self.insts) n = len(self.insts)
for i, inst in enumerate(self.insts): for i, inst in enumerate(self.insts):
self.offset2inst_index[inst.offset] = i
# We need to detect the difference between: # We need to detect the difference between:
# raise AssertionError # raise AssertionError
# and # and