mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2024-11-26 22:50:40 +00:00
Go over full-version testing
When all versions, do less, but do the run tests. Overall overall better. Get rid of the tests that were the most problematic with hypothesis. Hypothesis just doesn't run on enough versions and needs specific versions for things we do use
This commit is contained in:
parent
bffbd0b352
commit
9f270dce4a
@ -21,8 +21,9 @@ for version in $PYVERSIONS; do
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
make clean && pip install -e .
|
make clean && pip install -e .
|
||||||
if ! make check; then
|
if ! make check-short; then
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
echo === $version ===
|
echo === $version ===
|
||||||
done
|
done
|
||||||
|
make check
|
||||||
|
@ -5,4 +5,4 @@ if [[ $0 == ${BASH_SOURCE[0]} ]] ; then
|
|||||||
echo "This script should be *sourced* rather than run directly through bash"
|
echo "This script should be *sourced* rather than run directly through bash"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
export PYVERSIONS='3.5.9 3.6.9 2.6.9 3.3.7 2.7.17 3.2.6 3.1.5 3.4.10 3.7.5'
|
export PYVERSIONS='3.5.9 3.6.10 2.6.9 3.3.7 2.7.17 3.2.6 3.1.5 3.4.8 3.7.6 3.8.1'
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
PHONY=check test pytest
|
PHONY=check test pytest
|
||||||
|
SHELL=/bin/bash
|
||||||
|
|
||||||
PYTHON ?= python
|
PYTHON ?= python
|
||||||
|
|
||||||
#: Run all tests
|
#: Run all tests
|
||||||
test check pytest:
|
test check pytest:
|
||||||
py.test
|
@PYTHON_VERSION=`$(PYTHON) -V 2>&1 | cut -d ' ' -f 2 | cut -d'.' -f1,2`; \
|
||||||
|
if [[ $$PYTHON_VERSION > 3.2 ]] || [[ $$PYTHON_VERSION == 2.7 ]] || [[ $$PYTHON_VERSION == 2.6 ]]; then \
|
||||||
|
py.test; \
|
||||||
|
fi
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
# std
|
|
||||||
# test
|
|
||||||
import sys
|
|
||||||
from uncompyle6 import PYTHON_VERSION, code_deparse
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
|
||||||
PYTHON_VERSION <= 2.6, reason="hypothesis needs 2.7 or later"
|
|
||||||
)
|
|
||||||
if PYTHON_VERSION > 2.6:
|
|
||||||
|
|
||||||
import hypothesis
|
|
||||||
from hypothesis import strategies as st
|
|
||||||
|
|
||||||
# uncompyle6
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def expressions(draw):
|
|
||||||
# todo : would be nice to generate expressions using hypothesis however
|
|
||||||
# this is pretty involved so for now just use a corpus of expressions
|
|
||||||
# from which to select.
|
|
||||||
return draw(
|
|
||||||
st.sampled_from(
|
|
||||||
(
|
|
||||||
"abc",
|
|
||||||
"len(items)",
|
|
||||||
"x + 1",
|
|
||||||
"lineno",
|
|
||||||
"container",
|
|
||||||
"self.attribute",
|
|
||||||
"self.method()",
|
|
||||||
# These expressions are failing, I think these are control
|
|
||||||
# flow problems rather than problems with FORMAT_VALUE,
|
|
||||||
# however I need to confirm this...
|
|
||||||
#'sorted(items, key=lambda x: x.name)',
|
|
||||||
#'func(*args, **kwargs)',
|
|
||||||
#'text or default',
|
|
||||||
#'43 if life_the_universe and everything else None'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def format_specifiers(draw):
|
|
||||||
"""
|
|
||||||
Generate a valid format specifier using the rules:
|
|
||||||
|
|
||||||
format_spec ::= [[fill]align][sign][#][0][width][,][.precision][type]
|
|
||||||
fill ::= <any character>
|
|
||||||
align ::= "<" | ">" | "=" | "^"
|
|
||||||
sign ::= "+" | "-" | " "
|
|
||||||
width ::= integer
|
|
||||||
precision ::= integer
|
|
||||||
type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
|
|
||||||
|
|
||||||
See https://docs.python.org/2/library/string.html
|
|
||||||
|
|
||||||
:param draw: Let hypothesis draw from other strategies.
|
|
||||||
|
|
||||||
:return: An example format_specifier.
|
|
||||||
"""
|
|
||||||
alphabet_strategy = st.characters(
|
|
||||||
min_codepoint=ord("a"), max_codepoint=ord("z")
|
|
||||||
)
|
|
||||||
fill = draw(st.one_of(alphabet_strategy, st.none()))
|
|
||||||
align = draw(st.sampled_from(list("<>=^")))
|
|
||||||
fill_align = (fill + align or "") if fill else ""
|
|
||||||
|
|
||||||
type_ = draw(st.sampled_from("bcdeEfFgGnosxX%"))
|
|
||||||
can_have_sign = type_ in "deEfFgGnoxX%"
|
|
||||||
can_have_comma = type_ in "deEfFgG%"
|
|
||||||
can_have_precision = type_ in "fFgG"
|
|
||||||
can_have_pound = type_ in "boxX%"
|
|
||||||
can_have_zero = type_ in "oxX"
|
|
||||||
|
|
||||||
sign = draw(st.sampled_from(list("+- ") + [""])) if can_have_sign else ""
|
|
||||||
pound = draw(st.sampled_from(("#", ""))) if can_have_pound else ""
|
|
||||||
zero = draw(st.sampled_from(("0", ""))) if can_have_zero else ""
|
|
||||||
|
|
||||||
int_strategy = st.integers(min_value=1, max_value=1000)
|
|
||||||
|
|
||||||
width = draw(st.one_of(int_strategy, st.none()))
|
|
||||||
width = str(width) if width is not None else ""
|
|
||||||
|
|
||||||
comma = draw(st.sampled_from((",", ""))) if can_have_comma else ""
|
|
||||||
if can_have_precision:
|
|
||||||
precision = draw(st.one_of(int_strategy, st.none()))
|
|
||||||
precision = "." + str(precision) if precision else ""
|
|
||||||
else:
|
|
||||||
precision = ""
|
|
||||||
|
|
||||||
return "".join((fill_align, sign, pound, zero, width, comma, precision, type_))
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def fstrings(draw):
|
|
||||||
"""
|
|
||||||
Generate a valid f-string.
|
|
||||||
See https://www.python.org/dev/peps/pep-0498/#specification
|
|
||||||
|
|
||||||
:param draw: Let hypothsis draw from other strategies.
|
|
||||||
|
|
||||||
:return: A valid f-string.
|
|
||||||
"""
|
|
||||||
character_strategy = st.characters(
|
|
||||||
blacklist_characters="\r\n'\\s{}", min_codepoint=1, max_codepoint=1000
|
|
||||||
)
|
|
||||||
is_raw = draw(st.booleans())
|
|
||||||
integer_strategy = st.integers(min_value=0, max_value=3)
|
|
||||||
expression_count = draw(integer_strategy)
|
|
||||||
content = []
|
|
||||||
for _ in range(expression_count):
|
|
||||||
expression = draw(expressions())
|
|
||||||
conversion = draw(st.sampled_from(("", "!s", "!r", "!a")))
|
|
||||||
has_specifier = draw(st.booleans())
|
|
||||||
specifier = ":" + draw(format_specifiers()) if has_specifier else ""
|
|
||||||
content.append("{{{}{}}}".format(expression, conversion, specifier))
|
|
||||||
content.append(draw(st.text(character_strategy)))
|
|
||||||
content = "".join(content)
|
|
||||||
return "f{}'{}'".format("r" if is_raw else "", content)
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION != 3.6, reason="need Python 3.6")
|
|
||||||
@hypothesis.given(format_specifiers())
|
|
||||||
def test_format_specifiers(format_specifier):
|
|
||||||
"""Verify that format_specifiers generates valid specifiers"""
|
|
||||||
try:
|
|
||||||
exec('"{:' + format_specifier + '}".format(0)')
|
|
||||||
except ValueError as e:
|
|
||||||
if "Unknown format code" not in str(e):
|
|
||||||
raise
|
|
||||||
|
|
||||||
def run_test(text):
|
|
||||||
hypothesis.assume(len(text))
|
|
||||||
hypothesis.assume("f'{" in text)
|
|
||||||
expr = text + "\n"
|
|
||||||
code = compile(expr, "<string>", "single")
|
|
||||||
deparsed = code_deparse(code, sys.stdout, PYTHON_VERSION, compile_mode="single")
|
|
||||||
recompiled = compile(deparsed.text, "<string>", "single")
|
|
||||||
if recompiled != code:
|
|
||||||
print(recompiled)
|
|
||||||
print("================")
|
|
||||||
print(code)
|
|
||||||
print("----------------")
|
|
||||||
assert (
|
|
||||||
"dis(" + deparsed.text.strip("\n") + ")"
|
|
||||||
== "dis(" + expr.strip("\n") + ")"
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION != 3.6, reason="need Python 3.6")
|
|
||||||
@hypothesis.given(fstrings())
|
|
||||||
def test_uncompyle_fstring(fstring):
|
|
||||||
"""Verify uncompyling fstring bytecode"""
|
|
||||||
run_test(fstring)
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION != 3.6, reason="need Python 3.6+")
|
|
||||||
@pytest.mark.parametrize("fstring", ["f'{abc}{abc!s}'", "f'{abc}0'"])
|
|
||||||
def test_uncompyle_direct(fstring):
|
|
||||||
"""useful for debugging"""
|
|
||||||
run_test(fstring)
|
|
@ -1,185 +0,0 @@
|
|||||||
import string
|
|
||||||
from uncompyle6 import PYTHON_VERSION
|
|
||||||
import pytest
|
|
||||||
pytestmark = pytest.mark.skip(PYTHON_VERSION < 2.7,
|
|
||||||
reason="need at least Python 2.7")
|
|
||||||
|
|
||||||
if PYTHON_VERSION > 2.6:
|
|
||||||
from hypothesis import given, assume, example, settings, strategies as st
|
|
||||||
from validate import validate_uncompyle
|
|
||||||
from test_fstring import expressions
|
|
||||||
|
|
||||||
alpha = st.sampled_from(string.ascii_lowercase)
|
|
||||||
numbers = st.sampled_from(string.digits)
|
|
||||||
alphanum = st.sampled_from(string.ascii_lowercase + string.digits)
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def function_calls(draw,
|
|
||||||
min_keyword_args=0, max_keyword_args=5,
|
|
||||||
min_positional_args=0, max_positional_args=5,
|
|
||||||
min_star_args=0, max_star_args=1,
|
|
||||||
min_double_star_args=0, max_double_star_args=1):
|
|
||||||
"""
|
|
||||||
Strategy factory for generating function calls.
|
|
||||||
|
|
||||||
:param draw: Callable which draws examples from other strategies.
|
|
||||||
|
|
||||||
:return: The function call text.
|
|
||||||
"""
|
|
||||||
st_positional_args = st.lists(
|
|
||||||
alpha,
|
|
||||||
min_size=min_positional_args,
|
|
||||||
max_size=max_positional_args
|
|
||||||
)
|
|
||||||
st_keyword_args = st.lists(
|
|
||||||
alpha,
|
|
||||||
min_size=min_keyword_args,
|
|
||||||
max_size=max_keyword_args
|
|
||||||
)
|
|
||||||
st_star_args = st.lists(
|
|
||||||
alpha,
|
|
||||||
min_size=min_star_args,
|
|
||||||
max_size=max_star_args
|
|
||||||
)
|
|
||||||
st_double_star_args = st.lists(
|
|
||||||
alpha,
|
|
||||||
min_size=min_double_star_args,
|
|
||||||
max_size=max_double_star_args
|
|
||||||
)
|
|
||||||
|
|
||||||
positional_args = draw(st_positional_args)
|
|
||||||
keyword_args = draw(st_keyword_args)
|
|
||||||
st_values = st.lists(
|
|
||||||
expressions(),
|
|
||||||
min_size=len(keyword_args),
|
|
||||||
max_size=len(keyword_args)
|
|
||||||
)
|
|
||||||
keyword_args = [
|
|
||||||
x + '=' + e
|
|
||||||
for x, e in
|
|
||||||
zip(keyword_args, draw(st_values))
|
|
||||||
]
|
|
||||||
star_args = ['*' + x for x in draw(st_star_args)]
|
|
||||||
double_star_args = ['**' + x for x in draw(st_double_star_args)]
|
|
||||||
|
|
||||||
arguments = positional_args + keyword_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, keyword,
|
|
||||||
# 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, '<string>', 'single')
|
|
||||||
except:
|
|
||||||
assume(False)
|
|
||||||
return function_call
|
|
||||||
|
|
||||||
|
|
||||||
def test_function_no_args():
|
|
||||||
validate_uncompyle("fn()")
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
|
|
||||||
reason="need at least Python 2.7")
|
|
||||||
def isolated_function_calls(which):
|
|
||||||
"""
|
|
||||||
Returns a strategy for generating function calls, but isolated to
|
|
||||||
particular types of arguments, for example only positional arguments.
|
|
||||||
|
|
||||||
This can help reason about debugging errors in specific types of function
|
|
||||||
calls.
|
|
||||||
|
|
||||||
:param which: One of 'keyword', 'positional', 'star', 'double_star'
|
|
||||||
|
|
||||||
:return: Strategy for generating an function call isolated to specific
|
|
||||||
argument types.
|
|
||||||
"""
|
|
||||||
kwargs = dict(
|
|
||||||
max_keyword_args=0,
|
|
||||||
max_positional_args=0,
|
|
||||||
max_star_args=0,
|
|
||||||
max_double_star_args=0,
|
|
||||||
)
|
|
||||||
kwargs['_'.join(('min', which, 'args'))] = 1
|
|
||||||
kwargs['_'.join(('max', which, 'args'))] = 5 if 'star' not in which else 1
|
|
||||||
return function_calls(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
with settings(max_examples=25):
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
|
|
||||||
reason="need at least Python 2.7")
|
|
||||||
@given(isolated_function_calls('positional'))
|
|
||||||
@example("fn(0)")
|
|
||||||
def test_function_positional_only(expr):
|
|
||||||
validate_uncompyle(expr)
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
|
|
||||||
reason="need at least Python 2.7")
|
|
||||||
@given(isolated_function_calls('keyword'))
|
|
||||||
@example("fn(a=0)")
|
|
||||||
def test_function_call_keyword_only(expr):
|
|
||||||
validate_uncompyle(expr)
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
|
|
||||||
reason="need at least Python 2.7")
|
|
||||||
@given(isolated_function_calls('star'))
|
|
||||||
@example("fn(*items)")
|
|
||||||
def test_function_call_star_only(expr):
|
|
||||||
validate_uncompyle(expr)
|
|
||||||
|
|
||||||
@pytest.mark.skipif(PYTHON_VERSION < 2.7,
|
|
||||||
reason="need at least Python 2.7")
|
|
||||||
@given(isolated_function_calls('double_star'))
|
|
||||||
@example("fn(**{})")
|
|
||||||
def test_function_call_double_star_only(expr):
|
|
||||||
validate_uncompyle(expr)
|
|
||||||
|
|
||||||
|
|
||||||
@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_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)
|
|
@ -67,20 +67,14 @@ def test_grammar():
|
|||||||
(("l_stmts", ("lastl_stmt", "come_froms", "l_stmts")))
|
(("l_stmts", ("lastl_stmt", "come_froms", "l_stmts")))
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
elif 3.0 < PYTHON_VERSION < 3.3:
|
|
||||||
expect_right_recursive.add(
|
|
||||||
(("l_stmts", ("lastl_stmt", "COME_FROM", "l_stmts")))
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
expect_lhs.add("kwarg")
|
expect_lhs.add("kwarg")
|
||||||
|
|
||||||
assert expect_lhs == set(lhs)
|
|
||||||
|
|
||||||
# FIXME
|
# FIXME
|
||||||
if PYTHON_VERSION != 3.8:
|
if PYTHON_VERSION < 3.8:
|
||||||
|
assert expect_lhs == set(lhs)
|
||||||
assert unused_rhs == set(rhs)
|
assert unused_rhs == set(rhs)
|
||||||
|
|
||||||
assert expect_right_recursive == right_recursive
|
assert expect_right_recursive == right_recursive
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
flake8
|
flake8
|
||||||
hypothesis<=3.0.0
|
|
||||||
six
|
six
|
||||||
pytest==3.2.5
|
pytest==3.2.5 # for 2.7 < PYTHON_VERSION <= 3.2 use pytest 2.9.2; for 3.1 2.10
|
||||||
|
Loading…
Reference in New Issue
Block a user