python-future/tests/test_future/test_futurize.py

1433 lines
42 KiB
Python

# -*- 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):
def setUp(self):
# For tests that need a text file:
_, self.textfilename = tempfile.mkstemp(text=True)
super(TestLibFuturize, self).setUp()
def tearDown(self):
os.unlink(self.textfilename)
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
retcode = main([self.textfilename])
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 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_multiline_future_import(self):
"""
Issue #113: don't crash if a future import has multiple lines
"""
text = """
from __future__ import (
division
)
"""
self.convert(text)
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_input_without_import(self):
before = """
a = input()
"""
after = """
from builtins import input
a = eval(input())
"""
self.convert_check(before, after, ignore_imports=False, run=False)
def test_input_with_import(self):
before = """
from builtins import input
a = input()
"""
after = """
from builtins import input
a = input()
"""
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""]
"""
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(self.textfilename)
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
"""
# We can't run the converted code because configparser may
# not be there.
self.convert_check(before, after, run=False)
@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(self.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)
def test_basestring(self):
"""
The 2to3 basestring fixer breaks working Py2 code that uses basestring.
This tests whether something sensible is done instead.
"""
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)
def test_safe_division(self):
"""
Tests whether Py2 scripts using old-style division still work
after futurization.
"""
before = """
import random
class fraction(object):
numer = 0
denom = 0
def __init__(self, numer, denom):
self.numer = numer
self.denom = denom
def total_count(self):
return self.numer * 50
x = 3 / 2
y = 3. / 2
foo = list(range(100))
assert x == 1 and isinstance(x, int)
assert y == 1.5 and isinstance(y, float)
a = 1 + foo[len(foo) / 2]
b = 1 + foo[len(foo) * 3 / 4]
assert a == 51
assert b == 76
r = random.randint(0, 1000) * 1.0 / 1000
output = { "SUCCESS": 5, "TOTAL": 10 }
output["SUCCESS"] * 100 / output["TOTAL"]
obj = fraction(1, 50)
val = float(obj.numer) / obj.denom * 1e-9
obj.numer * obj.denom / val
obj.total_count() * val / 100
obj.numer / obj.denom * 1e-9
obj.numer / (obj.denom * 1e-9)
obj.numer / obj.denom / 1e-9
obj.numer / (obj.denom / 1e-9)
original_numer = 1
original_denom = 50
100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom))
100 * abs(obj.numer - original_numer) / max(obj.denom, original_denom)
float(original_numer) * float(original_denom) / float(obj.numer)
"""
after = """
from __future__ import division
from past.utils import old_div
import random
class fraction(object):
numer = 0
denom = 0
def __init__(self, numer, denom):
self.numer = numer
self.denom = denom
def total_count(self):
return self.numer * 50
x = old_div(3, 2)
y = 3. / 2
foo = list(range(100))
assert x == 1 and isinstance(x, int)
assert y == 1.5 and isinstance(y, float)
a = 1 + foo[old_div(len(foo), 2)]
b = 1 + foo[old_div(len(foo) * 3, 4)]
assert a == 51
assert b == 76
r = random.randint(0, 1000) * 1.0 / 1000
output = { "SUCCESS": 5, "TOTAL": 10 }
old_div(output["SUCCESS"] * 100, output["TOTAL"])
obj = fraction(1, 50)
val = float(obj.numer) / obj.denom * 1e-9
old_div(obj.numer * obj.denom, val)
old_div(obj.total_count() * val, 100)
old_div(obj.numer, obj.denom) * 1e-9
old_div(obj.numer, (obj.denom * 1e-9))
old_div(old_div(obj.numer, obj.denom), 1e-9)
old_div(obj.numer, (old_div(obj.denom, 1e-9)))
original_numer = 1
original_denom = 50
100 * abs(obj.numer - original_numer) / float(max(obj.denom, original_denom))
old_div(100 * abs(obj.numer - original_numer), max(obj.denom, original_denom))
float(original_numer) * float(original_denom) / float(obj.numer)
"""
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)
def test_basestring_issue_156(self):
before = """
x = str(3)
allowed_types = basestring, int
assert isinstance('', allowed_types)
assert isinstance(u'', allowed_types)
assert isinstance(u'foo', basestring)
"""
after = """
from builtins import str
from past.builtins import basestring
x = str(3)
allowed_types = basestring, int
assert isinstance('', allowed_types)
assert isinstance(u'', allowed_types)
assert isinstance(u'foo', basestring)
"""
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 from ``past``, 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)
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 absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from future import standard_library
standard_library.install_aliases()
from builtins import *
from builtins import range
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, ignore_imports=False)
if __name__ == '__main__':
unittest.main()