diff --git a/.gitignore b/.gitignore index 76e99b32..d4e5cf9c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ /unpyc __pycache__ build +/.venv* +/.idea \ No newline at end of file diff --git a/pytest/test_function_call.py b/pytest/test_function_call.py new file mode 100644 index 00000000..7b7b9729 --- /dev/null +++ b/pytest/test_function_call.py @@ -0,0 +1,128 @@ +# std +import string +# 3rd party +from hypothesis import given, assume, strategies as st +import pytest +# uncompyle +from validate import validate_uncompyle + + +alpha = st.sampled_from(string.ascii_lowercase) +numbers = st.sampled_from(string.digits) +alphanum = st.sampled_from(string.ascii_lowercase + string.digits) +expressions = st.sampled_from([x for x in string.ascii_lowercase + string.digits] + ['x+1']) + + +@st.composite +def function_calls(draw): + """ + Strategy factory for generating function calls. + + :param draw: Callable which draws examples from other strategies. + + :return: The function call text. + """ + list1 = st.lists(alpha, min_size=0, max_size=1) + list3 = st.lists(alpha, min_size=0, max_size=3) + + positional_args = draw(list3) + named_args = [x + '=0' for x in draw(list3)] + star_args = ['*' + x for x in draw(list1)] + double_star_args = ['**' + x for x in draw(list1)] + + arguments = positional_args + named_args + star_args + double_star_args + draw(st.randoms()).shuffle(arguments) + arguments = ','.join(arguments) + + function_call = 'fn({arguments})'.format(arguments=arguments) + try: + # TODO: Figure out the exact rules for ordering of positional, named, + # star args, double star args and in which versions the various + # types of arguments are supported so we don't need to check that the + # expression compiles like this. + compile(function_call, '', 'single') + except: + assume(False) + return function_call + + +@pytest.mark.xfail() +def test_CALL_FUNCTION(): + validate_uncompyle("fn(w,m,f)") + + +@pytest.mark.xfail() +def test_BUILD_CONST_KEY_MAP_BUILD_MAP_UNPACK_WITH_CALL_BUILD_TUPLE_CALL_FUNCTION_EX(): + validate_uncompyle("fn(w=0,m=0,**v)") + + +@pytest.mark.xfail() +def test_BUILD_MAP_BUILD_MAP_UNPACK_WITH_CALL_BUILD_TUPLE_CALL_FUNCTION_EX(): + validate_uncompyle("fn(a=0,**g)") + + +@pytest.mark.xfail() +def test_CALL_FUNCTION_KW(): + validate_uncompyle("fn(j=0)") + + +@pytest.mark.xfail() +def test_CALL_FUNCTION_EX(): + validate_uncompyle("fn(*g,**j)") + + +@pytest.mark.xfail() +def test_BUILD_MAP_CALL_FUNCTION_EX(): + validate_uncompyle("fn(*z,u=0)") + + +@pytest.mark.xfail() +def test_BUILD_TUPLE_CALL_FUNCTION_EX(): + validate_uncompyle("fn(**a)") + + +@pytest.mark.xfail() +def test_BUILD_MAP_BUILD_TUPLE_BUILD_TUPLE_UNPACK_WITH_CALL_CALL_FUNCTION_EX(): + validate_uncompyle("fn(b,b,b=0,*a)") + + +@pytest.mark.xfail() +def test_BUILD_TUPLE_BUILD_TUPLE_UNPACK_WITH_CALL_CALL_FUNCTION_EX(): + validate_uncompyle("fn(*c,v)") + + +@pytest.mark.xfail() +def test_BUILD_CONST_KEY_MAP_CALL_FUNCTION_EX(): + validate_uncompyle("fn(i=0,y=0,*p)") + + +@pytest.mark.skip(reason='skipping property based test until all individual tests are passing') +@given(function_calls()) +def test_function_call(function_call): + validate_uncompyle(function_call) + + +examples = set() +generate_examples = False + + +@pytest.mark.skipif(not generate_examples, reason='not generating examples') +@given(function_calls()) +def test_generate_hypothesis(function_call): + examples.add(function_call) + + +@pytest.mark.skipif(not generate_examples, reason='not generating examples') +def test_generate_examples(): + import dis + example_opcodes = {} + for example in examples: + opcodes = tuple(sorted(set( + instruction.opname + for instruction in dis.Bytecode(example) + if instruction.opname not in ('LOAD_CONST', 'LOAD_NAME', 'RETURN_VALUE') + ))) + example_opcodes[opcodes] = example + for k, v in example_opcodes.items(): + print('def test_' + '_'.join(k) + '():\n validate_uncompyle("' + v + '")\n\n') + return diff --git a/pytest/validate.py b/pytest/validate.py index 1a0500ab..f5cc1f7f 100644 --- a/pytest/validate.py +++ b/pytest/validate.py @@ -2,18 +2,23 @@ from __future__ import print_function # std import os -import dis import difflib import subprocess import tempfile +import functools # compatability import six # uncompyle6 / xdis -from uncompyle6 import PYTHON_VERSION, deparse_code +from uncompyle6 import PYTHON_VERSION, IS_PYPY, deparse_code +# TODO : I think we can get xdis to support the dis api (python 3 version) by doing something like this there +from xdis.bytecode import Bytecode +from xdis.main import get_opcode +opc = get_opcode(PYTHON_VERSION, IS_PYPY) +Bytecode = functools.partial(Bytecode, opc=opc) def _dis_to_text(co): - return dis.Bytecode(co).dis() + return Bytecode(co).dis() def print_diff(original, uncompyled): @@ -99,9 +104,8 @@ def are_code_objects_equal(co1, co2): :return: True if the two code objects are approximately equal, otherwise False. """ - # TODO : Use xdis for python2 compatability - instructions1 = dis.Bytecode(co1) - instructions2 = dis.Bytecode(co2) + instructions1 = Bytecode(co1) + instructions2 = Bytecode(co2) for opcode1, opcode2 in zip(instructions1, instructions2): if not are_instructions_equal(opcode1, opcode2): return False