# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function import pprint import tempfile from subprocess import Popen, PIPE import os from libfuturize.fixer_util import is_shebang_comment, is_encoding_comment from lib2to3.fixer_util import FromImport from lib2to3.pytree import Leaf, Node from lib2to3.pygram import token from future.tests.base import (CodeHandler, unittest, skip26, reformat_code, order_future_lines, expectedFailurePY26) from future.utils import PY2 class TestLibFuturize(unittest.TestCase): @skip26 # mysterious sporadic UnicodeDecodeError raised by lib2to3 ... def test_correct_exit_status(self): """ Issue #119: futurize and pasteurize were not exiting with the correct status code. This is because the status code returned from libfuturize.main.main() etc. was a ``newint``, which sys.exit() always translates into 1! """ from libfuturize.main import main # Try futurizing this test script: retcode = main([__file__]) self.assertTrue(isinstance(retcode, int)) # i.e. Py2 builtin int def test_is_shebang_comment(self): """ Tests whether the fixer_util.is_encoding_comment() function is working. """ shebang_comments = [u'#!/usr/bin/env python\n' u"#!/usr/bin/python2\n", u"#! /usr/bin/python3\n", ] not_shebang_comments = [u"# I saw a giant python\n", u"# I have never seen a python2\n", ] for comment in shebang_comments: node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) node.prefix = comment self.assertTrue(is_shebang_comment(node)) for comment in not_shebang_comments: node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) node.prefix = comment self.assertFalse(is_shebang_comment(node)) def test_is_encoding_comment(self): """ Tests whether the fixer_util.is_encoding_comment() function is working. """ encoding_comments = [u"# coding: utf-8", u"# encoding: utf-8", u"# -*- coding: latin-1 -*-", u"# vim: set fileencoding=iso-8859-15 :", ] not_encoding_comments = [u"# We use the file encoding utf-8", u"coding = 'utf-8'", u"encoding = 'utf-8'", ] for comment in encoding_comments: node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) node.prefix = comment self.assertTrue(is_encoding_comment(node)) for comment in not_encoding_comments: node = FromImport(u'math', [Leaf(token.NAME, u'cos', prefix=" ")]) node.prefix = comment self.assertFalse(is_encoding_comment(node)) class TestFuturizeSimple(CodeHandler): """ This class contains snippets of Python 2 code (invalid Python 3) and tests for whether they can be passed to ``futurize`` and immediately run under both Python 2 again and Python 3. """ def setUp(self): self.tempdir = tempfile.mkdtemp() + os.path.sep super(TestFuturizeSimple, self).setUp() def test_encoding_comments_kept_at_top(self): """ Issues #10 and #97: If there is a source encoding comment line (PEP 263), is it kept at the top of a module by ``futurize``? """ before = """ # coding=utf-8 print 'Hello' """ after = """ # coding=utf-8 from __future__ import print_function print('Hello') """ self.convert_check(before, after) before = """ #!/usr/bin/env python # -*- coding: latin-1 -*-" print 'Hello' """ after = """ #!/usr/bin/env python # -*- coding: latin-1 -*-" from __future__ import print_function print('Hello') """ self.convert_check(before, after) def test_shebang_blank_with_future_division_import(self): """ Issue #43: Is shebang line preserved as the first line by futurize when followed by a blank line? """ before = """ #!/usr/bin/env python import math 1 / 5 """ after = """ #!/usr/bin/env python from __future__ import division from past.utils import old_div import math old_div(1, 5) """ self.convert_check(before, after) def test_shebang_blank_with_print_import(self): before = """ #!/usr/bin/env python import math print 'Hello' """ after = """ #!/usr/bin/env python from __future__ import print_function import math print('Hello') """ self.convert_check(before, after) def test_shebang_comment(self): """ Issue #43: Is shebang line preserved as the first line by futurize when followed by a comment? """ before = """ #!/usr/bin/env python # some comments # and more comments import math print 'Hello!' """ after = """ #!/usr/bin/env python # some comments # and more comments from __future__ import print_function import math print('Hello!') """ self.convert_check(before, after) def test_shebang_docstring(self): """ Issue #43: Is shebang line preserved as the first line by futurize when followed by a docstring? """ before = ''' #!/usr/bin/env python """ a doc string """ import math print 'Hello!' ''' after = ''' #!/usr/bin/env python """ a doc string """ from __future__ import print_function import math print('Hello!') ''' self.convert_check(before, after) def test_oldstyle_classes(self): """ Stage 2 should convert old-style to new-style classes. This makes the new-style class explicit and reduces the gap between the behaviour (e.g. method resolution order) on Py2 and Py3. It also allows us to provide ``newobject`` (see test_oldstyle_classes_iterator). """ before = """ class Blah: pass """ after = """ from builtins import object class Blah(object): pass """ self.convert_check(before, after, ignore_imports=False) def test_oldstyle_classes_iterator(self): """ An old-style class used as an iterator should be converted properly. This requires ``futurize`` to do both steps (adding inheritance from object and adding the newobject import) in the right order. Any next() method should also be renamed to __next__. """ before = """ class Upper: def __init__(self, iterable): self._iter = iter(iterable) def next(self): return next(self._iter).upper() def __iter__(self): return self assert list(Upper('hello')) == list('HELLO') """ after = """ from builtins import next from builtins import object class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) def __next__(self): return next(self._iter).upper() def __iter__(self): return self assert list(Upper('hello')) == list('HELLO') """ self.convert_check(before, after, ignore_imports=False) # Try it again with this convention: class Upper(): before2 = """ class Upper(): def __init__(self, iterable): self._iter = iter(iterable) def next(self): return next(self._iter).upper() def __iter__(self): return self assert list(Upper('hello')) == list('HELLO') """ self.convert_check(before2, after) @unittest.expectedFailure def test_problematic_string(self): """ This string generates a SyntaxError on Python 3 unless it has an r prefix. """ before = r""" s = 'The folder is "C:\Users"'. """ after = r""" s = r'The folder is "C:\Users"'. """ self.convert_check(before, after) @unittest.skip('--tobytes feature removed for now ...') def test_tobytes(self): """ The --tobytes option converts all UNADORNED string literals 'abcd' to b'abcd'. It does apply to multi-line strings but doesn't apply if it's a raw string, because ur'abcd' is a SyntaxError on Python 2 and br'abcd' is a SyntaxError on Python 3. """ before = r""" s0 = '1234' s1 = '''5678 ''' s2 = "9abc" # Unchanged: s3 = r'1234' s4 = R"defg" s5 = u'hijk' s6 = u"lmno" s7 = b'lmno' s8 = b"pqrs" """ after = r""" s0 = b'1234' s1 = b'''5678 ''' s2 = b"9abc" # Unchanged: s3 = r'1234' s4 = R"defg" s5 = u'hijk' s6 = u"lmno" s7 = b'lmno' s8 = b"pqrs" """ self.convert_check(before, after, tobytes=True) def test_cmp(self): before = """ assert cmp(1, 2) == -1 assert cmp(2, 1) == 1 """ after = """ from past.builtins import cmp assert cmp(1, 2) == -1 assert cmp(2, 1) == 1 """ self.convert_check(before, after, stages=(1, 2), ignore_imports=False) def test_execfile(self): before = """ with open('mytempfile.py', 'w') as f: f.write('x = 1') execfile('mytempfile.py') x += 1 assert x == 2 """ after = """ from past.builtins import execfile with open('mytempfile.py', 'w') as f: f.write('x = 1') execfile('mytempfile.py') x += 1 assert x == 2 """ self.convert_check(before, after, stages=(1, 2), ignore_imports=False) @unittest.expectedFailure def test_izip(self): before = """ from itertools import izip for (a, b) in izip([1, 3, 5], [2, 4, 6]): pass """ after = """ from builtins import zip for (a, b) in zip([1, 3, 5], [2, 4, 6]): pass """ self.convert_check(before, after, stages=(1, 2), ignore_imports=False) def test_UserList(self): before = """ from UserList import UserList a = UserList([1, 3, 5]) assert len(a) == 3 """ after = """ from collections import UserList a = UserList([1, 3, 5]) assert len(a) == 3 """ self.convert_check(before, after, stages=(1, 2), ignore_imports=True) @unittest.expectedFailure def test_no_unneeded_list_calls(self): """ TODO: get this working """ code = """ for (a, b) in zip(range(3), range(3, 6)): pass """ self.unchanged(code) @expectedFailurePY26 def test_import_builtins(self): before = """ a = raw_input() b = open(a, b, c) c = filter(a, b) d = map(a, b) e = isinstance(a, str) f = bytes(a, encoding='utf-8') for g in xrange(10**10): pass h = reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) super(MyClass, self) """ after = """ from builtins import bytes from builtins import filter from builtins import input from builtins import map from builtins import range from functools import reduce a = input() b = open(a, b, c) c = list(filter(a, b)) d = list(map(a, b)) e = isinstance(a, str) f = bytes(a, encoding='utf-8') for g in range(10**10): pass h = reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) super(MyClass, self) """ self.convert_check(before, after, ignore_imports=False, run=False) def test_xrange(self): """ The ``from builtins import range`` line was being added to the bottom of the file as of v0.11.4, but only using Py2.7's lib2to3. (Py3.3's lib2to3 seems to work.) """ before = """ for i in xrange(10): pass """ after = """ from builtins import range for i in range(10): pass """ self.convert_check(before, after, ignore_imports=False) def test_source_coding_utf8(self): """ Tests to ensure that the source coding line is not corrupted or removed. It must be left as the first line in the file (including before any __future__ imports). Also tests whether the unicode characters in this encoding are parsed correctly and left alone. """ code = """ # -*- coding: utf-8 -*- icons = [u"◐", u"◓", u"◑", u"◒"] """ self.unchanged(code) def test_exception_syntax(self): """ Test of whether futurize handles the old-style exception syntax """ before = """ try: pass except IOError, e: val = e.errno """ after = """ try: pass except IOError as e: val = e.errno """ self.convert_check(before, after) def test_super(self): """ This tests whether futurize keeps the old two-argument super() calls the same as before. It should, because this still works in Py3. """ code = ''' class VerboseList(list): def append(self, item): print('Adding an item') super(VerboseList, self).append(item) ''' self.unchanged(code) @unittest.expectedFailure def test_file(self): """ file() as a synonym for open() is obsolete and invalid on Python 3. """ before = ''' f = file(__file__) data = f.read() f.close() ''' after = ''' f = open(__file__) data = f.read() f.close() ''' self.convert_check(before, after) def test_apply(self): before = ''' def addup(*x): return sum(x) assert apply(addup, (10,20)) == 30 ''' after = """ def addup(*x): return sum(x) assert addup(*(10,20)) == 30 """ self.convert_check(before, after) @unittest.skip('not implemented yet') def test_download_pypi_package_and_test(self): URL = 'http://pypi.python.org/pypi/{0}/json' import requests package = 'future' r = requests.get(URL.format(package)) pprint.pprint(r.json()) download_url = r.json()['urls'][0]['url'] filename = r.json()['urls'][0]['filename'] # r2 = requests.get(download_url) # with open('/tmp/' + filename, 'w') as tarball: # tarball.write(r2.content) @expectedFailurePY26 def test_raw_input(self): """ Passes in a string to the waiting input() after futurize conversion. The code is the first snippet from these docs: http://docs.python.org/2/library/2to3.html """ before = """ from io import BytesIO def greet(name): print "Hello, {0}!".format(name) print "What's your name?" import sys oldstdin = sys.stdin sys.stdin = BytesIO(b'Ed\\n') name = raw_input() greet(name.decode()) sys.stdin = oldstdin assert name == b'Ed' """ desired = """ from io import BytesIO def greet(name): print("Hello, {0}!".format(name)) print("What's your name?") import sys oldstdin = sys.stdin sys.stdin = BytesIO(b'Ed\\n') name = input() greet(name.decode()) sys.stdin = oldstdin assert name == b'Ed' """ self.convert_check(before, desired, run=False) for interpreter in self.interpreters: p1 = Popen([interpreter, self.tempdir + 'mytestscript.py'], stdout=PIPE, stdin=PIPE, stderr=PIPE) (stdout, stderr) = p1.communicate(b'Ed') self.assertEqual(stderr, b'') self.assertEqual(stdout, b"What's your name?\nHello, Ed!\n") def test_literal_prefixes_are_not_stripped(self): """ Tests to ensure that the u'' and b'' prefixes on unicode strings and byte strings are not removed by the futurize script. Removing the prefixes on Py3.3+ is unnecessary and loses some information -- namely, that the strings have explicitly been marked as unicode or bytes, rather than just e.g. a guess by some automated tool about what they are. """ code = ''' s = u'unicode string' b = b'byte string' ''' self.unchanged(code) def test_division(self): before = """ x = 1 / 2 """ after = """ from past.utils import old_div x = old_div(1, 2) """ self.convert_check(before, after, stages=[1, 2]) def test_already_future_division(self): code = """ from __future__ import division x = 1 / 2 assert x == 0.5 y = 3. / 2. assert y == 1.5 """ self.unchanged(code) class TestFuturizeRenamedStdlib(CodeHandler): @unittest.skip('Infinite loop?') def test_renamed_modules(self): before = """ import ConfigParser import copy_reg import cPickle import cStringIO """ after = """ import configparser import copyreg import pickle import io """ self.convert_check(before, after) @unittest.skip('Not working yet ...') def test_urllib_refactor(self): # Code like this using urllib is refactored by futurize --stage2 to use # the new Py3 module names, but ``future`` doesn't support urllib yet. before = """ import urllib URL = 'http://pypi.python.org/pypi/future/json' package = 'future' r = urllib.urlopen(URL.format(package)) data = r.read() """ after = """ from future import standard_library standard_library.install_aliases() import urllib.request URL = 'http://pypi.python.org/pypi/future/json' package = 'future' r = urllib.request.urlopen(URL.format(package)) data = r.read() """ self.convert_check(before, after) @unittest.skip('Infinite loop?') def test_renamed_copy_reg_and_cPickle_modules(self): """ Example from docs.python.org/2/library/copy_reg.html """ before = """ import copy_reg import copy import cPickle class C(object): def __init__(self, a): self.a = a def pickle_c(c): print('pickling a C instance...') return C, (c.a,) copy_reg.pickle(C, pickle_c) c = C(1) d = copy.copy(c) p = cPickle.dumps(c) """ after = """ import copyreg import copy import pickle class C(object): def __init__(self, a): self.a = a def pickle_c(c): print('pickling a C instance...') return C, (c.a,) copyreg.pickle(C, pickle_c) c = C(1) d = copy.copy(c) p = pickle.dumps(c) """ self.convert_check(before, after) @unittest.expectedFailure def test_Py2_StringIO_module(self): """ This requires that the argument to io.StringIO be made a unicode string explicitly if we're not using unicode_literals: Ideally, there would be a fixer for this. For now: TODO: add the Py3 equivalent for this to the docs. Also add back a test for the unicode_literals case. """ before = """ import cStringIO import StringIO s1 = cStringIO.StringIO('my string') s2 = StringIO.StringIO('my other string') assert isinstance(s1, cStringIO.InputType) """ # There is no io.InputType in Python 3. futurize should change this to # something like this. But note that the input to io.StringIO # must be a unicode string on both Py2 and Py3. after = """ import io import io s1 = io.StringIO(u'my string') s2 = io.StringIO(u'my other string') assert isinstance(s1, io.StringIO) """ self.convert_check(before, after) class TestFuturizeStage1(CodeHandler): """ Tests "stage 1": safe optimizations: modernizing Python 2 code so that it uses print functions, new-style exception syntax, etc. The behaviour should not change and this should introduce no dependency on the ``future`` package. It produces more modern Python 2-only code. The goal is to reduce the size of the real porting patch-set by performing the uncontroversial patches first. """ def test_apply(self): """ apply() should be changed by futurize --stage1 """ before = ''' def f(a, b): return a + b args = (1, 2) assert apply(f, args) == 3 assert apply(f, ('a', 'b')) == 'ab' ''' after = ''' def f(a, b): return a + b args = (1, 2) assert f(*args) == 3 assert f(*('a', 'b')) == 'ab' ''' self.convert_check(before, after, stages=[1]) def test_next_1(self): """ Custom next methods should not be converted to __next__ in stage1, but any obj.next() calls should be converted to next(obj). """ before = """ class Upper: def __init__(self, iterable): self._iter = iter(iterable) def next(self): # note the Py2 interface return next(self._iter).upper() def __iter__(self): return self itr = Upper('hello') assert itr.next() == 'H' assert next(itr) == 'E' assert list(itr) == list('LLO') """ after = """ class Upper: def __init__(self, iterable): self._iter = iter(iterable) def next(self): # note the Py2 interface return next(self._iter).upper() def __iter__(self): return self itr = Upper('hello') assert next(itr) == 'H' assert next(itr) == 'E' assert list(itr) == list('LLO') """ self.convert_check(before, after, stages=[1], run=PY2) @unittest.expectedFailure def test_next_2(self): """ This version of the above doesn't currently work: the self._iter.next() call in line 5 isn't converted to next(self._iter). """ before = """ class Upper: def __init__(self, iterable): self._iter = iter(iterable) def next(self): # note the Py2 interface return self._iter.next().upper() def __iter__(self): return self itr = Upper('hello') assert itr.next() == 'H' assert next(itr) == 'E' assert list(itr) == list('LLO') """ after = """ class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) def next(self): # note the Py2 interface return next(self._iter).upper() def __iter__(self): return self itr = Upper('hello') assert next(itr) == 'H' assert next(itr) == 'E' assert list(itr) == list('LLO') """ self.convert_check(before, after, stages=[1], run=PY2) def test_xrange(self): """ xrange should not be changed by futurize --stage1 """ code = ''' for i in xrange(10): pass ''' self.unchanged(code, stages=[1], run=PY2) @unittest.expectedFailure def test_absolute_import_changes(self): """ Implicit relative imports should be converted to absolute or explicit relative imports correctly. Issue #16 (with porting bokeh/bbmodel.py) """ with open(tempdir + 'specialmodels.py', 'w') as f: f.write('pass') before = """ import specialmodels.pandasmodel specialmodels.pandasmodel.blah() """ after = """ from __future__ import absolute_import from .specialmodels import pandasmodel pandasmodel.blah() """ self.convert_check(before, after, stages=[1]) def test_safe_futurize_imports(self): """ The standard library module names should not be changed until stage 2 """ before = """ import ConfigParser import HTMLParser from itertools import ifilterfalse ConfigParser.ConfigParser HTMLParser.HTMLParser assert list(ifilterfalse(lambda x: x % 2, [2, 4])) == [2, 4] """ self.unchanged(before, stages=[1], run=PY2) def test_print(self): before = """ print 'Hello' """ after = """ print('Hello') """ self.convert_check(before, after, stages=[1]) before = """ import sys print >> sys.stderr, 'Hello', 'world' """ after = """ import sys print('Hello', 'world', file=sys.stderr) """ self.convert_check(before, after, stages=[1]) def test_print_already_function(self): """ Running futurize --stage1 should not add a second set of parentheses """ before = """ print('Hello') """ self.unchanged(before, stages=[1]) @unittest.expectedFailure def test_print_already_function_complex(self): """ Running futurize --stage1 does add a second second set of parentheses in this case. This is because the underlying lib2to3 has two distinct grammars -- with a print statement and with a print function -- and, when going forwards (2 to both), futurize assumes print is a statement, which raises a ParseError. """ before = """ import sys print('Hello', 'world', file=sys.stderr) """ self.unchanged(before, stages=[1]) def test_exceptions(self): before = """ try: raise AttributeError('blah') except AttributeError, e: pass """ after = """ try: raise AttributeError('blah') except AttributeError as e: pass """ self.convert_check(before, after, stages=[1]) @unittest.expectedFailure def test_string_exceptions(self): """ 2to3 does not convert string exceptions: see http://python3porting.com/differences.html. """ before = """ try: raise "old string exception" except Exception, e: pass """ after = """ try: raise Exception("old string exception") except Exception as e: pass """ self.convert_check(before, after, stages=[1]) def test_oldstyle_classes(self): """ We don't convert old-style classes to new-style automatically in stage 1 (but we should in stage 2). So Blah should not inherit explicitly from object yet. """ before = """ class Blah: pass """ self.unchanged(before, stages=[1]) def test_stdlib_modules_not_changed(self): """ Standard library module names should not be changed in stage 1 """ before = """ import ConfigParser import HTMLParser import collections print 'Hello' try: raise AttributeError('blah') except AttributeError, e: pass """ after = """ import ConfigParser import HTMLParser import collections print('Hello') try: raise AttributeError('blah') except AttributeError as e: pass """ self.convert_check(before, after, stages=[1], run=PY2) def test_octal_literals(self): before = """ mode = 0644 """ after = """ mode = 0o644 """ self.convert_check(before, after) def test_long_int_literals(self): before = """ bignumber = 12345678901234567890L """ after = """ bignumber = 12345678901234567890 """ self.convert_check(before, after) def test___future___import_position(self): """ Issue #4: __future__ imports inserted too low in file: SyntaxError """ code = """ # Comments here # and here __version__=''' $Id$ ''' __doc__="A Sequencer class counts things. It aids numbering and formatting lists." __all__='Sequencer getSequencer setSequencer'.split() # # another comment # CONSTANTS = [ 0, 01, 011, 0111, 012, 02, 021, 0211, 02111, 013 ] _RN_LETTERS = "IVXLCDM" def my_func(value): pass ''' Docstring-like comment here ''' """ self.convert(code) def test_issue_45(self): """ Tests whether running futurize -f libfuturize.fixes.fix_future_standard_library_urllib on the code below causes a ValueError (issue #45). """ code = r""" from __future__ import print_function from urllib import urlopen, urlencode oeis_url = 'http://oeis.org/' def _fetch(url): try: f = urlopen(url) result = f.read() f.close() return result except IOError as msg: raise IOError("%s\nError fetching %s." % (msg, url)) """ self.convert(code) def test_order_future_lines(self): """ Tests the internal order_future_lines() function. """ before = ''' # comment here from __future__ import print_function from __future__ import absolute_import # blank line or comment here from future.utils import with_metaclass from builtins import zzz from builtins import aaa from builtins import blah # another comment import something_else code_here more_code_here ''' after = ''' # comment here from __future__ import absolute_import from __future__ import print_function # blank line or comment here from future.utils import with_metaclass from builtins import aaa from builtins import blah from builtins import zzz # another comment import something_else code_here more_code_here ''' self.assertEqual(order_future_lines(reformat_code(before)), reformat_code(after)) @unittest.expectedFailure def test_issue_12(self): """ Issue #12: This code shouldn't be upset by additional imports. __future__ imports must appear at the top of modules since about Python 2.5. """ code = """ from __future__ import with_statement f = open('setup.py') for i in xrange(100): pass """ self.unchanged(code) @expectedFailurePY26 def test_range_necessary_list_calls(self): """ On Py2.6 (only), the xrange_with_import fixer somehow seems to cause l = range(10) to be converted to: l = list(list(range(10))) with an extra list(...) call. """ before = """ l = range(10) assert isinstance(l, list) for i in range(3): print i for i in xrange(3): print i """ after = """ from __future__ import print_function from builtins import range l = list(range(10)) assert isinstance(l, list) for i in range(3): print(i) for i in range(3): print(i) """ self.convert_check(before, after) class TestConservativeFuturize(CodeHandler): @unittest.expectedFailure def test_basestring(self): """ In conservative mode, futurize would not modify "basestring" but merely import it, and the following code would still run on both Py2 and Py3. """ before = """ assert isinstance('hello', basestring) assert isinstance(u'hello', basestring) assert isinstance(b'hello', basestring) """ after = """ from past.builtins import basestring assert isinstance('hello', basestring) assert isinstance(u'hello', basestring) assert isinstance(b'hello', basestring) """ self.convert_check(before, after, conservative=True) @unittest.expectedFailure def test_open(self): """ In conservative mode, futurize would not import io.open because this changes the default return type from bytes to text. """ before = """ filename = 'temp_file_open.test' contents = 'Temporary file contents. Delete me.' with open(filename, 'w') as f: f.write(contents) with open(filename, 'r') as f: data = f.read() assert isinstance(data, str) assert data == contents """ after = """ from past.builtins import open, str as oldbytes, unicode filename = oldbytes(b'temp_file_open.test') contents = oldbytes(b'Temporary file contents. Delete me.') with open(filename, oldbytes(b'w')) as f: f.write(contents) with open(filename, oldbytes(b'r')) as f: data = f.read() assert isinstance(data, oldbytes) assert data == contents assert isinstance(oldbytes(b'hello'), basestring) assert isinstance(unicode(u'hello'), basestring) assert isinstance(oldbytes(b'hello'), basestring) """ self.convert_check(before, after, conservative=True) def test_safe_division(self): """ Tests whether Py2 scripts using old-style division still work after futurization. """ before = """ x = 3 / 2 y = 3. / 2 assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) """ after = """ from __future__ import division from past.utils import old_div x = old_div(3, 2) y = old_div(3., 2) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) """ self.convert_check(before, after) def test_safe_division_overloaded(self): """ If division is overloaded, futurize may produce spurious old_div calls. This test is for whether the code still works on Py2 despite these calls. """ before = """ class Path(str): def __div__(self, other): return self.__truediv__(other) def __truediv__(self, other): return Path(str(self) + '/' + str(other)) path1 = Path('home') path2 = Path('user') z = path1 / path2 assert isinstance(z, Path) assert str(z) == 'home/user' """ after = """ from __future__ import division from past.utils import old_div class Path(str): def __div__(self, other): return self.__truediv__(other) def __truediv__(self, other): return Path(str(self) + '/' + str(other)) path1 = Path('home') path2 = Path('user') z = old_div(path1, path2) assert isinstance(z, Path) assert str(z) == 'home/user' """ self.convert_check(before, after) class TestFuturizeAllImports(CodeHandler): """ Tests "futurize --all-imports". """ @expectedFailurePY26 def test_all_imports(self): before = """ import math import os l = range(10) assert isinstance(l, list) print 'Hello' for i in xrange(100): pass print('Hello') """ after = """ from __future__ import unicode_literals from __future__ import print_function from __future__ import division from __future__ import absolute_import from future import standard_library standard_library.install_aliases() from builtins import range from builtins import * import math import os l = list(range(10)) assert isinstance(l, list) print('Hello') for i in range(100): pass print('Hello') """ self.convert_check(before, after, all_imports=True) if __name__ == '__main__': unittest.main()