diff --git a/.pyflyby b/.pyflyby index 9a2a9fae..baf969e0 100644 --- a/.pyflyby +++ b/.pyflyby @@ -4,6 +4,9 @@ # # To regenerate this file, run: setup.py collect_imports +__mandatory_imports__ = [ + 'from __future__ import print_function', +] import pyflyby from pyflyby._autoimp import (auto_eval, auto_import, diff --git a/.travis.yml b/.travis.yml index 4d979ea1..e89cae8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,19 +11,32 @@ matrix: dist: xenial sudo: true - allow_failures: - - python: 3.7 - dist: xenial - sudo: true - install: - set -e - pip install -U pip - - pip install -U "pexpect>=3.3" pyflakes pytest epydoc rlipython requests jupyter flaky + # Install prompt-toolkit from git until a release is made with + # PROMPT_TOOLKIT_NO_CPR environment variable support. + # Install jupyter_console from + # https://github.com/jupyter/jupyter_console/pull/187 until it is included + # in a release + - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then + pip install -U "pexpect>=3.3" pyflakes pytest epydoc rlipython requests jupyter flaky flake8; + else + pip install -U "pexpect>=3.3" pyflakes pytest rlipython requests jupyter flaky flake8; + pip install -U git+https://github.com/prompt-toolkit/python-prompt-toolkit@2.0; + pip install -U git+https://github.com/asmeurer/jupyter_console@display_completions; + fi - pip list script: - - set -e - set -x + - shopt -s extglob globstar + # pyflakes all files except for known_imports/*, etc/*, and __init__.py, + # which are all unused imports. We use flake8 so we can use noqa if + # necessary. + - flake8 --exclude known_imports,etc,__init__.py --select=F + # Test for invalid escape sequences (will be syntax errors in a future + # Python version) + - python -We:invalid -m compileall -f -q lib/ etc/; - export DEBUG_TEST_PYFLYBY=1 - pytest --doctest-modules lib tests diff --git a/bin/autoipython b/bin/autoipython index 5159f069..9a88164d 100755 --- a/bin/autoipython +++ b/bin/autoipython @@ -41,6 +41,7 @@ Or, you can manually add 'pyflyby.enable_auto_importer()' wherever you prefer. # License: MIT http://opensource.org/licenses/MIT from __future__ import absolute_import, division, with_statement +from __future__ import print_function import sys @@ -51,10 +52,10 @@ from pyflyby._interactive import (install_in_ipython_config_file, def main(args): if any(a in ["--help", "-help", "-h"] for a in args): - print maindoc() - print - print "----------------------------------------------------------------------" - print + print(maindoc()) + print() + print("----------------------------------------------------------------------") + print() start_ipython_with_autoimporter(["--help"]) return if args and args[0] in ["--install", "-install", "install"]: diff --git a/bin/collect-exports b/bin/collect-exports index 425fda5b..ee71df3e 100755 --- a/bin/collect-exports +++ b/bin/collect-exports @@ -15,6 +15,7 @@ Print the result to stdout. from __future__ import absolute_import, division, with_statement +from __future__ import print_function import sys @@ -49,7 +50,7 @@ def main(): db = ImportDB.get_default(".") known = db.known_imports.imports args += sorted(set( - filter(None, [i.split.module_name for i in known]))) + [_f for _f in [i.split.module_name for i in known] if _f])) bad_module_names = [] for module_name in args: module = ModuleHandle(module_name) @@ -68,8 +69,8 @@ def main(): sys.stdout.write(imports.pretty_print( allow_conflicts=True, params=options.params)) if bad_module_names: - print >>sys.stderr, "collect-exports: there were problems with: %s" % ( - ' '.join(bad_module_names)) + print("collect-exports: there were problems with: %s" % ( + ' '.join(bad_module_names)), file=sys.stderr) sys.exit(1) diff --git a/bin/find-import b/bin/find-import index d29a8d3c..e97dcd8a 100755 --- a/bin/find-import +++ b/bin/find-import @@ -9,6 +9,7 @@ Prints how to import given name(s). # License: MIT http://opensource.org/licenses/MIT from __future__ import absolute_import, division, with_statement +from __future__ import print_function from pyflyby._cmdline import parse_args, syntax from pyflyby._importdb import ImportDB @@ -30,7 +31,7 @@ def main(): logger.error("Can't find import for %r", arg) else: for imp in imports: - print imp.pretty_print(params=options.params), + print(imp.pretty_print(params=options.params), end=' ') if errors: raise SystemExit(1) diff --git a/bin/list-bad-xrefs b/bin/list-bad-xrefs index 1b40518e..c85e0264 100755 --- a/bin/list-bad-xrefs +++ b/bin/list-bad-xrefs @@ -15,6 +15,7 @@ Similar to running C{epydoc -v}, but: # License: MIT http://opensource.org/licenses/MIT from __future__ import absolute_import, division, with_statement +from __future__ import print_function from pyflyby._cmdline import parse_args, syntax from pyflyby._docxref import find_bad_doc_cross_references @@ -27,8 +28,8 @@ def main(): for rec in find_bad_doc_cross_references(args): module, linenos, container_name, identifier = rec for lineno in linenos or ["?"]: - print "%s:%s: undefined docstring cross-reference in %s: %s" % ( - module.filename, lineno, container_name, identifier) + print("%s:%s: undefined docstring cross-reference in %s: %s" % ( + module.filename, lineno, container_name, identifier)) if __name__ == '__main__': diff --git a/conftest.py b/conftest.py index fdd9e43f..a92e19d1 100644 --- a/conftest.py +++ b/conftest.py @@ -6,6 +6,8 @@ import re import sys +from six import PY3 + _already_ran_setup = False def pytest_runtest_setup(item): @@ -23,6 +25,14 @@ def pytest_runtest_setup(item): _setup_logger() +def pytest_ignore_collect(path, config): + """ return True to prevent considering this path for collection. + This hook is consulted for all files and directories prior to calling + more specific hooks. + """ + if str(path).endswith('_docxref.py') and PY3: + return True + def pytest_report_header(config): import IPython print("IPython %s" % (IPython.__version__)) @@ -30,6 +40,8 @@ def pytest_report_header(config): dir = os.path.dirname(pyflyby.__file__) print("pyflyby %s from %s" % (pyflyby.__version__, dir)) +def pytest_cmdline_preparse(config, args): + args[:] = ["--no-success-flaky-report", "--no-flaky-report"] + args def _setup_logger(): """ diff --git a/lib/python/pyflyby/__init__.py b/lib/python/pyflyby/__init__.py index a7901b57..d8b120d3 100644 --- a/lib/python/pyflyby/__init__.py +++ b/lib/python/pyflyby/__init__.py @@ -2,7 +2,8 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._autoimp import (auto_eval, auto_import, find_missing_imports) diff --git a/lib/python/pyflyby/__main__.py b/lib/python/pyflyby/__main__.py index 2532a8a8..7768fd8b 100644 --- a/lib/python/pyflyby/__main__.py +++ b/lib/python/pyflyby/__main__.py @@ -2,7 +2,8 @@ # Copyright (C) 2014, 2015 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) if __name__ == "__main__": from pyflyby._py import py_main diff --git a/lib/python/pyflyby/_autoimp.py b/lib/python/pyflyby/_autoimp.py index e1bea833..6582baff 100644 --- a/lib/python/pyflyby/_autoimp.py +++ b/lib/python/pyflyby/_autoimp.py @@ -8,12 +8,13 @@ import ast import contextlib import copy -import six -from six import exec_, reraise -from six.moves import builtins import sys import types +import six +from six import PY2, PY3, exec_, reraise +from six.moves import builtins + from pyflyby._file import FileText, Filename from pyflyby._flags import CompilerFlags from pyflyby._idents import (BadDottedIdentifierError, @@ -24,7 +25,6 @@ from pyflyby._modules import ModuleHandle from pyflyby._parse import PythonBlock, infer_compile_mode - class _ClassScope(dict): pass @@ -439,7 +439,7 @@ def generic_visit(self, node): "unexpected %s" % (', '.join(type(v).__name__ for v in value))) elif isinstance(value, (six.integer_types, float, complex, - str, six.text_type, type(None))): + str, six.text_type, type(None), bytes)): pass else: raise TypeError( @@ -484,8 +484,14 @@ def visit_Assign(self, node): def visit_ClassDef(self, node): + if PY3: + assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list') + else: + assert node._fields == ('name', 'bases', 'body', 'decorator_list') self.visit(node.bases) self.visit(node.decorator_list) + if PY3: + self.visit(node.keywords) with self._NewScopeCtx(new_class_scope=True): self.visit(node.body) # The class's name is only visible to others (not to the body to the @@ -500,10 +506,16 @@ def visit_FunctionDef(self, node): # scope. # - Store the name in the current scope (but not visibly to # args/decorator_list). - assert node._fields == ('name', 'args', 'body', 'decorator_list') + if PY2: + assert node._fields == ('name', 'args', 'body', 'decorator_list'), node._fields + else: + assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns'), node._fields with self._NewScopeCtx(include_class_scopes=True): self.visit(node.args) self.visit(node.decorator_list) + if PY3: + if node.returns: + self.visit(node.returns) old_in_FunctionDef = self._in_FunctionDef self._in_FunctionDef = True with self._NewScopeCtx(): @@ -513,7 +525,7 @@ def visit_FunctionDef(self, node): def visit_Lambda(self, node): # Like FunctionDef, but without the decorator_list or name. - assert node._fields == ('args', 'body') + assert node._fields == ('args', 'body'), node._fields with self._NewScopeCtx(include_class_scopes=True): self.visit(node.args) old_in_FunctionDef = self._in_FunctionDef @@ -523,7 +535,10 @@ def visit_Lambda(self, node): self._in_FunctionDef = old_in_FunctionDef def visit_arguments(self, node): - assert node._fields == ('args', 'vararg', 'kwarg', 'defaults') + if PY2: + assert node._fields == ('args', 'vararg', 'kwarg', 'defaults'), node._fields + else: + assert node._fields == ('args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields # Argument/parameter list. Visit 'defaults' first because these # should be checked for missing imports before the stores from the # args/varargs/kwargs. @@ -532,12 +547,37 @@ def visit_arguments(self, node): # Both x and y should be considered undefined (unless they were indeed # defined before the def). self.visit(node.defaults) + if PY3: + self.visit(node.kw_defaults) # Store arg names. self.visit(node.args) + if PY3: + self.visit(node.kwonlyargs) # Store vararg/kwarg names. self._visit_Store(node.vararg) self._visit_Store(node.kwarg) + def visit_ExceptHandler(self, node): + assert node._fields == ('type', 'name', 'body') + if node.type: + self.visit(node.type) + if node.name: + # ExceptHandler.name is a string in Python 3 and a Name with Store in + # Python 2 + if PY3: + self._visit_Store(node.name) + else: + self.visit(node.name) + self.visit(node.body) + + def visit_Dict(self, node): + assert node._fields == ('keys', 'values') + # In Python 3, keys can be None, indicating a ** expression + for key in node.keys: + if key: + self.visit(key) + self.visit(node.values) + def visit_comprehension(self, node): # Visit a "comprehension" node, which is a component of list # comprehensions and generator expressions. @@ -566,10 +606,14 @@ def visit_ListComp(self, node): # For Python2, we intentionally don't enter a new scope here, because # a list comprehensive _does_ leak variables out of its scope (unlike # generator expressions). - # For Python3, we do need to enter a new scope here. TODO: figure out - # how to tell if we should be using py3 mode or py2 mode. - self.visit(node.generators) - self.visit(node.elt) + # For Python3, we do need to enter a new scope here. + if PY3: + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.generators) + self.visit(node.elt) + else: + self.visit(node.generators) + self.visit(node.elt) def visit_DictComp(self, node): # Visit a dict comprehension node. @@ -623,6 +667,12 @@ def visit_Name(self, node): logger.debug("visit_Name(%r)", node.id) self._visit_fullname(node.id, node.ctx) + def visit_arg(self, node): + if node.annotation: + self.visit(node.annotation) + # Treat it like a Name node would from Python 2 + self._visit_fullname(node.arg, ast.Param()) + def visit_Attribute(self, node): name_revparts = [] n = node @@ -671,6 +721,8 @@ def _visit_StoreImport(self, node, modulename): def _visit_Store(self, fullname, value=None): logger.debug("_visit_Store(%r)", fullname) scope = self.scopestack[-1] + if PY3 and isinstance(fullname, ast.arg): + fullname = fullname.arg if self.unused_imports is not None: # If we're redefining something, and it has not been used, then # record it as unused. @@ -838,11 +890,11 @@ def _find_missing_imports_in_code(co, namespaces): Helper function to L{find_missing_imports}. >>> f = lambda: foo.bar(x) + baz(y) - >>> map(str, _find_missing_imports_in_code(f.func_code, [{}])) + >>> [str(m) for m in _find_missing_imports_in_code(f.__code__, [{}])] ['baz', 'foo.bar', 'x', 'y'] >>> f = lambda x: (lambda: x+y) - >>> _find_missing_imports_in_code(f.func_code, [{}]) + >>> _find_missing_imports_in_code(f.__code__, [{}]) [DottedIdentifier('y')] @type co: @@ -869,7 +921,7 @@ def _find_loads_without_stores_in_code(co, loads_without_stores): @type co: C{types.CodeType} @param co: - Code object, e.g. C{function.func_code} + Code object, e.g. C{function.__code__} @type loads_without_stores: C{set} @param loads_without_stores: @@ -884,6 +936,7 @@ def _find_loads_without_stores_in_code(co, loads_without_stores): # Initialize local constants for fast access. from opcode import HAVE_ARGUMENT, EXTENDED_ARG, opmap LOAD_ATTR = opmap['LOAD_ATTR'] + LOAD_METHOD = opmap['LOAD_METHOD'] if PY3 else None LOAD_GLOBAL = opmap['LOAD_GLOBAL'] LOAD_NAME = opmap['LOAD_NAME'] STORE_ATTR = opmap['STORE_ATTR'] @@ -979,18 +1032,23 @@ def _find_loads_without_stores_in_code(co, loads_without_stores): # Loop through bytecode. while i < n: c = bytecode[i] - op = ord(c) + op = _op(c) i += 1 if op >= HAVE_ARGUMENT: - oparg = ord(bytecode[i]) + ord(bytecode[i+1])*256 + extended_arg - extended_arg = 0 - i = i+2 - if op == EXTENDED_ARG: - if six.PY2: - extended_arg = oparg*long(65536) - else: + if PY2: + oparg = _op(bytecode[i]) + _op(bytecode[i+1])*256 + extended_arg + extended_arg = 0 + i = i+2 + if op == EXTENDED_ARG: extended_arg = oparg*65536 - continue + continue + else: + oparg = bytecode[i] | extended_arg + extended_arg = 0 + if op == EXTENDED_ARG: + extended_arg = (oparg << 8) + continue + i += 1 if pending is not None: if op == STORE_ATTR: @@ -1000,7 +1058,7 @@ def _find_loads_without_stores_in_code(co, loads_without_stores): pending = None stores.add(fullname) continue - if op == LOAD_ATTR: + if op in [LOAD_ATTR, LOAD_METHOD]: # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* so far; # possibly more LOAD_ATTR/STORE_ATTR will follow pending.append(co.co_names[oparg]) @@ -1084,6 +1142,11 @@ def _find_loads_without_stores_in_code(co, loads_without_stores): if isinstance(arg, types.CodeType): _find_loads_without_stores_in_code(arg, loads_without_stores) +def _op(c): + # bytecode is bytes in Python 3, which when indexed gives integers + if PY2: + return ord(c) + return c def _find_earliest_backjump_label(bytecode): """ @@ -1140,7 +1203,7 @@ def _find_earliest_backjump_label(bytecode): The earliest target of a backward jump would be the 'while' loop at L7, at bytecode offset 38:: - >> _find_earliest_backjump_label(f.func_code.co_code) + >> _find_earliest_backjump_label(f.__code__.co_code) 38 Note that in this example there are earlier targets of jumps at bytecode @@ -1153,7 +1216,7 @@ def _find_earliest_backjump_label(bytecode): @type bytecode: C{bytes} @param bytecode: - Compiled bytecode, e.g. C{function.func_code.co_code}. + Compiled bytecode, e.g. C{function.__code__.co_code}. @rtype: C{int} @return: @@ -1168,11 +1231,11 @@ def _find_earliest_backjump_label(bytecode): i = 0 while i < n: c = bytecode[i] - op = ord(c) + op = _op(c) i += 1 if op < HAVE_ARGUMENT: continue - oparg = ord(bytecode[i]) + ord(bytecode[i+1])*256 + oparg = _op(bytecode[i]) + _op(bytecode[i+1])*256 i += 2 label = None if op in hasjrel: @@ -1226,7 +1289,7 @@ def find_missing_imports(arg, namespaces): >>> [str(m) for m in find_missing_imports("from numpy import pi; numpy.pi + pi + x", [{}])] ['numpy.pi', 'x'] - >>> [str(m) for m in find_missing_imports("for x in range(3): print numpy.arange(x)", [{}])] + >>> [str(m) for m in find_missing_imports("for x in range(3): print(numpy.arange(x))", [{}])] ['numpy.arange'] >>> [str(m) for m in find_missing_imports("foo1 = func(); foo1.bar + foo2.bar", [{}])] @@ -1245,8 +1308,13 @@ def find_missing_imports(arg, namespaces): ['x'] The (unintuitive) rules for generator expressions and list comprehensions - are handled correctly: - >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])] + in Python 2 are handled correctly: + >>> # Python 3 + >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])] # doctest: +SKIP + ['y', 'z'] + + >>> # Python 2 + >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])] # doctest: +SKIP ['z'] >>> [str(m) for m in find_missing_imports("(x+y+z for x,y in [(1,2)]), y", [{}])] @@ -1297,11 +1365,11 @@ def find_missing_imports(arg, namespaces): elif callable(arg): # Find the code object. try: - co = arg.func_code + co = arg.__code__ except AttributeError: # User-defined callable try: - co = arg.__call__.func_code + co = arg.__call__.__code__ except AttributeError: # Built-in function; no auto importing needed. return [] @@ -1595,13 +1663,13 @@ def auto_eval(arg, filename=None, mode=None, Evaluate/execute the given code, automatically importing as needed. C{auto_eval} will default the compilation C{mode} to "eval" if possible: - >>> auto_eval("b64decode('aGVsbG8=')") + "!" + >>> auto_eval("b64decode('aGVsbG8=')") + b"!" [PYFLYBY] from base64 import b64decode - 'hello!' + b'hello!' C{auto_eval} will default the compilation C{mode} to "exec" if the input is not a single expression: - >>> auto_eval("if True: print b64decode('aGVsbG8=')") + >>> auto_eval("if True: print(b64decode('aGVsbG8=').decode('utf-8'))") [PYFLYBY] from base64 import b64decode hello @@ -1707,18 +1775,18 @@ def load_symbol(fullname, namespaces, autoimport=False, db=None, Load the symbol C{fullname}. >>> import os - >>> load_symbol("os.path.join.func_name", {"os": os}) + >>> load_symbol("os.path.join.__name__", {"os": os}) 'join' >>> load_symbol("os.path.join.asdf", {"os": os}) Traceback (most recent call last): ... - LoadSymbolError: os.path.join.asdf: AttributeError: 'function' object has no attribute 'asdf' + pyflyby._autoimp.LoadSymbolError: os.path.join.asdf: AttributeError: 'function' object has no attribute 'asdf' >>> load_symbol("os.path.join", {}) Traceback (most recent call last): ... - LoadSymbolError: os.path.join: NameError: os + pyflyby._autoimp.LoadSymbolError: os.path.join: NameError: os @type fullname: C{str} diff --git a/lib/python/pyflyby/_cmdline.py b/lib/python/pyflyby/_cmdline.py index 6d439bca..5ba048a1 100644 --- a/lib/python/pyflyby/_cmdline.py +++ b/lib/python/pyflyby/_cmdline.py @@ -368,7 +368,11 @@ def on_error_filename_arg(arg): errors.append("%s: %s: %s" % (filename, type(e).__name__, e)) type_e = type(e) if str(filename) not in str(e): - e = type_e("While processing %s: %s" % (filename, e)) + try: + e = type_e("While processing %s: %s" % (filename, e)) + except TypeError: + # Exception takes more than one argument + pass if logger.debug_enabled: reraise(type_e, e, sys.exc_info()[2]) traceback.print_exception(*sys.exc_info()) @@ -417,7 +421,7 @@ def action(m): def action_query(prompt="Proceed?"): def action(m): p = prompt.format(filename=m.filename) - print + print() print("%s [y/N] " % (p), end="") try: if input().strip().lower().startswith('y'): diff --git a/lib/python/pyflyby/_dbg.py b/lib/python/pyflyby/_dbg.py index 84395926..8eea3e4a 100644 --- a/lib/python/pyflyby/_dbg.py +++ b/lib/python/pyflyby/_dbg.py @@ -13,11 +13,13 @@ import os import pwd import signal -import six import sys import time from types import CodeType, FrameType, TracebackType +import six +from six.moves import builtins + from pyflyby._file import Filename @@ -227,7 +229,7 @@ def _get_caller_frame(): @rtype: C{FrameType} ''' - this_filename = _get_caller_frame.func_code.co_filename + this_filename = _get_caller_frame.__code__.co_filename f = sys._getframe() while (f.f_back and ( f.f_code.co_filename == this_filename or @@ -757,7 +759,7 @@ def _signal_handler_debugger(signal_number, interrupted_frame): def enable_signal_handler_debugger(enable=True): - ''' + r''' Install a signal handler for SIGQUIT so that Control-\ or external SIGQUIT enters debugger. Suitable to be called from site.py. ''' @@ -836,7 +838,6 @@ def add_debug_functions_to_builtins(): ''' Install debugger(), etc. in the builtin global namespace. ''' - import __builtin__ functions_to_add = [ 'debugger', 'debug_on_exception', @@ -851,7 +852,7 @@ def add_debug_functions_to_builtins(): 'waitpoint', ] for name in functions_to_add: - setattr(__builtin__, name, globals()[name]) + setattr(builtins, name, globals()[name]) # TODO: allow attaching remotely (winpdb/rpdb2) upon sigquit. Or rpc like http://code.activestate.com/recipes/576515/ # TODO: http://sourceware.org/gdb/wiki/PythonGdb @@ -888,7 +889,7 @@ def get_executable(pid): _gdb_safe_chars = ( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789,./-_=+:;'[]{}\|`~!@#%^&*()<>? ") + r"0123456789,./-_=+:;'[]{}\|`~!@#%^&*()<>? ") def _escape_for_gdb(string): """ diff --git a/lib/python/pyflyby/_docxref.py b/lib/python/pyflyby/_docxref.py index 21bf1a80..04584b4b 100644 --- a/lib/python/pyflyby/_docxref.py +++ b/lib/python/pyflyby/_docxref.py @@ -24,7 +24,8 @@ # from, out of or in connection with the software or the use or other # dealings in the software. -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import re import six @@ -99,7 +100,7 @@ def scan_within_string(results, start_lineno, orig_full_string): "Found superstring in %r but not substring %r within superstring" % (module.filename, searchstring)) # Try a full text search. - for lineno, orig_full_string in map.itervalues(): + for lineno, orig_full_string in map.values(): scan_within_string(results, lineno, orig_full_string) if results: return tuple(sorted(results)) diff --git a/lib/python/pyflyby/_file.py b/lib/python/pyflyby/_file.py index 345171f7..8f7cedc9 100644 --- a/lib/python/pyflyby/_file.py +++ b/lib/python/pyflyby/_file.py @@ -2,14 +2,19 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) +from functools import total_ordering +import io import os import re import six import sys -from pyflyby._util import cached_attribute, memoize +from six import string_types + +from pyflyby._util import cached_attribute, cmp, memoize class UnsafeFilenameError(ValueError): pass @@ -17,6 +22,7 @@ class UnsafeFilenameError(ValueError): # TODO: statcache +@total_ordering class Filename(object): """ A filename. @@ -66,12 +72,14 @@ def __eq__(self, o): return NotImplemented return self._filename == o._filename - def __ne__(self, o): - if self is o: - return False + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, o): if not isinstance(o, Filename): return NotImplemented - return self._filename != o._filename + return self._filename < o._filename def __cmp__(self, o): if self is o: @@ -212,7 +220,7 @@ def which(program): Filename.STDIN = Filename("/dev/stdin") - +@total_ordering class FilePos(object): """ A (lineno, colno) position within a L{FileText}. @@ -296,11 +304,7 @@ def __eq__(self, other): return self._data == other._data def __ne__(self, other): - if self is other: - return False - if not isinstance(other, FilePos): - return NotImplemented - return self._data != other._data + return not (self == other) def __cmp__(self, other): if self is other: @@ -309,6 +313,7 @@ def __cmp__(self, other): return NotImplemented return cmp(self._data, other._data) + # The rest are defined by total_ordering def __lt__(self, other): if self is other: return 0 @@ -316,11 +321,6 @@ def __lt__(self, other): return NotImplemented return self._data < other._data - def __le__(self, other): - if not isinstance(other, FilePos): - return NotImplemented - return self._data <= other._data - def __hash__(self): return hash(self._data) @@ -329,6 +329,7 @@ def __hash__(self): FilePos._ONE_ONE = FilePos._from_lc(1, 1) +@total_ordering class FileText(object): """ Represents a contiguous sequence of lines from a file. @@ -382,7 +383,7 @@ def __new__(cls, arg, filename=None, startpos=None): def _from_lines(cls, lines, filename, startpos): assert type(lines) is tuple assert len(lines) > 0 - assert type(lines[0]) is str + assert isinstance(lines[0], string_types) assert not lines[-1].endswith("\n") self = object.__new__(cls) self.lines = lines @@ -608,10 +609,15 @@ def __eq__(self, o): self.joined == o.joined and self.startpos == o.startpos) - def __ne__(self, o): + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, o): if not isinstance(o, FileText): return NotImplemented - return not (self == o) + return ((self.filename, self.joined, self.startpos) < + (o .filename, o .joined, o .startpos)) def __cmp__(self, o): if self is o: @@ -632,7 +638,7 @@ def read_file(filename): if filename == Filename.STDIN: data = sys.stdin.read() else: - with open(str(filename), 'rU') as f: + with io.open(str(filename), 'r') as f: data = f.read() return FileText(data, filename=filename) diff --git a/lib/python/pyflyby/_flags.py b/lib/python/pyflyby/_flags.py index 3d94b234..0e8f55f4 100644 --- a/lib/python/pyflyby/_flags.py +++ b/lib/python/pyflyby/_flags.py @@ -2,7 +2,8 @@ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import __future__ import ast @@ -28,18 +29,19 @@ class CompilerFlags(int): """ Representation of Python "compiler flags", i.e. features from __future__. - >>> print CompilerFlags(0x18000).__interactive_display__() + >>> print(CompilerFlags(0x18000).__interactive_display__()) CompilerFlags(0x18000) # from __future__ import with_statement, print_function - >>> print CompilerFlags(0x10000, 0x8000).__interactive_display__() + >>> print(CompilerFlags(0x10000, 0x8000).__interactive_display__()) CompilerFlags(0x18000) # from __future__ import with_statement, print_function - >>> print CompilerFlags('with_statement', 'print_function').__interactive_display__() + >>> print(CompilerFlags('with_statement', 'print_function').__interactive_display__()) CompilerFlags(0x18000) # from __future__ import with_statement, print_function - This can be used as an argument to the built-in compile() function: + This can be used as an argument to the built-in compile() function. For + instance, in Python 2: - >>> compile("print('x', file=None)", "?", "exec", flags=0, dont_inherit=1) + >>> compile("print('x', file=None)", "?", "exec", flags=0, dont_inherit=1) #doctest:+SKIP Traceback (most recent call last): ... SyntaxError: invalid syntax diff --git a/lib/python/pyflyby/_format.py b/lib/python/pyflyby/_format.py index 72e03714..366ab3c5 100644 --- a/lib/python/pyflyby/_format.py +++ b/lib/python/pyflyby/_format.py @@ -2,7 +2,8 @@ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import six @@ -100,18 +101,16 @@ def pyfill(prefix, tokens, params=FormatParams()): """ Fill a Python statement. - >>> print pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"]), + >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"]), end='') print foo.bar, baz, quux, quuuuux - - >>> print pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"], - ... FormatParams(max_line_length=15, hanging_indent='auto')), + >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"], + ... FormatParams(max_line_length=15, hanging_indent='auto')), end='') print (foo.bar, baz, quux, quuuuux) - - >>> print pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"], - ... FormatParams(max_line_length=14, hanging_indent='auto')), + >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"], + ... FormatParams(max_line_length=14, hanging_indent='auto')), end='') print ( foo.bar, baz, quux, diff --git a/lib/python/pyflyby/_idents.py b/lib/python/pyflyby/_idents.py index 8824d585..c9a8535d 100644 --- a/lib/python/pyflyby/_idents.py +++ b/lib/python/pyflyby/_idents.py @@ -2,13 +2,15 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) +from functools import total_ordering from keyword import kwlist import re import six -from pyflyby._util import cached_attribute +from pyflyby._util import cached_attribute, cmp # Don't consider "print" a keyword, in order to be compatible with user code @@ -144,6 +146,8 @@ def brace_identifiers(text): >>> list(brace_identifiers("{salutation}, {your_name}.")) ['salutation', 'your_name'] """ + if isinstance(text, bytes): + text = text.decode('utf-8') for match in re.finditer("{([a-zA-Z_][a-zA-Z0-9_]*)}", text): yield match.group(1) @@ -153,6 +157,7 @@ class BadDottedIdentifierError(ValueError): # TODO: Use in various places, esp where e.g. dotted_prefixes is used. +@total_ordering class DottedIdentifier(object): def __new__(cls, arg): if isinstance(arg, cls): @@ -229,6 +234,12 @@ def __ne__(self, other): return NotImplemented return self.name != other.name + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, DottedIdentifier): + return NotImplemented + return self.name < other.name + def __cmp__(self, other): if self is other: return 0 diff --git a/lib/python/pyflyby/_importclns.py b/lib/python/pyflyby/_importclns.py index 1adbfd5d..8fac2f28 100644 --- a/lib/python/pyflyby/_importclns.py +++ b/lib/python/pyflyby/_importclns.py @@ -2,9 +2,11 @@ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from collections import defaultdict +from functools import total_ordering import six from pyflyby._flags import CompilerFlags @@ -13,7 +15,7 @@ ImportStatement, NonImportStatementError) from pyflyby._parse import PythonBlock -from pyflyby._util import (cached_attribute, partition, +from pyflyby._util import (cached_attribute, cmp, partition, stable_unique) @@ -24,7 +26,7 @@ class NoSuchImportError(ValueError): class ConflictingImportsError(ValueError): pass - +@total_ordering class ImportSet(object): r""" Representation of a set of imports organized into import statements. @@ -244,7 +246,7 @@ def get_statements(self, separate_from_imports=True): ... from _hello import world ... ''') - >>> for s in importset.get_statements(): print s + >>> for s in importset.get_statements(): print(s) from __future__ import division import a import b as B @@ -482,9 +484,13 @@ def __eq__(self, other): return self._importset == other._importset def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): if not isinstance(other, ImportSet): return NotImplemented - return not (self == other) + return self._importset < other._importset def __cmp__(self, other): if self is other: @@ -506,6 +512,7 @@ def __iter__(self): ImportSet._EMPTY = ImportSet._from_imports([]) +@total_ordering class ImportMap(object): r""" A map from import fullname identifier to fullname identifier. @@ -600,9 +607,13 @@ def __eq__(self, other): return self._data == other._data def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): if not isinstance(other, ImportMap): return NotImplemented - return not (self == other) + return self._data < other._data def __cmp__(self, other): if self is other: diff --git a/lib/python/pyflyby/_importdb.py b/lib/python/pyflyby/_importdb.py index d185cd12..3ce8154e 100644 --- a/lib/python/pyflyby/_importdb.py +++ b/lib/python/pyflyby/_importdb.py @@ -2,7 +2,8 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from collections import defaultdict import os diff --git a/lib/python/pyflyby/_imports2s.py b/lib/python/pyflyby/_imports2s.py index 4cfabaa2..6416ae8f 100644 --- a/lib/python/pyflyby/_imports2s.py +++ b/lib/python/pyflyby/_imports2s.py @@ -2,7 +2,8 @@ # Copyright (C) 2011-2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._autoimp import scan_for_import_issues from pyflyby._file import FileText, Filename @@ -250,12 +251,12 @@ def reformat_import_statements(codeblock, params=None): 'import' (or 'from ... import') statements. Other lines, including blanks and comment lines, are not touched. - >>> print reformat_import_statements( + >>> print(reformat_import_statements( ... 'from foo import bar2 as bar2x, bar1\n' ... 'import foo.bar3 as bar3x\n' ... 'import foo.bar4\n' ... '\n' - ... 'import foo.bar0 as bar0\n').text.joined + ... 'import foo.bar0 as bar0\n').text.joined) import foo.bar4 from foo import bar1, bar2 as bar2x, bar3 as bar3x @@ -309,7 +310,7 @@ def fix_unused_and_missing_imports(codeblock, ... 'from foo import m1, m2, m3, m4\n' ... 'm2, m4, np.foo', filename="/tmp/foo.py") - >>> print fix_unused_and_missing_imports(codeblock, add_mandatory=False) + >>> print(fix_unused_and_missing_imports(codeblock, add_mandatory=False)) [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m1' [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m3' [PYFLYBY] /tmp/foo.py: added 'import numpy as np' @@ -460,7 +461,7 @@ def replace_star_imports(codeblock, params=None): >>> codeblock = PythonBlock('from keyword import *', filename="/tmp/x.py") - >>> print replace_star_imports(codeblock) + >>> print(replace_star_imports(codeblock)) [PYFLYBY] /tmp/x.py: replaced 'from keyword import *' with 2 imports from keyword import iskeyword, kwlist @@ -545,7 +546,7 @@ def transform_imports(codeblock, transformations, params=None): want to do replacements even within in strings and comments. >>> result = transform_imports("from m import x", {"m.x": "m.y.z"}) - >>> print result.text.joined.strip() + >>> print(result.text.joined.strip()) from m.y import z as x @type codeblock: @@ -565,14 +566,14 @@ def transform_import(imp): # Transform a block of imports. # TODO: optimize # TODO: handle transformations containing both a.b=>x and a.b.c=>y - for k, v in transformations.iteritems(): + for k, v in transformations.items(): imp = imp.replace(k, v) return imp def transform_block(block): # Do a crude string replacement in the PythonBlock. block = PythonBlock(block) s = block.text.joined - for k, v in transformations.iteritems(): + for k, v in transformations.items(): s = re.sub("\\b%s\\b" % (re.escape(k)), v, s) return PythonBlock(s, flags=block.flags) # Loop over transformer blocks. diff --git a/lib/python/pyflyby/_importstmt.py b/lib/python/pyflyby/_importstmt.py index a83f9be2..1142bc0c 100644 --- a/lib/python/pyflyby/_importstmt.py +++ b/lib/python/pyflyby/_importstmt.py @@ -2,16 +2,18 @@ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import ast from collections import namedtuple +from functools import total_ordering from pyflyby._flags import CompilerFlags from pyflyby._format import FormatParams, pyfill from pyflyby._idents import is_identifier from pyflyby._parse import PythonStatement -from pyflyby._util import (Inf, cached_attribute, +from pyflyby._util import (Inf, cached_attribute, cmp, longest_common_prefix) @@ -59,6 +61,7 @@ class NonImportStatementError(TypeError): import as """ +@total_ordering class Import(object): """ Representation of the desire to import a single name into the current @@ -290,14 +293,26 @@ def __cmp__(self, other): return NotImplemented return cmp(self._data, other._data) + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, Import): + return NotImplemented + return self._data == other._data + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering def __lt__(self, other): if self is other: - return 0 + return False if not isinstance(other, Import): return NotImplemented return self._data < other._data +@total_ordering class ImportStatement(object): """ Token-level representation of an import statement containing multiple @@ -493,5 +508,21 @@ def __cmp__(self, other): return NotImplemented return cmp(self._data, other._data) + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, ImportStatement): + return NotImplemented + return self._data == other._data + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, ImportStatement): + return NotImplemented + return self._data < other._data + def __hash__(self): return hash(self._data) diff --git a/lib/python/pyflyby/_interactive.py b/lib/python/pyflyby/_interactive.py index cd353f2b..5128315f 100644 --- a/lib/python/pyflyby/_interactive.py +++ b/lib/python/pyflyby/_interactive.py @@ -11,11 +11,13 @@ import inspect import os import re -import six -from six.moves import builtins import subprocess import sys +import six +from six import PY2, text_type as unicode +from six.moves import builtins + from pyflyby._autoimp import (LoadSymbolError, ScopeStack, auto_eval, auto_import, clear_failed_imports_cache, @@ -252,11 +254,11 @@ def start_ipython_with_autoimporter(argv=None, app=None, _user_ns=None): # the exec in general (as a library function) and avoid changing # python versions. try: - from jupyter_console.app import ZMQTerminalIPythonApp + from ipkernel.app import IPKernelApp except (ImportError, AttributeError): pass else: - app = ZMQTerminalIPythonApp.instance() + app = IPKernelApp.instance() argv = argv[1:] elif subcmd == 'notebook': try: @@ -950,15 +952,15 @@ def complete_symbol(fullname, namespaces, db=None, autoimported=None, ip=None, are auto-imported first if not yet imported: >>> ns = {} - >>> complete_symbol(u"threading.Threa", namespaces=[ns]) + >>> complete_symbol("threading.Threa", namespaces=[ns]) [PYFLYBY] import threading - [u'threading.Thread', u'threading.ThreadError'] + ['threading.Thread', 'threading.ThreadError'] >>> 'threading' in ns True - >>> complete_symbol(u"threading.Threa", namespaces=[ns]) - [u'threading.Thread', u'threading.ThreadError'] + >>> complete_symbol("threading.Threa", namespaces=[ns]) + ['threading.Thread', 'threading.ThreadError'] We only need to import *parent* modules (packages) of the symbol being completed. If the user asks to complete "foo.bar.quu", we need to @@ -1185,6 +1187,7 @@ def _get_TerminalPdb_class(): # Pdb class separately. try: import IPython + del IPython except ImportError: raise NoIPythonPackageError() try: @@ -1632,19 +1635,19 @@ def _enable_start_kernel_hook(self, kernel_manager): ] try: # Tested with Jupyter/IPython 4.0 - from jupyter_client.manager import KernelManager + from jupyter_client.manager import KernelManager as JupyterKernelManager except ImportError: pass else: @self._advise(kernel_manager.start_kernel) - def start_kernel_with_autoimport(*args, **kwargs): + def start_kernel_with_autoimport_jupyter(*args, **kwargs): logger.debug("start_kernel()") # Advise format_kernel_cmd(), which is the function that # computes the command line for a subprocess to run a new # kernel. Note that we advise the method on the class, rather # than this instance of kernel_manager, because start_kernel() # actually creates a *new* KernelInstance for this. - @advise(KernelManager.format_kernel_cmd) + @advise(JupyterKernelManager.format_kernel_cmd) def format_kernel_cmd_with_autoimport(*args, **kwargs): result = __original__(*args, **kwargs) logger.debug("intercepting format_kernel_cmd(): orig = %r", result) @@ -1665,19 +1668,19 @@ def format_kernel_cmd_with_autoimport(*args, **kwargs): try: # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, # 3.2. - from IPython.kernel.manager import KernelManager + from IPython.kernel.manager import KernelManager as IPythonKernelManager except ImportError: pass else: @self._advise(kernel_manager.start_kernel) - def start_kernel_with_autoimport(*args, **kwargs): + def start_kernel_with_autoimport_ipython(*args, **kwargs): logger.debug("start_kernel()") # Advise format_kernel_cmd(), which is the function that # computes the command line for a subprocess to run a new # kernel. Note that we advise the method on the class, rather # than this instance of kernel_manager, because start_kernel() # actually creates a *new* KernelInstance for this. - @advise(KernelManager.format_kernel_cmd) + @advise(IPythonKernelManager.format_kernel_cmd) def format_kernel_cmd_with_autoimport(*args, **kwargs): result = __original__(*args, **kwargs) logger.debug("intercepting format_kernel_cmd(): orig = %r", result) @@ -1814,7 +1817,27 @@ def _enable_reset_hook(self, ip): # function to get called twice per cell. This seems like an # unintentional repeated call in IPython itself. This is harmless for # us, since doing an extra reset shouldn't hurt. - if hasattr(ip, "input_transformer_manager"): + if hasattr(ip, "input_transformers_post"): + # In IPython 7.0+, the input transformer API changed. + def reset_auto_importer_state(line): + # There is a bug in IPython that causes the transformer to be + # called multiple times + # (https://github.com/ipython/ipython/issues/11714). Until it + # is fixed, workaround it by skipping one of the calls. + stack = inspect.stack() + if any([ + stack[3].function == 'run_cell_async', + # These are the other places it is called. + # stack[3].function == 'should_run_async', + # stack[1].function == 'check_complete' + ]): + return line + logger.debug("reset_auto_importer_state(%r)", line) + self.reset_state_new_cell() + return line + ip.input_transformers_cleanup.append(reset_auto_importer_state) + return True + elif hasattr(ip, "input_transformer_manager"): # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, # 3.2, 4.0. class ResetAutoImporterState(object): @@ -2132,6 +2155,15 @@ def get_completer_namespaces(): # Use get_global_namespaces(), which relies on # _get_pdb_if_is_in_pdb(). return None + if getattr(completer, 'use_jedi', False): + # IPython 6.0+ uses jedi completion by default, which bypasses + # the global and attr matchers. For now we manually reenable + # them. A TODO would be to hook the Jedi completer itself. + if completer.python_matches not in completer.matchers: + @self._advise(type(completer).matchers) + def matchers_with_python_matches(completer): + return [completer.python_matches] + __original__.fget(completer) + @self._advise(completer.global_matches) def global_matches_with_autoimport(fullname): if len(fullname) == 0: @@ -2144,6 +2176,7 @@ def attr_matches_with_autoimport(fullname): logger.debug("attr_matches_with_autoimport(%r)", fullname) namespaces = get_completer_namespaces() return self.complete_symbol(fullname, namespaces, on_error=__original__) + return True elif hasattr(completer, "complete_request"): # This is a ZMQCompleter, so nothing to do. @@ -2261,11 +2294,11 @@ def _enable_ipython_shell_bugfixes(self, ip): # because it uses Unicode for the module name. This is a bug in # IPython itself ("run -n" is plain broken for ipython-2.x on # python-2.x); we patch it here. - if (sys.version_info < (3,) and + if (PY2 and hasattr(ip, "new_main_mod")): try: args = inspect.getargspec(ip.new_main_mod).args - except Exception as e: + except Exception: # getargspec fails if we already advised. # For now just skip under the assumption that we already # advised (or the code changed in some way that doesn't @@ -2300,8 +2333,8 @@ def _enable_ipython_bugfixes_LevelFormatter(self): except ImportError: return True if (not issubclass(LevelFormatter, object) and - "super" in LevelFormatter.format.im_func.func_code.co_names and - "logging" not in LevelFormatter.format.im_func.func_code.co_names): + "super" in LevelFormatter.format.__func__.__code__.co_names and + "logging" not in LevelFormatter.format.__func__.__code__.co_names): # In IPython 1.0, LevelFormatter uses super(), which assumes # that logging.Formatter is a subclass of object. However, # this is only true in Python 2.7+, not in Python 2.6. So diff --git a/lib/python/pyflyby/_livepatch.py b/lib/python/pyflyby/_livepatch.py index c67e3aa6..d82230dd 100644 --- a/lib/python/pyflyby/_livepatch.py +++ b/lib/python/pyflyby/_livepatch.py @@ -137,6 +137,9 @@ def __livepatch__(self, old, do_livepatch): import time import types +from six import PY2 +from six.moves import reload_module + import inspect from pyflyby._log import logger @@ -151,7 +154,7 @@ def __livepatch__(self, old, do_livepatch): # Todo: better fallback _PROCESS_START_TIME = time.time() else: - _PROCESS_START_TIME = psutil.Process(os.getpid()).create_time + _PROCESS_START_TIME = psutil.Process(os.getpid()).create_time() class UnknownModuleError(ImportError): @@ -274,9 +277,12 @@ def do_livepatch(): visit_stack=visit_stack) # Find out which optional kwargs the hook wants. kwargs = {} - argspec = inspect.getargspec(hook) + if PY2: + argspec = inspect.getargspec(hook) + else: + argspec = inspect.getfullargspec(hook) argnames = argspec.args - if hasattr(hook, "im_func"): + if hasattr(hook, "__func__"): # Skip 'self' arg. argnames = argnames[1:] # Pick kwargs that are wanted and available. @@ -285,7 +291,7 @@ def do_livepatch(): for n in argnames: try: kwargs[n] = avail_kwargs[n] - if argspec.keywords: + if argspec.keywords if PY2 else argspec.varkw: break except KeyError: # For compatibility, allow first argument to be 'old' with any @@ -298,7 +304,7 @@ def do_livepatch(): # Rely on default being set. If a default isn't set, the # user will get a TypeError. pass - if argspec.keywords: + if argspec.keywords if PY2 else argspec.varkw: # Use all available kwargs. kwargs = avail_kwargs # Call hook. @@ -396,7 +402,7 @@ def _livepatch__method(old_method, new_method, modname, cache, visit_stack): """ Livepatch a method. """ - _livepatch__function(old_method.im_func, new_method.im_func, + _livepatch__function(old_method.__func__, new_method.__func__, modname=modname, cache=cache, visit_stack=visit_stack) return old_method @@ -570,7 +576,7 @@ def _format_age(t): def _interpret_module(arg): def mod_fn(module): - return getattr(m, "__file__", None) + return getattr(module, "__file__", None) if isinstance(arg, six.string_types): try: return sys.modules[arg] @@ -639,7 +645,7 @@ def _xreload_module(module, filename, force=False): if not filename or not filename.endswith(".py"): # If there's no *.py source file for this module, then fallback to # built-in reload(). - return reload(module) + return reload_module(module) # Compare mtime of the file with the load time of the module. If the file # wasn't touched, we don't need to do anything. try: diff --git a/lib/python/pyflyby/_log.py b/lib/python/pyflyby/_log.py index 5bae401b..cc18d924 100644 --- a/lib/python/pyflyby/_log.py +++ b/lib/python/pyflyby/_log.py @@ -2,7 +2,8 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from contextlib import contextmanager import logging diff --git a/lib/python/pyflyby/_modules.py b/lib/python/pyflyby/_modules.py index e70b995b..1c1a88c7 100644 --- a/lib/python/pyflyby/_modules.py +++ b/lib/python/pyflyby/_modules.py @@ -2,8 +2,10 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) +from functools import total_ordering import os import re import six @@ -15,7 +17,8 @@ from pyflyby._idents import DottedIdentifier, is_identifier from pyflyby._log import logger from pyflyby._util import (ExcludeImplicitCwdFromPathCtx, - cached_attribute, memoize, prefixes) + cached_attribute, cmp, memoize, + prefixes) class ErrorDuringImportError(ImportError): @@ -115,7 +118,7 @@ def pyc_to_py(filename): return filename - +@total_ordering class ModuleHandle(object): """ A handle to a module. @@ -368,6 +371,22 @@ def __cmp__(self, o): return NotImplemented return cmp(self.name, o.name) + def __eq__(self, o): + if self is o: + return True + if not isinstance(o, ModuleHandle): + return NotImplemented + return self.name == o.name + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, o): + if not isinstance(o, ModuleHandle): + return NotImplemented + return self.name < o.name + def __getitem__(self, x): if isinstance(x, slice): return type(self)(self.name[x]) diff --git a/lib/python/pyflyby/_parse.py b/lib/python/pyflyby/_parse.py index 058ecf5d..756fc3c7 100644 --- a/lib/python/pyflyby/_parse.py +++ b/lib/python/pyflyby/_parse.py @@ -2,23 +2,31 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import ast from collections import namedtuple +from functools import total_ordering from itertools import groupby import re -import six -from six.moves import range import sys from textwrap import dedent import types +import six +from six import PY2, PY3, text_type as unicode +from six.moves import range + from pyflyby._file import FilePos, FileText, Filename from pyflyby._flags import CompilerFlags from pyflyby._log import logger -from pyflyby._util import cached_attribute +from pyflyby._util import cached_attribute, cmp +if PY3: + from ast import Bytes +else: + Bytes = ast.Str def _is_comment_or_blank(line): """ @@ -34,9 +42,9 @@ def _is_comment_or_blank(line): def _ast_str_literal_value(node): - if isinstance(node, ast.Str): + if isinstance(node, (ast.Str, Bytes)): return node.s - if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str): + if isinstance(node, ast.Expr) and isinstance(node.value, (ast.Str, Bytes)): return node.value.s else: return None @@ -137,7 +145,7 @@ def _flags_to_try(source, flags, auto_flags, mode): if not auto_flags: yield flags return - if sys.version_info[0] != 2: + if PY3: yield flags return if mode == "eval": @@ -176,6 +184,8 @@ def _parse_ast_nodes(text, flags, auto_flags, mode): filename = str(text.filename) if text.filename else "" source = text.joined source = dedent(source) + if PY2 and isinstance(source, unicode): + source = source.encode('utf-8') if not source.endswith("\n"): # Ensure that the last line ends with a newline (C{ast} barfs # otherwise). @@ -213,7 +223,7 @@ def _test_parse_string_literal(text, flags): except SyntaxError: return None body = module_node.body - if not isinstance(body, ast.Str): + if not isinstance(body, (ast.Str, Bytes)): return None return body.s @@ -352,9 +362,10 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags): # understandable that they did that. # Since we use startpos for breaking lines, we need to set startpos to # the beginning of the line. + # In Python 3, the col_offset for the with is 0 again. if (isinstance(ast_node, ast.With) and not isinstance(parent_ast_node, ast.With) and - sys.version_info >= (2,7)): + sys.version_info[:2] == (2,7)): assert ast_node.col_offset >= 5 if startpos.lineno == text.startpos.lineno: linestart = text.startpos.colno @@ -387,14 +398,14 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags): # # To fix that, we copy start_lineno and start_colno from the Str # node once we've corrected the values. - assert not isinstance(ast_node, ast.Str) + assert not isinstance(ast_node, (ast.Str, Bytes)) assert leftstr_node.lineno == ast_node.lineno assert leftstr_node.col_offset == -1 ast_node.startpos = leftstr_node.startpos return True # It should now be the case that we are looking at a multi-line string # literal. - if not isinstance(ast_node, ast.Str): + if not isinstance(ast_node, (ast.Str, Bytes)): raise ValueError( "got a non-string col_offset=-1: %s" % (ast.dump(ast_node))) # The C{lineno} attribute gives the ending line number of the multiline @@ -413,8 +424,8 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags): start_line_colno = (text.startpos.colno if start_lineno==text.startpos.lineno else 1) startpos_candidates.extend([ - (m.group()[-1], FilePos(start_lineno, m.start()+start_line_colno)) - for m in re.finditer("[bBrRuU]*[\"\']", start_line)]) + (_m.group()[-1], FilePos(start_lineno, _m.start()+start_line_colno)) + for _m in re.finditer("[bBrRuU]*[\"\']", start_line)]) target_str = ast_node.s # Loop over possible end_linenos. The first one we've identified is the # by far most likely one, but in theory it could be anywhere later in the @@ -441,8 +452,8 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags): end_line_startcol = ( text.startpos.colno if end_lineno==text.startpos.lineno else 1) endpos_candidates = [ - (m.group(), FilePos(end_lineno,m.start()+end_line_startcol+1)) - for m in re.finditer("[\"\']", end_line)] + (_m.group(), FilePos(end_lineno,_m.start()+end_line_startcol+1)) + for _m in re.finditer("[\"\']", end_line)] if not endpos_candidates: # We found no endpos_candidates. This should not happen for # first_end_lineno because there should be _some_ string that ends @@ -627,7 +638,7 @@ def _ast_node_is_in_docstring_position(ast_node): @return: Whether this string ast node is in docstring position. """ - if not isinstance(ast_node, ast.Str): + if not isinstance(ast_node, (ast.Str, Bytes)): raise TypeError expr_node = ast_node.context.parent if not isinstance(expr_node, ast.Expr): @@ -812,11 +823,13 @@ def __eq__(self, other): return self.block == other.block def __ne__(self, other): - if self is other: - return False + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): if not isinstance(other, PythonStatement): return NotImplemented - return self.block != other.block + return self.block < other.block def __cmp__(self, other): if self is other: @@ -829,19 +842,20 @@ def __hash__(self): return hash(self.block) +@total_ordering class PythonBlock(object): r""" Representation of a sequence of consecutive top-level L{PythonStatement}(s). - >>> source_code = '# 1\nprint 2\n# 3\n# 4\nprint 5\nx=[6,\n 7]\n# 8\n' + >>> source_code = '# 1\nprint(2)\n# 3\n# 4\nprint(5)\nx=[6,\n 7]\n# 8\n' >>> codeblock = PythonBlock(source_code) >>> for stmt in PythonBlock(codeblock).statements: - ... print stmt + ... print(stmt) PythonStatement('# 1\n') - PythonStatement('print 2\n', startpos=(2,1)) + PythonStatement('print(2)\n', startpos=(2,1)) PythonStatement('# 3\n# 4\n', startpos=(3,1)) - PythonStatement('print 5\n', startpos=(5,1)) + PythonStatement('print(5)\n', startpos=(5,1)) PythonStatement('x=[6,\n 7]\n', startpos=(6,1)) PythonStatement('# 8\n', startpos=(8,1)) @@ -975,7 +989,11 @@ def _ast_node_or_parse_exception(self): except Exception as e: # Add the filename to the exception message to be nicer. if self.text.filename: - e = type(e)("While parsing %s: %s" % (self.text.filename, e)) + try: + e = type(e)("While parsing %s: %s" % (self.text.filename, e)) + except TypeError: + # Exception takes more than one argument + pass # Cache the exception to avoid re-attempting while debugging. return e @@ -1103,7 +1121,7 @@ def statements(self): can contain no ast node to represent comments. >>> code = "# multiline\n# comment\n'''multiline\nstring'''\nblah\n" - >>> print PythonBlock(code).statements # doctest:+NORMALIZE_WHITESPACE + >>> print(PythonBlock(code).statements) # doctest:+NORMALIZE_WHITESPACE (PythonStatement('# multiline\n# comment\n'), PythonStatement("'''multiline\nstring'''\n", startpos=(3,1)), PythonStatement('blah\n', startpos=(5,1))) @@ -1186,10 +1204,10 @@ def string_literals(self): [('a', FilePos(1,1)), ('b', FilePos(1,8)), ('c', FilePos(2,1))] @return: - Iterable of C{ast.Str} nodes + Iterable of C{ast.Str} or C{ast.Bytes} nodes """ for node in _walk_ast_nodes_in_order(self.annotated_ast_node): - if isinstance(node, ast.Str): + if isinstance(node, (ast.Str, Bytes)): assert hasattr(node, 'startpos') yield node @@ -1302,9 +1320,13 @@ def __eq__(self, other): return self.text == other.text and self.flags == other.flags def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): if not isinstance(other, PythonBlock): return NotImplemented - return not (self == other) + return (self.text, self.flags) < (other.text, other.flags) def __cmp__(self, other): if self is other: diff --git a/lib/python/pyflyby/_py.py b/lib/python/pyflyby/_py.py index 2d80aefd..1e05d4f4 100644 --- a/lib/python/pyflyby/_py.py +++ b/lib/python/pyflyby/_py.py @@ -126,7 +126,7 @@ $ py b64decode aGVsbG8= [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('aGVsbG8=', altchars=None) - 'hello' + b'hello' Find the day of the week of some date (apply function in module): $ py calendar.weekday 2014 7 18 @@ -202,7 +202,7 @@ 6 Run stdin as code: - $ echo 'print sys.argv[1:]' | py - hello world + $ echo 'print(sys.argv[1:])' | py - hello world [PYFLYBY] import sys ['hello', 'world'] @@ -217,10 +217,11 @@ $ py b64decode? [PYFLYBY] from base64 import b64decode Python signature: - >> b64decode(s, altchars=None) + >> b64decode(s, altchars=None, validate=False) + Command-line signature: - $ py b64decode s [altchars] - $ py b64decode --s=... [--altchars=...] + $ py b64decode s [altchars [validate]] + $ py b64decode --s=... [--altchars=...] [--validate=...] ... Get module help: @@ -239,6 +240,10 @@ from __future__ import (absolute_import, division, print_function, with_statement) +from functools import total_ordering + +from pyflyby._util import cmp + usage = """ py --- command-line python multitool with automatic importing @@ -323,9 +328,9 @@ from pyflyby._file import Filename, UnsafeFilenameError, which from pyflyby._flags import CompilerFlags from pyflyby._idents import is_identifier -from pyflyby._interactive import ( - get_ipython_terminal_app_with_autoimporter, run_ipython_line_magic, - start_ipython_with_autoimporter) +from pyflyby._interactive import (get_ipython_terminal_app_with_autoimporter, + run_ipython_line_magic, + start_ipython_with_autoimporter) from pyflyby._log import logger from pyflyby._modules import ModuleHandle from pyflyby._parse import PythonBlock @@ -345,7 +350,7 @@ def _get_argspec(arg, _recurse=False): return getargspec(arg) elif isinstance(arg, MethodType): argspec = getargspec(arg) - if arg.im_self is not None: + if arg.__self__ is not None: # For bound methods, ignore the "self" argument. return ArgSpec(argspec.args[1:], *argspec[1:]) return argspec @@ -356,7 +361,9 @@ def _get_argspec(arg, _recurse=False): else: argspec = _get_argspec(arg.__init__) return ArgSpec(argspec.args[1:], *argspec[1:]) - elif isinstance(arg, types.ClassType): + # Old style class. Should only run in Python 2. types.ClassType doesn't + # exist in Python 3. + elif isinstance(arg, getattr(types, 'ClassType', type)): argspec = _get_argspec(arg.__init__) return ArgSpec(argspec.args[1:], *argspec[1:]) elif _recurse and hasattr(arg, '__call__'): @@ -521,7 +528,7 @@ class UserExpr(object): >>> UserExpr('base64.b64decode("SGFsbG93ZWVu")', ns, "auto").value [PYFLYBY] import base64 - 'Halloween' + b'Halloween' Returning an unparsable argument as a string: >>> UserExpr('Victory Loop', ns, "auto").value @@ -975,6 +982,7 @@ def auto_apply(function, commandline_args, namespace, arg_mode=None, _handle_user_exception() +@total_ordering class LoggedList(object): """ List that logs which members have not yet been accessed (nor removed). @@ -1039,6 +1047,20 @@ def __delitem__(self, x): del self._items[x] del self._unaccessed[x] + def __eq__(self, other): + if not isinstance(other, LoggedList): + return NotImplemented + return self._items == other._items + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, LoggedList): + return NotImplemented + return self._items < other._items + def __cmp__(self, x): return cmp(self._items, x) @@ -1218,9 +1240,9 @@ def _has_python_shebang(filename): otherwise the shebang is not necessary. """ filename = Filename(filename) - with open(str(filename)) as f: + with open(str(filename), 'rb') as f: line = f.readline(1024) - return line.startswith("#!") and "python" in line + return line.startswith(b"#!") and b"python" in line @@ -1231,7 +1253,7 @@ def _interpret_arg_mode(arg, default="auto"): """ if arg is None: arg = default - if arg is "auto" or arg is "eval" or arg is "string": + if arg == "auto" or arg == "eval" or arg == "string": return arg # optimization for interned strings rarg = str(arg).strip().lower() if rarg in ["eval", "evaluate", "exprs", "expr", "expressions", "expression", "e"]: @@ -1825,7 +1847,7 @@ def nocmdarg(): # Start IPython nbconvert. (autoimporter is irrelevant.) if equalsign: args.insert(0, cmdarg) - launch_ipython_with_autoimporter(["nbconvert"] + args) + start_ipython_with_autoimporter(["nbconvert"] + args) elif action in ["timeit"]: # TODO: make --timeit and --time flags which work with any mode # and heuristic, instead of only eval. @@ -1903,7 +1925,7 @@ def nocmdarg(): # TODO: refactor if (args and self.arg_mode == None and - not any(re.match("\s*$|-[a-zA-Z-]", a) for a in args)): + not any(re.match(r"\s*$|-[a-zA-Z-]", a) for a in args)): cmd = PythonBlock(" ".join([arg0]+args), flags=FLAGS, auto_flags=True) if cmd.parsable and self.namespace.auto_import(cmd): diff --git a/lib/python/pyflyby/_util.py b/lib/python/pyflyby/_util.py index 36b3adcd..5fd8d3de 100644 --- a/lib/python/pyflyby/_util.py +++ b/lib/python/pyflyby/_util.py @@ -2,15 +2,19 @@ # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from contextlib import contextmanager +import inspect import os import six -from six import reraise +from six import PY3, reraise import sys import types +# Python 2/3 compatibility +DictProxyType = type(object.__dict__) class Object(object): pass @@ -250,7 +254,12 @@ def __getattr__(self, k): def __get__(self, inst, cls=None): - return types.MethodType(self, inst, cls) + if PY3: + if inst is None: + return self + return types.MethodType(self, inst) + else: + return types.MethodType(self, inst, cls) @@ -328,31 +337,49 @@ def __init__(self, joinpoint): while hasattr(joinpoint, "__joinpoint__"): joinpoint = joinpoint.__joinpoint__ self._joinpoint = joinpoint - if isinstance(joinpoint, (types.FunctionType, six.class_types, type)): + if (isinstance(joinpoint, (types.FunctionType, six.class_types, type)) + and not (PY3 and joinpoint.__name__ != joinpoint.__qualname__)): self._qname = "%s.%s" % ( joinpoint.__module__, joinpoint.__name__) self._container = sys.modules[joinpoint.__module__].__dict__ self._name = joinpoint.__name__ self._original = spec - assert spec == self._container[self._name] - elif isinstance(joinpoint, types.MethodType): - self._qname = "%s.%s.%s" % ( - joinpoint.im_class.__module__, - joinpoint.im_class.__name__, - joinpoint.im_func.__name__) - self._name = joinpoint.im_func.__name__ - if joinpoint.im_self is None: - # Class method. - container_obj = joinpoint.im_class + assert spec == self._container[self._name], joinpoint + elif isinstance(joinpoint, types.MethodType) or (PY3 and isinstance(joinpoint, + types.FunctionType) and joinpoint.__name__ != + joinpoint.__qualname__) or isinstance(joinpoint, property): + if isinstance(joinpoint, property): + joinpoint = joinpoint.fget + self._wrapper = property + if PY3: + self._qname = '%s.%s' % (joinpoint.__module__, + joinpoint.__qualname__) + self._name = joinpoint.__name__ + else: + self._qname = "%s.%s.%s" % ( + joinpoint.__self__.__class__.__module__, + joinpoint.__self__.__class__.__name__, + joinpoint.__func__.__name__) + self._name = joinpoint.__func__.__name__ + if getattr(joinpoint, '__self__', None) is None: + # Unbound method in Python 2 only. In Python 3, there are no unbound methods + # (they are just functions). + if PY3: + container_obj = getattr(inspect.getmodule(joinpoint), + joinpoint.__qualname__.split('.', 1)[0].rsplit('.', 1)[0]) + else: + container_obj = joinpoint.im_class self._container = _WritableDictProxy(container_obj) - self._original = spec.im_func + # __func__ gives the function for the Python 2 unbound method. + # In Python 3, spec is already a function. + self._original = getattr(spec, '__func__', spec) else: # Instance method. - container_obj = joinpoint.im_self + container_obj = joinpoint.__self__ self._container = container_obj.__dict__ self._original = spec - assert spec == getattr(container_obj, self._name) + assert spec == getattr(container_obj, self._name), (container_obj, self._qname) assert self._original == self._container.get(self._name, self._original) elif isinstance(joinpoint, tuple) and len(joinpoint) == 2: container, name = joinpoint @@ -365,11 +392,11 @@ def __init__(self, joinpoint): self._container = container._trait_values self._original = self._container[name] self._qname = name - elif isinstance(container.__dict__, types.DictProxyType): + elif isinstance(container.__dict__, DictProxyType): original = getattr(container, name) - if hasattr(original, "im_func"): + if hasattr(original, "__func__"): # TODO: generalize this to work for all cases, not just classmethod - original = original.im_func + original = original.__func__ self._wrapper = classmethod self._original = original self._container = _WritableDictProxy(container) @@ -389,7 +416,7 @@ def __init__(self, joinpoint): self._name = name # TODO: unbound method else: - raise TypeError("JoinPoint: unexpected %s" + raise TypeError("JoinPoint: unexpected type %s" % (type(joinpoint).__name__,)) self._wrapped = None @@ -455,3 +482,7 @@ def AdviceCtx(joinpoint, hook): yield finally: advice.unadvise() + +# For Python 2/3 compatibility. cmp isn't included with six. +def cmp(a, b): + return (a > b) - (a < b) diff --git a/lib/python/pyflyby/_version.py b/lib/python/pyflyby/_version.py index dfb9f2ad..db87f7bb 100644 --- a/lib/python/pyflyby/_version.py +++ b/lib/python/pyflyby/_version.py @@ -3,6 +3,7 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) __version__ = '1.2.6' diff --git a/lib/python/pyflyby/autoimport.py b/lib/python/pyflyby/autoimport.py index 08d5b1a9..138a1163 100644 --- a/lib/python/pyflyby/autoimport.py +++ b/lib/python/pyflyby/autoimport.py @@ -11,7 +11,8 @@ # import pyflyby # pyflyby.enable_auto_importer() -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._interactive import enable_auto_importer diff --git a/lib/python/pyflyby/importdb.py b/lib/python/pyflyby/importdb.py index 6f8fb06b..fa374767 100644 --- a/lib/python/pyflyby/importdb.py +++ b/lib/python/pyflyby/importdb.py @@ -4,7 +4,8 @@ # Deprecated stub for backwards compatibility. -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._importdb import ImportDB diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..b3147073 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +# IGNORE_EXCEPTION_DETAIL prevents doctest from testing the exception message, but is necessary for Python 2/3 compatibility +doctest_optionflags= ALLOW_UNICODE ALLOW_BYTES IGNORE_EXCEPTION_DETAIL +filterwarnings = + error::DeprecationWarning diff --git a/setup.py b/setup.py index 159ccb23..294a10f8 100755 --- a/setup.py +++ b/setup.py @@ -98,6 +98,9 @@ def run(self): # # To regenerate this file, run: setup.py collect_imports + __mandatory_imports__ = [ + 'from __future__ import print_function', + ] """).lstrip(), file=f) f.flush() subprocess.call( diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..d1153d41 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +from __future__ import absolute_import, division, print_function + diff --git a/tests/test_0testconfig.py b/tests/test_0testconfig.py index f324ec73..0f13dfff 100644 --- a/tests/test_0testconfig.py +++ b/tests/test_0testconfig.py @@ -1,7 +1,8 @@ # Some self tests to check that our test setup is pointing to wrong paths etc. -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import os import pytest @@ -17,7 +18,7 @@ def pipe(command, stdin=""): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ).communicate(stdin)[0].strip() + ).communicate(stdin)[0].strip().decode('utf-8') def test_pytest_version_1(): @@ -49,7 +50,7 @@ def test_pyflyby_tox_path_1(): def test_pyflyby_subprocess_file_1(): # Check that our test setup is getting the right pyflyby. - cmd = "import os, pyflyby; print os.path.realpath(pyflyby.__file__)" + cmd = "import os, pyflyby; print(os.path.realpath(pyflyby.__file__))" result = pipe([sys.executable, '-c', cmd]).replace(".pyc", ".py") expected = os.path.realpath(pyflyby.__file__).replace(".pyc", ".py") assert expected == result @@ -57,7 +58,7 @@ def test_pyflyby_subprocess_file_1(): def test_pyflyby_subprocess_version_1(): # Check that our test setup is getting the right pyflyby. - cmd = "import pyflyby; print pyflyby.__version__" + cmd = "import pyflyby; print(pyflyby.__version__)" result = pipe([sys.executable, '-c', cmd]) expected = pyflyby.__version__ assert expected == result diff --git a/tests/test_autoimp.py b/tests/test_autoimp.py index 31bc3950..edfd7e4d 100644 --- a/tests/test_autoimp.py +++ b/tests/test_autoimp.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import ast import os @@ -13,6 +14,8 @@ from tempfile import mkdtemp from textwrap import dedent +from six import PY2, PY3 + from pyflyby import (Filename, ImportDB, auto_eval, auto_import, find_missing_imports) from pyflyby._autoimp import (LoadSymbolError, load_symbol, @@ -53,7 +56,7 @@ def writetext(filename, text, mode='w'): def _dilist2strlist(arg): assert type(arg) is list assert all(type(x) is DottedIdentifier for x in arg) - return map(str, arg) + return list(map(str, arg)) def test_find_missing_imports_basic_1(): @@ -99,7 +102,7 @@ def test_find_missing_imports_in_scope_2(): def test_find_missing_imports_in_scope_3(): - result = find_missing_imports("for x in range(3): print numpy.arange(x)", [{}]) + result = find_missing_imports("for x in range(3): print(numpy.arange(x))", [{}]) result = _dilist2strlist(result) expected = ['numpy.arange'] assert expected == result @@ -133,10 +136,20 @@ def test_find_missing_imports_lambda_2(): assert expected == result +def test_find_missing_imports_lambda_3(): + result = find_missing_imports("(lambda *a,**k: (a, k))(7, x=1)", [{}]) + result = _dilist2strlist(result) + expected = [] + assert expected == result + + def test_find_missing_imports_list_comprehension_1(): result = find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}]) result = _dilist2strlist(result) - expected = ['z'] + if PY2: + expected = ['z'] + else: + expected = ['y', 'z'] assert expected == result @@ -190,9 +203,11 @@ def test_find_missing_imports_print_function_1(): def test_find_missing_imports_assignment_1(): code = dedent(""" + from __future__ import print_function + def f(): x = 1 - print x, y, z + print(x, y, z) y = 2 """) result = find_missing_imports(code, [{}]) @@ -593,7 +608,7 @@ def f1(self): pass def test_find_missing_imports_class_member_generator_expression_1(): # Verify that variables leak out of list comprehensions but not out of - # generator expressions. + # generator expressions in Python 2. # Verify that both can see members of the same ClassDef. code = dedent(""" class Caleb(object): @@ -604,7 +619,10 @@ class Caleb(object): """) result = find_missing_imports(code, [{}]) result = _dilist2strlist(result) - expected = ['y1'] + if PY2: + expected = ['y1'] + else: + expected = ['y1', 'y2'] assert expected == result @@ -746,9 +764,14 @@ def func10663671(): def test_find_missing_imports_complex_1(): - code = dedent(""" - x = 3+4j+5L+k+u'a' - """) + if PY2: + code = dedent(""" + x = 3+4j+5L+k+u'a' + """) + else: + code = dedent(""" + x = 3+4j+5+k+u'a' + """) result = find_missing_imports(code, [{}]) result = _dilist2strlist(result) expected = ['k'] @@ -756,8 +779,8 @@ def test_find_missing_imports_complex_1(): def test_find_missing_imports_code_1(): - f = lambda: foo.bar(x) + baz(y) - result = find_missing_imports(f.func_code, [{}]) + f = lambda: foo.bar(x) + baz(y) # noqa: F821 + result = find_missing_imports(f.__code__, [{}]) result = _dilist2strlist(result) expected = ['baz', 'foo.bar', 'x', 'y'] assert expected == result @@ -765,8 +788,8 @@ def test_find_missing_imports_code_1(): def test_find_missing_imports_code_args_1(): def f(x, y, *a, **k): - return g(x, y, z, a, k) - result = find_missing_imports(f.func_code, [{}]) + return g(x, y, z, a, k) # noqa: F821 + result = find_missing_imports(f.__code__, [{}]) result = _dilist2strlist(result) expected = ['g', 'z'] assert expected == result @@ -776,15 +799,15 @@ def test_find_missing_imports_code_use_after_import_1(): def f(): import foo foo.bar() - result = find_missing_imports(f.func_code, [{}]) + result = find_missing_imports(f.__code__, [{}]) result = _dilist2strlist(result) expected = [] assert expected == result def test_find_missing_imports_code_lambda_scope_1(): - f = lambda x: (lambda: x+y) - result = find_missing_imports(f.func_code, [{}]) + f = lambda x: (lambda: x+y) # noqa: F821 + result = find_missing_imports(f.__code__, [{}]) result = _dilist2strlist(result) expected = ['y'] assert expected == result @@ -792,14 +815,14 @@ def test_find_missing_imports_code_lambda_scope_1(): def test_find_missing_imports_code_conditional_1(): def f(): - y0 = x0 - if c: - y1 = y0 + x1 + y0 = x0 # noqa: F821 + if c: # noqa: F821 + y1 = y0 + x1 # noqa: F821 else: - y2 = x2 + y0 - x3 + y0 + y2 = x2 + y0 # noqa: F821 + x3 + y0 # noqa: F821 y1 + y2 - result = find_missing_imports(f.func_code, [{}]) + result = find_missing_imports(f.__code__, [{}]) result = _dilist2strlist(result) expected = ['c', 'x0', 'x1', 'x2', 'x3'] assert expected == result @@ -809,15 +832,285 @@ def test_find_missing_imports_code_loop_1(): def f(): for i in range(10): if i > 0: - use(x) - use(y) + use(x) # noqa: F821 + use(y) # noqa: F821 else: - x = "hello" - result = find_missing_imports(f.func_code, [{}]) + x = "hello" # noqa: F841 + result = find_missing_imports(f.__code__, [{}]) result = _dilist2strlist(result) expected = ['use', 'y'] assert expected == result +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_keyword_only_args_1(): + code = dedent(""" + def func(*args, kwonly=b): + a = kwonly + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['b'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_annotations_1(): + code = dedent(""" + def func(a: b) -> c: + d = a + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['b', 'c'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_annotations_2(): + code = dedent(""" + a: b = c + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['b', 'c'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_star_assignments_1(): + code = dedent(""" + a, *b = c + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['c'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_star_expression_1(): + code = dedent(""" + [a, *b] + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'b'] + assert expected == result + + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_star_expression_2(): + code = dedent(""" + {a: 1, **b, **{c: 1}} + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'b', 'c'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_star_expression_function_call_1(): + code = dedent(""" + f(a, *b, **c, d=e, **g) + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'b', 'c', 'e', 'f', 'g'] + assert expected == result + +def test_find_missing_imports_star_expression_function_call_2(): + # Python 2-valid syntax + code = dedent(""" + f(a, b=c, *d, **e) + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'c', 'd', 'e', 'f'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_python_3_metaclass_1(): + code = dedent(""" + class Test(metaclass=TestMeta): + pass + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['TestMeta'] + assert expected == result + + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_f_string_1(): + code = dedent(""" + a = 1 + f'{a + 1} {b + 1}' + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['b'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_f_string_2(): + code = dedent(""" + a = 1 + f'{a!s} {b!r}' + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['b'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_f_string_3(): + # Recursive format spec + code = dedent(""" + f'{a:{b}!s}' + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'b'] + assert expected == result + +def test_find_missing_imports_bytes_1(): + code = dedent(""" + a = b'b' + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = [] + assert expected == result + +def test_find_missing_imports_true_false_none_1(): + # These nodes changed in Python 3, make sure they are handled correctly + code = dedent(""" + (True, False, None) + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = [] + assert expected == result + + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_matmul_1(): + code = dedent(""" + a@b + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'b'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_async_await_1(): + code = dedent(""" + async def f(): + async with a as b, c as d: + async for i in e: + g = await h() + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'c', 'e', 'h'] + assert expected == result + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_async_comprehension_1(): + code = dedent(""" + async def f(): + [i async for i in range(2)] + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = [] + assert expected == result + + +@pytest.mark.skipif( + PY2, + reason="Python 3-only syntax.") +def test_find_missing_imports_yield_from_1(): + code = dedent(""" + def f(): + yield from g() + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['g'] + assert expected == result + +def test_find_missing_imports_nested_with_1(): + # Handled differently in the ast in Python 2 and 3 + code = dedent(""" + with a as b, c as d: + pass + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['a', 'c'] + assert expected == result + +def test_find_missing_imports_exception_1(): + code = dedent(""" + try: + a = 1 + except: + pass + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = [] + assert expected == result + +def test_find_missing_imports_exception_2(): + code = dedent(""" + try: + a = 1 + except SomeException: + pass + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['SomeException'] + assert expected == result + +def test_find_missing_imports_exception_3(): + code = dedent(""" + try: + a = 1 + except SomeException as e: + pass + """) + result = find_missing_imports(code, [{}]) + result = _dilist2strlist(result) + expected = ['SomeException'] + assert expected == result def test_scan_for_import_issues_dictcomp_missing_1(): code = dedent(""" @@ -1118,7 +1411,7 @@ def test_load_symbol_1(): def test_load_symbol_2(): - assert load_symbol("os.path.join.func_name", {"os": os}) == "join" + assert load_symbol("os.path.join.__name__", {"os": os}) == "join" def test_load_symbol_missing_1(): @@ -1196,20 +1489,20 @@ def __getattr__(self, k): def test_auto_eval_1(): result = auto_eval("b64decode('aGVsbG8=')") - assert result == 'hello' + assert result == b'hello' def test_auto_eval_locals_import_1(): mylocals = {} result = auto_eval("b64decode('aGVsbG8=')", locals=mylocals) - assert result == 'hello' + assert result == b'hello' assert mylocals["b64decode"] is __import__("base64").b64decode def test_auto_eval_globals_import_1(): myglobals = {} result = auto_eval("b64decode('aGVsbG8=')", globals=myglobals) - assert result == 'hello' + assert result == b'hello' assert myglobals["b64decode"] is __import__("base64").b64decode @@ -1229,21 +1522,28 @@ def test_auto_eval_exec_1(): mylocals = dict(x=[]) auto_eval("if True: x.append(b64decode('aGVsbG8='))", locals=mylocals) - assert mylocals['x'] == ['hello'] + assert mylocals['x'] == [b'hello'] assert mylocals["b64decode"] is __import__("base64").b64decode def test_auto_eval_no_auto_flags_ps_flagps_1(capsys): - auto_eval("print 3.00", flags=0, auto_flags=False) - out, _ = capsys.readouterr() - assert out == "3.0\n" - + if PY2: + auto_eval("print 3.00", flags=0, auto_flags=False) + out, _ = capsys.readouterr() + assert out == "3.0\n" + else: + auto_eval("print(3.00)", flags=0, auto_flags=False) + out, _ = capsys.readouterr() + assert out == "3.0\n" def test_auto_eval_no_auto_flags_ps_flag_pf1(): with pytest.raises(SyntaxError): auto_eval("print 3.00", flags="print_function", auto_flags=False) +@pytest.mark.skipif( + PY3, + reason="print function is not invalid syntax in Python 3.") def test_auto_eval_no_auto_flags_pf_flagps_1(): with pytest.raises(SyntaxError): auto_eval("print(3.00, file=sys.stdout)", flags=0, auto_flags=False) @@ -1257,11 +1557,17 @@ def test_auto_eval_no_auto_flags_pf_flag_pf1(capsys): def test_auto_eval_auto_flags_ps_flagps_1(capsys): - auto_eval("print 3.00", flags=0, auto_flags=True) - out, _ = capsys.readouterr() - assert out == "3.0\n" - + if PY2: + auto_eval("print 3.00", flags=0, auto_flags=True) + out, _ = capsys.readouterr() + assert out == "3.0\n" + else: + with pytest.raises(SyntaxError): + auto_eval("print 3.00", flags=0, auto_flags=True) +@pytest.mark.skipif( + PY3, + reason="print not as a function cannot be valid syntax in Python 3.") def test_auto_eval_auto_flags_ps_flag_pf1(capsys): auto_eval("print 3.00", flags="print_function", auto_flags=True) out, _ = capsys.readouterr() @@ -1401,11 +1707,19 @@ def test_auto_import_unknown_but_in_db1(tpp, capsys): db = ImportDB('import photon70447198') auto_import("photon70447198.asdfasdf", [{}], db=db) out, _ = capsys.readouterr() - expected = dedent(""" - [PYFLYBY] import photon70447198 - [PYFLYBY] Error attempting to 'import photon70447198': ImportError: No module named photon70447198 - Traceback (most recent call last): - """).lstrip() + if PY2: + expected = dedent(""" + [PYFLYBY] import photon70447198 + [PYFLYBY] Error attempting to 'import photon70447198': ImportError: No module named photon70447198 + Traceback (most recent call last): + """).lstrip() + else: + expected = dedent(""" + [PYFLYBY] import photon70447198 + [PYFLYBY] Error attempting to 'import photon70447198': ModuleNotFoundError: No module named 'photon70447198' + Traceback (most recent call last): + """).lstrip() + assert out.startswith(expected) @@ -1429,11 +1743,19 @@ def test_auto_import_indirect_importerror_1(tpp, capsys): """) auto_import("neutron46291483.asdfasdf", [{}]) out, _ = capsys.readouterr() - expected = dedent(""" - [PYFLYBY] import neutron46291483 - [PYFLYBY] Error attempting to 'import neutron46291483': ImportError: No module named baryon96446873 - Traceback (most recent call last): - """).lstrip() + if PY2: + expected = dedent(""" + [PYFLYBY] import neutron46291483 + [PYFLYBY] Error attempting to 'import neutron46291483': ImportError: No module named baryon96446873 + Traceback (most recent call last): + """).lstrip() + else: + expected = dedent(""" + [PYFLYBY] import neutron46291483 + [PYFLYBY] Error attempting to 'import neutron46291483': ModuleNotFoundError: No module named 'baryon96446873' + Traceback (most recent call last): + """).lstrip() + assert out.startswith(expected) diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 942307e5..58487f3b 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -3,15 +3,18 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) +from io import BytesIO import os import pexpect -from six.moves import cStringIO as StringIO import subprocess import tempfile from textwrap import dedent +from six import PY2 + from pyflyby._util import EnvVarCtx PYFLYBY_HOME = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) @@ -24,7 +27,7 @@ def pipe(command, stdin=""): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ).communicate(stdin)[0].strip() + ).communicate(stdin.encode('utf-8'))[0].decode('utf-8').strip() def test_tidy_imports_stdin_1(): @@ -72,7 +75,7 @@ def test_tidy_imports_log_level_1(): def test_tidy_imports_filename_action_print_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(dedent(''' # hello def foo(): @@ -98,7 +101,7 @@ def foo(): def test_tidy_imports_filename_action_replace_1(): - with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f: + with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode='w+') as f: f.write(dedent(''' "hello" def foo(): @@ -186,7 +189,7 @@ def test_reformat_imports_1(): def test_collect_imports_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(dedent(''' "hello" from m1.m2 import f3, f4 @@ -209,7 +212,7 @@ def f6(): pass def test_collect_imports_include_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(dedent(''' from m1.m2 import f3, f4 from m3.m4 import f6, f4 @@ -236,7 +239,7 @@ def test_collect_imports_include_1(): def test_collect_imports_include_dot_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(dedent(''' from m1.m2 import f3, f4 from m3.m4 import f6, f4 @@ -279,18 +282,22 @@ def test_py_eval_1(): expected = dedent(""" [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('aGVsbG8=') - 'hello' + b'hello' """).strip() + if PY2: + expected = expected.replace("b'hello'", "'hello'") assert result == expected def test_py_exec_1(): - result = pipe([BIN_DIR+"/py", "-c", "print b64decode('aGVsbG8=')"]) + result = pipe([BIN_DIR+"/py", "-c", "if 1: print(b64decode('aGVsbG8='))"]) expected = dedent(""" [PYFLYBY] from base64 import b64decode - [PYFLYBY] print b64decode('aGVsbG8=') - hello + [PYFLYBY] if 1: print(b64decode('aGVsbG8=')) + b'hello' """).strip() + if PY2: + expected = expected.replace("b'hello'", "hello") assert result == expected @@ -324,8 +331,8 @@ def test_py_argv_2(): def test_py_file_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: - f.write('print sys.argv\n') + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: + f.write('print(sys.argv)\n') f.flush() result = pipe([BIN_DIR+"/py", f.name, "a", "b"]) expected = dedent(""" @@ -341,17 +348,17 @@ def test_tidy_imports_query_no_change_1(): import x1 x1 ''') - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(input) f.flush() child = pexpect.spawn(BIN_DIR+'/tidy-imports', [f.name], timeout=5.0) - child.logfile = StringIO() + child.logfile = BytesIO() # We expect no "Replace [y/N]" query, since nothing changed. child.expect(pexpect.EOF) with open(f.name) as f2: output = f2.read() proc_output = child.logfile.getvalue() - assert proc_output == "" + assert proc_output == b"" assert output == input @@ -361,18 +368,18 @@ def test_tidy_imports_query_y_1(): import x1, x2 x1 ''') - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(input) f.flush() child = pexpect.spawn(BIN_DIR+'/tidy-imports', [f.name], timeout=5.0) - child.logfile = StringIO() + child.logfile = BytesIO() child.expect_exact(" [y/N]") child.send("y\n") child.expect(pexpect.EOF) with open(f.name) as f2: output = f2.read() proc_output = child.logfile.getvalue() - assert "[y/N] y" in proc_output + assert b"[y/N] y" in proc_output expected = dedent(""" from __future__ import absolute_import, division import x1 @@ -387,18 +394,18 @@ def test_tidy_imports_query_n_1(): import x1, x2 x1 ''') - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(input) f.flush() child = pexpect.spawn(BIN_DIR+'/tidy-imports', [f.name], timeout=5.0) - child.logfile = StringIO() + child.logfile = BytesIO() child.expect_exact(" [y/N]") child.send("n\n") child.expect(pexpect.EOF) with open(f.name) as f2: output = f2.read() proc_output = child.logfile.getvalue() - assert "[y/N] n" in proc_output + assert b"[y/N] n" in proc_output assert output == input @@ -408,17 +415,17 @@ def test_tidy_imports_query_junk_1(): import x1, x2 x1 ''') - with tempfile.NamedTemporaryFile(suffix=".py") as f: + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: f.write(input) f.flush() child = pexpect.spawn(BIN_DIR+'/tidy-imports', [f.name], timeout=5.0) - child.logfile = StringIO() + child.logfile = BytesIO() child.expect_exact(" [y/N]") child.send("zxcv\n") child.expect(pexpect.EOF) with open(f.name) as f2: output = f2.read() proc_output = child.logfile.getvalue() - assert "[y/N] zxcv" in proc_output - assert "Aborted" in proc_output + assert b"[y/N] zxcv" in proc_output + assert b"Aborted" in proc_output assert output == input diff --git a/tests/test_docxref.py b/tests/test_docxref.py index 97568685..a08a8502 100644 --- a/tests/test_docxref.py +++ b/tests/test_docxref.py @@ -3,31 +3,38 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement - -from pyflyby._docxref import find_bad_doc_cross_references -from pyflyby._modules import ModuleHandle +from __future__ import (absolute_import, division, print_function, + with_statement) from . import xrefs +import pytest +from six import PY3 + +if PY3: + pytestmark = pytest.mark.skip("Epydoc does not support Python 3") + def test_find_bad_doc_cross_references_1(): + from pyflyby._docxref import find_bad_doc_cross_references + from pyflyby._modules import ModuleHandle + result = find_bad_doc_cross_references([xrefs]) expected = ( (ModuleHandle('tests.xrefs'), (3,), 'tests.xrefs', u'undefined_xref_from_module'), - (ModuleHandle('tests.xrefs'), (18,), 'tests.xrefs.FooClass', u'undefined_xref_from_class'), + (ModuleHandle('tests.xrefs'), (19,), 'tests.xrefs.FooClass', u'undefined_xref_from_class'), # TODO: undefined_xref_from_class_attribute - (ModuleHandle('tests.xrefs'), (30,), 'tests.xrefs.FooClass.foo_method', u'undefined_xref_from_method'), - (ModuleHandle('tests.xrefs'), (37,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_wrapped_method'), - (ModuleHandle('tests.xrefs'), (40,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_param'), - (ModuleHandle('tests.xrefs'), (42,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_type'), - (ModuleHandle('tests.xrefs'), (44,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_args_param'), - (ModuleHandle('tests.xrefs'), (46,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_args_type'), - (ModuleHandle('tests.xrefs'), (48,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_kwargs_param'), - (ModuleHandle('tests.xrefs'), (50,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_kwargs_type'), - (ModuleHandle('tests.xrefs'), (52,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_rtype'), - (ModuleHandle('tests.xrefs'), (60, 63), 'tests.xrefs.FooClass.foo_property', u'undefined_xref_from_property'), - (ModuleHandle('tests.xrefs'), (63,), '??.foo_property', u'undefined_xref_from_property_rtype'), - (ModuleHandle('tests.xrefs'), (70,), 'tests.xrefs.FooClass.__foo_private_method', u'undefined_xref_from_private_method'), - (ModuleHandle('tests.xrefs'), (76,), 'tests.xrefs.foo_global_function', u'undefined_xref_from_global_function'), + (ModuleHandle('tests.xrefs'), (31,), 'tests.xrefs.FooClass.foo_method', u'undefined_xref_from_method'), + (ModuleHandle('tests.xrefs'), (38,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_wrapped_method'), + (ModuleHandle('tests.xrefs'), (41,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_param'), + (ModuleHandle('tests.xrefs'), (43,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_type'), + (ModuleHandle('tests.xrefs'), (45,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_args_param'), + (ModuleHandle('tests.xrefs'), (47,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_args_type'), + (ModuleHandle('tests.xrefs'), (49,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_kwargs_param'), + (ModuleHandle('tests.xrefs'), (51,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_kwargs_type'), + (ModuleHandle('tests.xrefs'), (53,), 'tests.xrefs.FooClass.foo_wrapped_method', u'undefined_xref_from_rtype'), + (ModuleHandle('tests.xrefs'), (61, 64), 'tests.xrefs.FooClass.foo_property', u'undefined_xref_from_property'), + (ModuleHandle('tests.xrefs'), (64,), '??.foo_property', u'undefined_xref_from_property_rtype'), + (ModuleHandle('tests.xrefs'), (71,), 'tests.xrefs.FooClass.__foo_private_method', u'undefined_xref_from_private_method'), + (ModuleHandle('tests.xrefs'), (77,), 'tests.xrefs.foo_global_function', u'undefined_xref_from_global_function'), ) assert result == expected diff --git a/tests/test_file.py b/tests/test_file.py index 2d5058cd..78a1664d 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import pytest diff --git a/tests/test_flags.py b/tests/test_flags.py index b7754d59..58bb7313 100644 --- a/tests/test_flags.py +++ b/tests/test_flags.py @@ -3,9 +3,13 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import ast + +from six import PY3 + import pytest from pyflyby._flags import CompilerFlags @@ -70,6 +74,9 @@ def test_CompilerFlags_from_ast_1(): assert result == CompilerFlags(0x18000) +@pytest.mark.skipif( + PY3, + reason="print function is not invalid syntax in Python 3.") def test_CompilerFlags_compile_1(): # Should raise SyntaxError: with pytest.raises(SyntaxError): diff --git a/tests/test_format.py b/tests/test_format.py index e999d526..479da0ce 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from textwrap import dedent diff --git a/tests/test_idents.py b/tests/test_idents.py index 3bd47b44..25f045bb 100644 --- a/tests/test_idents.py +++ b/tests/test_idents.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._idents import (brace_identifiers, dotted_prefixes, is_identifier) diff --git a/tests/test_importclns.py b/tests/test_importclns.py index 32c30090..7263ac44 100644 --- a/tests/test_importclns.py +++ b/tests/test_importclns.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._importclns import ImportMap, ImportSet from pyflyby._importstmt import Import, ImportStatement diff --git a/tests/test_importdb.py b/tests/test_importdb.py index f68a51fe..a454f952 100644 --- a/tests/test_importdb.py +++ b/tests/test_importdb.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import os from shutil import rmtree @@ -73,7 +74,7 @@ def import_ImportDB_memoized_1(): def test_ImportDB_pyflyby_path_filename_1(): # Check that PYFLYBY_PATH set to a filename works. - with NamedTemporaryFile() as f: + with NamedTemporaryFile(mode='w+') as f: f.write("from m4065635 import f78841936, f44111337, f73485346\n") f.flush() with EnvVarCtx(PYFLYBY_PATH=f.name): @@ -86,7 +87,7 @@ def test_ImportDB_pyflyby_path_filename_1(): def test_ImportDB_pyflyby_path_no_default_1(): # Check that defaults can be turned off from PYFLYBY_PATH. - with NamedTemporaryFile() as f: + with NamedTemporaryFile(mode='w+') as f: f.write("from m27056973 import f8855924\n") f.flush() with EnvVarCtx(PYFLYBY_PATH=f.name): @@ -112,7 +113,7 @@ def test_ImportDB_pyflyby_path_no_default_1(): def test_ImportDB_pyflyby_path_change_1(): # Check that memoization takes into account changes in # os.environ["PYFLYBY_PATH"]. - with NamedTemporaryFile() as f: + with NamedTemporaryFile(mode='w+') as f: f.write("from m60309242 import f5781152\n") f.flush() with EnvVarCtx(PYFLYBY_PATH=f.name): diff --git a/tests/test_imports2s.py b/tests/test_imports2s.py index 50620303..e9201e62 100644 --- a/tests/test_imports2s.py +++ b/tests/test_imports2s.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import pytest import sys @@ -756,7 +757,7 @@ def test_last_line_escaped_string_no_trailing_newline_1(): def test_remove_broken_imports_1(): input = PythonBlock(dedent(''' import sys, os, omgdoesntexist_95421787, keyword - from email import MIMEAudio, omgdoesntexist_8824165 + from email.mime.audio import MIMEAudio, omgdoesntexist_8824165 code() ''').lstrip(), filename="/foo/test_remove_broken_imports_1.py") output = remove_broken_imports(input) @@ -764,7 +765,7 @@ def test_remove_broken_imports_1(): import keyword import os import sys - from email import MIMEAudio + from email.mime.audio import MIMEAudio code() ''').lstrip(), filename="/foo/test_remove_broken_imports_1.py") assert output == expected @@ -815,13 +816,13 @@ def test_transform_imports_1(): from m import x from m import x as X import m.x - print m.x, m.xx + print(m.x, m.xx) ''').lstrip(), filename="/foo/test_transform_imports_1.py") output = transform_imports(input, {"m.x": "m.y.z"}) expected = PythonBlock(dedent(''' import m.y.z from m.y import z as X, z as x - print m.y.z, m.xx + print(m.y.z, m.xx) ''').lstrip(), filename="/foo/test_transform_imports_1.py") assert output == expected @@ -831,7 +832,7 @@ def test_canonicalize_imports_1(): from m import x from m import x as X import m.x - print m.x, m.xx + print(m.x, m.xx) ''').lstrip(), filename="/foo/test_transform_imports_1.py") db = ImportDB(""" __canonical_imports__ = {"m.x": "m.y.z"} @@ -840,7 +841,7 @@ def test_canonicalize_imports_1(): expected = PythonBlock(dedent(''' import m.y.z from m.y import z as X, z as x - print m.y.z, m.xx + print(m.y.z, m.xx) ''').lstrip(), filename="/foo/test_transform_imports_1.py") assert output == expected diff --git a/tests/test_importstmt.py b/tests/test_importstmt.py index 2cb96626..47c3407a 100644 --- a/tests/test_importstmt.py +++ b/tests/test_importstmt.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._flags import CompilerFlags from pyflyby._importstmt import Import, ImportSplit, ImportStatement diff --git a/tests/test_interactive.py b/tests/test_interactive.py index 6847fb40..8e511554 100644 --- a/tests/test_interactive.py +++ b/tests/test_interactive.py @@ -1,16 +1,17 @@ +# -*- coding: utf-8 -*- # pyflyby/test_interactive.py # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ from __future__ import absolute_import, division, with_statement +from __future__ import print_function import IPython import atexit from contextlib import contextmanager import difflib import flaky -import inspect import json import os import pexpect @@ -21,8 +22,7 @@ import requests from shutil import rmtree import six -from six.moves import cStringIO as StringIO -import signal +from six import BytesIO, PY2, PY3 from subprocess import check_call import sys from tempfile import mkdtemp, mkstemp @@ -55,9 +55,12 @@ def assert_fail(): def pytest_generate_tests(metafunc): # IPython 4 and earlier only had readline frontend. # IPython 5.0 through 5.3 only allow prompt_toolkit. - # IPython 5.4+ defaults to prompt_toolkit, but allows choosing readline. + # IPython 5.4 through 6.5 defaults to prompt_toolkit, but allows choosing readline. + # IPython 7+ breaks rlipython (https://github.com/ipython/rlipython/issues/21). if 'frontend' in metafunc.fixturenames: - if _IPYTHON_VERSION >= (5,4): + if _IPYTHON_VERSION >= (7,0): + metafunc.parametrize('frontend', [ 'prompt_toolkit']) + elif _IPYTHON_VERSION >= (5,4): try: import rlipython rlipython # used @@ -111,10 +114,6 @@ def new_tempfile(self, dir=None): self._request.addfinalizer(lambda: os.unlink(f)) return Filename(f) - -# Some tests randomly fail, especially on Travis, with the prompt-toolkit -# frontend not showing the In prompt. Since these only fail sometimes, we -# automatically retry them with the flaky plugin. retry = flaky.flaky(max_runs=5) def writetext(filename, text, mode='w'): @@ -133,29 +132,29 @@ def assert_match(result, expected, ignore_prompt_number=False): * "...." (four dots) matches any text (including newline). """ __tracebackhide__ = True - expected = dedent(expected).strip() - parts = expected.split("...") + expected = dedent(expected.decode('utf-8')).strip().encode('utf-8') + parts = expected.split(b"...") regexp_parts = [re.escape(parts[0])] for s in parts[1:]: - if re.match(":( |$)", s, re.M) and regexp_parts[-1] == " ": + if re.match(b":( |$)", s, re.M) and regexp_parts[-1] == b" ": # Treat "\n ...: " specially; don't make it a glob. - regexp_parts.append("...") + regexp_parts.append(b"...") regexp_parts.append(re.escape(s)) - elif s.startswith("."): - regexp_parts.append("(?:.|\n)*") + elif s.startswith(b"."): + regexp_parts.append(b"(?:.|\n)*") regexp_parts.append(re.escape(s[1:])) else: - regexp_parts.append(".*") + regexp_parts.append(b".*") regexp_parts.append(re.escape(s)) - regexp = "".join(regexp_parts) + regexp = b"".join(regexp_parts) if ignore_prompt_number: - regexp = re.sub(r"(In\\? |Out)\\*\[[0-9]+\\*\]\\?:", r"\1\[[0-9]+\]:", regexp) + regexp = re.sub(br"(In\\? |Out)\\*\[[0-9]+\\*\]\\?:", br"\1\[[0-9]+\]:", regexp) if _IPYTHON_VERSION >= (4,): ignore = dedent(r""" (\[ZMQTerminalIPythonApp\] Loading IPython extension: storemagic )? - """).strip() - result = re.sub(ignore, "", result) + """).strip().encode('utf-8') + result = re.sub(ignore, b"", result) if _IPYTHON_VERSION < (1, 0): # IPython 0.13 console prints kernel info; ignore it. # [IPKernelApp] To connect another client to this kernel, use: @@ -164,15 +163,15 @@ def assert_match(result, expected, ignore_prompt_number=False): (\[IPKernelApp\] To connect another client to this kernel, use: \[IPKernelApp\] --existing kernel-[0-9]+\.json )? - """).strip() - result = re.sub(ignore, "", result) + """).strip().encode('utf-8') + result = re.sub(ignore, b"", result) if _IPYTHON_VERSION < (0, 11): # IPython 0.10 prompt counts are buggy, e.g. %time increments by 2. # Ignore prompt numbers and extra newlines before the output prompt. - regexp = re.sub(re.compile(r"^In\\? \\\[[0-9]+\\\]", re.M), - r"In \[[0-9]+\]", regexp) - regexp = re.sub(re.compile(r"^Out\\\[[0-9]+\\\]", re.M), - r"\n?Out\[[0-9]+\]", regexp) + regexp = re.sub(re.compile(br"^In\\? \\\[[0-9]+\\\]", re.M), + br"In \[[0-9]+\]", regexp) + regexp = re.sub(re.compile(br"^Out\\\[[0-9]+\\\]", re.M), + br"\n?Out\[[0-9]+\]", regexp) if _IPYTHON_VERSION < (0, 12): # Ignore ultratb problems (not pyflyby-related). # TODO: consider using --TerminalInteractiveShell.xmode=plain (-xmode) @@ -181,8 +180,8 @@ def assert_match(result, expected, ignore_prompt_number=False): The following traceback may be corrupted or invalid The error message is: .* )? - """).strip() - result = re.sub(ignore, "", result) + """).strip().encode('utf-8') + result = re.sub(ignore, b"", result) if _IPYTHON_VERSION < (1, 0): # Ignore zmq version warnings (not pyflyby-related). # TODO: install older version of zmq for older IPython versions. @@ -192,18 +191,18 @@ def assert_match(result, expected, ignore_prompt_number=False): \s*Please install libzmq stable.*? \s*RuntimeWarning\) )? - """).strip() - result = re.sub(ignore, "", result) + """).strip().encode('utf-8') + result = re.sub(ignore, b"", result) # Ignore the "Compiler time: 0.123 s" which may occasionally appear # depending on runtime. - regexp = re.sub(re.compile(r"^(1[\\]* loops[\\]*,[\\]* best[\\]* of[\\]* 1[\\]*:[\\]* .*[\\]* per[\\]* loop)($|[$]|[\\]*\n)", re.M), - "\\1(?:\nCompiler (?:time)?: [0-9.]+ s)?\\2", regexp) - regexp = re.sub(re.compile(r"^(Wall[\\]* time[\\]*:.*?)($|[$]|[\\]*\n)", re.M), - "\\1(?:\nCompiler (?:time)?: [0-9.]+ s)?\\2", regexp) - regexp += "$" + regexp = re.sub(re.compile(br"^(1[\\]* loops[\\]*,[\\]* best[\\]* of[\\]* 1[\\]*:[\\]* .*[\\]* per[\\]* loop)($|[$]|[\\]*\n)", re.M), + b"\\1(?:\nCompiler (?:time)?: [0-9.]+ s)?\\2", regexp) + regexp = re.sub(re.compile(br"^(Wall[\\]* time[\\]*:.*?)($|[$]|[\\]*\n)", re.M), + b"\\1(?:\nCompiler (?:time)?: [0-9.]+ s)?\\2", regexp) + regexp += b"$" # Check for match. regexp = re.compile(regexp) - result = '\n'.join(line.rstrip() for line in result.splitlines()) + result = b'\n'.join(line.rstrip() for line in result.splitlines()) result = result.strip() if DEBUG: print("expected: %r" % (expected,)) @@ -216,35 +215,36 @@ def assert_match(result, expected, ignore_prompt_number=False): msg.extend(" %s"%line for line in result.splitlines()) msg.append("Diff:") msg.extend(" %s"%line for line in difflib.ndiff( - expected.splitlines(), result.splitlines())) - if DEBUG or any(i in '\x1b\x07\b\t' for i in expected+result): + expected.decode('utf-8').splitlines(), result.decode('utf-8').splitlines())) + if DEBUG or any(i in b'\x1b\x07\b\t' for i in expected+result): msg.append("Diff Repr:") msg.extend(" %r"%line for line in difflib.ndiff( - expected.splitlines(), result.splitlines())) + expected.decode('utf-8').splitlines(), result.decode('utf-8').splitlines())) msg = "\n".join(msg) pytest.fail(msg) -def parse_template(template): - template = dedent(template).strip() +def parse_template(template, clear_tab_completions=False): + template = dedent(template).strip().encode('utf-8') + template = _normalize_python_2_3(template) input = [] - expected = [] - pattern = re.compile("^(?:In \[[0-9]+\]:| [.][.][.]+:|ipdb>|>>>)(?: |$)", re.M) + expected = b"" + pattern = re.compile(br"^(?:In \[[0-9]+\]:| [.][.][.]+:|ipdb>|>>>)(?: |$)", re.M) while template: m = pattern.search(template) if not m: - expected.append(template) + expected += template break expline = m.group(0) - expected.append(template[:m.end()]) + expected += template[:m.end()] template = template[m.end():] - while template and not template.startswith("\n"): + while template and not template.startswith(b"\n"): # We're in the input part of a template. Get input up to tab or # end of line. - m = re.match(re.compile("(.*?)(\t|$)", re.M), template) + m = re.match(re.compile(br"(.*?)(\t|$)", re.M), template) input.append(m.group(1)) expline += m.group(1) - expected.append(m.group(1)) + expected += m.group(1) tab = m.group(2) template = template[m.end():] if not tab: @@ -259,35 +259,41 @@ def parse_template(template): # be repeated multiple times, for a total of up to three # occurrences of the same prompt. We find where to continue # by looking for the line repeated in the template. - if template.startswith("\n"): + if template.startswith(b"\n"): + if clear_tab_completions: + # In prompt-toolkit 2.0, tab completion at the end of the line is cleared in the output + newline = expected.rfind(b'\n') + if newline == -1: newline = 0 + expected = expected[:newline] + rep = template.rfind(expline) if rep < 0: raise AssertionError( "expected next line of template following a " "tab completion to start with %r" % (expline,)) repend = rep + len(expline) - expected.append(template[:repend]) + expected += template[:repend] template = template[repend:] # Assume that all subsequent symbol characters (alphanumeric # and underscore) in the template represent tab completion # output. - m = re.match("[a-zA-Z0-9_]+", template) + m = re.match(br"[a-zA-Z0-9_]+", template) if m: expline += m.group(0) - expected.append(m.group(0)) + expected += m.group(0) template = template[m.end():] # Allow \x06 in the template to be a special character meaning # "end of tab completion output". - if template.startswith("\x06"): + if template.startswith(b"\x06"): template = template[1:] - input.append("\n") - input = "".join(input) - expected = "".join(expected) + input.append(b"\n") + input = b"".join(input) return input, expected @retry -def test_selftest_parse_template_1(): +@pytest.mark.parametrize('clear_tab_completions', [False, True]) +def test_selftest_parse_template_1(clear_tab_completions): template = """ In [1]: hello there @@ -297,48 +303,52 @@ def test_selftest_parse_template_1(): ...: baz """ - input, expected = parse_template(template) - assert input == "hello\nfoo\nbar\n\n" + input, expected = parse_template(template, clear_tab_completions=clear_tab_completions) + assert input == b"hello\nfoo\nbar\n\n" assert expected == ( - "In [1]: hello\nthere\nworld\n" - "In [2]: foo\n ...: bar\n ...:\nbaz") + b"In [1]: hello\nthere\nworld\n" + b"In [2]: foo\n ...: bar\n ...:\nbaz") @retry -def test_selftest_parse_template_tab_punctuation_1(): +@pytest.mark.parametrize('clear_tab_completions', [False, True]) +def test_selftest_parse_template_tab_punctuation_1(clear_tab_completions): template = """ In [1]: hello\t_there(3) goodbye """ - input, expected = parse_template(template) - assert input == "hello\t(3)\n" - assert expected == ("In [1]: hello_there(3)\ngoodbye") + input, expected = parse_template(template, clear_tab_completions=clear_tab_completions) + assert input == b"hello\t(3)\n" + assert expected == (b"In [1]: hello_there(3)\ngoodbye") @retry -def test_selftest_parse_template_tab_newline_(): +@pytest.mark.parametrize('clear_tab_completions', [False, True]) +def test_selftest_parse_template_tab_newline_(clear_tab_completions): template = """ In [1]: hello_\tthere goodbye """ - input, expected = parse_template(template) - assert input == "hello_\t\n" - assert expected == ("In [1]: hello_there\ngoodbye") + input, expected = parse_template(template, clear_tab_completions=clear_tab_completions) + assert input == b"hello_\t\n" + assert expected == (b"In [1]: hello_there\ngoodbye") @retry -def test_selftest_parse_template_tab_continue_1(): +@pytest.mark.parametrize('clear_tab_completions', [False, True]) +def test_selftest_parse_template_tab_continue_1(clear_tab_completions): template = """ In [1]: hello\t_the\x06re(3) goodbye """ input, expected = parse_template(template) - assert input == "hello\tre(3)\n" - assert expected == ("In [1]: hello_there(3)\ngoodbye") + assert input == b"hello\tre(3)\n" + assert expected == (b"In [1]: hello_there(3)\ngoodbye") @retry -def test_selftest_parse_template_tab_log_1(): +@pytest.mark.parametrize('clear_tab_completions', [False, True]) +def test_selftest_parse_template_tab_log_1(clear_tab_completions): template = """ In [1]: hello\t bonjour @@ -347,20 +357,29 @@ def test_selftest_parse_template_tab_log_1(): In [1]: hello_there(5) goodbye """ - input, expected = parse_template(template) - assert input == "hello\t(5)\n" - assert expected == ( - "In [1]: hello\n" - "bonjour\n" - "In [1]: hello\n" - "hallo\n" - "In [1]: hello_there(5)\n" - "goodbye") + input, expected = parse_template(template, clear_tab_completions=clear_tab_completions) + assert input == b"hello\t(5)\n" + if clear_tab_completions: + assert expected == ( + b"\n" + b"bonjour\n" + b"In [1]: hello\n" + b"hallo\n" + b"In [1]: hello_there(5)\n" + b"goodbye") + else: + assert expected == ( + b"In [1]: hello\n" + b"bonjour\n" + b"In [1]: hello\n" + b"hallo\n" + b"In [1]: hello_there(5)\n" + b"goodbye") @retry def test_selftest_assert_match_1(): - expected = """ + expected = b""" hello 1...2 goodbye @@ -373,18 +392,18 @@ def test_selftest_assert_match_1(): goodbye I don't know why you say goodbye I say hello - """) + """).encode('utf-8') assert_match(result, expected) @retry def test_selftest_assert_match_2(): - result = """ + result = b""" hello 1...2 there """ - expected = """ + expected = b""" hello 14712047 @@ -427,16 +446,16 @@ def _parse_version(version_string): _IPYTHON_VERSION = _parse_version(IPython.__version__) # Prompts that we expect for. -_IPYTHON_PROMPT1 = r"\nIn \[([0-9]+)\]: " -_IPYTHON_PROMPT2 = r"\n [.][.][.]+: " -_PYTHON_PROMPT = r">>> " -_IPDB_PROMPT = r"\nipdb> " +_IPYTHON_PROMPT1 = br"\nIn \[([0-9]+)\]: " +_IPYTHON_PROMPT2 = br"\n [.][.][.]+: " +_PYTHON_PROMPT = br">>> " +_IPDB_PROMPT = br"\nipdb> " _IPYTHON_PROMPTS = [_IPYTHON_PROMPT1, _IPYTHON_PROMPT2, _PYTHON_PROMPT, _IPDB_PROMPT] # Currently _interact_ipython() assumes the first is "In [nn]:" -assert "In " in _IPYTHON_PROMPTS[0] +assert b"In " in _IPYTHON_PROMPTS[0] @memoize @@ -484,7 +503,39 @@ def _init_ipython_dir(ipython_dir): if _IPYTHON_VERSION >= (0, 11): os.makedirs(str(ipython_dir/"profile_default")) os.makedirs(str(ipython_dir/"profile_default/startup")) - writetext(ipython_dir/"profile_default/ipython_config.py", + if _IPYTHON_VERSION >= (7,): + writetext(ipython_dir/"profile_default/ipython_config.py", + dedent(""" + c = get_config() + # Prompt-toolkit 2.0 still prints some escape codes for the + # completion display even if there is only one completion. + c.TerminalInteractiveShell.display_completions = "readlinelike" + c.TerminalInteractiveShell.colors = 'NoColor' + # Disable bracket highlighting, which prints escape codes that confuse the decoder. + c.TerminalInteractiveShell.highlight_matching_brackets = False + """)) + writetext(ipython_dir/"jupyter_console_config.py", + dedent(""" + c = get_config() + # Disable bracket highlighting, which prints escape codes that confuse the decoder. + c.ZMQTerminalInteractiveShell.display_completions = "readlinelike" + # Not supported in Jupyter console + # c.ZMQTerminalInteractiveShell.colors = 'NoColor' + # Prompt-toolkit 2.0 still prints some escape codes for the + # completion display even if there is only one completion. + c.ZMQTerminalInteractiveShell.highlight_matching_brackets = False + """)) + elif _IPYTHON_VERSION >= (5,): + writetext(ipython_dir/"profile_default/ipython_config.py", + dedent(""" + c = get_config() + c.TerminalInteractiveShell.colors = 'NoColor' + # Prompt-toolkit 2.0 still prints some escape codes for the + # completion display even if there is only one completion. + c.TerminalInteractiveShell.highlight_matching_brackets = False + """)) + else: + writetext(ipython_dir/"profile_default/ipython_config.py", "c = get_config()\n") elif _IPYTHON_VERSION >= (0, 10): writetext(ipython_dir/"ipythonrc", """ @@ -535,7 +586,7 @@ def opt(arg): else: raise NotImplementedError("Don't know how to test IPython version %s" % (_IPYTHON_VERSION,)) - cmd = ['env', 'IPYTHONDIR=%s' % (ipython_dir,), 'INPUTRC=none'] + cmd + cmd = ['env', 'IPYTHONDIR=%s' % (ipython_dir,), 'JUPYTER_CONFIG_DIR=%s' % (ipython_dir,), 'INPUTRC=none', 'PROMPT_TOOLKIT_NO_CPR=1'] + cmd if app == "terminal" and prog != "py": cmd += [opt("--no-confirm-exit")] cmd += [opt("--no-banner")] @@ -543,14 +594,15 @@ def opt(arg): cmd += [opt("--no-confirm-exit")] if _IPYTHON_VERSION < (4,): cmd += [opt("--no-banner")] - if app != "notebook" and _IPYTHON_VERSION < (5,) and prog != "py": + if app != "notebook" and prog != "py": cmd += [opt("--colors=NoColor")] - if frontend == 'prompt_toolkit': + if frontend == 'prompt_toolkit' and _IPYTHON_VERSION < (7,) or prog == "py": # prompt_toolkit (IPython 5) doesn't support turning off autoindent. It # has various command-line options which toggle the internal # shell.autoindent flag, but turning that internal flag off doesn't do # anything. Instead we'll just have to send a ^U at the beginning of - # each line to defeat the autoindent. + # each line to defeat the autoindent. The feature was re-enabled in + # IPython 7, so we don't need to worry there. pass elif _IPYTHON_VERSION >= (3,): cmd += ["--InteractiveShell.autoindent=False"] @@ -588,65 +640,110 @@ def opt(arg): class AnsiFilterDecoder(object): def __init__(self): - self._buffer = "" + self._buffer = b"" def decode(self, arg, final=False): arg0 = arg = self._buffer + arg - self._buffer = "" - arg = re.sub("\r+\n", "\n", arg) - arg = arg.replace("\x1b[J", "") # clear to end of display - arg = re.sub(r"\x1b\[[0-9]+(?:;[0-9]+)*m", "", arg) # color - arg = arg.replace("\x1b[6n", "") # query cursor position - arg = arg.replace("\x1b[?1l", "") # normal cursor keys - arg = arg.replace("\x1b[?7l", "") # no wraparound mode - arg = arg.replace("\x1b[?12l", "") # stop blinking cursor - arg = arg.replace("\x1b[?25l", "") # hide cursor - arg = arg.replace("\x1b[?2004l", "") # no bracketed paste mode - arg = arg.replace("\x1b[?7h", "") # wraparound mode - arg = arg.replace("\x1b[?25h", "") # show cursor - arg = arg.replace("\x1b[?2004h", "") # bracketed paste mode - arg = arg.replace('\x1b[?5h\x1b[?5l', '') # visual bell - arg = re.sub(r"\x1b\[([0-9]+)D\x1b\[\1C", "", arg) # left8,right8 no-op (srsly?) - arg = arg.replace('\x1b[?1034h', '') # meta key - arg = arg.replace('\x1b>', '') # keypad numeric mode (???) + self._buffer = b"" + arg = re.sub(b"\r+\n", b"\n", arg) + arg = arg.replace(b"\x1b[J", b"") # clear to end of display + arg = re.sub(br"\x1b\[[0-9]+(?:;[0-9]+)*m", b"", arg) # color + arg = arg.replace(b"\x1b[6n", b"") # query cursor position + arg = arg.replace(b"\x1b[?1l", b"") # normal cursor keys + arg = arg.replace(b"\x1b[?7l", b"") # no wraparound mode + arg = arg.replace(b"\x1b[?12l", b"") # stop blinking cursor + arg = arg.replace(b"\x1b[?25l", b"") # hide cursor + arg = arg.replace(b"\x1b[?2004l", b"") # no bracketed paste mode + arg = arg.replace(b"\x1b[?7h", b"") # wraparound mode + arg = arg.replace(b"\x1b[?25h", b"") # show cursor + arg = arg.replace(b"\x1b[?2004h", b"") # bracketed paste mode + arg = arg.replace(b'\x1b[?5h\x1b[?5l', b'') # visual bell + arg = re.sub(br"\x1b\[([0-9]+)D\x1b\[\1C", b"", arg) # left8,right8 no-op (srsly?) + arg = arg.replace(b'\x1b[?1034h', b'') # meta key + arg = arg.replace(b'\x1b>', b'') # keypad numeric mode (???) + arg = re.sub(br"\n\x1b\[[0-9]*C", b"", arg) # move cursor right immediately after a newline # Cursor movement. We assume this is used only for places that have '...' # in the tests. - # arg = re.sub("\\\x1b\\[\\?1049h.*\\\x1b\\[\\?1049l", "", arg) + # arg = re.sub(b"\\\x1b\\[\\?1049h.*\\\x1b\\[\\?1049l", b"", arg) - # Assume ESC[5Dabcd\n is rewriting previous text; delete it. - # Only do so if the line does NOT have '[PYFLYBY]'. TODO: find a less - # hacky way to handle this without hardcoding '[PYFLYBY]'. - left = "" + # Assume ESC[5Dabcd\n is rewriting previous text; delete it. Only do + # so if the line does NOT have '[PYFLYBY]' or a CPR request warning. + # TODO: find a less hacky way to handle this without hardcoding + # '[PYFLYBY]'. + left = b"" right = arg while right: - m = re.search(r"\x1b\[[0-9]+D.*?\n", right) + m = re.search(br"\x1b\[[0-9]+D.*?\n", right) if not m: break - if '[PYFLYBY]' in m.group(): + if b'[PYFLYBY]' in m.group() or b'WARNING' in m.group(): left += right[:m.end()] else: - left += right[:m.start()] + '\n' + left += right[:m.start()] + b'\n' right = right[m.end():] arg = left + right # Assume ESC[3A\nline1\nline2\nline3\n is rewriting previous text; # delete it. - left = "" + left = b"" right = arg while right: - m = re.search(r"\x1b\[([0-9]+)A\n", right) + m = re.search(br"\x1b\[([0-9]+)A\n", right) if not m: break num = int(m.group(1)) end = m.end() suffix = right[end:] - suffix_lines = suffix.splitlines(True) + # splitlines includes \r as a line delimiter, which we do not want + suffix_lines = suffix.split(b'\n') left = left + right[:m.start()] - if len(suffix_lines) < num: + if len(suffix_lines) <= num: self._buffer += right[m.start():] - right = "" + right = b"" break - right = '\n'.join(suffix_lines[num:]) + '\n' + right = b'\n'.join(suffix_lines[num:]) + if suffix.endswith(b'\n'): + right += b'\n' arg = left + right + + # \rESC[5C moves the cursor to the beginning of the line, then right 5 + # characters. Assume anything after any of these is not printed + # (should be only space and invisible characters). Everything replaced + # above is zero width, so we can safely do this last. + lines = [] + for line in arg.split(b'\n'): + n = len(line) + m = None + for m in re.finditer(br'\r\x1b\[([0-9]+)C', line): + n = int(m.group(1)) + if n > len(line): + self._buffer += arg + arg = b"" + break + elif m and b'\x1b' in line[m.end():]: + # Some escape code was only seen partially and hence wasn't + # replaced above. If we cleared it now, the remainder would be + # shown as plain text in the next arg. + self._buffer += arg + arg = b"" + break + else: + lines.append(line[:n]) + else: + arg = b'\n'.join(lines) + + if arg.endswith(b' '*10): + # Probably going to have some \rESC[5C type clearing. There can be + # 9 spaces from a double indentation after ...: (currently the + # most indentation used), but the clearing generally uses hundreds + # of spaces, so this should distinguish them. + self._buffer = arg + arg = b"" + + # Uncompleted escape sequence at the end of the string + if re.search(br"\x1b[^a-zA-Z]*$", arg): + self._buffer = arg + arg = b"" + if DEBUG: if self._buffer: print("AnsiFilterDecoder: %r => %r, pending: %r" % (arg0,arg,self._buffer)) @@ -736,7 +833,7 @@ def IPythonCtx(prog="ipython", # Figure out frontend to use. frontend = _interpret_frontend_arg(frontend) child = None - output = StringIO() + output = BytesIO() try: # Prepare environment variables. env = {} @@ -755,9 +852,9 @@ def IPythonCtx(prog="ipython", dimensions=(100,900), timeout=10.0) # Record frontend for others. child.ipython_frontend = frontend - # Log output to a StringIO. Note that we use "logfile_read", not + # Log output to a BytesIO. Note that we use "logfile_read", not # "logfile". If we used logfile, that would double-log the input - # commands, since we used echo=True. (Using logfile=StringIO and + # commands, since we used echo=True. (Using logfile=BytesIO and # echo=False works for most inputs, but doesn't work for things like # tab completion output.) child.logfile_read = output @@ -778,20 +875,20 @@ def IPythonCtx(prog="ipython", raise ExpectError(e, child) #, None, sys.exc_info()[2] finally: # Clean up. - if child is not None and child.isalive(): - child.kill(signal.SIGKILL) + if child is not None: + child.close(force=True) for d in cleanup_dirs: rmtree(d) -def _interact_ipython(child, input, exitstr="exit()\n", +def _interact_ipython(child, input, exitstr=b"exit()\n", sendeof=False, waiteof=True): is_prompt_toolkit = child.ipython_frontend == "prompt_toolkit" # Canonicalize input lines. - input = dedent(input) - input = re.sub("^\n+", "", input) - input = re.sub("\n+$", "", input) - input += "\n" + input = dedent(input.decode('utf-8')).encode('utf-8') + input = re.sub(br"^\n+", b"", input) + input = re.sub(br"\n+$", b"", input) + input += b"\n" input += exitstr lines = input.splitlines(False) prev_prompt_in_idx = None @@ -822,25 +919,25 @@ def _interact_ipython(child, input, exitstr="exit()\n", # is there a better way? _wait_for_output(child, timeout=0.05) break - if line.startswith(" ") and is_prompt_toolkit: - # Clear the line via ^U. This is needed with prompt_toolkit - # (IPython 5+) because it is no longer possible to turn off - # autoindent. (IPython now relies on bracketed paste mode; they - # assumed that was the only reason to turn off autoindent. - # Another idea is to use bracket paste mode, - # i.e. ESC[200~blahESC[201~. But that causes IPython to not print - # a "...:" prompt that it would be nice to see.) We also sleep - # afterwards to make sure that IPython doesn't optimize the + if line.startswith(b" ") and is_prompt_toolkit and _IPYTHON_VERSION < (7,): + # Clear the line via ^U. This is needed with prompt_toolkit + # (IPython 5 and 6) because it is no longer possible to turn off + # autoindent. (IPython now relies on bracketed paste mode; they + # assumed that was the only reason to turn off autoindent. Another + # idea is to use bracket paste mode, i.e. ESC[200~blahESC[201~. + # But that causes IPython to not print a "...:" prompt that it + # would be nice to see. This was fixed in IPython 7.0) We also + # sleep afterwards to make sure that IPython doesn't optimize the # output. # For example, without the sleep, if IPython defaulted 4 spaces and # we wanted 1 space, we would send "^U blah"; after IPython printed # out the 4 spaces it would backspace 3 of them and ultimately do # " ESC[3D ESC[3Dblah". That's hard for us to match. It's # better if it sends out " ESC[4D ESC[4D blah". - child.send("\x15") # ^U + child.send(b"\x15") # ^U time.sleep(0.02) while line: - left, tab, right = line.partition("\t") + left, tab, right = line.partition(b"\t") # if DEBUG: # print("_interact_ipython(): line=%r, left=%r, tab=%r, right=%r" % (line, left, tab, right)) # Send the input (up to tab or newline). @@ -861,7 +958,7 @@ def _interact_ipython(child, input, exitstr="exit()\n", # earlier), we can use the nonce trick. _wait_nonce(child) line = right - child.send("\n") + child.send(b"\n") # We're finished sending input commands. Wait for process to complete. if sendeof: child.sendeof() @@ -894,13 +991,8 @@ def ipython(template, **kwargs): the template. Assert that the result matches. """ __tracebackhide__ = True - parent_frame = inspect.currentframe().f_back - parent_globals = parent_frame.f_globals - parent_locals = parent_frame.f_locals - parent_vars = dict(parent_globals, **parent_locals) template = dedent(template).strip() - template = template.format(**parent_vars) - input, expected = parse_template(template) + input, expected = parse_template(template, clear_tab_completions=_IPYTHON_VERSION>=(7,)) args = kwargs.pop("args", ()) if isinstance(args, six.string_types): args = [args] @@ -916,7 +1008,7 @@ def ipython(template, **kwargs): # IPython console 3.2+ kills the kernel upon exit, unless you # explicitly ask to keep it open. If we're connecting to an # existing kernel, default to keeping it alive upon exit. - kwargs.setdefault("exitstr", "exit(keep_kernel=True)\n") + kwargs.setdefault("exitstr", b"exit(keep_kernel=True)\n") kwargs.setdefault("sendeof", False) elif _IPYTHON_VERSION >= (3,): # IPython console 3.0, 3.1 always kill the kernel upon exit. @@ -926,25 +1018,25 @@ def ipython(template, **kwargs): # https://github.com/ipython/ipython/pull/8483 # Instead of cleanly telling the client to exit, we'll just kill # it with SIGKILL (in the 'finally' clause of IPythonCtx). - kwargs.setdefault("exitstr", "") + kwargs.setdefault("exitstr", b"") kwargs.setdefault("sendeof", False) kwargs.setdefault("waiteof", False) else: kwargs.setdefault("sendeof", True) - kwargs.setdefault("exitstr", "" if kwargs['sendeof'] else "exit()") + kwargs.setdefault("exitstr", b"" if kwargs['sendeof'] else b"exit()") else: if _IPYTHON_VERSION >= (5,): kwargs.setdefault("sendeof", False) else: kwargs.setdefault("sendeof", True) - kwargs.setdefault("exitstr", "" if kwargs['sendeof'] else "exit()") + kwargs.setdefault("exitstr", b"" if kwargs['sendeof'] else b"exit()") kwargs.setdefault("ignore_prompt_number", True) - exitstr = kwargs.pop("exitstr" , "exit()\n") + exitstr = kwargs.pop("exitstr" , b"exit()\n") sendeof = kwargs.pop("sendeof" , False) waiteof = kwargs.pop("waiteof" , True) ignore_prompt_number = kwargs.pop("ignore_prompt_number", False) if kernel is not None: - args += kernel.kernel_info + args += [i.decode('utf-8') for i in kernel.kernel_info] kwargs.setdefault("ipython_dir", kernel.ipython_dir) print("Input:") print("".join(" %s\n"%line for line in input.splitlines())) @@ -965,7 +1057,7 @@ def IPythonKernelCtx(**kwargs): with IPythonCtx(args='kernel', **kwargs) as child: # Get the kernel info: --existing kernel-1234.json child.expect(r"To connect another client to this kernel, use:\s*" - "(?:\[IPKernelApp\])?\s*(--existing .*?json)") + r"(?:\[IPKernelApp\])?\s*(--existing .*?json)") kernel_info = child.match.group(1).split() # Yield control to caller. child.kernel_info = kernel_info @@ -1024,7 +1116,7 @@ def _format_patched(self, record): if _IPYTHON_VERSION >= (5,): # Get the base URL from the notebook app. child.expect(r"\s*(http://[0-9.:]+)/[?]token=([0-9a-f]+)\n") - baseurl = child.match.group(1) + baseurl = child.match.group(1).decode('utf-8') token = child.match.group(2) params = dict(token=token) response = requests.post(baseurl + "/api/contents", @@ -1048,7 +1140,7 @@ def _format_patched(self, record): elif _IPYTHON_VERSION >= (2,): # Get the base URL from the notebook app. child.expect(r"The (?:IPython|Jupyter) Notebook is running at: (http://[A-Za-z0-9:.]+)[/\r\n]") - baseurl = child.match.group(1) + baseurl = child.match.group(1).decode('utf-8') # Login. response = requests.post( baseurl + "/login", @@ -1080,7 +1172,7 @@ def _format_patched(self, record): elif _IPYTHON_VERSION >= (0, 12): # Get the base URL from the notebook app. child.expect(r"The (?:IPython|Jupyter) Notebook is running at: (http://[A-Za-z0-9:.]+)[/\r\n]") - baseurl = child.match.group(1) + baseurl = child.match.group(1).decode('utf-8') # Login. response = requests.post( baseurl + "/login", @@ -1094,7 +1186,7 @@ def _format_patched(self, record): assert response.status_code == 200 # Get the notebook_id for the new notebook. text = response.text - m = re.search("data-notebook-id\s*=\s*([0-9a-f-]+)", text) + m = re.search(r"data-notebook-id\s*=\s*([0-9a-f-]+)", text) assert m is not None notebook_id = m.group(1) # Start a kernel for the notebook. @@ -1107,7 +1199,7 @@ def _format_patched(self, record): raise NotImplementedError( "Not implemented for IPython %s" % (IPython.__version__)) # Construct the kernel info line: --existing kernel-123-abcd-...456.json - kernel_info = ['--existing', "kernel-%s.json" % kernel_id] + kernel_info = [b'--existing', b"kernel-%s.json" % kernel_id.encode('utf-8')] # Yield control to caller. child.kernel_info = kernel_info yield child @@ -1219,10 +1311,10 @@ def _wait_nonce(child): def _clean_backspace(arg): # Handle foo123\b\b\bbar => foobar - left = "" + left = b"" right = arg while right: - m = re.search("\x08+", right) + m = re.search(b"\x08+", right) if not m: break left = left + right[:m.start()] @@ -1231,20 +1323,20 @@ def _clean_backspace(arg): right = right[m.end():] arg = left + right # Handle foo123\x1b[3Dbar => foobar - left = "" + left = b"" right = arg while right: - m = re.search(r"\x1b\[([0-9]+)D", right) + m = re.search(br"\x1b\[([0-9]+)D", right) if not m: break left = left + right[:m.start()] count = int(m.group(1)) right = right[m.end():] - if right.startswith("[PYFLYBY]"): + if _IPYTHON_VERSION < (7,) and right.startswith(b"[PYFLYBY]"): # For purposes of comparing IPython output in prompt_toolkit mode, # include the pre-backspace stuff as a separate line. TODO: do # this in a more less hacky way. - left = left + "\n" + left = left + b"\n" else: left = left[:-count] arg = left + right @@ -1256,47 +1348,81 @@ def _clean_ipython_output(result): """Clean up IPython output.""" result0 = result # Canonicalize newlines. - result = re.sub("\r+\n", "\n", result) + result = re.sub(b"\r+\n", b"\n", result) # Clean things like " ESC[4D". result = _clean_backspace(result) # Make traceback output stable across IPython versions and runs. - result = re.sub(re.compile(r"(^/.*?/)?<(ipython-input-[0-9]+-[0-9a-f]+|ipython console)>", re.M), "", result) - result = re.sub(re.compile(r"^----> .*?\n", re.M), "", result) + result = re.sub(re.compile(br"(^/.*?/)?<(ipython-input-[0-9]+-[0-9a-f]+|ipython console)>", re.M), b"", result) + result = re.sub(re.compile(br"^----> .*?\n", re.M), b"", result) # Remove cruft resulting from flakiness in 'ipython console' if _IPYTHON_VERSION < (2,): - result = re.sub(re.compile(r"Exception in thread Thread-[0-9]+ [(]most likely raised during interpreter shutdown[)]:.*", re.S), "", result) + result = re.sub(re.compile(br"Exception in thread Thread-[0-9]+ [(]most likely raised during interpreter shutdown[)]:.*", re.S), b"", result) # Remove trailing post-exit message. if _IPYTHON_VERSION >= (3,): - result = re.sub("(?:Shutting down kernel|keeping kernel alive)\n?$", "", result) + result = re.sub(b"(?:Shutting down kernel|keeping kernel alive)\n?$", b"", result) + # Work around + # https://github.com/prompt-toolkit/python-prompt-toolkit/issues/886 + result = re.sub(br"Exception in default exception handler.*?During handling of the above exception, another exception occurred:.*?assert app\._is_running\nAssertionError\n", b"", result, flags=re.DOTALL) + # CPR stuff from prompt-toolkit 2.0 + result = result.replace(b"WARNING: your terminal doesn't support cursor position requests (CPR).\n", b"") # Remove trailing "In [N]:", if any. - result = re.sub("%s\n?$"%_IPYTHON_PROMPT1, "", result) + result = re.sub(br"%s\n?$"%_IPYTHON_PROMPT1, b"", result) # Remove trailing "In [N]: exit()". - result = re.sub("%sexit[(](?:keep_kernel=True)?[)]\n?$"%_IPYTHON_PROMPT1, "", result) + result = re.sub(br"%sexit[(](?:keep_kernel=True)?[)]\n?$"%_IPYTHON_PROMPT1, b"", result) # Compress newlines. - result = re.sub("\n\n+", "\n", result) + result = re.sub(br"\n\n+", b"\n", result) # Remove xterm title setting. - result = re.sub("\x1b]0;[^\x1b\x07]*\x07", "", result) + result = re.sub(b"\x1b]0;[^\x1b\x07]*\x07", b"", result) # Remove BELs (done after the above codes, which use \x07 as a delimiter) - result = result.replace('\x07', '') + result = result.replace(b"\x07", b"") # Remove code to clear to end of line. This is done here instead of in # decode() because _wait_nonce looks for this code. - result = result.replace("\x1b[K", "") + result = result.replace(b"\x1b[K", b"") result = result.lstrip() if _IPYTHON_VERSION >= (5,): # In IPython 5 kernel/console/etc, it seems to be impossible to turn # off the banner. For now just delete the output up to the first # prompt. - result = re.sub(".*?(In \[1\])", "\\1", result, flags=re.S) + result = re.sub(br".*?(In \[1\])", br"\1", result, flags=re.S) if DEBUG: print("_clean_ipython_output(): %r => %r" % (result0, result,)) return result +def _normalize_python_2_3(template): + """ + Change some Python 3 outputs to be compatible with Python 2 + """ + template0 = template + if PY3: + # We have to leave NameError: global name ... as in Python 2, because + # in Python 3, "global" is never printed, but it is only printed for + # some messages in Python 2. + template = template.replace(b"NameError: global name", b"NameError: name") + # The templates are written with outputs from Python 3 + return template + + template = template.replace(' ', ' ()') + for module in ['os', 'base64', 'profile']: + template = template.replace("module '{module}' has no attribute".format(module=module), "'module' object has no attribute") + template = template.replace('sturbridge9088333 has no attribute', "'module' object has no attribute") + template = re.sub(r"([ \(\n])b'(.*?)'", r"\1'\2'", template) + template = template.replace("ZeroDivisionError: division by zero", "ZeroDivisionError: integer division or modulo by zero") + template = re.sub(r"\.\.\. per loop \(mean ± std. dev\. of (.*) run, (.*) loops each\)", + r"\2 loops, best of \1: ... per loop" , template) + template = re.sub(r"ModuleNotFoundError: No module named '(.*?)'", + r"ImportError: No module named \1", template) + template = re.sub(r"\*\*\* NameError: name '(.*)' is not defined", + r"""*** NameError: NameError("name '\1' is not defined",)""", template) + + if DEBUG: + print("_normalize_python_2_3() %r => %r" % (template0, template)) + return template @retry def test_ipython_1(frontend): # Test that we can run ipython and get results back. ipython(""" - In [1]: print 6*7 + In [1]: print(6*7) 42 In [2]: 6*9 Out[2]: 54 @@ -1307,7 +1433,7 @@ def test_ipython_1(frontend): def test_ipython_assert_fail_1(frontend): with assert_fail(): ipython(""" - In [1]: print 6*7 + In [1]: print(6*7) 42 In [2]: 6*9 Out[2]: 53 @@ -1317,11 +1443,11 @@ def test_ipython_assert_fail_1(frontend): @retry def test_ipython_indented_block_4spaces_1(frontend): # Test that indented blocks work vs IPython's autoindent. - # 4 spaces is the IPython default auotindent. + # 4 spaces is the IPython default autoindent. ipython(""" In [1]: if 1: - ...: print 6*7 - ...: print 6*9 + ...: print(6*7) + ...: print(6*9) ...: 42 54 @@ -1330,19 +1456,20 @@ def test_ipython_indented_block_4spaces_1(frontend): """, frontend=frontend) + @retry def test_ipython_indented_block_5spaces_1(frontend): # Test that indented blocks work vs IPython's autoindent. ipython(""" In [1]: if 1: - ...: print 6*7 - ...: print 6*9 + ...: print(6*7) + ...: print(6*9) ...: 42 54 In [2]: 6*8 Out[2]: 48 - """, frontend=frontend) + """, frontend=frontend) @retry @@ -1350,8 +1477,8 @@ def test_ipython_indented_block_6spaces_1(frontend): # Test that indented blocks work vs IPython's autoindent. ipython(""" In [1]: if 1: - ...: print 6*7 - ...: print 6*9 + ...: print(6*7) + ...: print(6*9) ...: 42 54 @@ -1366,8 +1493,8 @@ def test_ipython_indented_block_3spaces_1(frontend): # Using ^U plus 3 spaces causes IPython to output " \x08". ipython(""" In [1]: if 1: - ...: print 6*7 - ...: print 6*9 + ...: print(6*7) + ...: print(6*9) ...: 42 54 @@ -1382,8 +1509,8 @@ def test_ipython_indented_block_2spaces_1(frontend): # Using ^U plus 2 spaces causes IPython 5 to output " \x1b[2D \x1b[2D". ipython(""" In [1]: if 1: - ...: print 6*7 - ...: print 6*9 + ...: print(6*7) + ...: print(6*9) ...: 42 54 @@ -1410,8 +1537,8 @@ def test_ipython_tab_fail_1(frontend): In [2]: os.foo27817796\t() --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) - in () - AttributeError: 'module' object has no attribute 'foo27817796' + in + AttributeError: module 'os' has no attribute 'foo27817796' """, frontend=frontend) @@ -1438,9 +1565,9 @@ def test_pyflyby_file_1(): f = os.path.realpath(pyflyby.__file__.replace(".pyc", ".py")) ipython(""" In [1]: import os, pyflyby - In [2]: print os.path.realpath(pyflyby.__file__.replace(".pyc", ".py")) + In [2]: print(os.path.realpath(pyflyby.__file__.replace(".pyc", ".py"))) {f} - """) + """.format(f=f)) @retry @@ -1448,9 +1575,9 @@ def test_pyflyby_version_1(): # Verify that our test setup is getting the right pyflyby. ipython(""" In [1]: import pyflyby - In [2]: print pyflyby.__version__ + In [2]: print(pyflyby.__version__) {pyflyby.__version__} - """) + """.format(pyflyby=pyflyby)) @retry @@ -1459,9 +1586,9 @@ def test_ipython_file_1(): f = os.path.realpath(IPython.__file__) ipython(""" In [1]: import IPython, os - In [2]: print os.path.realpath(IPython.__file__) + In [2]: print(os.path.realpath(IPython.__file__)) {f} - """) + """.format(f=f)) @retry @@ -1469,18 +1596,18 @@ def test_ipython_version_1(): # Verify that our test setup is getting the right IPython. ipython(""" In [1]: import IPython - In [2]: print IPython.__version__ + In [2]: print(IPython.__version__) {IPython.__version__} - """) + """.format(IPython=IPython)) @retry def test_autoimport_1(): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: '@'+b64decode('SGVsbG8=')+'@' + In [2]: b'@'+b64decode('SGVsbG8=')+b'@' [PYFLYBY] from base64 import b64decode - Out[2]: '@Hello@' + Out[2]: b'@Hello@' """) @@ -1490,10 +1617,10 @@ def test_no_autoimport_1(): # really a test that our testing infrastructure is OK and not accidentally # picking up pyflyby configuration installed in a system or user config. ipython(""" - In [1]: '@'+b64decode('SGVsbG8=')+'@' + In [1]: b'@'+b64decode('SGVsbG8=')+b'@' --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined """) @@ -1523,19 +1650,19 @@ def test_unload_ext_1(): # Test that %unload_ext works. # Autoimporting should stop working, but previously imported thi ipython(""" - In [1]: b64encode('tortoise') + In [1]: b64encode(b'tortoise') .... NameError: name 'b64encode' is not defined In [2]: %load_ext pyflyby - In [3]: b64encode('tortoise') + In [3]: b64encode(b'tortoise') [PYFLYBY] from base64 import b64encode - Out[3]: 'dG9ydG9pc2U=' + Out[3]: b'dG9ydG9pc2U=' In [4]: %unload_ext pyflyby - In [5]: b64decode('aGFyZQ==') + In [5]: b64decode(b'aGFyZQ==') .... NameError: name 'b64decode' is not defined - In [6]: b64encode('turtle') - Out[6]: 'dHVydGxl' + In [6]: b64encode(b'turtle') + Out[6]: b'dHVydGxl' """) @@ -1544,17 +1671,17 @@ def test_unload_ext_1(): def test_reload_ext_1(): # Test that autoimporting still works after %reload_ext. ipython(""" - In [1]: b64encode('east') + In [1]: b64encode(b'east') .... NameError: name 'b64encode' is not defined In [2]: %load_ext pyflyby - In [3]: b64encode('east') + In [3]: b64encode(b'east') [PYFLYBY] from base64 import b64encode - Out[3]: 'ZWFzdA==' + Out[3]: b'ZWFzdA==' In [4]: %reload_ext pyflyby - In [5]: b64decode('d2VzdA==') + In [5]: b64decode(b'd2VzdA==') [PYFLYBY] from base64 import b64decode - Out[5]: 'west' + Out[5]: b'west' """) @@ -1571,7 +1698,7 @@ def test_reload_ext_reload_importdb_1(tmp): In [3]: list(combinations('abc',2)) --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'combinations' is not defined In [4]: with open('{tmp.file}', 'a') as f: ...: f.write('from itertools import combinations\\n') @@ -1579,13 +1706,13 @@ def test_reload_ext_reload_importdb_1(tmp): In [5]: list(combinations('abc',2)) --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'combinations' is not defined In [6]: %reload_ext pyflyby In [7]: list(combinations('abc',2)) [PYFLYBY] from itertools import combinations Out[7]: [('a', 'b'), ('a', 'c'), ('b', 'c')] - """, PYFLYBY_PATH=tmp.file) + """.format(tmp=tmp), PYFLYBY_PATH=tmp.file) @skipif_ipython_too_old_for_load_ext @@ -1607,26 +1734,26 @@ def test_reload_ext_retry_failed_imports_1(tmp): In [2]: rhino13609135 [PYFLYBY] from hippo84402009 import rhino13609135 hello from hippo84402009: attempt 1 - [PYFLYBY] Error attempting to 'from hippo84402009 import rhino13609135': ZeroDivisionError: integer division or modulo by zero + [PYFLYBY] Error attempting to 'from hippo84402009 import rhino13609135': ZeroDivisionError: division by zero .... --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'rhino13609135' is not defined In [3]: rhino13609135 --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'rhino13609135' is not defined In [4]: %reload_ext pyflyby In [5]: rhino13609135 [PYFLYBY] from hippo84402009 import rhino13609135 hello from hippo84402009: attempt 2 - [PYFLYBY] Error attempting to 'from hippo84402009 import rhino13609135': ZeroDivisionError: integer division or modulo by zero + [PYFLYBY] Error attempting to 'from hippo84402009 import rhino13609135': ZeroDivisionError: division by zero .... --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'rhino13609135' is not defined """, PYTHONPATH=tmp.dir, PYFLYBY_PATH=tmp.file) @@ -1645,7 +1772,7 @@ def test_autoimport_symbol_1(): def test_autoimport_statement_1(): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print b64decode('SGVsbG8=') + In [2]: if 1: print(b64decode(b'SGVsbG8=').decode('utf-8')) [PYFLYBY] from base64 import b64decode Hello """) @@ -1655,10 +1782,10 @@ def test_autoimport_statement_1(): def test_autoimport_multiple_imports_1(): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print b64encode("koala"), b64decode("a2FuZ2Fyb28=") + In [2]: print((b64encode(b"koala"), b64decode(b"a2FuZ2Fyb28="))) [PYFLYBY] from base64 import b64decode [PYFLYBY] from base64 import b64encode - a29hbGE= kangaroo + (b'a29hbGE=', b'kangaroo') """) @@ -1667,11 +1794,11 @@ def test_autoimport_multiline_statement_1(): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: if 1: - ...: print b64decode('dHVydGxl') + ...: print(b64decode(b'dHVydGxl').decode('utf-8')) ...: [PYFLYBY] from base64 import b64decode turtle - In [3]: print b64decode('bGFtYQ==') + In [3]: if 1: print(b64decode(b'bGFtYQ==').decode('utf-8')) lama """) @@ -1691,7 +1818,7 @@ def test_autoimport_multiline_continued_statement_1(frontend): [PYFLYBY] from base64 import b64decode [PYFLYBY] import sys microphone - In [3]: print b64decode('bG91ZHNwZWFrZXI=') + In [3]: if 1: sys.stdout.write(b64decode('bG91ZHNwZWFrZXI=')) loudspeaker """, frontend=frontend) @@ -1702,7 +1829,7 @@ def test_autoimport_multiline_continued_statement_fake_1(frontend): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: if 1: - ...: print (unknown_symbol_37320899. + ...: print(unknown_symbol_37320899. ...: b64encode ...: ) ...: @@ -1711,11 +1838,11 @@ def test_autoimport_multiline_continued_statement_fake_1(frontend): .... NameError: name 'unknown_symbol_37320899' is not defined In [3]: if 1: - ...: print b64encode('y') + ...: print(b64encode(b'y').decode('utf-8')) ...: [PYFLYBY] from base64 import b64encode eQ== - In [4]: print b64decode('YmFzZWJhbGw=') + In [4]: if 1: print(b64decode('YmFzZWJhbGw=').decode('utf-8')) [PYFLYBY] from base64 import b64decode baseball """, frontend=frontend) @@ -1732,7 +1859,7 @@ def test_autoimport_pyflyby_path_1(tmp): In [3]: groupby --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'groupby' is not defined """, PYFLYBY_PATH=tmp.file) @@ -1740,26 +1867,48 @@ def test_autoimport_pyflyby_path_1(tmp): @retry def test_autoimport_autocall_arg_1(): # Verify that we can autoimport the argument of an autocall. - ipython(""" - In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: str.upper b64decode('a2V5Ym9hcmQ=') - ------> str.upper(b64decode('a2V5Ym9hcmQ=')) - [PYFLYBY] from base64 import b64decode - Out[2]: 'KEYBOARD' - """, autocall=True) - + if PY2: + ipython(""" + In [1]: import pyflyby; pyflyby.enable_auto_importer() + In [2]: str.upper b64decode('a2V5Ym9hcmQ=') + ------> str.upper(b64decode('a2V5Ym9hcmQ=')) + [PYFLYBY] from base64 import b64decode + Out[2]: 'KEYBOARD' + """, autocall=True) + else: + # The autocall arrows are printed twice in newer versions of IPython + # (https://github.com/ipython/ipython/issues/11714). + ipython(""" + In [1]: import pyflyby; pyflyby.enable_auto_importer() + In [2]: bytes.upper b64decode('a2V5Ym9hcmQ=') + ------> bytes.upper(b64decode('a2V5Ym9hcmQ=')) + ------> bytes.upper(b64decode('a2V5Ym9hcmQ=')) + [PYFLYBY] from base64 import b64decode + Out[2]: b'KEYBOARD' + """, autocall=True) @retry def test_autoimport_autocall_function_1(): # Verify that we can autoimport the function to autocall. - ipython(""" - In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: b64decode 'bW91c2U=' - [PYFLYBY] from base64 import b64decode - ------> b64decode('bW91c2U=') - Out[2]: 'mouse' - """, autocall=True) - + if PY2: + ipython(""" + In [1]: import pyflyby; pyflyby.enable_auto_importer() + In [2]: b64decode 'bW91c2U=' + [PYFLYBY] from base64 import b64decode + ------> b64decode('bW91c2U=') + Out[2]: 'mouse' + """, autocall=True) + else: + # The autocall arrows are printed twice in newer versions of IPython + # (https://github.com/ipython/ipython/issues/11714). + ipython(""" + In [1]: import pyflyby; pyflyby.enable_auto_importer() + In [2]: b64decode 'bW91c2U=' + [PYFLYBY] from base64 import b64decode + ------> b64decode('bW91c2U=') + ------> b64decode('bW91c2U=') + Out[2]: b'mouse' + """, autocall=True) @retry def test_autoimport_multiple_candidates_ast_transformer_1(tmp): @@ -1779,7 +1928,7 @@ def test_autoimport_multiple_candidates_ast_transformer_1(tmp): [PYFLYBY] import foo50853429 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined """, PYFLYBY_PATH=tmp.file) @@ -1799,7 +1948,7 @@ def test_autoimport_multiple_candidates_repeated_1(tmp): [PYFLYBY] import foo70603247 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined In [3]: bar(42) [PYFLYBY] Multiple candidate imports for bar. Please pick one: @@ -1807,7 +1956,7 @@ def test_autoimport_multiple_candidates_repeated_1(tmp): [PYFLYBY] import foo70603247 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined """, PYFLYBY_PATH=tmp.file) @@ -1833,7 +1982,7 @@ def test_autoimport_multiple_candidates_multiple_in_expression_1(tmp): [PYFLYBY] import foo85957810 as foo --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'foo' is not defined """, PYFLYBY_PATH=tmp.file) @@ -1854,7 +2003,7 @@ def test_autoimport_multiple_candidates_repeated_in_expression_1(tmp): [PYFLYBY] import foo83958492 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined """, PYFLYBY_PATH=tmp.file) @@ -1892,7 +2041,7 @@ def test_autoimport_multiple_candidates_multi_joinpoint_1(tmp): [PYFLYBY] import foo85223658 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined """, PYFLYBY_PATH=tmp.file, autocall=True) @@ -1912,7 +2061,7 @@ def test_autoimport_multiple_candidates_multi_joinpoint_repeated_1(tmp): [PYFLYBY] import foo85223658 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined In [3]: bar [PYFLYBY] Multiple candidate imports for bar. Please pick one: @@ -1920,7 +2069,7 @@ def test_autoimport_multiple_candidates_multi_joinpoint_repeated_1(tmp): [PYFLYBY] import foo85223658 as bar --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar' is not defined """, PYFLYBY_PATH=tmp.file, autocall=True) @@ -1932,7 +2081,7 @@ def test_complete_symbol_basic_1(): In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: b64deco\tde('eHl6enk=') [PYFLYBY] from base64 import b64decode - Out[2]: 'xyzzy' + Out[2]: b'xyzzy' """) @@ -1941,9 +2090,9 @@ def test_complete_symbol_multiple_1(frontend): if frontend == "prompt_toolkit": pytest.skip() ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print b64\t + In [2]: print(b64\t b64decode b64encode - In [2]: print b64\x06decode + In [2]: print(b64\x06decode) [PYFLYBY] from base64 import b64decode """, frontend=frontend) @@ -1954,9 +2103,9 @@ def test_complete_symbol_partial_multiple_1(frontend): if frontend == "prompt_toolkit": pytest.skip() ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print b6\t + In [2]: print(b6\t b64decode b64encode - In [2]: print b64\x06d\tecode + In [2]: print(b64\x06d\tecode) [PYFLYBY] from base64 import b64decode """, frontend=frontend) @@ -1975,7 +2124,7 @@ def test_complete_symbol_import_check_1(): Out[3]: False In [4]: b64deco\tde('UnViaWNvbg==') [PYFLYBY] from base64 import b64decode - Out[4]: 'Rubicon' + Out[4]: b'Rubicon' In [5]: 'base64' in globals() Out[5]: False In [6]: 'b64decode' in globals() @@ -2010,7 +2159,7 @@ def test_complete_symbol_member_1(frontend): In [2]: base64.b64d\t [PYFLYBY] import base64 In [2]: base64.b64decode('bW9udHk=') - Out[2]: 'monty' + Out[2]: b'monty' """, frontend=frontend) @@ -2019,15 +2168,15 @@ def test_complete_symbol_member_multiple_1(frontend): if frontend == "prompt_toolkit": pytest.skip() ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print base64.b64\t + In [2]: print(base64.b64\t [PYFLYBY] import base64 - In [2]: print base64.b64 + In [2]: print(base64.b64 base64.b64decode base64.b64encode - In [2]: print base64.b64 + In [2]: print(base64.b64) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) - in () - AttributeError: 'module' object has no attribute 'b64' + in + AttributeError: module 'base64' has no attribute 'b64' """, frontend=frontend) @@ -2036,15 +2185,15 @@ def test_complete_symbol_member_partial_multiple_1(frontend): if frontend == "prompt_toolkit": pytest.skip() ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print base64.b6\t + In [2]: print(base64.b6\t [PYFLYBY] import base64 - In [2]: print base64.b6 + In [2]: print(base64.b6 base64.b64decode base64.b64encode - In [2]: print base64.b64 + In [2]: print(base64.b64) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) - in () - AttributeError: 'module' object has no attribute 'b64' + in + AttributeError: module 'base64' has no attribute 'b64' """, frontend=frontend) @@ -2056,7 +2205,7 @@ def test_complete_symbol_import_module_as_1(frontend, tmp): In [2]: b64.b64d\t [PYFLYBY] import base64 as b64 In [2]: b64.b64decode('cm9zZWJ1ZA==') - Out[2]: 'rosebud' + Out[2]: b'rosebud' """, PYFLYBY_PATH=tmp.file, frontend=frontend) @@ -2068,8 +2217,8 @@ def test_complete_symbol_statement_1(): In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: x = b64deco\tde('SHVudGVy') [PYFLYBY] from base64 import b64decode - In [3]: print x - Hunter + In [3]: x + Out[3]: b'Hunter' """) @@ -2078,13 +2227,13 @@ def test_complete_symbol_multiline_statement_1(): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: if 1: - ...: print b64deco\tde('emVicmE=') - ...: print 42 + ...: print(b64deco\tde('emVicmE=').decode('utf-8')) + ...: print(42) ...: [PYFLYBY] from base64 import b64decode zebra 42 - In [3]: print b64decode('dGlnZXI=') + In [3]: if 1: print(b64decode('dGlnZXI=').decode('utf-8')) tiger """) @@ -2095,14 +2244,14 @@ def test_complete_symbol_multiline_statement_member_1(frontend): ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: if 1: - ...: print base64.b64d\t + ...: print(base64.b64d\t [PYFLYBY] import base64 - ...: print base64.b64decode('Z2lyYWZmZQ==') - ...: print 42 + ...: print(base64.b64decode('Z2lyYWZmZQ==')) + ...: print(42) ...: giraffe 42 - In [3]: print b64d\tecode('bGlvbg==') + In [3]: print(b64d\tecode('bGlvbg==')) [PYFLYBY] from base64 import b64decode lion """, frontend=frontend) @@ -2111,14 +2260,25 @@ def test_complete_symbol_multiline_statement_member_1(frontend): @retry def test_complete_symbol_autocall_arg_1(): # Verify that tab completion works with autocall. - ipython(""" - In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: str.upper b64deco\tde('Q2hld2JhY2Nh') - ------> str.upper(b64decode('Q2hld2JhY2Nh')) - [PYFLYBY] from base64 import b64decode - Out[2]: 'CHEWBACCA' - """, autocall=True) - + if PY2: + ipython(""" + In [1]: import pyflyby; pyflyby.enable_auto_importer() + In [2]: str.upper b64deco\tde('Q2hld2JhY2Nh') + ------> str.upper(b64decode('Q2hld2JhY2Nh')) + [PYFLYBY] from base64 import b64decode + Out[2]: 'CHEWBACCA' + """, autocall=True) + else: + # The autocall arrows are printed twice in newer versions of IPython + # (https://github.com/ipython/ipython/issues/11714). + ipython(""" + In [1]: import pyflyby; pyflyby.enable_auto_importer() + In [2]: bytes.upper b64deco\tde('Q2hld2JhY2Nh') + ------> bytes.upper(b64decode('Q2hld2JhY2Nh')) + ------> bytes.upper(b64decode('Q2hld2JhY2Nh')) + [PYFLYBY] from base64 import b64decode + Out[2]: b'CHEWBACCA' + """, autocall=True) @retry def test_complete_symbol_any_module_1(frontend, tmp): @@ -2159,11 +2319,11 @@ def test_complete_symbol_bad_1(frontend, tmp): In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: foo_31221052_\tbar [PYFLYBY] import foo_31221052_bar - [PYFLYBY] Error attempting to 'import foo_31221052_bar': ImportError: No module named foo_31221052_bar + [PYFLYBY] Error attempting to 'import foo_31221052_bar': ModuleNotFoundError: No module named 'foo_31221052_bar' .... --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'foo_31221052_bar' is not defined """, PYFLYBY_PATH=tmp.file, frontend=frontend) @@ -2175,11 +2335,11 @@ def test_complete_symbol_bad_as_1(frontend, tmp): In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: bar_98073069_\tquux.asdf [PYFLYBY] import foo_86487172 as bar_98073069_quux - [PYFLYBY] Error attempting to 'import foo_86487172 as bar_98073069_quux': ImportError: No module named foo_86487172 + [PYFLYBY] Error attempting to 'import foo_86487172 as bar_98073069_quux': ModuleNotFoundError: No module named 'foo_86487172' .... --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'bar_98073069_quux' is not defined """, PYFLYBY_PATH=tmp.file, frontend=frontend) @@ -2200,24 +2360,24 @@ def test_complete_symbol_nonmodule_1(frontend, tmp): class M(object): @property def river(self): - print "in the river" + print("in the river") return 'Medway' @property def island(self): - print "on the island" + print("on the island") return 'Canvey' __name__ = __name__ sys.modules[__name__] = M() """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print gravesend60063\t393.r\t + In [2]: print(gravesend60063\t393.r\t [PYFLYBY] import gravesend60063393 - In [2]: print gravesend60063393.river + In [2]: print(gravesend60063393.river) in the river in the river Medway - In [3]: print gravesend600633\t93.is\tland + In [3]: print(gravesend600633\t93.is\tland) on the island on the island Canvey @@ -2287,7 +2447,7 @@ def test_complete_symbol_error_in_getattr_1(frontend): --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) .... - ZeroDivisionError: integer division or modulo by zero + ZeroDivisionError: division by zero In [5]: sys.settra\t [PYFLYBY] import sys In [5]: sys.settrace @@ -2304,7 +2464,7 @@ def test_property_no_superfluous_access_1(tmp): class A(object): @property def ellsworth(self): - print "edgegrove" + print("edgegrove") return "darlington" """) ipython(""" @@ -2322,21 +2482,21 @@ def test_disable_reenable_autoimport_1(): ipython(""" In [1]: import pyflyby In [2]: pyflyby.enable_auto_importer() - In [3]: b64encode('blue') + In [3]: b64encode(b'blue') [PYFLYBY] from base64 import b64encode - Out[3]: 'Ymx1ZQ==' + Out[3]: b'Ymx1ZQ==' In [4]: pyflyby.disable_auto_importer() In [5]: b64decode('cmVk') # expect NameError since no auto importer --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined - In [6]: b64encode('green') # should still work because already imported - Out[6]: 'Z3JlZW4=' + In [6]: b64encode(b'green') # should still work because already imported + Out[6]: b'Z3JlZW4=' In [7]: pyflyby.enable_auto_importer() In [8]: b64decode('eWVsbG93') # should work now [PYFLYBY] from base64 import b64decode - Out[8]: 'yellow' + Out[8]: b'yellow' """) @@ -2345,21 +2505,21 @@ def test_disable_reenable_completion_1(): ipython(""" In [1]: import pyflyby In [2]: pyflyby.enable_auto_importer() - In [3]: b64enco\tde('flower') + In [3]: b64enco\tde(b'flower') [PYFLYBY] from base64 import b64encode - Out[3]: 'Zmxvd2Vy' + Out[3]: b'Zmxvd2Vy' In [4]: pyflyby.disable_auto_importer() In [5]: b64deco\t('Y2xvdWQ=') # expect NameError since no auto importer --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64deco' is not defined - In [6]: b64enco\tde('tree') # should still work because already imported - Out[6]: 'dHJlZQ==' + In [6]: b64enco\tde(b'tree') # should still work because already imported + Out[6]: b'dHJlZQ==' In [7]: pyflyby.enable_auto_importer() In [8]: b64deco\tde('Y2xvdWQ=') # should work now [PYFLYBY] from base64 import b64decode - Out[8]: 'cloud' + Out[8]: b'cloud' """) @@ -2394,12 +2554,12 @@ def test_error_during_auto_import_symbol_1(tmp): [PYFLYBY] Disabling pyflyby auto importer. --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'unknown_symbol_68470042' is not defined In [5]: unknown_symbol_76663387 --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'unknown_symbol_76663387' is not defined """, PYFLYBY_PATH=tmp.file) @@ -2418,12 +2578,12 @@ def test_error_during_auto_import_expression_1(tmp): [PYFLYBY] Disabling pyflyby auto importer. --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'unknown_symbol_72161870' is not defined In [5]: 42+unknown_symbol_48517397 --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'unknown_symbol_48517397' is not defined """, PYFLYBY_PATH=tmp.file) @@ -2444,14 +2604,14 @@ def test_error_during_completion_1(frontend, tmp): In [4]: unknown_symbol_14954304_\x06foo --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'unknown_symbol_14954304_foo' is not defined In [5]: 200 Out[5]: 200 In [6]: unknown_symbol_69697066_\t\x06foo --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'unknown_symbol_69697066_foo' is not defined In [7]: 300 Out[7]: 300 @@ -2469,7 +2629,7 @@ def test_syntax_error_in_user_code_1(): SyntaxError: invalid syntax In [3]: b64decode("bWlkbmlnaHQ=") [PYFLYBY] from base64 import b64decode - Out[3]: 'midnight' + Out[3]: b'midnight' """) @@ -2477,8 +2637,8 @@ def test_syntax_error_in_user_code_1(): def test_run_1(tmp): # Test that %run works and autoimports. writetext(tmp.file, """ - print 'hello' - print b64decode('RXVjbGlk') + print('hello') + print(b64decode('RXVjbGlk').decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -2486,7 +2646,7 @@ def test_run_1(tmp): [PYFLYBY] from base64 import b64decode hello Euclid - """) + """.format(tmp=tmp)) @retry @@ -2494,7 +2654,7 @@ def test_run_repeat_1(tmp): # Test that repeated %run works, and continues autoimporting, since we # start from a fresh namespace each time (since no "-i" option to %run). writetext(tmp.file, """ - print b64decode('Q2FudG9y') + print(b64decode('Q2FudG9y').decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -2504,24 +2664,24 @@ def test_run_repeat_1(tmp): In [3]: run {tmp.file} [PYFLYBY] from base64 import b64decode Cantor - """) + """.format(tmp=tmp)) @retry def test_run_separate_script_namespace_1(tmp): # Another explicit test that we start %run from a fresh namespace writetext(tmp.file, """ - print b64decode('UmllbWFubg==') + print(b64decode('UmllbWFubg==').decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print b64decode('Rmlib25hY2Np') + In [2]: b64decode('Rmlib25hY2Np') [PYFLYBY] from base64 import b64decode - Fibonacci + Out[2]: b'Fibonacci' In [3]: run {tmp.file} [PYFLYBY] from base64 import b64decode Riemann - """) + """.format(tmp=tmp)) @retry @@ -2529,7 +2689,7 @@ def test_run_separate_script_namespace_2(tmp): # Another explicit test that we start %run from a fresh namespace, not # inheriting even explicitly defined functions. writetext(tmp.file, """ - print b64decode('SGlsYmVydA==') + print(b64decode('SGlsYmVydA==').decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -2541,7 +2701,7 @@ def test_run_separate_script_namespace_2(tmp): In [4]: run {tmp.file} [PYFLYBY] from base64 import b64decode Hilbert - """) + """.format(tmp=tmp)) @retry @@ -2555,50 +2715,50 @@ def test_run_modify_interactive_namespace_1(tmp): In [2]: run {tmp.file} [PYFLYBY] from base64 import b64decode In [3]: x - Out[3]: 'Fermat' + Out[3]: b'Fermat' In [4]: b64decode('TGFwbGFjZQ==') - Out[4]: 'Laplace' - """) + Out[4]: b'Laplace' + """.format(tmp=tmp)) @retry def test_run_i_auto_import_1(tmp): # Verify that '%run -i' works and autoimports. writetext(tmp.file, """ - print b64decode('RGVzY2FydGVz') + print(b64decode('RGVzY2FydGVz').decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: run -i {tmp.file} [PYFLYBY] from base64 import b64decode Descartes - In [3]: print b64decode('R2F1c3M=') - Gauss - """) + In [3]: b64decode('R2F1c3M=') + Out[3]: b'Gauss' + """.format(tmp=tmp)) @retry def test_run_i_already_imported_1(tmp): # Verify that '%run -i' inherits the interactive namespace. writetext(tmp.file, """ - print b64decode(k) + print(b64decode(k).decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() - In [2]: print b64decode('R3JvdGhlbmRpZWNr') + In [2]: b64decode('R3JvdGhlbmRpZWNr') [PYFLYBY] from base64 import b64decode - Grothendieck + Out[2]: b'Grothendieck' In [3]: k = 'QXJjaGltZWRlcw==' In [4]: run -i {tmp.file} Archimedes - """) + """.format(tmp=tmp)) @retry def test_run_i_repeated_1(tmp): # Verify that '%run -i' affects the next namespace of the next '%run -i'. writetext(tmp.file, """ - print b64decode('S29sbW9nb3Jvdg==') + print(b64decode('S29sbW9nb3Jvdg==').decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -2607,14 +2767,14 @@ def test_run_i_repeated_1(tmp): Kolmogorov In [3]: run -i {tmp.file} Kolmogorov - """) + """.format(tmp=tmp)) @retry def test_run_i_locally_defined_1(tmp): # Verify that '%run -i' can inherit interactively defined symbols. writetext(tmp.file, """ - print b64decode('zzz') + print(b64decode('zzz')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -2623,7 +2783,7 @@ def test_run_i_locally_defined_1(tmp): ...: In [3]: run -i {tmp.file} Bernoulli - """) + """.format(tmp=tmp)) @retry @@ -2631,8 +2791,8 @@ def test_run_syntax_error_1(tmp): # Verify that a syntax error in a user-run script doesn't affect # autoimporter functionality. writetext(tmp.file, """ - print 'hello' - print b64decode('UHl0aGFnb3Jhcw==') + print('hello') + print(b64decode('UHl0aGFnb3Jhcw==').decode('utf-8')) 1 / """) ipython(""" @@ -2640,61 +2800,61 @@ def test_run_syntax_error_1(tmp): In [2]: run {tmp.file} .... SyntaxError: invalid syntax.... - In [3]: print b64decode('Q29ud2F5') + In [3]: b64decode('Q29ud2F5') [PYFLYBY] from base64 import b64decode - Conway - """) + Out[3]: b'Conway' + """.format(tmp=tmp)) @retry def test_run_name_main_1(tmp): # Verify that __name__ == "__main__" in a %run script. writetext(tmp.file, """ - print b64encode(__name__) + print(b64encode(__name__.encode('utf-8')).decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: run {tmp.file} [PYFLYBY] from base64 import b64encode X19tYWluX18= - """) + """.format(tmp=tmp)) @retry def test_run_name_not_main_1(tmp): # Verify that __name__ == basename(filename) using '%run -n'. f = writetext(tmp.dir/"f81564382.py", """ - print b64encode(__name__) + print(b64encode(__name__.encode('utf-8')).decode('utf-8')) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: run -n {f} [PYFLYBY] from base64 import b64encode ZjgxNTY0Mzgy - """) + """.format(f=f)) @retry def test_timeit_1(): # Verify that %timeit works. - ipython(""" + ipython(u""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: %timeit -n 2 -r 1 b64decode('TWljaGVsYW5nZWxv') [PYFLYBY] from base64 import b64decode - 2 loops, best of 1: ... per loop + ... per loop (mean ± std. dev. of 1 run, 2 loops each) In [3]: %timeit -n 2 -r 1 b64decode('RGF2aWQ=') - 2 loops, best of 1: ... per loop + ... per loop (mean ± std. dev. of 1 run, 2 loops each) """) @retry def test_timeit_complete_1(frontend): # Verify that tab completion works with %timeit. - ipython(""" + ipython(u""" In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: %timeit -n 2 -r 1 b64de\tcode('cGlsbG93') [PYFLYBY] from base64 import b64decode - 2 loops, best of 1: ... per loop + ... per loop (mean ± std. dev. of 1 run, 2 loops each) """, frontend=frontend) @@ -2736,7 +2896,7 @@ def test_noninteractive_timeit_unaffected_1(): [PYFLYBY] import timeit --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in .... NameError: global name 'base64' is not defined """) @@ -2751,7 +2911,7 @@ def test_time_1(frontend): [PYFLYBY] from base64 import b64decode CPU times: ... Wall time: ... - Out[2]: 'telephone' + Out[2]: b'telephone' """, frontend=frontend) @@ -2764,11 +2924,11 @@ def test_time_repeat_1(frontend): [PYFLYBY] from base64 import b64decode CPU times: ... Wall time: ... - Out[2]: 'telegraph' + Out[2]: b'telegraph' In [3]: %time b64decode("ZW1haWw=") CPU times: ... Wall time: ... - Out[3]: 'email' + Out[3]: b'email' """, frontend=frontend) @@ -2781,7 +2941,7 @@ def test_time_complete_1(frontend): [PYFLYBY] from base64 import b64decode CPU times: ... Wall time: ... - Out[2]: 'shirt' + Out[2]: b'shirt' """, frontend=frontend) @@ -2797,7 +2957,7 @@ def test_time_complete_menu_1(frontend): [PYFLYBY] from base64 import b64decode CPU times: ... Wall time: ... - Out[2]: 'pants' + Out[2]: b'pants' """, frontend=frontend) @@ -2813,7 +2973,7 @@ def test_time_complete_autoimport_member_1(frontend): In [2]: time base64.b64\x06dec\tode('amFja2V0') CPU times: ... Wall time: ... - Out[2]: 'jacket' + Out[2]: b'jacket' """, frontend=frontend) @@ -2828,7 +2988,7 @@ def test_prun_1(): .... function calls in ... seconds .... In [3]: b64decode("SGF3a2luZw==") - Out[3]: 'Hawking' + Out[3]: b'Hawking' In [4]: %prun b64decode("TG9yZW50eg==") .... function calls in ... seconds .... @@ -2845,7 +3005,7 @@ def test_noninteractive_profile_unaffected_1(): [PYFLYBY] import profile --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in .... NameError: name 'base64' is not defined """) @@ -2862,12 +3022,12 @@ def test_error_during_enable_1(): [PYFLYBY] TypeError: 'NoneType' object is not callable [PYFLYBY] Set the env var PYFLYBY_LOG_LEVEL=DEBUG to debug. [PYFLYBY] Disabling pyflyby auto importer. - In [4]: print 'hello' + In [4]: print('hello') hello In [5]: sys --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'sys' is not defined In [6]: pyflyby.enable_auto_importer() [PYFLYBY] Not reattempting to enable auto importer after earlier error @@ -2878,6 +3038,10 @@ def test_error_during_enable_1(): _IPYTHON_VERSION < (0, 12), reason="IPython version %s does not support kernel, so nothing to test") +# We could write to jupyter_console_config.py and set JUPYTER_CONFIG_DIR, but +# it only supports highlight_matching_brackets, not +# display_completions='readlinelike', so any test that uses tab completion +# won't work. @skipif_ipython_too_old_for_kernel # @retry(ExpectError) @@ -2901,7 +3065,7 @@ def test_ipython_console_1(sendeof): In [3]: import pyflyby; pyflyby.enable_auto_importer() In [4]: b64deco\tde('cGVhbnV0') [PYFLYBY] from base64 import b64decode - Out[4]: 'peanut' + Out[4]: b'peanut' """, args='console', sendeof=sendeof) @@ -2917,7 +3081,7 @@ def test_ipython_kernel_console_existing_1(): In [1]: import pyflyby; pyflyby.enable_auto_importer() In [2]: b64deco\tde('bGVndW1l') [PYFLYBY] from base64 import b64decode - Out[2]: 'legume' + Out[2]: b'legume' """, args=['console'], kernel=kernel) @@ -2934,7 +3098,7 @@ def test_ipython_kernel_console_multiple_existing_1(): In [1]: b64decode('x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined """, args=['console'], kernel=kernel) # Enable the auto importer. @@ -2945,7 +3109,7 @@ def test_ipython_kernel_console_multiple_existing_1(): ipython(""" In [3]: b64deco\tde('YWxtb25k') [PYFLYBY] from base64 import b64decode - Out[3]: 'almond' + Out[3]: b'almond' """, args=['console'], kernel=kernel) @@ -2973,7 +3137,7 @@ def test_ipython_notebook_1(): In [1]: b64decode('x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined""" # Enable the auto importer. """ @@ -2982,7 +3146,7 @@ def test_ipython_notebook_1(): """ In [3]: b64deco\tde('aGF6ZWxudXQ=') [PYFLYBY] from base64 import b64decode - Out[3]: 'hazelnut' + Out[3]: b'hazelnut' """, args=['console'], kernel=kernel) @@ -2997,7 +3161,7 @@ def test_ipython_notebook_reconnect_1(): In [1]: b64decode('x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined """, args=['console'], kernel=kernel) # Enable the auto importer. @@ -3009,7 +3173,7 @@ def test_ipython_notebook_reconnect_1(): ipython(""" In [3]: b64deco\tde('aGF6ZWxudXQ=') [PYFLYBY] from base64 import b64decode - Out[3]: 'hazelnut' + Out[3]: b'hazelnut' """, args=['console'], kernel=kernel) @@ -3019,7 +3183,7 @@ def test_py_interactive_1(): ipython(""" In [1]: b64deco\tde('cGlzdGFjaGlv') [PYFLYBY] from base64 import b64decode - Out[1]: 'pistachio' + Out[1]: b'pistachio' """, prog="py") @@ -3045,7 +3209,7 @@ def test_py_console_1(): ipython(""" In [1]: b64deco\tde('d2FsbnV0') [PYFLYBY] from base64 import b64decode - Out[1]: 'walnut' + Out[1]: b'walnut' """, prog="py", args=['console']) @@ -3059,7 +3223,7 @@ def test_py_kernel_1(): ipython(""" In [1]: b64deco\tde('bWFjYWRhbWlh') [PYFLYBY] from base64 import b64decode - Out[1]: 'macadamia' + Out[1]: b'macadamia' """, args=['console'], kernel=kernel) @@ -3074,7 +3238,7 @@ def test_py_console_existing_1(): In [1]: b64decode('x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined """, prog="py", args=['console'], kernel=kernel) @@ -3087,7 +3251,7 @@ def test_py_notebook_1(): ipython(""" In [1]: b64deco\tde('Y2FzaGV3') [PYFLYBY] from base64 import b64decode - Out[1]: 'cashew' + Out[1]: b'cashew' """, args=['console'], kernel=kernel) @@ -3098,26 +3262,26 @@ def test_py_disable_1(): ipython(""" In [1]: b64deco\tde('aGlja29yeQ==') [PYFLYBY] from base64 import b64decode - Out[1]: 'hickory' + Out[1]: b'hickory' In [2]: pyflyby.disable_auto_importer() [PYFLYBY] import pyflyby - In [3]: b64encode('x') + In [3]: b64encode(b'x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64encode' is not defined In [4]: b64decode('bW9ja2VybnV0') - Out[4]: 'mockernut' + Out[4]: b'mockernut' In [5]: pyflyby.enable_auto_importer() - In [6]: b64encode('pecan') + In [6]: b64encode(b'pecan') [PYFLYBY] from base64 import b64encode - Out[6]: 'cGVjYW4=' + Out[6]: b'cGVjYW4=' """, prog="py") def _install_load_ext_pyflyby_in_config(ipython_dir): with open(str(ipython_dir/"profile_default/ipython_config.py"), 'a') as f: - print>>f, 'c.InteractiveShellApp.extensions.append("pyflyby")' + print('c.InteractiveShellApp.extensions.append("pyflyby")', file=f) @retry @@ -3128,14 +3292,14 @@ def test_installed_in_config_ipython_cmdline_1(tmp): ipython(""" In [1]: b64deco\tde('bWFwbGU=') [PYFLYBY] from base64 import b64decode - Out[1]: 'maple' + Out[1]: b'maple' """, ipython_dir=tmp.ipython_dir) # Double-check that we only modified tmp.ipython_dir. ipython(""" In [1]: b64decode('x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined """) @@ -3148,14 +3312,14 @@ def test_installed_in_config_redundant_1(tmp): ipython(""" In [1]: b64deco\tde('bWFwbGU=') [PYFLYBY] from base64 import b64decode - Out[1]: 'maple' + Out[1]: b'maple' """, ipython_dir=tmp.ipython_dir) # Double-check that we only modified tmp.ipython_dir. ipython(""" In [1]: b64decode('x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64decode' is not defined """) @@ -3169,7 +3333,7 @@ def test_installed_in_config_ipython_console_1(tmp): ipython(""" In [1]: b64deco\tde('c3BydWNl') [PYFLYBY] from base64 import b64decode - Out[1]: 'spruce' + Out[1]: b'spruce' """, args=['console'], ipython_dir=tmp.ipython_dir) @@ -3183,7 +3347,7 @@ def test_installed_in_config_ipython_kernel_1(tmp): ipython(""" In [1]: b64deco\tde('b2Fr') [PYFLYBY] from base64 import b64decode - Out[1]: 'oak' + Out[1]: b'oak' """, args=['console'], kernel=kernel) @@ -3195,7 +3359,7 @@ def test_installed_in_config_ipython_notebook_1(tmp): ipython(""" In [1]: b64deco\tde('c3ljYW1vcmU=') [PYFLYBY] from base64 import b64decode - Out[1]: 'sycamore' + Out[1]: b'sycamore' """, args=['console'], kernel=kernel) @@ -3207,20 +3371,20 @@ def test_installed_in_config_disable_1(tmp): ipython(""" In [1]: b64deco\tde('cGluZQ==') [PYFLYBY] from base64 import b64decode - Out[1]: 'pine' + Out[1]: b'pine' In [2]: pyflyby.disable_auto_importer() [PYFLYBY] import pyflyby - In [3]: b64encode('x') + In [3]: b64encode(b'x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64encode' is not defined In [4]: b64decode('d2lsbG93') - Out[4]: 'willow' + Out[4]: b'willow' In [5]: pyflyby.enable_auto_importer() - In [6]: b64encode('elm') + In [6]: b64encode(b'elm') [PYFLYBY] from base64 import b64encode - Out[6]: 'ZWxt' + Out[6]: b'ZWxt' """, ipython_dir=tmp.ipython_dir) @@ -3234,19 +3398,19 @@ def test_installed_in_config_enable_noop_1(tmp): [PYFLYBY] import pyflyby In [2]: b64deco\tde('Y2hlcnJ5') [PYFLYBY] from base64 import b64decode - Out[2]: 'cherry' + Out[2]: b'cherry' In [3]: pyflyby.disable_auto_importer() - In [4]: b64encode('x') + In [4]: b64encode(b'x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64encode' is not defined In [5]: b64decode('YmlyY2g=') - Out[5]: 'birch' + Out[5]: b'birch' In [6]: pyflyby.enable_auto_importer() - In [7]: b64encode('fir') + In [7]: b64encode(b'fir') [PYFLYBY] from base64 import b64encode - Out[7]: 'Zmly' + Out[7]: b'Zmly' """, ipython_dir=tmp.ipython_dir) @@ -3257,20 +3421,20 @@ def test_installed_in_config_ipython_py_1(tmp): ipython(""" In [1]: b64deco\tde('YmFzc3dvb2Q=') [PYFLYBY] from base64 import b64decode - Out[1]: 'basswood' + Out[1]: b'basswood' In [2]: pyflyby.disable_auto_importer() [PYFLYBY] import pyflyby - In [3]: b64encode('x') + In [3]: b64encode(b'x') --------------------------------------------------------------------------- NameError Traceback (most recent call last) - in () + in NameError: name 'b64encode' is not defined In [4]: b64decode('YnV0dGVybnV0') - Out[4]: 'butternut' + Out[4]: b'butternut' In [5]: pyflyby.enable_auto_importer() - In [6]: b64encode('larch') + In [6]: b64encode(b'larch') [PYFLYBY] from base64 import b64encode - Out[6]: 'bGFyY2g=' + Out[6]: b'bGFyY2g=' """, prog="py", ipython_dir=tmp.ipython_dir) @@ -3285,7 +3449,7 @@ def test_manual_install_profile_startup_1(tmp): ipython(""" In [1]: b64deco\tde('ZG92ZQ==') [PYFLYBY] from base64 import b64decode - Out[1]: 'dove' + Out[1]: b'dove' """, ipython_dir=tmp.ipython_dir) @@ -3298,11 +3462,11 @@ def test_manual_install_ipython_config_direct_1(tmp): # at top level. writetext(tmp.ipython_dir/"profile_default/ipython_config.py", """ __import__("pyflyby").enable_auto_importer() - """) + """, mode='a') ipython(""" In [1]: b64deco\tde('aHVtbWluZ2JpcmQ=') [PYFLYBY] from base64 import b64decode - Out[1]: 'hummingbird' + Out[1]: b'hummingbird' """, ipython_dir=tmp.ipython_dir) @@ -3315,11 +3479,11 @@ def test_manual_install_exec_lines_1(tmp): c.InteractiveShellApp.exec_lines = [ '__import__("pyflyby").enable_auto_importer()', ] - """) + """, mode='a') ipython(""" In [1]: b64deco\tde('c2VhZ3VsbA==') [PYFLYBY] from base64 import b64decode - Out[1]: 'seagull' + Out[1]: b'seagull' """, ipython_dir=tmp.ipython_dir) @@ -3334,11 +3498,11 @@ def test_manual_install_exec_files_1(tmp): writetext(tmp.ipython_dir/"profile_default/ipython_config.py", """ c = get_config() c.InteractiveShellApp.exec_files = [%r] - """ % (str(tmp.file),)) + """ % (str(tmp.file),), mode='a') ipython(""" In [1]: b64deco\tde('Y3Vja29v') [PYFLYBY] from base64 import b64decode - Out[1]: 'cuckoo' + Out[1]: b'cuckoo' """, ipython_dir=tmp.ipython_dir) @@ -3352,7 +3516,7 @@ def test_manual_install_ipythonrc_execute_1(tmp): ipython(""" In [1]: b64deco\tde('cGVuZ3Vpbg==') [PYFLYBY] from base64 import b64decode - Out[1]: 'penguin' + Out[1]: b'penguin' """, ipython_dir=tmp.ipython_dir) @@ -3367,7 +3531,7 @@ def test_manual_install_ipy_user_conf_1(tmp): ipython(""" In [1]: b64deco\tde('bG9vbg==') [PYFLYBY] from base64 import b64decode - Out[1]: 'loon' + Out[1]: b'loon' """, ipython_dir=tmp.ipython_dir) @@ -3379,7 +3543,7 @@ def test_cmdline_enable_c_i_1(tmp): ipython(""" In [1]: b64deco\tde('Zm94aG91bmQ=') [PYFLYBY] from base64 import b64decode - Out[1]: 'foxhound' + Out[1]: b'foxhound' """, args=['-c', 'import pyflyby; pyflyby.enable_auto_importer()', '-i']) @@ -3391,7 +3555,7 @@ def test_cmdline_enable_code_to_run_i_1(tmp): ipython(""" In [1]: b64deco\tde('cm90dHdlaWxlcg==') [PYFLYBY] from base64 import b64decode - Out[1]: 'rottweiler' + Out[1]: b'rottweiler' """, args=['--InteractiveShellApp.code_to_run=' 'import pyflyby; pyflyby.enable_auto_importer()', '-i']) @@ -3404,7 +3568,7 @@ def test_cmdline_enable_exec_lines_1(tmp): ipython(""" In [1]: b64deco\tde('cG9vZGxl') [PYFLYBY] from base64 import b64decode - Out[1]: 'poodle' + Out[1]: b'poodle' """, args=[ '--InteractiveShellApp.exec_lines=' '''["__import__('pyflyby').enable_auto_importer()"]''']) @@ -3421,7 +3585,7 @@ def test_cmdline_enable_exec_files_1(tmp): ipython(""" In [1]: b64deco\tde('Y3Vja29v') [PYFLYBY] from base64 import b64decode - Out[1]: 'cuckoo' + Out[1]: b'cuckoo' """, args=[ '--InteractiveShellApp.exec_files=[%r]' % (str(tmp.file),)]) @@ -3451,7 +3615,7 @@ def test_debug_without_autoimport_1(frontend): In [2]: %debug .... ipdb> p b64decode("QXVkdWJvbg==") - *** NameError: NameError("name 'b64decode' is not defined",) + *** NameError: name 'b64decode' is not defined ipdb> q """, frontend=frontend) @@ -3467,7 +3631,7 @@ def test_debug_auto_import_p_1(frontend): .... ipdb> p b64decode("S2Vuc2luZ3Rvbg==") [PYFLYBY] from base64 import b64decode - 'Kensington' + b'Kensington' ipdb> q """, frontend=frontend) @@ -3484,7 +3648,7 @@ def test_debug_auto_import_pp_1(frontend): .... ipdb> p b64decode("R2FyZGVu") [PYFLYBY] from base64 import b64decode - 'Garden' + b'Garden' ipdb> q """, frontend=frontend) @@ -3501,7 +3665,7 @@ def test_debug_auto_import_default_1(frontend): .... ipdb> b64decode("UHJvc3BlY3Q=") [PYFLYBY] from base64 import b64decode - 'Prospect' + b'Prospect' ipdb> q """, frontend=frontend) @@ -3517,7 +3681,7 @@ def test_debug_auto_import_print_1(frontend): ZeroDivisionError: ... In [3]: %debug .... - ipdb> print b64decode("TW9udGdvbWVyeQ==") + ipdb> if 1: print(b64decode("TW9udGdvbWVyeQ==").decode('utf-8')) [PYFLYBY] from base64 import b64decode Montgomery ipdb> q @@ -3537,7 +3701,7 @@ def test_debug_auto_import_bang_default_1(frontend): ipdb> !q = b64decode("SGF3dGhvcm5l") [PYFLYBY] from base64 import b64decode ipdb> !q - 'Hawthorne' + b'Hawthorne' ipdb> q """, frontend=frontend) @@ -3557,7 +3721,7 @@ def test_debug_postmortem_auto_import_1(frontend): TypeError: unsupported operand type(s) for /: 'str' and 'str' In [4]: %debug .... - ipdb> print x + b64decode("QA==") + y + ipdb> print(x + b64decode("QA==").decode('utf-8') + y) [PYFLYBY] from base64 import b64decode Bowcraft@Mountain ipdb> q @@ -3574,7 +3738,7 @@ def test_debug_tab_completion_db_1(frontend): ZeroDivisionError: ... In [3]: %debug .... - ipdb> print b64dec\tode("R2FyZmllbGQ=") + ipdb> print(b64dec\tode("R2FyZmllbGQ=").decode('utf-8')) [PYFLYBY] from base64 import b64decode Garfield ipdb> q @@ -3594,9 +3758,9 @@ def test_debug_tab_completion_module_1(frontend, tmp): ZeroDivisionError: ... In [3]: %debug .... - ipdb> print thornton60097\t181.rando\t + ipdb> print(thornton60097\t181.rando\t [PYFLYBY] import thornton60097181 - ipdb> print thornton60097181.randolph + ipdb> print(thornton60097181.randolph) 14164598 ipdb> q """, PYTHONPATH=tmp.dir, frontend=frontend) @@ -3617,12 +3781,12 @@ def test_debug_tab_completion_multiple_1(frontend, tmp): ZeroDivisionError: ... In [3]: %debug .... - ipdb> print sturbridge9088333.neb\t + ipdb> print(sturbridge9088333.neb\t [PYFLYBY] import sturbridge9088333 - ipdb> print sturbridge9088333.neb + ipdb> print(sturbridge9088333.neb sturbridge9088333.nebula_10983840 sturbridge9088333.nebula_41695458 - ipdb> print sturbridge9088333.nebula_ - *** AttributeError: 'module' object has no attribute 'nebula_' + ipdb> print(sturbridge9088333.nebula_) + *** AttributeError: sturbridge9088333 has no attribute 'nebula_' ipdb> q """, PYTHONPATH=tmp.dir, frontend=frontend) @@ -3642,9 +3806,9 @@ def test_debug_postmortem_tab_completion_1(frontend): TypeError: unsupported operand type(s) for /: 'str' and 'str' In [4]: %debug .... - ipdb> print x + base64.b64d\t + ipdb> print(x + base64.b64d\t [PYFLYBY] import base64 - ipdb> print x + base64.b64decode("Lw==") + y + ipdb> print(x + base64.b64decode("Lw==").decode('utf-8') + y) Camden/Hopkinson ipdb> q """, frontend=frontend) @@ -3668,17 +3832,17 @@ def test_debug_namespace_1(frontend): TypeError: unsupported operand type(s) for /: 'str' and 'str' In [4]: %debug .... - ipdb> print base64.cap\titalize() + b64deco\tde("UGFjaWZpYw==") + ipdb> print(base64.cap\titalize() + b64deco\tde("UGFjaWZpYw==").decode('utf-8')) [PYFLYBY] from base64 import b64decode AtlanticPacific ipdb> p b64deco\tde("Q29udGluZW50YWw=") - 'Continental' + b'Continental' ipdb> q In [5]: base64.b64de\t [PYFLYBY] import base64 In [5]: base64.b64decode("SGlsbA==") + b64deco\tde("TGFrZQ==") [PYFLYBY] from base64 import b64decode - Out[5]: 'HillLake' + Out[5]: b'HillLake' """, frontend=frontend) @@ -3698,11 +3862,11 @@ def test_debug_second_1(frontend): TypeError: unsupported operand type(s) for /: 'str' and 'str' In [4]: %debug .... - ipdb> print b64deco\tde("Sm9zZXBo") + ipdb> b64deco\tde("Sm9zZXBo") [PYFLYBY] from base64 import b64decode - Joseph - ipdb> print b64deco\tde("U2VtaW5vbGU=") - Seminole + b'Joseph' + ipdb> b64deco\tde("U2VtaW5vbGU=") + b'Seminole' ipdb> q In [5]: foo("Quince", "Lilac") --------------------------------------------------------------------------- @@ -3711,9 +3875,9 @@ def test_debug_second_1(frontend): TypeError: unsupported operand type(s) for /: 'str' and 'str' In [6]: %debug .... - ipdb> print b64deco\tde("Q3JvY3Vz") + ipdb> b64deco\tde("Q3JvY3Vz") [PYFLYBY] from base64 import b64decode - Crocus + b'Crocus' ipdb> q """, frontend=frontend) @@ -3732,7 +3896,7 @@ def test_debug_auto_import_string_1(frontend): > (1)() ipdb> p b64decode("TGluc2xleQ==") [PYFLYBY] from base64 import b64decode - 'Linsley' + b'Linsley' ipdb> q """, frontend=frontend) @@ -3744,7 +3908,7 @@ def test_debug_auto_import_of_string_1(frontend, tmp): # Verify that auto importing works for the string to be debugged. writetext(tmp.dir/"peekskill43666930.py", """ def hollow(x): - print x * 2 + print(x * 2) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -3766,7 +3930,7 @@ def test_debug_auto_import_statement_step_1(frontend, tmp): writetext(tmp.dir/"taconic72383428.py", """ def pudding(x): y = x * 5 - print y + print(y) """) ipython(""" In [1]: import pyflyby; pyflyby.enable_auto_importer() @@ -3778,7 +3942,7 @@ def pudding(x): .... ipdb> n .... - ipdb> print x + ipdb> print(x) 48364325 ipdb> x = os.path.sep [PYFLYBY] import os.path @@ -3795,13 +3959,13 @@ def pudding(x): # In [2]: pyflyby.enable_auto_importer() # In [3]: b64decode("cG93bmFs") # [PYFLYBY] from base64 import b64decode -# Out[3]: 'pownal' +# Out[3]: b'pownal' # In [4]: sys # Out[4]: 86176104 # In [5]: exit() # >>> b64decode("...") # ... -# >>> b64encode("...") +# >>> b64encode(b"...") # NameError: ... # TODO: add tests for when IPython is not installed. either using a tox diff --git a/tests/test_livepatch.py b/tests/test_livepatch.py index 3e75c812..cbca15ff 100644 --- a/tests/test_livepatch.py +++ b/tests/test_livepatch.py @@ -1,5 +1,6 @@ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import os import pytest @@ -9,6 +10,8 @@ from textwrap import dedent import types +from six import PY2, PY3 + from pyflyby import Filename, xreload from pyflyby._livepatch import UnknownModuleError @@ -593,7 +596,9 @@ def rock(self): return self.stone() + 6 xreload("shoulder77076723") assert t.rock() == 23495306 - +@pytest.mark.skipif( + PY3, + reason="Python 3 doesn't have old style classes") def test_xreload_oldstyle_to_newstyle_1(tpp): # Verify that when a class changes from old-style to new-style we fallback # to not patching. @@ -617,7 +622,9 @@ def judge(self): return 21901074 assert isinstance( Bride, types.ClassType) assert not isinstance(m.Bride, types.ClassType) - +@pytest.mark.skipif( + PY3, + reason="Python 3 doesn't have old style classes") def test_xreload_newstyle_to_oldstyle_1(tpp): # Verify that when a class changes from new-style to old-style we fallback # to not patching. @@ -1220,7 +1227,7 @@ def riot(): return 21818407 def r(OLDFUNC): assert OLDFUNC() == 12188608 - OLDFUNC.func_code = riot.func_code + OLDFUNC.__code__ = riot.__code__ assert OLDFUNC() == 21818407 return OLDFUNC riot.__livepatch__ = r @@ -1774,32 +1781,57 @@ def f(self): return 21904892 def test_xreload_metaclass_function_1(tpp): # Verify that xreload() works correctly when the metaclass is a custom # function. - writetext(tpp/"weird32312765.py", """ - def my_meta(name, bases, attrs): - attrs['bar'] = attrs.pop("foo") - return type(name, bases, attrs) - class Sport(object): - __metaclass__ = my_meta - def __init__(self, x): - self.x = x - def foo(self, y): - return self.x + y - """) + if PY2: + writetext(tpp/"weird32312765.py", """ + def my_meta(name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type(name, bases, attrs) + class Sport(object): + __metaclass__ = my_meta + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + y + """) + else: + writetext(tpp/"weird32312765.py", """ + def my_meta(name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type(name, bases, attrs) + class Sport(object, metaclass=my_meta): + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + y + """) + from weird32312765 import Sport a = Sport(34129000) assert a.bar(3) == 34129003 assert not hasattr(a, 'foo') - writetext(tpp/"weird32312765.py", """ - def my_meta(name, bases, attrs): - attrs['bar'] = attrs.pop("foo") - return type(name, bases, attrs) - class Sport(object): - __metaclass__ = my_meta - def __init__(self, x): - self.x = x - def foo(self, y): - return self.x + 2*y - """) + if PY2: + writetext(tpp/"weird32312765.py", """ + def my_meta(name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type(name, bases, attrs) + class Sport(object): + __metaclass__ = my_meta + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + 2*y + """) + else: + writetext(tpp/"weird32312765.py", """ + def my_meta(name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type(name, bases, attrs) + class Sport(object, metaclass=my_meta): + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + 2*y + """) xreload("weird32312765") assert a.bar(3) == 34129006 assert not hasattr(a, 'foo') @@ -1814,28 +1846,51 @@ def __new__(cls, name, bases, attrs): attrs['bar'] = attrs.pop("foo") return type.__new__(cls, name, bases, attrs) """) - writetext(tpp/"research72020159.py", """ - from metaclass17670900 import MyType - class Employment(object): - __metaclass__ = MyType - def __init__(self, x): - self.x = x - def foo(self, y): - return self.x + y - """) + if PY2: + writetext(tpp/"research72020159.py", """ + from metaclass17670900 import MyType + class Employment(object): + __metaclass__ = MyType + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + y + """) + else: + writetext(tpp/"research72020159.py", """ + from metaclass17670900 import MyType + class Employment(object, metaclass=MyType): + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + y + """) + + from research72020159 import Employment a = Employment(97993000) assert a.bar(4) == 97993004 assert not hasattr(a, 'foo') - writetext(tpp/"research72020159.py", """ - from metaclass17670900 import MyType - class Employment(object): - __metaclass__ = MyType - def __init__(self, x): - self.x = x - def foo(self, y): - return self.x + 2*y - """) + if PY2: + writetext(tpp/"research72020159.py", """ + from metaclass17670900 import MyType + class Employment(object): + __metaclass__ = MyType + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + 2*y + """) + else: + writetext(tpp/"research72020159.py", """ + from metaclass17670900 import MyType + class Employment(object, metaclass=MyType): + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + 2*y + """) + xreload("research72020159") assert a.bar(4) == 97993008 assert not hasattr(a, 'foo') @@ -1844,34 +1899,62 @@ def foo(self, y): def test_xreload_metaclass_subclass_type_same_file_1(tpp): # Verify that xreload() works correctly when the metaclass is a custom # class of type, and the metaclass is defined in the same file. - writetext(tpp/"damage28847789.py", """ - class MyType(type): - def __new__(cls, name, bases, attrs): - attrs['bar'] = attrs.pop("foo") - return type.__new__(cls, name, bases, attrs) - class Agriculture(object): - __metaclass__ = MyType - def __init__(self, x): - self.x = x - def foo(self, y): - return self.x + y - """) + if PY2: + writetext(tpp/"damage28847789.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type.__new__(cls, name, bases, attrs) + class Agriculture(object): + __metaclass__ = MyType + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + y + """) + else: + writetext(tpp/"damage28847789.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type.__new__(cls, name, bases, attrs) + class Agriculture(object, metaclass=MyType): + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + y + """) + from damage28847789 import Agriculture a = Agriculture(72991000) assert a.bar(3) == 72991003 assert not hasattr(a, 'foo') - writetext(tpp/"damage28847789.py", """ - class MyType(type): - def __new__(cls, name, bases, attrs): - attrs['bar'] = attrs.pop("foo") - return type.__new__(cls, name, bases, attrs) - class Agriculture(object): - __metaclass__ = MyType - def __init__(self, x): - self.x = x - def foo(self, y): - return self.x + 2*y - """) + if PY2: + writetext(tpp/"damage28847789.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type.__new__(cls, name, bases, attrs) + class Agriculture(object): + __metaclass__ = MyType + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + 2*y + """) + else: + writetext(tpp/"damage28847789.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['bar'] = attrs.pop("foo") + return type.__new__(cls, name, bases, attrs) + class Agriculture(object, metaclass=MyType): + def __init__(self, x): + self.x = x + def foo(self, y): + return self.x + 2*y + """) + xreload("damage28847789") assert a.bar(3) == 72991006 assert not hasattr(a, 'fooxreload_') @@ -1880,34 +1963,62 @@ def foo(self, y): def test_xreload_metaclass_changed_1(tpp): # Verify that xreload() works correctly when the metaclass definition # changed. - writetext(tpp/"commitee91173998.py", """ - class MyType(type): - def __new__(cls, name, bases, attrs): - attrs['technology'] = attrs.pop("procedure") - return type.__new__(cls, name, bases, attrs) - class Significance(object): - __metaclass__ = MyType - def __init__(self, x): - self.x = x - def procedure(self, y): - return self.x + y - """) + if PY2: + writetext(tpp/"commitee91173998.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['technology'] = attrs.pop("procedure") + return type.__new__(cls, name, bases, attrs) + class Significance(object): + __metaclass__ = MyType + def __init__(self, x): + self.x = x + def procedure(self, y): + return self.x + y + """) + else: + writetext(tpp/"commitee91173998.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['technology'] = attrs.pop("procedure") + return type.__new__(cls, name, bases, attrs) + class Significance(object, metaclass=MyType): + def __init__(self, x): + self.x = x + def procedure(self, y): + return self.x + y + """) + from commitee91173998 import Significance x = Significance(56638000) assert x.technology(3) == 56638003 assert not hasattr(x, 'procedure') - writetext(tpp/"commitee91173998.py", """ - class MyType(type): - def __new__(cls, name, bases, attrs): - attrs['cloud'] = attrs.pop("procedure") - return type.__new__(cls, name, bases, attrs) - class Significance(object): - __metaclass__ = MyType - def __init__(self, x): - self.x = x - def procedure(self, y): - return self.x + 2*y - """) + if PY2: + writetext(tpp/"commitee91173998.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['cloud'] = attrs.pop("procedure") + return type.__new__(cls, name, bases, attrs) + class Significance(object): + __metaclass__ = MyType + def __init__(self, x): + self.x = x + def procedure(self, y): + return self.x + 2*y + """) + else: + writetext(tpp/"commitee91173998.py", """ + class MyType(type): + def __new__(cls, name, bases, attrs): + attrs['cloud'] = attrs.pop("procedure") + return type.__new__(cls, name, bases, attrs) + class Significance(object, metaclass=MyType): + def __init__(self, x): + self.x = x + def procedure(self, y): + return self.x + 2*y + """) + xreload("commitee91173998") assert x.cloud(3) == 56638006 assert not hasattr(x, 'procedure') diff --git a/tests/test_modules.py b/tests/test_modules.py index 29662112..f9e00774 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import logging.handlers diff --git a/tests/test_parse.py b/tests/test_parse.py index fa32815a..b57abc46 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -3,12 +3,15 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import pytest import sys from textwrap import dedent +from six import PY2, PY3 + from pyflyby._file import FilePos, FileText, Filename from pyflyby._flags import CompilerFlags from pyflyby._parse import PythonBlock, PythonStatement @@ -83,19 +86,19 @@ def test_PythonBlock_lineno_1(): def test_PythonBlock_statements_comments_1(): block = PythonBlock(dedent(''' # 1 - print 2 + print(2) # 3 # 4 - print 5 + print(5) x=[6, 7] # 8 ''').lstrip()) expected = ( PythonStatement('# 1\n' ), - PythonStatement('print 2\n', startpos=(2,1)), + PythonStatement('print(2)\n', startpos=(2,1)), PythonStatement('# 3\n# 4\n', startpos=(3,1)), - PythonStatement('print 5\n', startpos=(5,1)), + PythonStatement('print(5)\n', startpos=(5,1)), PythonStatement('x=[6,\n 7]\n', startpos=(6,1)), PythonStatement('# 8\n', startpos=(8,1)), ) @@ -447,6 +450,9 @@ def __init__(self): assert block.get_doctests() == expected +@pytest.mark.skipif( + PY3, + reason="print function is not invalid syntax in Python 3.") def test_PythonBlock_flags_bad_1(): # In Python2.x, this should cause a syntax error, since we didn't do # flags='print_function': @@ -538,7 +544,9 @@ def test_PythonStatement_bad_from_multi_statements_1(): with pytest.raises(ValueError): PythonStatement("a\nb\n") - +@pytest.mark.skipif( + PY3, + reason="print function is not invalid syntax in Python 3.") def test_PythonStatement_flags_bad_1(): # In Python2.x, this should cause a syntax error, since we didn't do # flags='print_function': @@ -645,24 +653,35 @@ def test_str_lineno_in_dict_1(): def test_str_lineno_strprefix_1(): - block = PythonBlock(dedent(r''' + code = r''' r"aa\nbb" 0 - Ur"""cc\n - dd""" r"x" - ''').lstrip(), startpos=(101,1)) +''' + if PY2: + # ur"" is not valid syntax in Python 3 + code += r''' Ur"""cc\n + dd""" + ''' + block = PythonBlock(dedent(code).lstrip(), startpos=(101,1)) expected_statements = ( PythonStatement('r"aa\\nbb"\n' , startpos=(101,1)), PythonStatement('0\n' , startpos=(102,1)), - PythonStatement('Ur"""cc\\n\ndd"""\n', startpos=(103,1)), - PythonStatement('r"x"\n' , startpos=(105,1)), + PythonStatement('r"x"\n' , startpos=(103,1)), + ) + if PY2: + expected_statements += ( + PythonStatement('Ur"""cc\\n\ndd"""\n', startpos=(104,1)), ) assert block.statements == expected_statements literals = [(f.s, f.startpos) for f in block.string_literals()] - expected_literals = [('aa\\nbb' , FilePos(101,1)), - ('cc\\n\ndd', FilePos(103,1)), - ('x' , FilePos(105,1))] + expected_literals = [ + ('aa\\nbb' , FilePos(101,1)), + ('x' , FilePos(103,1)), + ] + + if PY2: + expected_literals += [('cc\\n\ndd', FilePos(104,1))] assert literals == expected_literals @@ -687,7 +706,7 @@ def test_str_lineno_escaped_single_1(): def test_str_lineno_concatenated_1(): - block = PythonBlock(dedent(''' + code = ''' "A" "a" "B" 'b' 'C' 'c' @@ -709,8 +728,7 @@ def test_str_lineno_concatenated_1(): J""" 'j'.split() 'K' 'L' - r"M" u'm' b"""M""" Ur\'\'\' - m\'\'\' + r"M" u'm' "N" ''"" "n" """"""\'\'\'\'\'\'\'N\'\'\' """ O @@ -721,7 +739,20 @@ def test_str_lineno_concatenated_1(): "Q" "q""" "R" "r""" + "S""""s""S""""s"""" S""" - ''').lstrip(), startpos=(101,1)) +''' + if PY2: + # ur"" is not valid syntax in Python 3 + code += """\ + Ur''' + t''' +""" + # Implicit string concatenation of non-bytes and bytes literals is not + # valid syntax in Python 3 + code += '''\ + r"U" u'u' b"""U""" + ''' + + block = PythonBlock(dedent(code).lstrip(), startpos=(101,1)) expected_statements = ( PythonStatement('''"A" "a"\n''' , startpos=(101,1)), PythonStatement('''"B" 'b'\n''' , startpos=(102,1)), @@ -735,13 +766,18 @@ def test_str_lineno_concatenated_1(): PythonStatement('''"J" """j\nJ""" 'j'.split()\n''' , startpos=(118,1)), PythonStatement("""'K'\n""" , startpos=(120,1)), PythonStatement("""'L'\n""" , startpos=(121,1)), - PythonStatement('''r"M" u'm' b"""M""" Ur\'\'\'\nm\'\'\'\n''' , startpos=(122,1)), - PythonStatement('''"N" ''"" "n" """"""\'\'\'\'\'\'\'N\'\'\'\n''' , startpos=(124,1)), - PythonStatement('''"""\nO\n"""\n''' , startpos=(125,1)), - PythonStatement('''"""\nP\n"""\n''' , startpos=(128,1)), - PythonStatement('''"Q" "q"""\n''' , startpos=(131,1)), - PythonStatement('''"R" "r""" + "S""""s""S""""s""""\nS"""\n''' , startpos=(132,1)), + PythonStatement('''r"M" u\'m\'\n''' , startpos=(122,1)), + PythonStatement('''"N" ''"" "n" """"""\'\'\'\'\'\'\'N\'\'\'\n''' , startpos=(123,1)), + PythonStatement('''"""\nO\n"""\n''' , startpos=(124,1)), + PythonStatement('''"""\nP\n"""\n''' , startpos=(127,1)), + PythonStatement('''"Q" "q"""\n''' , startpos=(130,1)), + PythonStatement('''"R" "r""" + "S""""s""S""""s""""\nS"""\n''' , startpos=(131,1)), ) + if PY2: + expected_statements += ( + PythonStatement("""Ur'''\nt'''\n""" , startpos=(133,1)), + PythonStatement('''r"U" u'u' b"""U"""\n''' , startpos=(135,1)), + ) assert block.statements == expected_statements literals = [(f.s, f.startpos) for f in block.string_literals()] expected_literals = [ @@ -757,14 +793,19 @@ def test_str_lineno_concatenated_1(): ("Jj\nJj", FilePos(118,1)), ("K", FilePos(120,1)), ("L", FilePos(121,1)), - ("MmM\nm", FilePos(122,1)), - ("NnN", FilePos(124,1)), - ("\nO\n", FilePos(125,1)), - ("\nP\n", FilePos(128,1)), - ("Qq", FilePos(131,1)), - ("Rr", FilePos(132,1)), - ('Ss""Ss\nS', FilePos(132,13)), + ("Mm", FilePos(122,1)), + ("NnN", FilePos(123,1)), + ("\nO\n", FilePos(124,1)), + ("\nP\n", FilePos(127,1)), + ("Qq", FilePos(130,1)), + ("Rr", FilePos(131,1)), + ('Ss""Ss\nS', FilePos(131,13)), ] + if PY2: + expected_literals += [ + ('\nt', FilePos(133, 1)), + ("UuU", FilePos(135,1)), + ] assert literals == expected_literals @@ -876,6 +917,9 @@ def test_PythonBlock_with_multi_1(): # flagpf = flags contains CompilerFlags("print_function") # futpf = code contains 'from __future__ import print_function' +@pytest.mark.skipif( + PY3, + reason="print statement not valid syntax in Python 3.") def test_PythonBlock_no_auto_flags_ps_flagps_1(): block = PythonBlock(dedent(''' print 42 @@ -884,7 +928,6 @@ def test_PythonBlock_no_auto_flags_ps_flagps_1(): assert not (block.ast_node.input_flags & "print_function") assert not (block.source_flags & "print_function") - def test_PythonBlock_no_auto_flags_ps_flagpf_1(): block = PythonBlock(dedent(''' print 42 @@ -892,7 +935,9 @@ def test_PythonBlock_no_auto_flags_ps_flagpf_1(): with pytest.raises(SyntaxError): block.ast_node - +@pytest.mark.skipif( + PY3, + reason="print function is not invalid syntax in Python 3.") def test_PythonBlock_no_auto_flags_pf_flagps_1(): block = PythonBlock(dedent(''' print(42, out=x) @@ -976,6 +1021,9 @@ def test_PythonBlock_no_auto_flags_pn_futpf_1(): assert (block.source_flags & "print_function") +@pytest.mark.skipif( + PY3, + reason="print statement is not valid syntax in Python 3.") def test_PythonBlock_auto_flags_ps_flagps_1(): block = PythonBlock(dedent(''' print 42 @@ -985,6 +1033,9 @@ def test_PythonBlock_auto_flags_ps_flagps_1(): assert not (block.source_flags & "print_function") +@pytest.mark.skipif( + PY3, + reason="print statement is not valid syntax in Python 3.") def test_PythonBlock_auto_flags_ps_flagpf_1(): block = PythonBlock(dedent(''' print 42 @@ -1016,9 +1067,14 @@ def test_PythonBlock_auto_flags_pf_flagps_1(): block = PythonBlock(dedent(''' print(42, out=x) ''').lstrip(), auto_flags=True) - assert (block.flags & "print_function") - assert (block.ast_node.input_flags & "print_function") - assert not (block.source_flags & "print_function") + if PY2: + assert (block.flags & "print_function") + assert (block.ast_node.input_flags & "print_function") + assert not (block.source_flags & "print_function") + else: + assert not (block.flags & "print_function") + assert not (block.ast_node.input_flags & "print_function") + assert not (block.source_flags & "print_function") def test_PythonBlock_auto_flags_pf_flagpf_1(): @@ -1143,7 +1199,10 @@ def test_PythonStatement_auto_flags_1(): s0, s1 = block.statements assert s0.block.source_flags == CompilerFlags("unicode_literals") assert s1.block.source_flags == CompilerFlags(0) - expected = CompilerFlags("unicode_literals", "division", "print_function") + if PY2: + expected = CompilerFlags("unicode_literals", "division", "print_function") + else: + expected = CompilerFlags("unicode_literals", "division") assert s0.block.flags == expected assert s1.block.flags == expected @@ -1163,6 +1222,9 @@ def test_parsable_explicit_flags_1(): assert block.parsable +@pytest.mark.skipif( + PY3, + reason="print function is not invalid syntax in Python 3.") def test_parsable_missing_flags_no_auto_flags_1(): block = PythonBlock("print(3, file=4)") assert not block.parsable diff --git a/tests/test_py.py b/tests/test_py.py index 2dee6665..369c655c 100644 --- a/tests/test_py.py +++ b/tests/test_py.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) import os import pytest @@ -15,6 +16,8 @@ from tempfile import NamedTemporaryFile, mkdtemp from textwrap import dedent +from six import PY2, PY3, string_types + import pyflyby from pyflyby._file import Filename from pyflyby._util import cached_attribute @@ -48,7 +51,7 @@ def pipe(command, stdin="", env=None, retretcode="automatic"): stderr=subprocess.STDOUT, env=env ) - stdout = proc.communicate(stdin)[0].strip() + stdout = proc.communicate(stdin)[0].strip().decode('utf-8') retcode = proc.returncode assert retcode >= 0 if retretcode == True: @@ -94,7 +97,7 @@ def _build_pythonpath(PYTHONPATH): C{str} """ pypath = [os.path.dirname(os.path.dirname(pyflyby.__file__))] - if isinstance(PYTHONPATH, (Filename, basestring)): + if isinstance(PYTHONPATH, (Filename,) + string_types): PYTHONPATH = [PYTHONPATH] PYTHONPATH = [str(Filename(d)) for d in PYTHONPATH] pypath += PYTHONPATH @@ -132,7 +135,7 @@ def writetext(filename, text, mode='w'): def test_0prefix_raw_1(): # Verify that we're testing the virtualenv we think we are. - result = pipe(['python', '-c', 'import sys; print sys.prefix']) + result = pipe(['python', '-c', 'import sys; print(sys.prefix)']) expected = sys.prefix assert expected == result @@ -156,8 +159,10 @@ def test_eval_1(): expected = dedent(""" [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('VGhvbXBzb24=') - 'Thompson' + b'Thompson' """).strip() + if PY2: + expected = expected.replace("b'Thompson'", "'Thompson'") assert expected == result @@ -166,8 +171,10 @@ def test_eval_single_dash_1(): expected = dedent(""" [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('V29vc3Rlcg==') - 'Wooster' + b'Wooster' """).strip() + if PY2: + expected = expected.replace("b'Wooster'", "'Wooster'") assert expected == result @@ -176,8 +183,10 @@ def test_eval_equals_1(): expected = dedent(""" [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('TWVyY2Vy') - 'Mercer' + b'Mercer' """).strip() + if PY2: + expected = expected.replace("b'Mercer'", "'Mercer'") assert expected == result @@ -186,8 +195,10 @@ def test_eval_single_dash_equals_1(): expected = dedent(""" [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('VW5pdmVyc2l0eQ==') - 'University' + b'University' """).strip() + if PY2: + expected = expected.replace("b'University'", "'University'") assert expected == result @@ -196,14 +207,19 @@ def test_eval_c_1(): expected = dedent(""" [PYFLYBY] from base64 import b64decode [PYFLYBY] b64decode('QmxlZWNrZXI=') - 'Bleecker' + b'Bleecker' """).strip() + if PY2: + expected = expected.replace("b'Bleecker'", "'Bleecker'") assert expected == result def test_eval_quiet_1(): result = py("-q", "-c", "b64decode('U3VsbGl2YW4=')") - expected = "'Sullivan'" + if PY2: + expected = "'Sullivan'" + else: + expected = "b'Sullivan'" assert expected == result @@ -224,12 +240,14 @@ def test_eval_expression_quiet_1(): def test_exec_1(): - result = py("-c", "print b64decode('UHJpbmNl')") + result = py("-c", "if 1: print(b64decode('UHJpbmNl'))") expected = dedent(""" [PYFLYBY] from base64 import b64decode - [PYFLYBY] print b64decode('UHJpbmNl') - Prince + [PYFLYBY] if 1: print(b64decode('UHJpbmNl')) + b'Prince' """).strip() + if PY2: + expected = expected.replace("b'Prince'", "Prince") assert expected == result @@ -254,13 +272,13 @@ def test_argv_2(): def test_file_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: - f.write('print "Boone", sys.argv\n') + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: + f.write('print(("Boone", sys.argv))\n') f.flush() result = py(f.name, "a", "b") expected = dedent(""" [PYFLYBY] import sys - Boone [%r, 'a', 'b'] + ('Boone', [%r, 'a', 'b']) """).strip() % (f.name,) assert expected == result @@ -274,13 +292,13 @@ def test_file_1(): "--run", "-run", "run", "%run", ]) def test_file_variants_1(arg): - with tempfile.NamedTemporaryFile(suffix=".py") as f: - f.write('print "Longfellow", sys.argv\n') + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: + f.write('print(("Longfellow", sys.argv))\n') f.flush() result = py(f.name, "a", "b") expected = dedent(""" [PYFLYBY] import sys - Longfellow [%r, 'a', 'b'] + ('Longfellow', [%r, 'a', 'b']) """).strip() % (f.name,) assert expected == result @@ -295,7 +313,7 @@ def test_apply_1(): def test_apply_stdin_1(): - result = py("--apply", "str.upper", "-", stdin="Eagle") + result = py("--apply", "str.upper", "-", stdin=b"Eagle") expected = dedent(""" [PYFLYBY] str.upper('Eagle') 'EAGLE' @@ -304,7 +322,7 @@ def test_apply_stdin_1(): def test_apply_stdin_more_args_1(): - result = py("--apply", "str.find", "-", "'k'", stdin="Jackson\n") + result = py("--apply", "str.find", "-", "'k'", stdin=b"Jackson\n") expected = dedent(r""" [PYFLYBY] str.find('Jackson\n', 'k') 3 @@ -613,11 +631,11 @@ def test_heuristic_eval_exponentiation_1(): def test_heuristic_eval_with_argv_1(): - result = py('for x in sys.argv[1:]: print x.capitalize()', + result = py('for x in sys.argv[1:]: print(x.capitalize())', 'canal', 'grand') expected = dedent(""" [PYFLYBY] import sys - [PYFLYBY] for x in sys.argv[1:]: print x.capitalize() + [PYFLYBY] for x in sys.argv[1:]: print(x.capitalize()) Canal Grand """).strip() @@ -625,19 +643,19 @@ def test_heuristic_eval_with_argv_1(): def test_heuristic_exec_statement_1(): - result = py('''if 1: print "Mulberry"''') + result = py('''if 1: print("Mulberry")''') expected = dedent(""" - [PYFLYBY] if 1: print "Mulberry" + [PYFLYBY] if 1: print("Mulberry") Mulberry """).strip() assert expected == result def test_heuristic_exec_multiline_statement_1(): - result = py('''if 1:\n print "Mott"''') + result = py('''if 1:\n print("Mott")''') expected = dedent(""" [PYFLYBY] if 1: - [PYFLYBY] print "Mott" + [PYFLYBY] print("Mott") Mott """).strip() assert expected == result @@ -653,7 +671,7 @@ def test_heuristic_apply_1(): def test_heuristic_apply_stdin_1(): - result = py("str.upper", "-", stdin="Nassau") + result = py("str.upper", "-", stdin=b"Nassau") expected = dedent(""" [PYFLYBY] str.upper('Nassau') 'NASSAU' @@ -662,7 +680,7 @@ def test_heuristic_apply_stdin_1(): def test_heuristic_apply_stdin_2(): - result = py("sys.stdout.write", "-", stdin="Downing") + result = py("--output=silent", "sys.stdout.write", "-", stdin=b"Downing") expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] sys.stdout.write('Downing') @@ -672,7 +690,7 @@ def test_heuristic_apply_stdin_2(): def test_heuristic_apply_stdin_no_eval_1(): - result = py("sys.stdout.write", "-", stdin="3+4") + result = py("--output=silent", "sys.stdout.write", "-", stdin=b"3+4") expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] sys.stdout.write('3+4') @@ -682,7 +700,7 @@ def test_heuristic_apply_stdin_no_eval_1(): def test_heuristic_apply_stdin_quiet_1(): - result = py("-q", "sys.stdout.write", "-", stdin="Houston") + result = py("--output=silent", "-q", "sys.stdout.write", "-", stdin=b"Houston") expected = "Houston" assert expected == result @@ -718,10 +736,16 @@ def test_heuristic_apply_builtin_args_1(): def test_heuristic_apply_builtin_args_2(): result = py("round", "2.984375") - expected = dedent(""" - [PYFLYBY] round(2.984375) - 3.0 - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] round(2.984375) + 3.0 + """).strip() + else: + expected = dedent(""" + [PYFLYBY] round(2.984375) + 3 + """).strip() assert expected == result @@ -821,7 +845,10 @@ def test_heuristic_apply_method_arg_1(): def test_apply_builtin_too_few_args_1(): result, retcode = py("round") assert retcode == 1 - assert "TypeError: Required argument 'number' (pos 1) not found" in result + if PY2: + assert "TypeError: Required argument 'number' (pos 1) not found" in result + else: + assert "TypeError: round() missing required argument 'number' (pos 1)" in result def test_apply_builtin_too_many_args_1(): @@ -890,7 +917,10 @@ def test_apply_argspec_too_few_args_1(): result, retcode = py("base64.b64decode") assert retcode == 1 assert "[PYFLYBY] missing required argument s" in result - assert "$ py base64.b64decode s [altchars]" in result + if PY2: + assert "$ py base64.b64decode s [altchars]" in result, result + else: + assert "$ py base64.b64decode s [altchars [validate]]" in result def test_apply_argspec_too_few_args_2(): @@ -901,12 +931,18 @@ def test_apply_argspec_too_few_args_2(): def test_apply_argspec_too_many_args_1(): - result, retcode = py("base64.b64decode", "a", "b", "c") + result, retcode = py("base64.b64decode", "a", "b", "c", "d") assert retcode == 1 - assert ("[PYFLYBY] Too many positional arguments. " - "Expected 1-2 positional argument(s): s, altchars. " - "Got 3 args: a b c") in result - assert "$ py base64.b64decode s [altchars]" in result + if PY2: + assert ("[PYFLYBY] Too many positional arguments. " + "Expected 1-2 positional argument(s): s, altchars. " + "Got 4 args: a b c d") in result, result + assert "$ py base64.b64decode s [altchars]" in result + else: + assert ("[PYFLYBY] Too many positional arguments. " + "Expected 1-3 positional argument(s): s, altchars, validate. " + "Got 4 args: a b c d") in result, result + assert "$ py base64.b64decode s [altchars [validate]]" in result def test_apply_argspec_too_many_args_2(): @@ -922,7 +958,10 @@ def test_apply_argspec_bad_kwarg_1(): result, retcode = py("base64.b64decode", "x", "--christopher=sheridan") assert retcode == 1 assert "[PYFLYBY] Unknown option name christopher" in result - assert "$ py base64.b64decode s [altchars]" in result + if PY2: + assert "$ py base64.b64decode s [altchars]" in result + else: + assert "$ py base64.b64decode s [altchars [validate]]" in result def test_apply_dashdash_1(): @@ -954,6 +993,9 @@ def test_repr_str_1(): assert expected == result +@pytest.mark.skipif( + PY3, + reason="Long integers are not valid syntax in Python 3.") def test_repr_long_1(): result = py("5L") expected = dedent(""" @@ -982,9 +1024,9 @@ def test_integer_division_1(): def test_print_statement_1(): - result = py("print 42") + result = py("print(42)") expected = dedent(""" - [PYFLYBY] print 42 + [PYFLYBY] print(42) 42 """).strip() assert expected == result @@ -992,10 +1034,16 @@ def test_print_statement_1(): def test_print_statement_sep_1(): result = py("print", "43") - expected = dedent(""" - [PYFLYBY] print 43 - 43 - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] print 43 + 43 + """).strip() + else: + expected = dedent(""" + [PYFLYBY] print(43) + 43 + """).strip() assert expected == result @@ -1019,8 +1067,8 @@ def test_print_function_tuple_1(): def test_write_1(): - with NamedTemporaryFile() as f: - output = py("-q", "open(%r,'w').write"%f.name, "-", stdin="Greenwich") + with NamedTemporaryFile(mode='w+') as f: + output = py("--output=silent", "-q", "open(%r,'w').write"%f.name, "-", stdin=b"Greenwich") assert output == "" result = f.read() expected = "Greenwich" @@ -1028,9 +1076,9 @@ def test_write_1(): def test_print_args_1(): - with NamedTemporaryFile() as f: + with NamedTemporaryFile(mode='w+') as f: output = py("-q", "print", "-", "--file=open(%r,'w')"%f.name, - stdin="Spring") + stdin=b"Spring") assert output == "" result = f.read() expected = "Spring\n" @@ -1051,12 +1099,15 @@ def test_program_help_full_1(): def test_function_help_1(): output = py("base64.b64encode", "--help") assert "s [altchars]" in output - assert "--version" not in output - assert "[PYFLYBY] import base64" in output assert ">>> base64.b64encode(s, altchars=None)" in output assert "$ py base64.b64encode s [altchars]" in output assert "$ py base64.b64encode --s=... [--altchars=...]" in output - assert "\n Encode a string using Base64." in output + assert "--version" not in output + assert "[PYFLYBY] import base64" in output + if PY2: + assert "\n Encode a string using Base64." in output + else: + assert "\n Encode the bytes-like object s using Base64 and return a bytes object." in output assert "binascii.b2a_base64" not in output @@ -1072,7 +1123,10 @@ def test_function_help_expression_1(): output = py("sys.stdout.write", "--help") assert '>>> sys.stdout.write(' in output assert "$ py sys.stdout.write " in output - assert "Write string str to file." in output + if PY2: + assert "Write string str to file." in output, output + else: + assert "Write string to stream." in output, output def test_function_help_quote_need_parens_1(): @@ -1181,7 +1235,10 @@ def test_function_source_1(): assert "$ py base64.b64encode s [altchars]" in output assert "$ py base64.b64encode --s=... [--altchars=...]" in output assert "binascii.b2a_base64" in output # from source code - assert output.count("Encode a string using Base64") == 1 + if PY2: + assert output.count("Encode a string using Base64") == 1, output + else: + assert output.count("Encode the bytes-like object s using Base64 and return a bytes object.") == 1 assert "--version" not in output @@ -1192,7 +1249,10 @@ def test_function_source_autoimport_1(): assert "$ py b64encode s [altchars]" in output assert "$ py b64encode --s=... [--altchars=...]" in output assert "binascii.b2a_base64" in output # from source code - assert output.count("Encode a string using Base64") == 1 + if PY2: + assert output.count("Encode a string using Base64") == 1, output + else: + assert output.count("Encode the bytes-like object s using Base64 and return a bytes object.") == 1 @pytest.mark.parametrize("cmdline", [ @@ -1256,7 +1316,7 @@ def test_module_help_1(): ]) def test_module_help_variants_1(args): output = py(args.split()) - assert "RFC 3548" in output + assert "RFC 3548" in output, output assert "import binascii" not in output @@ -1500,7 +1560,7 @@ def test_kwargs_no_dashdash_1(): [PYFLYBY] (lambda *a,**k: (a,k))(3.5, foo=7.5) ((3.5,), {'foo': 7.5}) """).strip() - assert expected == result + assert expected == result, result def test_kwargs_dashdash_1(): @@ -1524,20 +1584,50 @@ def test_joinstr_1(): def test_print_joinstr_1(): result = py("print", "3", "+", "5") - expected = dedent(""" - [PYFLYBY] print 3 + 5 - 8 - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] print 3 + 5 + 8 + """).strip() + else: + expected = dedent(""" + [PYFLYBY] print(3, '+', 5) + 3 + 5 + """).strip() + assert expected == result + +def test_print_joinstr_2(): + result = py("print", "3 + 5") + if PY2: + expected = dedent(""" + [PYFLYBY] print 3 + 5 + 8 + """).strip() + else: + expected = dedent(""" + [PYFLYBY] print(8) + 8 + """).strip() assert expected == result def test_join_single_arg_1(): result = py("print", "sys") - expected = dedent(""" - [PYFLYBY] import sys - [PYFLYBY] print sys - - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] import sys + [PYFLYBY] print sys + + """).strip() + else: + # In autocall mode, the arguments are evaluated and the repr() is + # printed for the [PYFLYBY] line + expected = dedent(""" + [PYFLYBY] import sys + [PYFLYBY] print() + + """).strip() + assert expected == result @@ -1940,7 +2030,7 @@ def test_outputmode_bad_1(): def test_run_module_1(): - result = py("-m", "base64", "-d", "-", stdin="VHJpbml0eQ==") + result = py("-m", "base64", "-d", "-", stdin=b"VHJpbml0eQ==") expected = dedent(""" [PYFLYBY] python -m base64 -d - Trinity @@ -1957,7 +2047,7 @@ def test_run_module_1(): "-mbase64", ]) def test_run_module_variants_1(args): - result = py((args + " -d -").split(), stdin="VHJvdXRtYW4=") + result = py((args + " -d -").split(), stdin=b"VHJvdXRtYW4=") expected = dedent(""" [PYFLYBY] python -m base64 -d - Troutman @@ -1969,9 +2059,10 @@ def test_run_module_argstr_1(tmp): # runmodule. Verify that arguments are strings and not evaluated, and # verify that argv works correctly. writetext(tmp.dir/"odule12786636.py", """ + from __future__ import print_function if __name__ == "__main__": import sys - print "Rector", sys.argv + print("Rector", sys.argv) """) result = py("-module12786636", "22524739.000", "math", PYTHONPATH=tmp.dir) @@ -1983,7 +2074,7 @@ def test_run_module_argstr_1(tmp): def test_run_module_under_package_1(tmp): - result = py("encodings.rot_13", stdin="Tenavgr") + result = py("encodings.rot_13", stdin=b"Tenavgr") expected = dedent(""" [PYFLYBY] import encodings [PYFLYBY] python -m encodings.rot_13 @@ -1993,7 +2084,7 @@ def test_run_module_under_package_1(tmp): def test_heuristic_run_module_1(): - result = py("base64", "-d", "-", stdin="U2VuZWNh") + result = py("base64", "-d", "-", stdin=b"U2VuZWNh") expected = dedent(""" [PYFLYBY] python -m base64 -d - Seneca @@ -2002,7 +2093,7 @@ def test_heuristic_run_module_1(): def test_heuristic_run_module_under_package_1(tmp): - result = py("encodings.rot_13", stdin="Qrpxre") + result = py("encodings.rot_13", stdin=b"Qrpxre") expected = dedent(""" [PYFLYBY] import encodings [PYFLYBY] python -m encodings.rot_13 @@ -2015,13 +2106,16 @@ def test_heuristic_run_module_under_package_2(tmp): os.mkdir("%s/bard22402805" % tmp.dir) os.mkdir("%s/bard22402805/douglas" % tmp.dir) writetext(tmp.dir/"bard22402805/__init__.py", """ - print 'Davis', __name__ + from __future__ import print_function + print('Davis', __name__) """) writetext(tmp.dir/"bard22402805/douglas/__init__.py", """ - print 'Clove', __name__ + from __future__ import print_function + print('Clove', __name__) """) writetext(tmp.dir/"bard22402805/douglas/thames.py", """ - print 'Huron', __name__ + from __future__ import print_function + print('Huron', __name__) """) result = py("bard22402805.douglas.thames", PYTHONPATH=tmp.dir) expected = dedent(""" @@ -2039,7 +2133,7 @@ def test_heuristic_run_module_no_auto_import_1(tmp): # Heuristic runmodule. Verify that we don't auto-import anything. writetext(tmp.dir/"vernon84909775.py", """ if __name__ == "__main__": - print 'Basin' + print('Basin') os # expect NameError """) result, retcode = py("vernon84909775", PYTHONPATH=tmp.dir) @@ -2066,16 +2160,20 @@ def test_heuristic_run_module_importerror_1(tmp): """) result, retcode = py("griswold73262001", PYTHONPATH=tmp.dir) assert retcode == 1 - assert result.endswith("ImportError: No module named whitewood62047754") + if PY2: + assert result.endswith("ImportError: No module named whitewood62047754") + else: + assert result.endswith("ModuleNotFoundError: No module named 'whitewood62047754'") def test_heuristic_run_module_argstr_1(tmp): # Heuristic runmodule. Verify that arguments are strings and not # evaluated, and verify that argv works correctly. writetext(tmp.dir/"gantry20720070.py", """ + from __future__ import print_function if __name__ == "__main__": import sys - print "Belmont", sys.argv + print("Belmont", sys.argv) """) result = py("gantry20720070", "26792622.000", "math", PYTHONPATH=tmp.dir) @@ -2089,13 +2187,19 @@ def test_heuristic_run_module_argstr_1(tmp): def test_builtin_no_run_module_1(tmp): # Verify that builtins take precedence over modules. writetext(tmp.dir/"round.py", """ - print 'bad morrison56321353' + print('bad morrison56321353') """) result = py("round", "17534159.5", PYTHONPATH=tmp.dir) - expected = dedent(""" - [PYFLYBY] round(17534159.5) - 17534160.0 - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] round(17534159.5) + 17534160.0 + """).strip() + else: + expected = dedent(""" + [PYFLYBY] round(17534159.5) + 17534160 + """).strip() assert expected == result @@ -2103,7 +2207,8 @@ def test_run_module_no_superfluous_import_1(tmp): # Verify that 'py -m foomodule' doesn't cause a superfluous import of # foomodule. writetext(tmp.dir/"delafield47227231.py", """ - print 'Oakwood', __name__ + from __future__ import print_function + print('Oakwood', __name__) """) result = py("-m", "delafield47227231", PYTHONPATH=tmp.dir) expected = dedent(""" @@ -2117,7 +2222,8 @@ def test_heuristic_run_module_no_superfluous_import_1(tmp): # Verify that 'py foomodule' doesn't cause a superfluous import of # foomodule. writetext(tmp.dir/"pelton58495419.py", """ - print 'Bement', __name__ + from __future__ import print_function + print('Bement', __name__) """) result = py("pelton58495419", PYTHONPATH=tmp.dir) expected = dedent(""" @@ -2171,20 +2277,33 @@ def test_safe_fully_qualified_module_1(tmp): def test_unsafe_args_1(): result = py("type", "sys") - expected = dedent(""" - [PYFLYBY] import sys - [PYFLYBY] type() - - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] import sys + [PYFLYBY] type() + + """).strip() + else: + expected = dedent(""" + [PYFLYBY] import sys + [PYFLYBY] type() + + """).strip() assert expected == result def test_safe_args_1(): result = py("--safe", "type", "sys") - expected = dedent(""" - [PYFLYBY] type('sys') - - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] type('sys') + + """).strip() + else: + expected = dedent(""" + [PYFLYBY] type('sys') + + """).strip() assert expected == result @@ -2206,16 +2325,6 @@ def test_safe_dashdash_1(): assert expected == result -def test_unsafe_concat_1(): - result = py("print", "sys") - expected = dedent(""" - [PYFLYBY] import sys - [PYFLYBY] print sys - - """).strip() - assert expected == result - - def test_safe_no_concat_1(): result = py("--safe", "print", "sys") expected = dedent(""" @@ -2255,13 +2364,14 @@ def test_argmode_auto_no_concat_1(): def test_exec_stdin_print_statement_1(): - result = py(stdin="print 'Carnegie'") + # This is still a print statement in Python 2 (with redundant parentheses) + result = py(stdin=b"print('Carnegie')") expected = "Carnegie" assert expected == result def test_exec_stdin_print_function_1(): - result = py(stdin="print('Sinai', file=sys.stdout)") + result = py(stdin=b"print('Sinai', file=sys.stdout)") expected = dedent(""" [PYFLYBY] import sys Sinai @@ -2270,12 +2380,12 @@ def test_exec_stdin_print_function_1(): def test_exec_stdin_noresult_1(): - result = py(stdin="42") + result = py(stdin=b"42") assert "" == result def test_argv_stdin_noarg_1(): - result = py(stdin="print sys.argv") + result = py(stdin=b"print(sys.argv)") expected = dedent(""" [PYFLYBY] import sys [''] @@ -2284,7 +2394,7 @@ def test_argv_stdin_noarg_1(): def test_argv_stdin_dash_1(): - result = py("-", stdin="print sys.argv") + result = py("-", stdin=b"print(sys.argv)") expected = dedent(""" [PYFLYBY] import sys ['-'] @@ -2293,7 +2403,7 @@ def test_argv_stdin_dash_1(): def test_argv_stdin_dash_args_1(): - result = py("-", "sys", stdin="print sys.argv") + result = py("-", "sys", stdin=b"print(sys.argv)") expected = dedent(""" [PYFLYBY] import sys ['-', 'sys'] @@ -2442,7 +2552,7 @@ def test_output_exit_variants_1(args): def test_info_function_simple_1(): - result = py('sys.stdout.write', 'Franklin') + result = py('--output=silent', 'sys.stdout.write', 'Franklin') expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] sys.stdout.write('Franklin') @@ -2452,7 +2562,7 @@ def test_info_function_simple_1(): def test_info_function_lambda_1(): - result = py('(lambda: sys.stdout.write)()', 'Chambers') + result = py('--output=silent', '(lambda: sys.stdout.write)()', 'Chambers') expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] (lambda: sys.stdout.write)() @@ -2472,14 +2582,22 @@ def test_ctypes_1(): libname = "libc.dylib" else: raise AssertionError - result = py('--output=silent', 'ctypes.CDLL("%s").printf'%libname, "%03d", "7") - expected = dedent(""" - [PYFLYBY] import ctypes - [PYFLYBY] ctypes.CDLL("{libname}").printf - [PYFLYBY] ctypes.CDLL("{libname}").printf('%03d', 7) - 007 - """).strip().format(libname=libname) - assert expected == result + result = py('--output=silent', 'ctypes.CDLL("%s").printf'%libname, "b'%03d'", "7") + if PY2: + expected = dedent(""" + [PYFLYBY] import ctypes + [PYFLYBY] ctypes.CDLL("{libname}").printf + [PYFLYBY] ctypes.CDLL("{libname}").printf('%03d', 7) + 007 + """).strip().format(libname=libname) + else: + expected = dedent(""" + [PYFLYBY] import ctypes + [PYFLYBY] ctypes.CDLL("{libname}").printf + [PYFLYBY] ctypes.CDLL("{libname}").printf(b'%03d', 7) + 007 + """).strip().format(libname=libname) + assert expected == result, repr(result) def test_name_eval_1(): @@ -2501,7 +2619,7 @@ def test_name_heuristic_eval_1(): def test_name_heuristic_apply_eval_1(): - result = py("sys.stdout.write", "__name__") + result = py("--output=silent", "sys.stdout.write", "__name__") expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] sys.stdout.write('__main__') @@ -2512,32 +2630,38 @@ def test_name_heuristic_apply_eval_1(): def test_name_heuristic_join_eval_1(): result = py("print", "'Castle'", ",", "__name__") - expected = dedent(""" - [PYFLYBY] print 'Castle' , __name__ - Castle __main__ - """).strip() + if PY2: + expected = dedent(""" + [PYFLYBY] print 'Castle' , __name__ + Castle __main__ + """).strip() + else: + expected = dedent(""" + [PYFLYBY] print('Castle', ',', '__main__') + Castle , __main__ + """).strip() assert expected == result def test_name_stdin_1(): - result = py(stdin="print 'Winter', __name__") + result = py(stdin=b"print(('Winter', __name__))") expected = dedent(""" - Winter __main__ + ('Winter', '__main__') """).strip() assert expected == result def test_name_dash_stdin_1(): - result = py("-", stdin="print 'Victory', __name__") + result = py("-", stdin=b"print(('Victory', __name__))") expected = dedent(""" - Victory __main__ + ('Victory', '__main__') """).strip() assert expected == result def test_name_file_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: - f.write('print "Forest", __name__\n') + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: + f.write('from __future__ import print_function\nprint("Forest", __name__)\n') f.flush() result = py("--file", f.name) expected = dedent(""" @@ -2547,8 +2671,8 @@ def test_name_file_1(): def test_name_heuristic_file_1(): - with tempfile.NamedTemporaryFile(suffix=".py") as f: - f.write('print "Oakland", __name__\n') + with tempfile.NamedTemporaryFile(suffix=".py", mode='w+') as f: + f.write('from __future__ import print_function\nprint("Oakland", __name__)\n') f.flush() result = py(f.name) expected = dedent(""" @@ -2561,7 +2685,8 @@ def test_name_module_1(tmp): # Verify that 'py -m modulename' works. Also verify that we don't import the # module by name before run_module. writetext(tmp.dir/"swan80274886.py", """ - print 'Lafayette', __name__ + from __future__ import print_function + print('Lafayette', __name__) """) result = py("-m", "swan80274886", PYTHONPATH=tmp.dir) expected = dedent(""" @@ -2575,7 +2700,8 @@ def test_name_heuristic_module_1(tmp): # Verify that 'py modulename' works. Also verify that we don't import the # module by name before run_module. writetext(tmp.dir/"arnold17339681.py", """ - print 'Hendricks', __name__ + from __future__ import print_function + print('Hendricks', __name__) """) result = py("arnold17339681", PYTHONPATH=tmp.dir) expected = dedent(""" @@ -2610,8 +2736,9 @@ def test_single_char_arg0_1(tmp, m): # Verify that single characters without dashes aren't treated as options, # i.e. verify that we don't treat e.g. "py c 42" as "py -c 42". writetext(tmp.dir/("%s.py"%m), """ + from __future__ import print_function import sys - print 'Hayward', sys.argv[1] + print('Hayward', sys.argv[1]) """) result = py(m, "42", PYTHONPATH=tmp.dir) expected = dedent(""" @@ -2622,7 +2749,7 @@ def test_single_char_arg0_1(tmp, m): def test_auto_arg_goodname_1(): - result = py("sys.stdout.write", "os.path.sep") + result = py("--output=silent", "sys.stdout.write", "os.path.sep") expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] import os.path @@ -2633,7 +2760,7 @@ def test_auto_arg_goodname_1(): def test_auto_arg_badname_1(): - result = py("sys.stdout.write", "Burnside55731946.Valentine") + result = py("--output=silent", "sys.stdout.write", "Burnside55731946.Valentine") expected = dedent(""" [PYFLYBY] import sys [PYFLYBY] sys.stdout.write('Burnside55731946.Valentine') @@ -2650,7 +2777,7 @@ def sedgwick(self): return 'arden' creston = Creston() """) - result = py("sys.stdout.write", "quarry47946518.creston.sedgwick", + result = py("--output=silent", "sys.stdout.write", "quarry47946518.creston.sedgwick", PYTHONPATH=tmp.dir) expected = dedent(""" [PYFLYBY] import sys @@ -2676,7 +2803,10 @@ def cortlandt(self): PYTHONPATH=tmp.dir) assert retcode == 1 assert "[PYFLYBY] import kingsbridge90850275\n" in result - assert "NameError: global name 'Woodlawn' is not defined" in result + if PY2: + assert "NameError: global name 'Woodlawn' is not defined" in result + else: + assert "NameError: name 'Woodlawn' is not defined" in result @pytest.mark.xfail # TODO FIXME @@ -2710,6 +2840,8 @@ def test_function_defaults_1(tmp): # Verify that default values get passed through without being disturbed, # without being round-tripped through strings, etc. writetext(tmp.dir / "dobbin69118865.py", """ + from __future__ import print_function + class X(object): _ctr = 0 def __init__(self): @@ -2720,7 +2852,7 @@ def __repr__(self): def __str__(self): return "<>" % (self._i) def meserole(a, b, c=X(), d="77610270.000", e=None, f=X()): - print a, b, c, d, e, f + print(a, b, c, d, e, f) """) result = py("dobbin69118865.meserole", "java", "'kent'", "--e=paidge", PYTHONPATH=tmp.dir) diff --git a/tests/test_util.py b/tests/test_util.py index 76b23786..37d87abb 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -3,7 +3,8 @@ # License for THIS FILE ONLY: CC0 Public Domain Dedication # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) from pyflyby._util import (longest_common_prefix, partition, prefixes, stable_unique) diff --git a/tests/xrefs.py b/tests/xrefs.py index 6afacf22..6b29952f 100644 --- a/tests/xrefs.py +++ b/tests/xrefs.py @@ -10,9 +10,10 @@ # http://creativecommons.org/publicdomain/zero/1.0/ -from __future__ import absolute_import, division, with_statement +from __future__ import (absolute_import, division, print_function, + with_statement) -class FooClass: +class FooClass(object): """ Blah. L{undefined_xref_from_class}