diff --git a/Makefile b/Makefile index e4a5aeb5..85148528 100644 --- a/Makefile +++ b/Makefile @@ -11,22 +11,22 @@ RM ?= rm LINT = flake8 #EXTRA_DIST=ipython/ipy_trepan.py trepan -PHONY=check clean dist distclean lint flake8 test test-unit test-functional rmChangeLog clean_pyc nosetests +PHONY=check clean pytest dist distclean lint flake8 test test-unit test-functional rmChangeLog clean_pyc nosetests #: Default target - same as "check" all: check -#: Make HTML docs -html: - cd docs && $(MAKE) html - #: Same as "check" -test: check +test check: pytest check-short #: Run tests -check check-short: +check-short: pytest $(MAKE) -C test $@ +#: Run tests +pytest: + $(MAKE) -C pytest check + #: Clean up temporary files and .pyc files clean: clean_pyc $(PYTHON) ./setup.py $@ diff --git a/circle.yml b/circle.yml index bc6becee..91e316f4 100644 --- a/circle.yml +++ b/circle.yml @@ -4,6 +4,9 @@ machine: environment: COMPILE: --compile +dependencies: + override: + - pip install -r test-requirements.txt test: override: - python ./setup.py develop && make check diff --git a/pytest/Makefile b/pytest/Makefile new file mode 100644 index 00000000..0eff67aa --- /dev/null +++ b/pytest/Makefile @@ -0,0 +1,7 @@ +PHONY=check test pytest + +PYTHON ?= python + +#: Run all tests +test check pytest: + py.test diff --git a/pytest/test_disasm.py b/pytest/test_disasm.py new file mode 100644 index 00000000..9b299ee1 --- /dev/null +++ b/pytest/test_disasm.py @@ -0,0 +1,33 @@ +import os.path +import pytest + +from uncompyle6.disas import disassemble_file + +def get_srcdir(): + filename = os.path.normcase(os.path.dirname(__file__)) + return os.path.realpath(filename) + +src_dir = get_srcdir() +os.chdir(src_dir) + + +@pytest.mark.parametrize(("test_tuple", "function_to_test"), [ + ( + ('../test/bytecode_2.5/test_import.pyc', 'testdata/test_import_25.right',), + disassemble_file + ), + ( + ('../test/bytecode_2.7/test1.pyc', 'testdata/test1.right',), + disassemble_file + ), +]) +def test_funcoutput(capfd, test_tuple, function_to_test): + + in_file , filename_expected = test_tuple + function_to_test(in_file) + resout, reserr = capfd.readouterr() + expected = open(filename_expected, "r").read() + if resout != expected: + with open(filename_expected + ".got", "w") as out: + out.write(resout) + assert resout == expected diff --git a/pytest/testdata/test1.right b/pytest/testdata/test1.right new file mode 100644 index 00000000..9666609e --- /dev/null +++ b/pytest/testdata/test1.right @@ -0,0 +1,88 @@ +# Python 2.7 +# Embedded file name: /src/external-vcs/github/rocky/python-uncompyle6/__pkginfo__.py + + 1 0 LOAD_CONST 'uncompyle6 packaging information' + 3 STORE_NAME '__doc__' + + 13 6 LOAD_CONST '\nCopyright (C) 2015 Rocky Bernstein .\n' + 9 STORE_NAME 'copyright' + + 15 12 LOAD_CONST 'Development Status :: 3 - Alpha' + + 16 15 LOAD_CONST 'Intended Audience :: Developers' + + 17 18 LOAD_CONST 'Operating System :: OS Independent' + + 18 21 LOAD_CONST 'Programming Language :: Python' + + 19 24 LOAD_CONST 'Topic :: Software Development :: Debuggers' + + 20 27 LOAD_CONST 'Topic :: Software Development :: Libraries :: Python Modules' + 30 BUILD_LIST_6 None + 33 STORE_NAME 'classifiers' + + 24 36 LOAD_CONST 'Rocky Bernstein' + 39 STORE_NAME 'author' + + 25 42 LOAD_CONST 'rb@dustyfeet.com' + 45 STORE_NAME 'author_email' + + 26 48 LOAD_CONST None + 51 STORE_NAME 'ftp_url' + + 28 54 LOAD_CONST 'python-debugger@googlegroups.com' + 57 STORE_NAME 'mailing_list' + + 29 60 LOAD_CONST 'uncompyle6' + 63 STORE_NAME 'modname' + + 30 66 LOAD_CONST 'uncompyle6' + 69 LOAD_CONST 'uncompyle6.opcodes' + 72 BUILD_LIST_2 None + 75 STORE_NAME 'packages' + + 31 78 LOAD_CONST None + 81 STORE_NAME 'py_modules' + + 32 84 LOAD_CONST 'Python byte-code disassembler and source-code converter' + 87 STORE_NAME 'short_desc' + + 33 90 LOAD_CONST 'bin/uncompyle6' + 93 LOAD_CONST 'bin/pydisassemble' + 96 BUILD_LIST_2 None + 99 STORE_NAME 'scripts' + + 35 102 LOAD_CONST -1 + 105 LOAD_CONST None + 108 IMPORT_NAME 'os.path' + 111 STORE_NAME 'os' + + 38 114 LOAD_CONST '' + 117 MAKE_FUNCTION_0 None + 120 STORE_NAME 'get_srcdir' + + 43 123 BUILD_MAP None + 126 STORE_NAME 'ns' + + 44 129 LOAD_CONST '2.0' + 132 STORE_NAME 'version' + + 45 135 LOAD_CONST 'https://github.com/rocky/python-uncompyle6/' + 138 STORE_NAME 'web' + + 48 141 LOAD_NAME 'True' + 144 STORE_NAME 'zip_safe' + + 51 147 LOAD_CONST '' + 150 MAKE_FUNCTION_0 None + 153 STORE_NAME 'read' + + 54 156 LOAD_NAME 'read' + 159 LOAD_CONST 'README.rst' + 162 CALL_FUNCTION_1 None + 165 LOAD_CONST '\n' + 168 BINARY_ADD None + 169 STORE_NAME 'long_description' + 172 LOAD_CONST None + 175 RETURN_VALUE None + diff --git a/pytest/testdata/test_import_25.right b/pytest/testdata/test_import_25.right new file mode 100644 index 00000000..3cd42544 --- /dev/null +++ b/pytest/testdata/test_import_25.right @@ -0,0 +1,75 @@ +# Python 2.5 +# Embedded file name: test_import.py + + 9 0 LOAD_CONST '\ntest_import.py -- source test pattern for import statements\n\nThis source is part of the decompyle test suite.\n\ndecompyle is a Python byte-code decompiler\nSee http://www.goebel-consult.de/decompyle/ for download and\nfor further information\n' + 3 STORE_NAME '__doc__' + + 11 6 LOAD_CONST -1 + 9 LOAD_CONST None + 12 IMPORT_NAME 'sys' + 15 STORE_NAME 'sys' + + 12 18 LOAD_CONST -1 + 21 LOAD_CONST None + 24 IMPORT_NAME 'os' + 27 STORE_NAME 'os' + 30 LOAD_CONST -1 + 33 LOAD_CONST None + 36 IMPORT_NAME_CONT 'sys' + 39 STORE_NAME 'sys' + 42 LOAD_CONST -1 + 45 LOAD_CONST None + 48 IMPORT_NAME_CONT 'BaseHTTPServer' + 51 STORE_NAME 'BaseHTTPServer' + + 14 54 LOAD_CONST -1 + 57 LOAD_CONST None + 60 IMPORT_NAME 'test.test_MimeWriter' + 63 STORE_NAME 'test' + + 16 66 LOAD_CONST -1 + 69 LOAD_CONST ('Message',) + 72 IMPORT_NAME 'rfc822' + 75 IMPORT_FROM 'Message' + 78 STORE_NAME 'Message' + 81 POP_TOP None + + 17 82 LOAD_CONST -1 + 85 LOAD_CONST ('Message', 'decode', 'choose_boundary') + 88 IMPORT_NAME 'mimetools' + 91 IMPORT_FROM 'Message' + 94 STORE_NAME 'Message' + 97 IMPORT_FROM 'decode' + 100 STORE_NAME 'decode' + 103 IMPORT_FROM 'choose_boundary' + 106 STORE_NAME 'choose_boundary' + 109 POP_TOP None + + 18 110 LOAD_CONST -1 + 113 LOAD_CONST ('*',) + 116 IMPORT_NAME 'os' + 119 IMPORT_STAR None + + 20 120 SETUP_LOOP '162' + 123 LOAD_NAME 'globals' + 126 CALL_FUNCTION_0 None + 129 LOAD_ATTR 'items' + 132 CALL_FUNCTION_0 None + 135 GET_ITER None + 136 FOR_ITER '161' + 139 UNPACK_SEQUENCE_2 None + 142 STORE_NAME 'k' + 145 STORE_NAME 'v' + + 21 148 LOAD_NAME 'k' + 151 UNARY_CONVERT None + 152 PRINT_ITEM None + 153 LOAD_NAME 'v' + 156 PRINT_ITEM_CONT None + 157 PRINT_NEWLINE_CONT None + 158 JUMP_BACK '136' + 161 POP_BLOCK None + 162_0 COME_FROM '120' + 162 LOAD_CONST None + 165 RETURN_VALUE None + diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..e079f8a6 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +pytest diff --git a/test/Makefile b/test/Makefile index f8ac49f7..17eb0c3c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -38,4 +38,4 @@ clean-unverified: #: Clean temporary compile/decompile/verify direcotries in /tmp clean-py-dis: - rm -fvr /tmp/py-dis-* || true + rm -fr /tmp/py-dis-* || true diff --git a/test/test_pythonlib.py b/test/test_pythonlib.py index 8e05e942..31a27ab5 100755 --- a/test/test_pythonlib.py +++ b/test/test_pythonlib.py @@ -138,7 +138,7 @@ def do_tests(src_dir, obj_patterns, target_dir, opts): if opts['start_with']: try: - start_with = files.index(start_with) + start_with = files.index(opts['start_with']) files = files[start_with:] print('>>> starting with file', files[0]) except ValueError: @@ -203,7 +203,7 @@ if __name__ == '__main__': print("Can't find directory %s. Skipping" % src_dir, file=sys.stderr) continue - if last_compile_version and last_compile_version != compile_version: + if last_compile_version and last_compile_version != compiled_version: print("Warning: mixed python version decompylation") else: last_compile_version = compiled_version diff --git a/uncompyle6/marsh.py b/uncompyle6/marsh.py index 0f6b1141..6720b4da 100644 --- a/uncompyle6/marsh.py +++ b/uncompyle6/marsh.py @@ -62,16 +62,16 @@ def load_code_internal(fp, magic_int): # a range here. if 3000 < magic_int < 20121: fp.read(4) - co_code = load_code(fp, magic_int) - co_consts = load_code(fp, magic_int) - co_names = load_code(fp, magic_int) - co_varnames = load_code(fp, magic_int) - co_freevars = load_code(fp, magic_int) - co_cellvars = load_code(fp, magic_int) - co_filename = load_code(fp, magic_int) - co_name = load_code(fp, magic_int) + co_code = load_code_internal(fp, magic_int) + co_consts = load_code_internal(fp, magic_int) + co_names = load_code_internal(fp, magic_int) + co_varnames = load_code_internal(fp, magic_int) + co_freevars = load_code_internal(fp, magic_int) + co_cellvars = load_code_internal(fp, magic_int) + co_filename = load_code_internal(fp, magic_int) + co_name = load_code_internal(fp, magic_int) co_firstlineno = unpack('i', fp.read(4))[0] - co_lnotab = load_code(fp, magic_int) + co_lnotab = load_code_internal(fp, magic_int) # The Python3 code object is different than Python2's which # we are reading if we get here. # Also various parameters which were strings are now @@ -159,7 +159,7 @@ def load_code_internal(fp, magic_int): tuplesize = unpack('i', fp.read(4))[0] ret = tuple() while tuplesize > 0: - ret += load_code(fp, magic_int), + ret += load_code_internal(fp, magic_int), tuplesize -= 1 return ret elif marshalType == '[': diff --git a/uncompyle6/verify.py b/uncompyle6/verify.py index a38adee6..2b22ccf1 100755 --- a/uncompyle6/verify.py +++ b/uncompyle6/verify.py @@ -181,7 +181,7 @@ def cmp_code_objects(version, code_obj1, code_obj2, name=''): elif version == 3.4: import uncompyle6.scanners.scanner34 as scan scanner = scan.Scanner34() - scanner.setShowAsm( showasm=0 ) + scanner.setShowAsm( showasm=False ) global JUMP_OPs JUMP_OPs = scan.JUMP_OPs + ['JUMP_BACK']