diff options
| author | Stefan Behnel <stefan_ml@behnel.de> | 2020-08-29 09:27:02 +0200 |
|---|---|---|
| committer | Stefan Behnel <stefan_ml@behnel.de> | 2020-08-29 09:27:02 +0200 |
| commit | be43235ba3f4fca32dceda46424b36afb3cb00c9 (patch) | |
| tree | 4626fd9133d14e6e1481861e3ec70d9a2f46a9ea /tests/run/test_fstring.pyx | |
| parent | 4d54aeff34753551cf0ac9977d50c292dbf9d5d5 (diff) | |
| download | cython-be43235ba3f4fca32dceda46424b36afb3cb00c9.tar.gz | |
Update CPython "test_fstring" copy to Py3.9.
Diffstat (limited to 'tests/run/test_fstring.pyx')
| -rw-r--r-- | tests/run/test_fstring.pyx | 454 |
1 files changed, 430 insertions, 24 deletions
diff --git a/tests/run/test_fstring.pyx b/tests/run/test_fstring.pyx index b8e93673a..f2e0825be 100644 --- a/tests/run/test_fstring.pyx +++ b/tests/run/test_fstring.pyx @@ -3,10 +3,10 @@ # tag: allow_unknown_names, f_strings, pep498 import ast +import os import types import decimal import unittest -import contextlib import sys IS_PY2 = sys.version_info[0] < 3 @@ -63,9 +63,6 @@ class TestCase(CythonTest): super(TestCase, self).assertEqual(first, second, msg) def test__format__lookup(self): - if IS_PY2: - raise unittest.SkipTest("Py3-only") - # Make sure __format__ is looked up on the type, not the instance. class X: def __format__(self, spec): @@ -116,14 +113,263 @@ f'{a * x()}'""" # Make sure x was called. self.assertTrue(x.called) + def __test_ast_line_numbers(self): + expr = """ +a = 10 +f'{a * x()}'""" + t = ast.parse(expr) + self.assertEqual(type(t), ast.Module) + self.assertEqual(len(t.body), 2) + # check `a = 10` + self.assertEqual(type(t.body[0]), ast.Assign) + self.assertEqual(t.body[0].lineno, 2) + # check `f'...'` + self.assertEqual(type(t.body[1]), ast.Expr) + self.assertEqual(type(t.body[1].value), ast.JoinedStr) + self.assertEqual(len(t.body[1].value.values), 1) + self.assertEqual(type(t.body[1].value.values[0]), ast.FormattedValue) + self.assertEqual(t.body[1].lineno, 3) + self.assertEqual(t.body[1].value.lineno, 3) + self.assertEqual(t.body[1].value.values[0].lineno, 3) + # check the binop location + binop = t.body[1].value.values[0].value + self.assertEqual(type(binop), ast.BinOp) + self.assertEqual(type(binop.left), ast.Name) + self.assertEqual(type(binop.op), ast.Mult) + self.assertEqual(type(binop.right), ast.Call) + self.assertEqual(binop.lineno, 3) + self.assertEqual(binop.left.lineno, 3) + self.assertEqual(binop.right.lineno, 3) + self.assertEqual(binop.col_offset, 3) + self.assertEqual(binop.left.col_offset, 3) + self.assertEqual(binop.right.col_offset, 7) + + def __test_ast_line_numbers_multiple_formattedvalues(self): + expr = """ +f'no formatted values' +f'eggs {a * x()} spam {b + y()}'""" + t = ast.parse(expr) + self.assertEqual(type(t), ast.Module) + self.assertEqual(len(t.body), 2) + # check `f'no formatted value'` + self.assertEqual(type(t.body[0]), ast.Expr) + self.assertEqual(type(t.body[0].value), ast.JoinedStr) + self.assertEqual(t.body[0].lineno, 2) + # check `f'...'` + self.assertEqual(type(t.body[1]), ast.Expr) + self.assertEqual(type(t.body[1].value), ast.JoinedStr) + self.assertEqual(len(t.body[1].value.values), 4) + self.assertEqual(type(t.body[1].value.values[0]), ast.Constant) + self.assertEqual(type(t.body[1].value.values[0].value), str) + self.assertEqual(type(t.body[1].value.values[1]), ast.FormattedValue) + self.assertEqual(type(t.body[1].value.values[2]), ast.Constant) + self.assertEqual(type(t.body[1].value.values[2].value), str) + self.assertEqual(type(t.body[1].value.values[3]), ast.FormattedValue) + self.assertEqual(t.body[1].lineno, 3) + self.assertEqual(t.body[1].value.lineno, 3) + self.assertEqual(t.body[1].value.values[0].lineno, 3) + self.assertEqual(t.body[1].value.values[1].lineno, 3) + self.assertEqual(t.body[1].value.values[2].lineno, 3) + self.assertEqual(t.body[1].value.values[3].lineno, 3) + # check the first binop location + binop1 = t.body[1].value.values[1].value + self.assertEqual(type(binop1), ast.BinOp) + self.assertEqual(type(binop1.left), ast.Name) + self.assertEqual(type(binop1.op), ast.Mult) + self.assertEqual(type(binop1.right), ast.Call) + self.assertEqual(binop1.lineno, 3) + self.assertEqual(binop1.left.lineno, 3) + self.assertEqual(binop1.right.lineno, 3) + self.assertEqual(binop1.col_offset, 8) + self.assertEqual(binop1.left.col_offset, 8) + self.assertEqual(binop1.right.col_offset, 12) + # check the second binop location + binop2 = t.body[1].value.values[3].value + self.assertEqual(type(binop2), ast.BinOp) + self.assertEqual(type(binop2.left), ast.Name) + self.assertEqual(type(binop2.op), ast.Add) + self.assertEqual(type(binop2.right), ast.Call) + self.assertEqual(binop2.lineno, 3) + self.assertEqual(binop2.left.lineno, 3) + self.assertEqual(binop2.right.lineno, 3) + self.assertEqual(binop2.col_offset, 23) + self.assertEqual(binop2.left.col_offset, 23) + self.assertEqual(binop2.right.col_offset, 27) + + def __test_ast_line_numbers_nested(self): + expr = """ +a = 10 +f'{a * f"-{x()}-"}'""" + t = ast.parse(expr) + self.assertEqual(type(t), ast.Module) + self.assertEqual(len(t.body), 2) + # check `a = 10` + self.assertEqual(type(t.body[0]), ast.Assign) + self.assertEqual(t.body[0].lineno, 2) + # check `f'...'` + self.assertEqual(type(t.body[1]), ast.Expr) + self.assertEqual(type(t.body[1].value), ast.JoinedStr) + self.assertEqual(len(t.body[1].value.values), 1) + self.assertEqual(type(t.body[1].value.values[0]), ast.FormattedValue) + self.assertEqual(t.body[1].lineno, 3) + self.assertEqual(t.body[1].value.lineno, 3) + self.assertEqual(t.body[1].value.values[0].lineno, 3) + # check the binop location + binop = t.body[1].value.values[0].value + self.assertEqual(type(binop), ast.BinOp) + self.assertEqual(type(binop.left), ast.Name) + self.assertEqual(type(binop.op), ast.Mult) + self.assertEqual(type(binop.right), ast.JoinedStr) + self.assertEqual(binop.lineno, 3) + self.assertEqual(binop.left.lineno, 3) + self.assertEqual(binop.right.lineno, 3) + self.assertEqual(binop.col_offset, 3) + self.assertEqual(binop.left.col_offset, 3) + self.assertEqual(binop.right.col_offset, 7) + # check the nested call location + self.assertEqual(len(binop.right.values), 3) + self.assertEqual(type(binop.right.values[0]), ast.Constant) + self.assertEqual(type(binop.right.values[0].value), str) + self.assertEqual(type(binop.right.values[1]), ast.FormattedValue) + self.assertEqual(type(binop.right.values[2]), ast.Constant) + self.assertEqual(type(binop.right.values[2].value), str) + self.assertEqual(binop.right.values[0].lineno, 3) + self.assertEqual(binop.right.values[1].lineno, 3) + self.assertEqual(binop.right.values[2].lineno, 3) + call = binop.right.values[1].value + self.assertEqual(type(call), ast.Call) + self.assertEqual(call.lineno, 3) + self.assertEqual(call.col_offset, 11) + + def __test_ast_line_numbers_duplicate_expression(self): + """Duplicate expression + + NOTE: this is currently broken, always sets location of the first + expression. + """ + expr = """ +a = 10 +f'{a * x()} {a * x()} {a * x()}' +""" + t = ast.parse(expr) + self.assertEqual(type(t), ast.Module) + self.assertEqual(len(t.body), 2) + # check `a = 10` + self.assertEqual(type(t.body[0]), ast.Assign) + self.assertEqual(t.body[0].lineno, 2) + # check `f'...'` + self.assertEqual(type(t.body[1]), ast.Expr) + self.assertEqual(type(t.body[1].value), ast.JoinedStr) + self.assertEqual(len(t.body[1].value.values), 5) + self.assertEqual(type(t.body[1].value.values[0]), ast.FormattedValue) + self.assertEqual(type(t.body[1].value.values[1]), ast.Constant) + self.assertEqual(type(t.body[1].value.values[1].value), str) + self.assertEqual(type(t.body[1].value.values[2]), ast.FormattedValue) + self.assertEqual(type(t.body[1].value.values[3]), ast.Constant) + self.assertEqual(type(t.body[1].value.values[3].value), str) + self.assertEqual(type(t.body[1].value.values[4]), ast.FormattedValue) + self.assertEqual(t.body[1].lineno, 3) + self.assertEqual(t.body[1].value.lineno, 3) + self.assertEqual(t.body[1].value.values[0].lineno, 3) + self.assertEqual(t.body[1].value.values[1].lineno, 3) + self.assertEqual(t.body[1].value.values[2].lineno, 3) + self.assertEqual(t.body[1].value.values[3].lineno, 3) + self.assertEqual(t.body[1].value.values[4].lineno, 3) + # check the first binop location + binop = t.body[1].value.values[0].value + self.assertEqual(type(binop), ast.BinOp) + self.assertEqual(type(binop.left), ast.Name) + self.assertEqual(type(binop.op), ast.Mult) + self.assertEqual(type(binop.right), ast.Call) + self.assertEqual(binop.lineno, 3) + self.assertEqual(binop.left.lineno, 3) + self.assertEqual(binop.right.lineno, 3) + self.assertEqual(binop.col_offset, 3) + self.assertEqual(binop.left.col_offset, 3) + self.assertEqual(binop.right.col_offset, 7) + # check the second binop location + binop = t.body[1].value.values[2].value + self.assertEqual(type(binop), ast.BinOp) + self.assertEqual(type(binop.left), ast.Name) + self.assertEqual(type(binop.op), ast.Mult) + self.assertEqual(type(binop.right), ast.Call) + self.assertEqual(binop.lineno, 3) + self.assertEqual(binop.left.lineno, 3) + self.assertEqual(binop.right.lineno, 3) + self.assertEqual(binop.col_offset, 3) # FIXME: this is wrong + self.assertEqual(binop.left.col_offset, 3) # FIXME: this is wrong + self.assertEqual(binop.right.col_offset, 7) # FIXME: this is wrong + # check the third binop location + binop = t.body[1].value.values[4].value + self.assertEqual(type(binop), ast.BinOp) + self.assertEqual(type(binop.left), ast.Name) + self.assertEqual(type(binop.op), ast.Mult) + self.assertEqual(type(binop.right), ast.Call) + self.assertEqual(binop.lineno, 3) + self.assertEqual(binop.left.lineno, 3) + self.assertEqual(binop.right.lineno, 3) + self.assertEqual(binop.col_offset, 3) # FIXME: this is wrong + self.assertEqual(binop.left.col_offset, 3) # FIXME: this is wrong + self.assertEqual(binop.right.col_offset, 7) # FIXME: this is wrong + + def __test_ast_line_numbers_multiline_fstring(self): + # See bpo-30465 for details. + expr = """ +a = 10 +f''' + {a + * + x()} +non-important content +''' +""" + t = ast.parse(expr) + self.assertEqual(type(t), ast.Module) + self.assertEqual(len(t.body), 2) + # check `a = 10` + self.assertEqual(type(t.body[0]), ast.Assign) + self.assertEqual(t.body[0].lineno, 2) + # check `f'...'` + self.assertEqual(type(t.body[1]), ast.Expr) + self.assertEqual(type(t.body[1].value), ast.JoinedStr) + self.assertEqual(len(t.body[1].value.values), 3) + self.assertEqual(type(t.body[1].value.values[0]), ast.Constant) + self.assertEqual(type(t.body[1].value.values[0].value), str) + self.assertEqual(type(t.body[1].value.values[1]), ast.FormattedValue) + self.assertEqual(type(t.body[1].value.values[2]), ast.Constant) + self.assertEqual(type(t.body[1].value.values[2].value), str) + self.assertEqual(t.body[1].lineno, 3) + self.assertEqual(t.body[1].value.lineno, 3) + self.assertEqual(t.body[1].value.values[0].lineno, 3) + self.assertEqual(t.body[1].value.values[1].lineno, 3) + self.assertEqual(t.body[1].value.values[2].lineno, 3) + self.assertEqual(t.body[1].col_offset, 0) + self.assertEqual(t.body[1].value.col_offset, 0) + self.assertEqual(t.body[1].value.values[0].col_offset, 0) + self.assertEqual(t.body[1].value.values[1].col_offset, 0) + self.assertEqual(t.body[1].value.values[2].col_offset, 0) + # NOTE: the following lineno information and col_offset is correct for + # expressions within FormattedValues. + binop = t.body[1].value.values[1].value + self.assertEqual(type(binop), ast.BinOp) + self.assertEqual(type(binop.left), ast.Name) + self.assertEqual(type(binop.op), ast.Mult) + self.assertEqual(type(binop.right), ast.Call) + self.assertEqual(binop.lineno, 4) + self.assertEqual(binop.left.lineno, 4) + self.assertEqual(binop.right.lineno, 6) + self.assertEqual(binop.col_offset, 4) + self.assertEqual(binop.left.col_offset, 4) + self.assertEqual(binop.right.col_offset, 7) + def test_docstring(self): def f(): f'''Not a docstring''' - self.assertTrue(f.__doc__ is None) + self.assertIsNone(f.__doc__) def g(): '''Not a docstring''' \ f'' - self.assertTrue(g.__doc__ is None) + self.assertIsNone(g.__doc__) def __test_literal_eval(self): with self.assertRaisesRegex(ValueError, 'malformed node or string'): @@ -159,9 +405,27 @@ f'{a * x()}'""" ]) def test_mismatched_parens(self): - self.assertAllRaise(SyntaxError, 'f-string: mismatched', + self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " + r"does not match opening parenthesis '\('", ["f'{((}'", ]) + self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' " + r"does not match opening parenthesis '\['", + ["f'{a[4)}'", + ]) + self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' " + r"does not match opening parenthesis '\('", + ["f'{a(4]}'", + ]) + self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " + r"does not match opening parenthesis '\['", + ["f'{a[4}'", + ]) + self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " + r"does not match opening parenthesis '\('", + ["f'{a(4}'", + ]) + self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'") def test_double_braces(self): self.assertEqual(f'{{', '{') @@ -239,7 +503,9 @@ f'{a * x()}'""" ["f'{1#}'", # error because the expression becomes "(1#)" "f'{3(#)}'", "f'{#}'", - "f'{)#}'", # When wrapped in parens, this becomes + ]) + self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", + ["f'{)#}'", # When wrapped in parens, this becomes # '()#)'. Make sure that doesn't compile. ]) @@ -256,20 +522,23 @@ f'{a * x()}'""" # Test around 256. # for i in range(250, 260): - # self.assertEqual(cy_eval(build_fstr(i), x=x, width=width), (x+' ')*i) + # self.assertEqual(eval(build_fstr(i)), (x+' ')*i) self.assertEqual( cy_eval('[' + ', '.join(build_fstr(i) for i in range(250, 260)) + ']', x=x, width=width), [(x+' ')*i for i in range(250, 260)], ) # Test concatenating 2 largs fstrings. + # self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256)) self.assertEqual(cy_eval(build_fstr(255)*3, x=x, width=width), (x+' ')*(255*3)) # CPython uses 255*256 s = build_fstr(253, '{x:{width}} ') + # self.assertEqual(eval(s), (x+' ')*254) self.assertEqual(cy_eval(s, x=x, width=width), (x+' ')*254) # Test lots of expressions and constants, concatenated. s = "f'{1}' 'x' 'y'" * 1024 + # self.assertEqual(eval(s), '1xy' * 1024) self.assertEqual(cy_eval(s, x=x, width=width), '1xy' * 1024) def test_format_specifier_expressions(self): @@ -293,7 +562,7 @@ f'{a * x()}'""" # This looks like a nested format spec. ]) - self.assertAllRaise(SyntaxError, "invalid syntax", + self.assertAllRaise(SyntaxError, "f-string: invalid syntax", [# Invalid syntax inside a nested spec. "f'{4:{/5}}'", ]) @@ -357,7 +626,7 @@ f'{a * x()}'""" ]) # Different error message is raised for other whitespace characters. - self.assertAllRaise(SyntaxError, 'invalid character in identifier', + self.assertAllRaise(SyntaxError, r"invalid non-printable character U\+00A0", ["f'''{\xa0}'''", #"\xa0", ]) @@ -369,12 +638,12 @@ f'{a * x()}'""" # are added around it. But we shouldn't go from an invalid # expression to a valid one. The added parens are just # supposed to allow whitespace (including newlines). - self.assertAllRaise(SyntaxError, 'invalid syntax', + self.assertAllRaise(SyntaxError, 'f-string: invalid syntax', ["f'{,}'", "f'{,}'", # this is (,), which is an error ]) - self.assertAllRaise(SyntaxError, "f-string: expecting '}'", + self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", ["f'{3)+(4}'", ]) @@ -488,7 +757,7 @@ f'{a * x()}'""" # lambda doesn't work without parens, because the colon # makes the parser think it's a format_spec - self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', + self.assertAllRaise(SyntaxError, 'f-string: invalid syntax', ["f'{lambda x:x}'", ]) @@ -497,9 +766,11 @@ f'{a * x()}'""" # a function into a generator def fn(y): f'y:{yield y*2}' + f'{yield}' g = fn(4) self.assertEqual(next(g), 8) + self.assertEqual(next(g), None) def test_yield_send(self): def fn(x): @@ -616,8 +887,7 @@ f'{a * x()}'""" self.assertEqual(f'{f"{y}"*3}', '555') def test_invalid_string_prefixes(self): - self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', - ["fu''", + single_quote_cases = ["fu''", "uf''", "Fu''", "fU''", @@ -638,8 +908,10 @@ f'{a * x()}'""" "bf''", "bF''", "Bf''", - "BF''", - ]) + "BF''",] + double_quote_cases = [case.replace("'", '"') for case in single_quote_cases] + self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', + single_quote_cases + double_quote_cases) def test_leading_trailing_spaces(self): self.assertEqual(f'{ 3}', '3') @@ -662,6 +934,12 @@ f'{a * x()}'""" self.assertEqual(f'{3!=4!s}', 'True') self.assertEqual(f'{3!=4!s:.3}', 'Tru') + def test_equal_equal(self): + # Because an expression ending in = has special meaning, + # there's a special test for ==. Make sure it works. + + self.assertEqual(f'{0==1}', 'False') + def test_conversions(self): self.assertEqual(f'{3.14:10.10}', ' 3.14') self.assertEqual(f'{3.14!s:10.10}', '3.14 ') @@ -801,12 +1079,6 @@ f'{a * x()}'""" self.assertEqual('{d[a]}'.format(d=d), 'string') self.assertEqual('{d[0]}'.format(d=d), 'integer') - def test_invalid_expressions(self): - self.assertAllRaise(SyntaxError, 'invalid syntax', - [r"f'{a[4)}'", - r"f'{a(4]}'", - ]) - def test_errors(self): # see issue 26287 exc = ValueError if sys.version_info < (3, 4) else TypeError @@ -819,6 +1091,16 @@ f'{a * x()}'""" r"f'{1000:j}'", ]) + def __test_filename_in_syntaxerror(self): + # see issue 38964 + with temp_cwd() as cwd: + file_path = os.path.join(cwd, 't.py') + with open(file_path, 'w') as f: + f.write('f"{a b}"') # This generates a SyntaxError + _, _, stderr = assert_python_failure(file_path, + PYTHONIOENCODING='ascii') + self.assertIn(file_path.encode('ascii', 'backslashreplace'), stderr) + def test_loop(self): for i in range(1000): self.assertEqual(f'i:{i}', 'i:' + str(i)) @@ -840,5 +1122,129 @@ f'{a * x()}'""" self.assertEqual(cy_eval('f"\\\n"'), '') self.assertEqual(cy_eval('f"\\\r"'), '') + """ + def __test_debug_conversion(self): + x = 'A string' + self.assertEqual(f'{x=}', 'x=' + repr(x)) + self.assertEqual(f'{x =}', 'x =' + repr(x)) + self.assertEqual(f'{x=!s}', 'x=' + str(x)) + self.assertEqual(f'{x=!r}', 'x=' + repr(x)) + self.assertEqual(f'{x=!a}', 'x=' + ascii(x)) + + x = 2.71828 + self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f')) + self.assertEqual(f'{x=:}', 'x=' + format(x, '')) + self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20')) + self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20')) + self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20')) + + x = 9 + self.assertEqual(f'{3*x+15=}', '3*x+15=42') + + # There is code in ast.c that deals with non-ascii expression values. So, + # use a unicode identifier to trigger that. + tenπ = 31.4 + self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40') + + # Also test with Unicode in non-identifiers. + self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'') + + # Make sure nested fstrings still work. + self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****') + + # Make sure text before and after an expression with = works + # correctly. + pi = 'π' + self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega") + + # Check multi-line expressions. + self.assertEqual(f'''{ +3 +=}''', '\n3\n=3') + + # Since = is handled specially, make sure all existing uses of + # it still work. + + self.assertEqual(f'{0==1}', 'False') + self.assertEqual(f'{0!=1}', 'True') + self.assertEqual(f'{0<=1}', 'True') + self.assertEqual(f'{0>=1}', 'False') + self.assertEqual(f'{(x:="5")}', '5') + self.assertEqual(x, '5') + self.assertEqual(f'{(x:=5)}', '5') + self.assertEqual(x, 5) + self.assertEqual(f'{"="}', '=') + + x = 20 + # This isn't an assignment expression, it's 'x', with a format + # spec of '=10'. See test_walrus: you need to use parens. + self.assertEqual(f'{x:=10}', ' 20') + + # Test named function parameters, to make sure '=' parsing works + # there. + def f(a): + nonlocal x + oldx = x + x = a + return oldx + x = 0 + self.assertEqual(f'{f(a="3=")}', '0') + self.assertEqual(x, '3=') + self.assertEqual(f'{f(a=4)}', '3=') + self.assertEqual(x, 4) + + # Make sure __format__ is being called. + class C: + def __format__(self, s): + return f'FORMAT-{s}' + def __repr__(self): + return 'REPR' + + self.assertEqual(f'{C()=}', 'C()=REPR') + self.assertEqual(f'{C()=!r}', 'C()=REPR') + self.assertEqual(f'{C()=:}', 'C()=FORMAT-') + self.assertEqual(f'{C()=: }', 'C()=FORMAT- ') + self.assertEqual(f'{C()=:x}', 'C()=FORMAT-x') + self.assertEqual(f'{C()=!r:*^20}', 'C()=********REPR********') + + self.assertRaises(SyntaxError, eval, "f'{C=]'") + + # Make sure leading and following text works. + x = 'foo' + self.assertEqual(f'X{x=}Y', 'Xx='+repr(x)+'Y') + + # Make sure whitespace around the = works. + self.assertEqual(f'X{x =}Y', 'Xx ='+repr(x)+'Y') + self.assertEqual(f'X{x= }Y', 'Xx= '+repr(x)+'Y') + self.assertEqual(f'X{x = }Y', 'Xx = '+repr(x)+'Y') + + # These next lines contains tabs. Backslash escapes don't + # work in f-strings. + # patchcheck doesn't like these tabs. So the only way to test + # this will be to dynamically created and exec the f-strings. But + # that's such a hassle I'll save it for another day. For now, convert + # the tabs to spaces just to shut up patchcheck. + #self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y') + #self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y') + """ + + def test_walrus(self): + x = 20 + # This isn't an assignment expression, it's 'x', with a format + # spec of '=10'. + self.assertEqual(f'{x:=10}', ' 20') + + """ + # This is an assignment expression, which requires parens. + self.assertEqual(f'{(x:=10)}', '10') + self.assertEqual(x, 10) + """ + + def test_invalid_syntax_error_message(self): + # with self.assertRaisesRegex(SyntaxError, "f-string: invalid syntax"): + # compile("f'{a $ b}'", "?", "exec") + self.assertAllRaise(CompileError, "f-string: invalid syntax", ["f'{a $ b}'"]) + + if __name__ == '__main__': unittest.main() |
