|
Packit |
562c7a |
# cython: language_level=3
|
|
Packit |
562c7a |
# mode: run
|
|
Packit |
562c7a |
# tag: allow_unknown_names, f_strings, pep498
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
import ast
|
|
Packit |
562c7a |
import types
|
|
Packit |
562c7a |
import decimal
|
|
Packit |
562c7a |
import unittest
|
|
Packit |
562c7a |
import contextlib
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
import sys
|
|
Packit |
562c7a |
IS_PY2 = sys.version_info[0] < 3
|
|
Packit |
562c7a |
IS_PY26 = sys.version_info[:2] < (2, 7)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
from Cython.Build.Inline import cython_inline
|
|
Packit |
562c7a |
from Cython.TestUtils import CythonTest
|
|
Packit |
562c7a |
from Cython.Compiler.Errors import CompileError, hold_errors, release_errors, error_stack, held_errors
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def cy_eval(s, **kwargs):
|
|
Packit |
562c7a |
return cython_inline('return ' + s, force=True, **kwargs)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
a_global = 'global variable'
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# You could argue that I'm too strict in looking for specific error
|
|
Packit |
562c7a |
# values with assertRaisesRegex, but without it it's way too easy to
|
|
Packit |
562c7a |
# make a syntax error in the test strings. Especially with all of the
|
|
Packit |
562c7a |
# triple quotes, raw strings, backslashes, etc. I think it's a
|
|
Packit |
562c7a |
# worthwhile tradeoff. When I switched to this method, I found many
|
|
Packit |
562c7a |
# examples where I wasn't testing what I thought I was.
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
class TestCase(CythonTest):
|
|
Packit |
562c7a |
def assertAllRaise(self, exception_type, regex, error_strings):
|
|
Packit |
562c7a |
for str in error_strings:
|
|
Packit |
562c7a |
hold_errors()
|
|
Packit |
562c7a |
if exception_type is SyntaxError:
|
|
Packit |
562c7a |
try:
|
|
Packit |
562c7a |
self.fragment(str)
|
|
Packit |
562c7a |
except CompileError:
|
|
Packit |
562c7a |
assert True
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
assert held_errors(), "Invalid Cython code failed to raise SyntaxError: %r" % str
|
|
Packit |
562c7a |
finally:
|
|
Packit |
562c7a |
release_errors(ignore=True)
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
try:
|
|
Packit |
562c7a |
cython_inline(str, quiet=True)
|
|
Packit |
562c7a |
except exception_type:
|
|
Packit |
562c7a |
assert True
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
assert False, "Invalid Cython code failed to raise %s: %r" % (exception_type, str)
|
|
Packit |
562c7a |
finally:
|
|
Packit |
562c7a |
if error_stack:
|
|
Packit |
562c7a |
release_errors(ignore=True)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
if IS_PY2:
|
|
Packit |
562c7a |
def assertEqual(self, first, second, msg=None):
|
|
Packit |
562c7a |
# strip u'' string prefixes in Py2
|
|
Packit |
562c7a |
if first != second and isinstance(first, unicode):
|
|
Packit |
562c7a |
stripped_first = first.replace("u'", "'").replace('u"', '"')
|
|
Packit |
562c7a |
if stripped_first == second:
|
|
Packit |
562c7a |
first = stripped_first
|
|
Packit |
562c7a |
elif stripped_first.decode('unicode_escape') == second:
|
|
Packit |
562c7a |
first = stripped_first.decode('unicode_escape')
|
|
Packit |
562c7a |
super(TestCase, self).assertEqual(first, second, msg)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
if IS_PY26:
|
|
Packit |
562c7a |
@contextlib.contextmanager
|
|
Packit |
562c7a |
def assertRaises(self, exc):
|
|
Packit |
562c7a |
try:
|
|
Packit |
562c7a |
yield
|
|
Packit |
562c7a |
except exc:
|
|
Packit |
562c7a |
pass
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
assert False, "exception '%s' not raised" % exc
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def assertIn(self, value, collection):
|
|
Packit |
562c7a |
self.assertTrue(value in collection)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test__format__lookup(self):
|
|
Packit |
562c7a |
if IS_PY26:
|
|
Packit |
562c7a |
return
|
|
Packit |
562c7a |
elif IS_PY2:
|
|
Packit |
562c7a |
raise unittest.SkipTest("Py3-only")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Make sure __format__ is looked up on the type, not the instance.
|
|
Packit |
562c7a |
class X:
|
|
Packit |
562c7a |
def __format__(self, spec):
|
|
Packit |
562c7a |
return 'class'
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
x = X()
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Add a bound __format__ method to the 'y' instance, but not
|
|
Packit |
562c7a |
# the 'x' instance.
|
|
Packit |
562c7a |
y = X()
|
|
Packit |
562c7a |
y.__format__ = types.MethodType(lambda self, spec: 'instance', y)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{y}', format(y))
|
|
Packit |
562c7a |
self.assertEqual(f'{y}', 'class')
|
|
Packit |
562c7a |
self.assertEqual(format(x), format(y))
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# __format__ is not called this way, but still make sure it
|
|
Packit |
562c7a |
# returns what we expect (so we can make sure we're bypassing
|
|
Packit |
562c7a |
# it).
|
|
Packit |
562c7a |
self.assertEqual(x.__format__(''), 'class')
|
|
Packit |
562c7a |
self.assertEqual(y.__format__(''), 'instance')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# This is how __format__ is actually called.
|
|
Packit |
562c7a |
self.assertEqual(type(x).__format__(x, ''), 'class')
|
|
Packit |
562c7a |
self.assertEqual(type(y).__format__(y, ''), 'class')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def __test_ast(self):
|
|
Packit |
562c7a |
# Inspired by http://bugs.python.org/issue24975
|
|
Packit |
562c7a |
class X:
|
|
Packit |
562c7a |
def __init__(self):
|
|
Packit |
562c7a |
self.called = False
|
|
Packit |
562c7a |
def __call__(self):
|
|
Packit |
562c7a |
self.called = True
|
|
Packit |
562c7a |
return 4
|
|
Packit |
562c7a |
x = X()
|
|
Packit |
562c7a |
expr = """
|
|
Packit |
562c7a |
a = 10
|
|
Packit |
562c7a |
f'{a * x()}'"""
|
|
Packit |
562c7a |
t = ast.parse(expr)
|
|
Packit |
562c7a |
c = compile(t, '', 'exec')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Make sure x was not called.
|
|
Packit |
562c7a |
self.assertFalse(x.called)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Actually run the code.
|
|
Packit |
562c7a |
exec(c)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Make sure x was called.
|
|
Packit |
562c7a |
self.assertTrue(x.called)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_docstring(self):
|
|
Packit |
562c7a |
def f():
|
|
Packit |
562c7a |
f'''Not a docstring'''
|
|
Packit |
562c7a |
self.assertTrue(f.__doc__ is None)
|
|
Packit |
562c7a |
def g():
|
|
Packit |
562c7a |
'''Not a docstring''' \
|
|
Packit |
562c7a |
f''
|
|
Packit |
562c7a |
self.assertTrue(g.__doc__ is None)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def __test_literal_eval(self):
|
|
Packit |
562c7a |
with self.assertRaisesRegex(ValueError, 'malformed node or string'):
|
|
Packit |
562c7a |
ast.literal_eval("f'x'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def __test_ast_compile_time_concat(self):
|
|
Packit |
562c7a |
x = ['']
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
expr = """x[0] = 'foo' f'{3}'"""
|
|
Packit |
562c7a |
t = ast.parse(expr)
|
|
Packit |
562c7a |
c = compile(t, '', 'exec')
|
|
Packit |
562c7a |
exec(c)
|
|
Packit |
562c7a |
self.assertEqual(x[0], 'foo3')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_compile_time_concat_errors(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError,
|
|
Packit |
562c7a |
'cannot mix bytes and nonbytes literals',
|
|
Packit |
562c7a |
[r"""f'' b''""",
|
|
Packit |
562c7a |
r"""b'' f''""",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_literal(self):
|
|
Packit |
562c7a |
self.assertEqual(f'', '')
|
|
Packit |
562c7a |
self.assertEqual(f'a', 'a')
|
|
Packit |
562c7a |
self.assertEqual(f' ', ' ')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_unterminated_string(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
|
|
Packit |
562c7a |
[r"""f'{"x'""",
|
|
Packit |
562c7a |
r"""f'{"x}'""",
|
|
Packit |
562c7a |
r"""f'{("x'""",
|
|
Packit |
562c7a |
r"""f'{("x}'""",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_mismatched_parens(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'f-string: mismatched',
|
|
Packit |
562c7a |
["f'{((}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_double_braces(self):
|
|
Packit |
562c7a |
self.assertEqual(f'{{', '{')
|
|
Packit |
562c7a |
self.assertEqual(f'a{{', 'a{')
|
|
Packit |
562c7a |
self.assertEqual(f'{{b', '{b')
|
|
Packit |
562c7a |
self.assertEqual(f'a{{b', 'a{b')
|
|
Packit |
562c7a |
self.assertEqual(f'}}', '}')
|
|
Packit |
562c7a |
self.assertEqual(f'a}}', 'a}')
|
|
Packit |
562c7a |
self.assertEqual(f'}}b', '}b')
|
|
Packit |
562c7a |
self.assertEqual(f'a}}b', 'a}b')
|
|
Packit |
562c7a |
self.assertEqual(f'{{}}', '{}')
|
|
Packit |
562c7a |
self.assertEqual(f'a{{}}', 'a{}')
|
|
Packit |
562c7a |
self.assertEqual(f'{{b}}', '{b}')
|
|
Packit |
562c7a |
self.assertEqual(f'{{}}c', '{}c')
|
|
Packit |
562c7a |
self.assertEqual(f'a{{b}}', 'a{b}')
|
|
Packit |
562c7a |
self.assertEqual(f'a{{}}c', 'a{}c')
|
|
Packit |
562c7a |
self.assertEqual(f'{{b}}c', '{b}c')
|
|
Packit |
562c7a |
self.assertEqual(f'a{{b}}c', 'a{b}c')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{{{10}', '{10')
|
|
Packit |
562c7a |
self.assertEqual(f'}}{10}', '}10')
|
|
Packit |
562c7a |
self.assertEqual(f'}}{{{10}', '}{10')
|
|
Packit |
562c7a |
self.assertEqual(f'}}a{{{10}', '}a{10')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{10}{{', '10{')
|
|
Packit |
562c7a |
self.assertEqual(f'{10}}}', '10}')
|
|
Packit |
562c7a |
self.assertEqual(f'{10}}}{{', '10}{')
|
|
Packit |
562c7a |
self.assertEqual(f'{10}}}a{{' '}', '10}a{}')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Inside of strings, don't interpret doubled brackets.
|
|
Packit |
562c7a |
self.assertEqual(f'{"{{}}"}', '{{}}')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(TypeError, 'unhashable type',
|
|
Packit |
562c7a |
["f'{ {{}} }'", # dict in a set
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_compile_time_concat(self):
|
|
Packit |
562c7a |
x = 'def'
|
|
Packit |
562c7a |
self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
|
|
Packit |
562c7a |
self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi')
|
|
Packit |
562c7a |
self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ')
|
|
Packit |
562c7a |
self.assertEqual('{x}' f'{x}', '{x}def')
|
|
Packit |
562c7a |
self.assertEqual('{x' f'{x}', '{xdef')
|
|
Packit |
562c7a |
self.assertEqual('{x}' f'{x}', '{x}def')
|
|
Packit |
562c7a |
self.assertEqual('{{x}}' f'{x}', '{{x}}def')
|
|
Packit |
562c7a |
self.assertEqual('{{x' f'{x}', '{{xdef')
|
|
Packit |
562c7a |
self.assertEqual('x}}' f'{x}', 'x}}def')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}' 'x}}', 'defx}}')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}' '', 'def')
|
|
Packit |
562c7a |
self.assertEqual('' f'{x}' '', 'def')
|
|
Packit |
562c7a |
self.assertEqual('' f'{x}', 'def')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}' '2', 'def2')
|
|
Packit |
562c7a |
self.assertEqual('1' f'{x}' '2', '1def2')
|
|
Packit |
562c7a |
self.assertEqual('1' f'{x}', '1def')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}' f'-{x}', 'def-def')
|
|
Packit |
562c7a |
self.assertEqual('' f'', '')
|
|
Packit |
562c7a |
self.assertEqual('' f'' '', '')
|
|
Packit |
562c7a |
self.assertEqual('' f'' '' f'', '')
|
|
Packit |
562c7a |
self.assertEqual(f'', '')
|
|
Packit |
562c7a |
self.assertEqual(f'' '', '')
|
|
Packit |
562c7a |
self.assertEqual(f'' '' f'', '')
|
|
Packit |
562c7a |
self.assertEqual(f'' '' f'' '', '')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
|
|
Packit |
562c7a |
["f'{3' f'}'", # can't concat to get a valid f-string
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_comments(self):
|
|
Packit |
562c7a |
# These aren't comments, since they're in strings.
|
|
Packit |
562c7a |
d = {'#': 'hash'}
|
|
Packit |
562c7a |
self.assertEqual(f'{"#"}', '#')
|
|
Packit |
562c7a |
self.assertEqual(f'{d["#"]}', 'hash')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'",
|
|
Packit |
562c7a |
["f'{1#}'", # error because the expression becomes "(1#)"
|
|
Packit |
562c7a |
"f'{3(#)}'",
|
|
Packit |
562c7a |
"f'{#}'",
|
|
Packit |
562c7a |
"f'{)#}'", # When wrapped in parens, this becomes
|
|
Packit |
562c7a |
# '()#)'. Make sure that doesn't compile.
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_many_expressions(self):
|
|
Packit |
562c7a |
# Create a string with many expressions in it. Note that
|
|
Packit |
562c7a |
# because we have a space in here as a literal, we're actually
|
|
Packit |
562c7a |
# going to use twice as many ast nodes: one for each literal
|
|
Packit |
562c7a |
# plus one for each expression.
|
|
Packit |
562c7a |
def build_fstr(n, extra=''):
|
|
Packit |
562c7a |
return "f'" + ('{x} ' * n) + extra + "'"
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
x = 'X'
|
|
Packit |
562c7a |
width = 1
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Test around 256.
|
|
Packit |
562c7a |
for i in range(250, 260):
|
|
Packit |
562c7a |
self.assertEqual(cy_eval(build_fstr(i), x=x, width=width), (x+' ')*i)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Test concatenating 2 largs fstrings.
|
|
Packit |
562c7a |
self.assertEqual(cy_eval(build_fstr(255)*3, x=x, width=width), (x+' ')*(255*3)) # CPython uses 255*256
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
s = build_fstr(253, '{x:{width}} ')
|
|
Packit |
562c7a |
self.assertEqual(cy_eval(s, x=x, width=width), (x+' ')*254)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Test lots of expressions and constants, concatenated.
|
|
Packit |
562c7a |
s = "f'{1}' 'x' 'y'" * 1024
|
|
Packit |
562c7a |
self.assertEqual(cy_eval(s, x=x, width=width), '1xy' * 1024)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_format_specifier_expressions(self):
|
|
Packit |
562c7a |
width = 10
|
|
Packit |
562c7a |
precision = 4
|
|
Packit |
562c7a |
value = decimal.Decimal('12.34567')
|
|
Packit |
562c7a |
if not IS_PY26:
|
|
Packit |
562c7a |
self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35')
|
|
Packit |
562c7a |
self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35')
|
|
Packit |
562c7a |
self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35')
|
|
Packit |
562c7a |
self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35')
|
|
Packit |
562c7a |
self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35')
|
|
Packit |
562c7a |
self.assertEqual(f'{10:#{1}0x}', ' 0xa')
|
|
Packit |
562c7a |
self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa')
|
|
Packit |
562c7a |
self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa')
|
|
Packit |
562c7a |
self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa')
|
|
Packit |
562c7a |
self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
|
|
Packit |
562c7a |
["""f'{"s"!r{":10"}}'""",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# This looks like a nested format spec.
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "invalid syntax",
|
|
Packit |
562c7a |
[# Invalid syntax inside a nested spec.
|
|
Packit |
562c7a |
"f'{4:{/5}}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# CYTHON: The nesting restriction seems rather arbitrary. Ignoring it for now and instead test that it works.
|
|
Packit |
562c7a |
if not IS_PY26:
|
|
Packit |
562c7a |
self.assertEqual(f'result: {value:{width:{0}}.{precision:1}}', 'result: 12.35')
|
|
Packit |
562c7a |
#self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply",
|
|
Packit |
562c7a |
# [# Can't nest format specifiers.
|
|
Packit |
562c7a |
# "f'result: {value:{width:{0}}.{precision:1}}'",
|
|
Packit |
562c7a |
# ])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
|
|
Packit |
562c7a |
[# No expansion inside conversion or for
|
|
Packit |
562c7a |
# the : or ! itself.
|
|
Packit |
562c7a |
"""f'{"s"!{"r"}}'""",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_side_effect_order(self):
|
|
Packit |
562c7a |
class X:
|
|
Packit |
562c7a |
def __init__(self):
|
|
Packit |
562c7a |
self.i = 0
|
|
Packit |
562c7a |
def __format__(self, spec):
|
|
Packit |
562c7a |
self.i += 1
|
|
Packit |
562c7a |
return str(self.i)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
x = X()
|
|
Packit |
562c7a |
self.assertEqual(f'{x} {x}', '1 2')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_missing_expression(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
|
|
Packit |
562c7a |
["f'{}'",
|
|
Packit |
562c7a |
"f'{ }'"
|
|
Packit |
562c7a |
"f' {} '",
|
|
Packit |
562c7a |
"f'{!r}'",
|
|
Packit |
562c7a |
"f'{ !r}'",
|
|
Packit |
562c7a |
"f'{10:{ }}'",
|
|
Packit |
562c7a |
"f' { } '",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# The Python parser ignores also the following
|
|
Packit |
562c7a |
# whitespace characters in additional to a space.
|
|
Packit |
562c7a |
"f'''{\t\f\r\n}'''",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Catch the empty expression before the
|
|
Packit |
562c7a |
# invalid conversion.
|
|
Packit |
562c7a |
"f'{!x}'",
|
|
Packit |
562c7a |
"f'{ !xr}'",
|
|
Packit |
562c7a |
"f'{!x:}'",
|
|
Packit |
562c7a |
"f'{!x:a}'",
|
|
Packit |
562c7a |
"f'{ !xr:}'",
|
|
Packit |
562c7a |
"f'{ !xr:a}'",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
"f'{!}'",
|
|
Packit |
562c7a |
"f'{:}'",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# We find the empty expression before the
|
|
Packit |
562c7a |
# missing closing brace.
|
|
Packit |
562c7a |
"f'{!'",
|
|
Packit |
562c7a |
"f'{!s:'",
|
|
Packit |
562c7a |
"f'{:'",
|
|
Packit |
562c7a |
"f'{:x'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Different error message is raised for other whitespace characters.
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'invalid character in identifier',
|
|
Packit |
562c7a |
["f'''{\xa0}'''",
|
|
Packit |
562c7a |
#"\xa0",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_parens_in_expressions(self):
|
|
Packit |
562c7a |
self.assertEqual(f'{3,}', '(3,)')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Add these because when an expression is evaluated, parens
|
|
Packit |
562c7a |
# are added around it. But we shouldn't go from an invalid
|
|
Packit |
562c7a |
# expression to a valid one. The added parens are just
|
|
Packit |
562c7a |
# supposed to allow whitespace (including newlines).
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'invalid syntax',
|
|
Packit |
562c7a |
["f'{,}'",
|
|
Packit |
562c7a |
"f'{,}'", # this is (,), which is an error
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
|
|
Packit |
562c7a |
["f'{3)+(4}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'EOL while scanning string literal',
|
|
Packit |
562c7a |
["f'{\n}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_backslashes_in_string_part(self):
|
|
Packit |
562c7a |
self.assertEqual(f'\t', '\t')
|
|
Packit |
562c7a |
self.assertEqual(r'\t', '\\t')
|
|
Packit |
562c7a |
self.assertEqual(rf'\t', '\\t')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\t', '2\t')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\t{3}', '2\t3')
|
|
Packit |
562c7a |
self.assertEqual(f'\t{3}', '\t3')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'\u0394', '\u0394')
|
|
Packit |
562c7a |
self.assertEqual(r'\u0394', '\\u0394')
|
|
Packit |
562c7a |
self.assertEqual(rf'\u0394', '\\u0394')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\u0394', '2\u0394')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\u0394{3}', '2\u03943')
|
|
Packit |
562c7a |
self.assertEqual(f'\u0394{3}', '\u03943')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'\U00000394', '\u0394')
|
|
Packit |
562c7a |
self.assertEqual(r'\U00000394', '\\U00000394')
|
|
Packit |
562c7a |
self.assertEqual(rf'\U00000394', '\\U00000394')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\U00000394', '2\u0394')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\U00000394{3}', '2\u03943')
|
|
Packit |
562c7a |
self.assertEqual(f'\U00000394{3}', '\u03943')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943')
|
|
Packit |
562c7a |
self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943')
|
|
Packit |
562c7a |
self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
|
|
Packit |
562c7a |
self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943')
|
|
Packit |
562c7a |
self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'\x20', ' ')
|
|
Packit |
562c7a |
self.assertEqual(r'\x20', '\\x20')
|
|
Packit |
562c7a |
self.assertEqual(rf'\x20', '\\x20')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\x20', '2 ')
|
|
Packit |
562c7a |
self.assertEqual(f'{2}\x20{3}', '2 3')
|
|
Packit |
562c7a |
self.assertEqual(f'\x20{3}', ' 3')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'2\x20', '2 ')
|
|
Packit |
562c7a |
self.assertEqual(f'2\x203', '2 3')
|
|
Packit |
562c7a |
self.assertEqual(f'\x203', ' 3')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
#with self.assertWarns(DeprecationWarning): # invalid escape sequence
|
|
Packit |
562c7a |
# value = cy_eval(r"f'\{6*7}'")
|
|
Packit |
562c7a |
#self.assertEqual(value, '\\42')
|
|
Packit |
562c7a |
self.assertEqual(f'\\{6*7}', '\\42')
|
|
Packit |
562c7a |
self.assertEqual(fr'\{6*7}', '\\42')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
AMPERSAND = 'spam'
|
|
Packit |
562c7a |
# Get the right unicode character (&), or pick up local variable
|
|
Packit |
562c7a |
# depending on the number of backslashes.
|
|
Packit |
562c7a |
self.assertEqual(f'\N{AMPERSAND}', '&')
|
|
Packit |
562c7a |
self.assertEqual(f'\\N{AMPERSAND}', '\\Nspam')
|
|
Packit |
562c7a |
self.assertEqual(fr'\N{AMPERSAND}', '\\Nspam')
|
|
Packit |
562c7a |
self.assertEqual(f'\\\N{AMPERSAND}', '\\&')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_misformed_unicode_character_name(self):
|
|
Packit |
562c7a |
# These test are needed because unicode names are parsed
|
|
Packit |
562c7a |
# differently inside f-strings.
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape",
|
|
Packit |
562c7a |
[r"f'\N'",
|
|
Packit |
562c7a |
r"f'\N{'",
|
|
Packit |
562c7a |
r"f'\N{GREEK CAPITAL LETTER DELTA'",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Here are the non-f-string versions,
|
|
Packit |
562c7a |
# which should give the same errors.
|
|
Packit |
562c7a |
r"'\N'",
|
|
Packit |
562c7a |
r"'\N{'",
|
|
Packit |
562c7a |
r"'\N{GREEK CAPITAL LETTER DELTA'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_no_backslashes_in_expression_part(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash',
|
|
Packit |
562c7a |
[r"f'{\'a\'}'",
|
|
Packit |
562c7a |
r"f'{\t3}'",
|
|
Packit |
562c7a |
r"f'{\}'",
|
|
Packit |
562c7a |
r"rf'{\'a\'}'",
|
|
Packit |
562c7a |
r"rf'{\t3}'",
|
|
Packit |
562c7a |
r"rf'{\}'",
|
|
Packit |
562c7a |
r"""rf'{"\N{LEFT CURLY BRACKET}"}'""",
|
|
Packit |
562c7a |
r"f'{\n}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_no_escapes_for_braces(self):
|
|
Packit |
562c7a |
"""
|
|
Packit |
562c7a |
Only literal curly braces begin an expression.
|
|
Packit |
562c7a |
"""
|
|
Packit |
562c7a |
# \x7b is '{'.
|
|
Packit |
562c7a |
self.assertEqual(f'\x7b1+1}}', '{1+1}')
|
|
Packit |
562c7a |
self.assertEqual(f'\x7b1+1', '{1+1')
|
|
Packit |
562c7a |
self.assertEqual(f'\u007b1+1', '{1+1')
|
|
Packit |
562c7a |
self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_newlines_in_expressions(self):
|
|
Packit |
562c7a |
self.assertEqual(f'{0}', '0')
|
|
Packit |
562c7a |
self.assertEqual(rf'''{3+
|
|
Packit |
562c7a |
4}''', '7')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_lambda(self):
|
|
Packit |
562c7a |
x = 5
|
|
Packit |
562c7a |
self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
|
|
Packit |
562c7a |
if not IS_PY2:
|
|
Packit |
562c7a |
self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ")
|
|
Packit |
562c7a |
self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# lambda doesn't work without parens, because the colon
|
|
Packit |
562c7a |
# makes the parser think it's a format_spec
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
|
|
Packit |
562c7a |
["f'{lambda x:x}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_yield(self):
|
|
Packit |
562c7a |
# Not terribly useful, but make sure the yield turns
|
|
Packit |
562c7a |
# a function into a generator
|
|
Packit |
562c7a |
def fn(y):
|
|
Packit |
562c7a |
f'y:{yield y*2}'
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
g = fn(4)
|
|
Packit |
562c7a |
self.assertEqual(next(g), 8)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_yield_send(self):
|
|
Packit |
562c7a |
def fn(x):
|
|
Packit |
562c7a |
yield f'x:{yield (lambda i: x * i)}'
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
g = fn(10)
|
|
Packit |
562c7a |
the_lambda = next(g)
|
|
Packit |
562c7a |
self.assertEqual(the_lambda(4), 40)
|
|
Packit |
562c7a |
self.assertEqual(g.send('string'), 'x:string')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_expressions_with_triple_quoted_strings(self):
|
|
Packit |
562c7a |
self.assertEqual(f"{'''x'''}", 'x')
|
|
Packit |
562c7a |
self.assertEqual(f"{'''eric's'''}", "eric's")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Test concatenation within an expression
|
|
Packit |
562c7a |
self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
|
|
Packit |
562c7a |
self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
|
|
Packit |
562c7a |
self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
|
|
Packit |
562c7a |
self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
|
|
Packit |
562c7a |
self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
|
|
Packit |
562c7a |
self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_multiple_vars(self):
|
|
Packit |
562c7a |
x = 98
|
|
Packit |
562c7a |
y = 'abc'
|
|
Packit |
562c7a |
self.assertEqual(f'{x}{y}', '98abc')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'X{x}{y}', 'X98abc')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}X{y}', '98Xabc')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}{y}X', '98abcX')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
|
|
Packit |
562c7a |
self.assertEqual(f'X{x}{y}Y', 'X98abcY')
|
|
Packit |
562c7a |
self.assertEqual(f'{x}X{y}Y', '98XabcY')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_closure(self):
|
|
Packit |
562c7a |
def outer(x):
|
|
Packit |
562c7a |
def inner():
|
|
Packit |
562c7a |
return f'x:{x}'
|
|
Packit |
562c7a |
return inner
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(outer('987')(), 'x:987')
|
|
Packit |
562c7a |
self.assertEqual(outer(7)(), 'x:7')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_arguments(self):
|
|
Packit |
562c7a |
y = 2
|
|
Packit |
562c7a |
def f(x, width):
|
|
Packit |
562c7a |
return f'x={x*y:{width}}'
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f('foo', 10), 'x=foofoo ')
|
|
Packit |
562c7a |
x = 'bar'
|
|
Packit |
562c7a |
self.assertEqual(f(10, 10), 'x= 20')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_locals(self):
|
|
Packit |
562c7a |
value = 123
|
|
Packit |
562c7a |
self.assertEqual(f'v:{value}', 'v:123')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_missing_variable(self):
|
|
Packit |
562c7a |
with self.assertRaises(NameError):
|
|
Packit |
562c7a |
f'v:{value}'
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_missing_format_spec(self):
|
|
Packit |
562c7a |
class O:
|
|
Packit |
562c7a |
def __format__(self, spec):
|
|
Packit |
562c7a |
if not spec:
|
|
Packit |
562c7a |
return '*'
|
|
Packit |
562c7a |
return spec
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{O():x}', 'x')
|
|
Packit |
562c7a |
self.assertEqual(f'{O()}', '*')
|
|
Packit |
562c7a |
self.assertEqual(f'{O():}', '*')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{3:}', '3')
|
|
Packit |
562c7a |
self.assertEqual(f'{3!s:}', '3')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_global(self):
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global}', 'g:global variable')
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
a_local = 'local variable'
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global} l:{a_local}',
|
|
Packit |
562c7a |
'g:global variable l:local variable')
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global!r}',
|
|
Packit |
562c7a |
"g:'global variable'")
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global} l:{a_local!r}',
|
|
Packit |
562c7a |
"g:global variable l:'local variable'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertIn("module 'unittest' from", f'{unittest}')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_shadowed_global(self):
|
|
Packit |
562c7a |
a_global = 'really a local'
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global}', 'g:really a local')
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
a_local = 'local variable'
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global} l:{a_local}',
|
|
Packit |
562c7a |
'g:really a local l:local variable')
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global!r}',
|
|
Packit |
562c7a |
"g:'really a local'")
|
|
Packit |
562c7a |
self.assertEqual(f'g:{a_global} l:{a_local!r}',
|
|
Packit |
562c7a |
"g:really a local l:'local variable'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_call(self):
|
|
Packit |
562c7a |
def foo(x):
|
|
Packit |
562c7a |
return 'x=' + str(x)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{foo(10)}', 'x=10')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_nested_fstrings(self):
|
|
Packit |
562c7a |
y = 5
|
|
Packit |
562c7a |
self.assertEqual(f'{f"{0}"*3}', '000')
|
|
Packit |
562c7a |
self.assertEqual(f'{f"{y}"*3}', '555')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_invalid_string_prefixes(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
|
|
Packit |
562c7a |
["fu''",
|
|
Packit |
562c7a |
"uf''",
|
|
Packit |
562c7a |
"Fu''",
|
|
Packit |
562c7a |
"fU''",
|
|
Packit |
562c7a |
"Uf''",
|
|
Packit |
562c7a |
"uF''",
|
|
Packit |
562c7a |
"ufr''",
|
|
Packit |
562c7a |
"urf''",
|
|
Packit |
562c7a |
"fur''",
|
|
Packit |
562c7a |
"fru''",
|
|
Packit |
562c7a |
"rfu''",
|
|
Packit |
562c7a |
"ruf''",
|
|
Packit |
562c7a |
"FUR''",
|
|
Packit |
562c7a |
"Fur''",
|
|
Packit |
562c7a |
"fb''",
|
|
Packit |
562c7a |
"fB''",
|
|
Packit |
562c7a |
"Fb''",
|
|
Packit |
562c7a |
"FB''",
|
|
Packit |
562c7a |
"bf''",
|
|
Packit |
562c7a |
"bF''",
|
|
Packit |
562c7a |
"Bf''",
|
|
Packit |
562c7a |
"BF''",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_leading_trailing_spaces(self):
|
|
Packit |
562c7a |
self.assertEqual(f'{ 3}', '3')
|
|
Packit |
562c7a |
self.assertEqual(f'{ 3}', '3')
|
|
Packit |
562c7a |
self.assertEqual(f'{3 }', '3')
|
|
Packit |
562c7a |
self.assertEqual(f'{3 }', '3')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
|
|
Packit |
562c7a |
'expr={1: 2}')
|
|
Packit |
562c7a |
self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
|
|
Packit |
562c7a |
'expr={1: 2}')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_not_equal(self):
|
|
Packit |
562c7a |
# There's a special test for this because there's a special
|
|
Packit |
562c7a |
# case in the f-string parser to look for != as not ending an
|
|
Packit |
562c7a |
# expression. Normally it would, while looking for !s or !r.
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{3!=4}', 'True')
|
|
Packit |
562c7a |
self.assertEqual(f'{3!=4:}', 'True')
|
|
Packit |
562c7a |
self.assertEqual(f'{3!=4!s}', 'True')
|
|
Packit |
562c7a |
self.assertEqual(f'{3!=4!s:.3}', 'Tru')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_conversions(self):
|
|
Packit |
562c7a |
self.assertEqual(f'{3.14:10.10}', ' 3.14')
|
|
Packit |
562c7a |
if not IS_PY26:
|
|
Packit |
562c7a |
self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
|
|
Packit |
562c7a |
self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
|
|
Packit |
562c7a |
self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{"a"}', 'a')
|
|
Packit |
562c7a |
self.assertEqual(f'{"a"!r}', "'a'")
|
|
Packit |
562c7a |
self.assertEqual(f'{"a"!a}', "'a'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Not a conversion.
|
|
Packit |
562c7a |
self.assertEqual(f'{"a!r"}', "a!r")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Not a conversion, but show that ! is allowed in a format spec.
|
|
Packit |
562c7a |
self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
|
|
Packit |
562c7a |
["f'{3!g}'",
|
|
Packit |
562c7a |
"f'{3!A}'",
|
|
Packit |
562c7a |
"f'{3!3}'",
|
|
Packit |
562c7a |
"f'{3!G}'",
|
|
Packit |
562c7a |
"f'{3!!}'",
|
|
Packit |
562c7a |
"f'{3!:}'",
|
|
Packit |
562c7a |
"f'{3! s}'", # no space before conversion char
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
|
|
Packit |
562c7a |
["f'{x!s{y}}'",
|
|
Packit |
562c7a |
"f'{3!ss}'",
|
|
Packit |
562c7a |
"f'{3!ss:}'",
|
|
Packit |
562c7a |
"f'{3!ss:s}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_assignment(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'invalid syntax',
|
|
Packit |
562c7a |
["f'' = 3",
|
|
Packit |
562c7a |
"f'{0}' = x",
|
|
Packit |
562c7a |
"f'{x}' = x",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_del(self):
|
|
Packit |
562c7a |
self.assertAllRaise(CompileError, 'invalid syntax', # CPython raises SyntaxError
|
|
Packit |
562c7a |
["del f''",
|
|
Packit |
562c7a |
"del '' f''",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_mismatched_braces(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
|
|
Packit |
562c7a |
["f'{{}'",
|
|
Packit |
562c7a |
"f'{{}}}'",
|
|
Packit |
562c7a |
"f'}'",
|
|
Packit |
562c7a |
"f'x}'",
|
|
Packit |
562c7a |
"f'x}x'",
|
|
Packit |
562c7a |
r"f'\u007b}'",
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# Can't have { or } in a format spec.
|
|
Packit |
562c7a |
"f'{3:}>10}'",
|
|
Packit |
562c7a |
"f'{3:}}>10}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
|
|
Packit |
562c7a |
["f'{3:{{>10}'",
|
|
Packit |
562c7a |
"f'{3'",
|
|
Packit |
562c7a |
"f'{3!'",
|
|
Packit |
562c7a |
"f'{3:'",
|
|
Packit |
562c7a |
"f'{3!s'",
|
|
Packit |
562c7a |
"f'{3!s:'",
|
|
Packit |
562c7a |
"f'{3!s:3'",
|
|
Packit |
562c7a |
"f'x{'",
|
|
Packit |
562c7a |
"f'x{x'",
|
|
Packit |
562c7a |
"f'{x'",
|
|
Packit |
562c7a |
"f'{3:s'",
|
|
Packit |
562c7a |
"f'{{{'",
|
|
Packit |
562c7a |
"f'{{}}{'",
|
|
Packit |
562c7a |
"f'{'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
# But these are just normal strings.
|
|
Packit |
562c7a |
self.assertEqual(f'{"{"}', '{')
|
|
Packit |
562c7a |
self.assertEqual(f'{"}"}', '}')
|
|
Packit |
562c7a |
self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
|
|
Packit |
562c7a |
self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_if_conditional(self):
|
|
Packit |
562c7a |
# There's special logic in compile.c to test if the
|
|
Packit |
562c7a |
# conditional for an if (and while) are constants. Exercise
|
|
Packit |
562c7a |
# that code.
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_fstring(x, expected):
|
|
Packit |
562c7a |
flag = 0
|
|
Packit |
562c7a |
if f'{x}':
|
|
Packit |
562c7a |
flag = 1
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
flag = 2
|
|
Packit |
562c7a |
self.assertEqual(flag, expected)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_concat_empty(x, expected):
|
|
Packit |
562c7a |
flag = 0
|
|
Packit |
562c7a |
if '' f'{x}':
|
|
Packit |
562c7a |
flag = 1
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
flag = 2
|
|
Packit |
562c7a |
self.assertEqual(flag, expected)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_concat_non_empty(x, expected):
|
|
Packit |
562c7a |
flag = 0
|
|
Packit |
562c7a |
if ' ' f'{x}':
|
|
Packit |
562c7a |
flag = 1
|
|
Packit |
562c7a |
else:
|
|
Packit |
562c7a |
flag = 2
|
|
Packit |
562c7a |
self.assertEqual(flag, expected)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
test_fstring('', 2)
|
|
Packit |
562c7a |
test_fstring(' ', 1)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
test_concat_empty('', 2)
|
|
Packit |
562c7a |
test_concat_empty(' ', 1)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
test_concat_non_empty('', 1)
|
|
Packit |
562c7a |
test_concat_non_empty(' ', 1)
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_empty_format_specifier(self):
|
|
Packit |
562c7a |
x = 'test'
|
|
Packit |
562c7a |
self.assertEqual(f'{x}', 'test')
|
|
Packit |
562c7a |
self.assertEqual(f'{x:}', 'test')
|
|
Packit |
562c7a |
self.assertEqual(f'{x!s:}', 'test')
|
|
Packit |
562c7a |
self.assertEqual(f'{x!r:}', "'test'")
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_str_format_differences(self):
|
|
Packit |
562c7a |
d = {'a': 'string',
|
|
Packit |
562c7a |
0: 'integer',
|
|
Packit |
562c7a |
}
|
|
Packit |
562c7a |
a = 0
|
|
Packit |
562c7a |
self.assertEqual(f'{d[0]}', 'integer')
|
|
Packit |
562c7a |
self.assertEqual(f'{d["a"]}', 'string')
|
|
Packit |
562c7a |
self.assertEqual(f'{d[a]}', 'integer')
|
|
Packit |
562c7a |
self.assertEqual('{d[a]}'.format(d=d), 'string')
|
|
Packit |
562c7a |
self.assertEqual('{d[0]}'.format(d=d), 'integer')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_invalid_expressions(self):
|
|
Packit |
562c7a |
self.assertAllRaise(SyntaxError, 'invalid syntax',
|
|
Packit |
562c7a |
[r"f'{a[4)}'",
|
|
Packit |
562c7a |
r"f'{a(4]}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_errors(self):
|
|
Packit |
562c7a |
# see issue 26287
|
|
Packit |
562c7a |
exc = ValueError if sys.version_info < (3, 4) else TypeError
|
|
Packit |
562c7a |
self.assertAllRaise(exc, 'unsupported',
|
|
Packit |
562c7a |
[r"f'{(lambda: 0):x}'",
|
|
Packit |
562c7a |
r"f'{(0,):x}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
self.assertAllRaise(ValueError, 'Unknown format code',
|
|
Packit |
562c7a |
[r"f'{1000:j}'",
|
|
Packit |
562c7a |
r"f'{1000:j}'",
|
|
Packit |
562c7a |
])
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_loop(self):
|
|
Packit |
562c7a |
for i in range(1000):
|
|
Packit |
562c7a |
self.assertEqual(f'i:{i}', 'i:' + str(i))
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def test_dict(self):
|
|
Packit |
562c7a |
d = {'"': 'dquote',
|
|
Packit |
562c7a |
"'": 'squote',
|
|
Packit |
562c7a |
'foo': 'bar',
|
|
Packit |
562c7a |
}
|
|
Packit |
562c7a |
self.assertEqual(f'''{d["'"]}''', 'squote')
|
|
Packit |
562c7a |
self.assertEqual(f"""{d['"']}""", 'dquote')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
self.assertEqual(f'{d["foo"]}', 'bar')
|
|
Packit |
562c7a |
self.assertEqual(f"{d['foo']}", 'bar')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
def __test_backslash_char(self):
|
|
Packit |
562c7a |
# Check eval of a backslash followed by a control char.
|
|
Packit |
562c7a |
# See bpo-30682: this used to raise an assert in pydebug mode.
|
|
Packit |
562c7a |
self.assertEqual(cy_eval('f"\\\n"'), '')
|
|
Packit |
562c7a |
self.assertEqual(cy_eval('f"\\\r"'), '')
|
|
Packit |
562c7a |
|
|
Packit |
562c7a |
if __name__ == '__main__':
|
|
Packit |
562c7a |
unittest.main()
|