# 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 ::= 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, "", "single") deparsed = code_deparse(code, sys.stdout, PYTHON_VERSION, compile_mode="single") recompiled = compile(deparsed.text, "", "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)