blob: e61f63594fab4a4b54a705388f6820d21b892bdd [file] [log] [blame]
Eric V. Smith235a6f02015-09-19 14:51:32 -04001import ast
2import types
3import decimal
4import unittest
5
6a_global = 'global variable'
7
8# You could argue that I'm too strict in looking for specific error
9# values with assertRaisesRegex, but without it it's way too easy to
10# make a syntax error in the test strings. Especially with all of the
11# triple quotes, raw strings, backslashes, etc. I think it's a
12# worthwhile tradeoff. When I switched to this method, I found many
13# examples where I wasn't testing what I thought I was.
14
15class TestCase(unittest.TestCase):
16 def assertAllRaise(self, exception_type, regex, error_strings):
17 for str in error_strings:
18 with self.subTest(str=str):
19 with self.assertRaisesRegex(exception_type, regex):
20 eval(str)
21
22 def test__format__lookup(self):
23 # Make sure __format__ is looked up on the type, not the instance.
24 class X:
25 def __format__(self, spec):
26 return 'class'
27
28 x = X()
29
30 # Add a bound __format__ method to the 'y' instance, but not
31 # the 'x' instance.
32 y = X()
33 y.__format__ = types.MethodType(lambda self, spec: 'instance', y)
34
35 self.assertEqual(f'{y}', format(y))
36 self.assertEqual(f'{y}', 'class')
37 self.assertEqual(format(x), format(y))
38
39 # __format__ is not called this way, but still make sure it
40 # returns what we expect (so we can make sure we're bypassing
41 # it).
42 self.assertEqual(x.__format__(''), 'class')
43 self.assertEqual(y.__format__(''), 'instance')
44
45 # This is how __format__ is actually called.
46 self.assertEqual(type(x).__format__(x, ''), 'class')
47 self.assertEqual(type(y).__format__(y, ''), 'class')
48
49 def test_ast(self):
50 # Inspired by http://bugs.python.org/issue24975
51 class X:
52 def __init__(self):
53 self.called = False
54 def __call__(self):
55 self.called = True
56 return 4
57 x = X()
58 expr = """
59a = 10
60f'{a * x()}'"""
61 t = ast.parse(expr)
62 c = compile(t, '', 'exec')
63
64 # Make sure x was not called.
65 self.assertFalse(x.called)
66
67 # Actually run the code.
68 exec(c)
69
70 # Make sure x was called.
71 self.assertTrue(x.called)
72
73 def test_literal_eval(self):
74 # With no expressions, an f-string is okay.
75 self.assertEqual(ast.literal_eval("f'x'"), 'x')
76 self.assertEqual(ast.literal_eval("f'x' 'y'"), 'xy')
77
78 # But this should raise an error.
79 with self.assertRaisesRegex(ValueError, 'malformed node or string'):
80 ast.literal_eval("f'x{3}'")
81
82 # As should this, which uses a different ast node
83 with self.assertRaisesRegex(ValueError, 'malformed node or string'):
84 ast.literal_eval("f'{3}'")
85
86 def test_ast_compile_time_concat(self):
87 x = ['']
88
89 expr = """x[0] = 'foo' f'{3}'"""
90 t = ast.parse(expr)
91 c = compile(t, '', 'exec')
92 exec(c)
93 self.assertEqual(x[0], 'foo3')
94
95 def test_literal(self):
96 self.assertEqual(f'', '')
97 self.assertEqual(f'a', 'a')
98 self.assertEqual(f' ', ' ')
Eric V. Smith235a6f02015-09-19 14:51:32 -040099
100 def test_unterminated_string(self):
101 self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
102 [r"""f'{"x'""",
103 r"""f'{"x}'""",
104 r"""f'{("x'""",
105 r"""f'{("x}'""",
106 ])
107
108 def test_mismatched_parens(self):
109 self.assertAllRaise(SyntaxError, 'f-string: mismatched',
110 ["f'{((}'",
111 ])
112
113 def test_double_braces(self):
114 self.assertEqual(f'{{', '{')
115 self.assertEqual(f'a{{', 'a{')
116 self.assertEqual(f'{{b', '{b')
117 self.assertEqual(f'a{{b', 'a{b')
118 self.assertEqual(f'}}', '}')
119 self.assertEqual(f'a}}', 'a}')
120 self.assertEqual(f'}}b', '}b')
121 self.assertEqual(f'a}}b', 'a}b')
Eric V. Smith451d0e32016-09-09 21:56:20 -0400122 self.assertEqual(f'{{}}', '{}')
123 self.assertEqual(f'a{{}}', 'a{}')
124 self.assertEqual(f'{{b}}', '{b}')
125 self.assertEqual(f'{{}}c', '{}c')
126 self.assertEqual(f'a{{b}}', 'a{b}')
127 self.assertEqual(f'a{{}}c', 'a{}c')
128 self.assertEqual(f'{{b}}c', '{b}c')
129 self.assertEqual(f'a{{b}}c', 'a{b}c')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400130
131 self.assertEqual(f'{{{10}', '{10')
132 self.assertEqual(f'}}{10}', '}10')
133 self.assertEqual(f'}}{{{10}', '}{10')
134 self.assertEqual(f'}}a{{{10}', '}a{10')
135
136 self.assertEqual(f'{10}{{', '10{')
137 self.assertEqual(f'{10}}}', '10}')
138 self.assertEqual(f'{10}}}{{', '10}{')
139 self.assertEqual(f'{10}}}a{{' '}', '10}a{}')
140
141 # Inside of strings, don't interpret doubled brackets.
142 self.assertEqual(f'{"{{}}"}', '{{}}')
143
144 self.assertAllRaise(TypeError, 'unhashable type',
145 ["f'{ {{}} }'", # dict in a set
146 ])
147
148 def test_compile_time_concat(self):
149 x = 'def'
150 self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
151 self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi')
152 self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ')
153 self.assertEqual('{x}' f'{x}', '{x}def')
154 self.assertEqual('{x' f'{x}', '{xdef')
155 self.assertEqual('{x}' f'{x}', '{x}def')
156 self.assertEqual('{{x}}' f'{x}', '{{x}}def')
157 self.assertEqual('{{x' f'{x}', '{{xdef')
158 self.assertEqual('x}}' f'{x}', 'x}}def')
159 self.assertEqual(f'{x}' 'x}}', 'defx}}')
160 self.assertEqual(f'{x}' '', 'def')
161 self.assertEqual('' f'{x}' '', 'def')
162 self.assertEqual('' f'{x}', 'def')
163 self.assertEqual(f'{x}' '2', 'def2')
164 self.assertEqual('1' f'{x}' '2', '1def2')
165 self.assertEqual('1' f'{x}', '1def')
166 self.assertEqual(f'{x}' f'-{x}', 'def-def')
167 self.assertEqual('' f'', '')
168 self.assertEqual('' f'' '', '')
169 self.assertEqual('' f'' '' f'', '')
170 self.assertEqual(f'', '')
171 self.assertEqual(f'' '', '')
172 self.assertEqual(f'' '' f'', '')
173 self.assertEqual(f'' '' f'' '', '')
174
175 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
176 ["f'{3' f'}'", # can't concat to get a valid f-string
177 ])
178
179 def test_comments(self):
180 # These aren't comments, since they're in strings.
181 d = {'#': 'hash'}
182 self.assertEqual(f'{"#"}', '#')
183 self.assertEqual(f'{d["#"]}', 'hash')
184
185 self.assertAllRaise(SyntaxError, "f-string cannot include '#'",
186 ["f'{1#}'", # error because the expression becomes "(1#)"
187 "f'{3(#)}'",
188 ])
189
190 def test_many_expressions(self):
191 # Create a string with many expressions in it. Note that
192 # because we have a space in here as a literal, we're actually
193 # going to use twice as many ast nodes: one for each literal
194 # plus one for each expression.
195 def build_fstr(n, extra=''):
196 return "f'" + ('{x} ' * n) + extra + "'"
197
198 x = 'X'
199 width = 1
200
201 # Test around 256.
202 for i in range(250, 260):
203 self.assertEqual(eval(build_fstr(i)), (x+' ')*i)
204
205 # Test concatenating 2 largs fstrings.
206 self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256))
207
208 s = build_fstr(253, '{x:{width}} ')
209 self.assertEqual(eval(s), (x+' ')*254)
210
211 # Test lots of expressions and constants, concatenated.
212 s = "f'{1}' 'x' 'y'" * 1024
213 self.assertEqual(eval(s), '1xy' * 1024)
214
215 def test_format_specifier_expressions(self):
216 width = 10
217 precision = 4
218 value = decimal.Decimal('12.34567')
219 self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35')
220 self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35')
221 self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35')
222 self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35')
223 self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35')
224 self.assertEqual(f'{10:#{1}0x}', ' 0xa')
225 self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa')
226 self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa')
227 self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa')
228 self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa')
229
230 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
231 ["""f'{"s"!r{":10"}}'""",
232
233 # This looks like a nested format spec.
234 ])
235
236 self.assertAllRaise(SyntaxError, "invalid syntax",
Martin Panter263893c2016-07-28 01:25:31 +0000237 [# Invalid syntax inside a nested spec.
Eric V. Smith235a6f02015-09-19 14:51:32 -0400238 "f'{4:{/5}}'",
239 ])
240
241 self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply",
242 [# Can't nest format specifiers.
243 "f'result: {value:{width:{0}}.{precision:1}}'",
244 ])
245
246 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
247 [# No expansion inside conversion or for
248 # the : or ! itself.
249 """f'{"s"!{"r"}}'""",
250 ])
251
252 def test_side_effect_order(self):
253 class X:
254 def __init__(self):
255 self.i = 0
256 def __format__(self, spec):
257 self.i += 1
258 return str(self.i)
259
260 x = X()
261 self.assertEqual(f'{x} {x}', '1 2')
262
263 def test_missing_expression(self):
264 self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
265 ["f'{}'",
266 "f'{ }'"
267 "f' {} '",
268 "f'{!r}'",
269 "f'{ !r}'",
270 "f'{10:{ }}'",
271 "f' { } '",
Eric V. Smith1d44c412015-09-23 07:49:00 -0400272
273 # Catch the empty expression before the
274 # invalid conversion.
275 "f'{!x}'",
276 "f'{ !xr}'",
277 "f'{!x:}'",
278 "f'{!x:a}'",
279 "f'{ !xr:}'",
280 "f'{ !xr:a}'",
Eric V. Smith548c4d32015-09-23 08:00:01 -0400281
282 "f'{!}'",
283 "f'{:}'",
Eric V. Smithb2080f62015-09-23 10:24:43 -0400284
285 # We find the empty expression before the
286 # missing closing brace.
287 "f'{!'",
288 "f'{!s:'",
289 "f'{:'",
290 "f'{:x'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400291 ])
292
293 def test_parens_in_expressions(self):
294 self.assertEqual(f'{3,}', '(3,)')
295
296 # Add these because when an expression is evaluated, parens
297 # are added around it. But we shouldn't go from an invalid
298 # expression to a valid one. The added parens are just
299 # supposed to allow whitespace (including newlines).
300 self.assertAllRaise(SyntaxError, 'invalid syntax',
301 ["f'{,}'",
302 "f'{,}'", # this is (,), which is an error
303 ])
304
305 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
306 ["f'{3)+(4}'",
307 ])
308
309 self.assertAllRaise(SyntaxError, 'EOL while scanning string literal',
310 ["f'{\n}'",
311 ])
312
Eric V. Smith451d0e32016-09-09 21:56:20 -0400313 def test_backslashes_in_string_part(self):
314 self.assertEqual(f'\t', '\t')
315 self.assertEqual(r'\t', '\\t')
316 self.assertEqual(rf'\t', '\\t')
317 self.assertEqual(f'{2}\t', '2\t')
318 self.assertEqual(f'{2}\t{3}', '2\t3')
319 self.assertEqual(f'\t{3}', '\t3')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400320
Eric V. Smith451d0e32016-09-09 21:56:20 -0400321 self.assertEqual(f'\u0394', '\u0394')
322 self.assertEqual(r'\u0394', '\\u0394')
323 self.assertEqual(rf'\u0394', '\\u0394')
324 self.assertEqual(f'{2}\u0394', '2\u0394')
325 self.assertEqual(f'{2}\u0394{3}', '2\u03943')
326 self.assertEqual(f'\u0394{3}', '\u03943')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400327
Eric V. Smith451d0e32016-09-09 21:56:20 -0400328 self.assertEqual(f'\U00000394', '\u0394')
329 self.assertEqual(r'\U00000394', '\\U00000394')
330 self.assertEqual(rf'\U00000394', '\\U00000394')
331 self.assertEqual(f'{2}\U00000394', '2\u0394')
332 self.assertEqual(f'{2}\U00000394{3}', '2\u03943')
333 self.assertEqual(f'\U00000394{3}', '\u03943')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400334
Eric V. Smith451d0e32016-09-09 21:56:20 -0400335 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394')
336 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
337 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943')
338 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943')
339 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394')
340 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943')
341 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400342
Eric V. Smith451d0e32016-09-09 21:56:20 -0400343 self.assertEqual(f'\x20', ' ')
344 self.assertEqual(r'\x20', '\\x20')
345 self.assertEqual(rf'\x20', '\\x20')
346 self.assertEqual(f'{2}\x20', '2 ')
347 self.assertEqual(f'{2}\x20{3}', '2 3')
348 self.assertEqual(f'\x20{3}', ' 3')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400349
Eric V. Smith451d0e32016-09-09 21:56:20 -0400350 self.assertEqual(f'2\x20', '2 ')
351 self.assertEqual(f'2\x203', '2 3')
352 self.assertEqual(f'\x203', ' 3')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400353
Eric V. Smith451d0e32016-09-09 21:56:20 -0400354 def test_misformed_unicode_character_name(self):
355 # These test are needed because unicode names are parsed
356 # differently inside f-strings.
357 self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape",
358 [r"f'\N'",
359 r"f'\N{'",
360 r"f'\N{GREEK CAPITAL LETTER DELTA'",
361
362 # Here are the non-f-string versions,
363 # which should give the same errors.
364 r"'\N'",
365 r"'\N{'",
366 r"'\N{GREEK CAPITAL LETTER DELTA'",
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400367 ])
368
Eric V. Smith451d0e32016-09-09 21:56:20 -0400369 def test_no_backslashes_in_expression_part(self):
370 self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash',
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400371 [r"f'{\'a\'}'",
372 r"f'{\t3}'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400373 r"f'{\}'",
374 r"rf'{\'a\'}'",
375 r"rf'{\t3}'",
376 r"rf'{\}'",
377 r"""rf'{"\N{LEFT CURLY BRACKET}"}'""",
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400378 ])
379
Eric V. Smith451d0e32016-09-09 21:56:20 -0400380 def test_no_escapes_for_braces(self):
381 # \x7b is '{'. Make sure it doesn't start an expression.
382 self.assertEqual(f'\x7b2}}', '{2}')
383 self.assertEqual(f'\x7b2', '{2')
384 self.assertEqual(f'\u007b2', '{2')
385 self.assertEqual(f'\N{LEFT CURLY BRACKET}2\N{RIGHT CURLY BRACKET}', '{2}')
Eric V. Smith6a4efce2016-09-03 09:18:34 -0400386
Eric V. Smith235a6f02015-09-19 14:51:32 -0400387 def test_newlines_in_expressions(self):
388 self.assertEqual(f'{0}', '0')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400389 self.assertEqual(rf'''{3+
3904}''', '7')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400391
392 def test_lambda(self):
393 x = 5
394 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
395 self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ")
396 self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ")
397
398 # lambda doesn't work without parens, because the colon
399 # makes the parser think it's a format_spec
400 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
401 ["f'{lambda x:x}'",
402 ])
403
404 def test_yield(self):
405 # Not terribly useful, but make sure the yield turns
406 # a function into a generator
407 def fn(y):
408 f'y:{yield y*2}'
409
410 g = fn(4)
411 self.assertEqual(next(g), 8)
412
413 def test_yield_send(self):
414 def fn(x):
415 yield f'x:{yield (lambda i: x * i)}'
416
417 g = fn(10)
418 the_lambda = next(g)
419 self.assertEqual(the_lambda(4), 40)
420 self.assertEqual(g.send('string'), 'x:string')
421
422 def test_expressions_with_triple_quoted_strings(self):
423 self.assertEqual(f"{'''x'''}", 'x')
424 self.assertEqual(f"{'''eric's'''}", "eric's")
Eric V. Smith235a6f02015-09-19 14:51:32 -0400425
426 # Test concatenation within an expression
427 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
428 self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
429 self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
430 self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
431 self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
432 self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
433
434 def test_multiple_vars(self):
435 x = 98
436 y = 'abc'
437 self.assertEqual(f'{x}{y}', '98abc')
438
439 self.assertEqual(f'X{x}{y}', 'X98abc')
440 self.assertEqual(f'{x}X{y}', '98Xabc')
441 self.assertEqual(f'{x}{y}X', '98abcX')
442
443 self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
444 self.assertEqual(f'X{x}{y}Y', 'X98abcY')
445 self.assertEqual(f'{x}X{y}Y', '98XabcY')
446
447 self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
448
449 def test_closure(self):
450 def outer(x):
451 def inner():
452 return f'x:{x}'
453 return inner
454
455 self.assertEqual(outer('987')(), 'x:987')
456 self.assertEqual(outer(7)(), 'x:7')
457
458 def test_arguments(self):
459 y = 2
460 def f(x, width):
461 return f'x={x*y:{width}}'
462
463 self.assertEqual(f('foo', 10), 'x=foofoo ')
464 x = 'bar'
465 self.assertEqual(f(10, 10), 'x= 20')
466
467 def test_locals(self):
468 value = 123
469 self.assertEqual(f'v:{value}', 'v:123')
470
471 def test_missing_variable(self):
472 with self.assertRaises(NameError):
473 f'v:{value}'
474
475 def test_missing_format_spec(self):
476 class O:
477 def __format__(self, spec):
478 if not spec:
479 return '*'
480 return spec
481
482 self.assertEqual(f'{O():x}', 'x')
483 self.assertEqual(f'{O()}', '*')
484 self.assertEqual(f'{O():}', '*')
485
486 self.assertEqual(f'{3:}', '3')
487 self.assertEqual(f'{3!s:}', '3')
488
489 def test_global(self):
490 self.assertEqual(f'g:{a_global}', 'g:global variable')
491 self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
492
493 a_local = 'local variable'
494 self.assertEqual(f'g:{a_global} l:{a_local}',
495 'g:global variable l:local variable')
496 self.assertEqual(f'g:{a_global!r}',
497 "g:'global variable'")
498 self.assertEqual(f'g:{a_global} l:{a_local!r}',
499 "g:global variable l:'local variable'")
500
501 self.assertIn("module 'unittest' from", f'{unittest}')
502
503 def test_shadowed_global(self):
504 a_global = 'really a local'
505 self.assertEqual(f'g:{a_global}', 'g:really a local')
506 self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
507
508 a_local = 'local variable'
509 self.assertEqual(f'g:{a_global} l:{a_local}',
510 'g:really a local l:local variable')
511 self.assertEqual(f'g:{a_global!r}',
512 "g:'really a local'")
513 self.assertEqual(f'g:{a_global} l:{a_local!r}',
514 "g:really a local l:'local variable'")
515
516 def test_call(self):
517 def foo(x):
518 return 'x=' + str(x)
519
520 self.assertEqual(f'{foo(10)}', 'x=10')
521
522 def test_nested_fstrings(self):
523 y = 5
524 self.assertEqual(f'{f"{0}"*3}', '000')
525 self.assertEqual(f'{f"{y}"*3}', '555')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400526
527 def test_invalid_string_prefixes(self):
528 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
529 ["fu''",
530 "uf''",
531 "Fu''",
532 "fU''",
533 "Uf''",
534 "uF''",
535 "ufr''",
536 "urf''",
537 "fur''",
538 "fru''",
539 "rfu''",
540 "ruf''",
541 "FUR''",
542 "Fur''",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400543 "fb''",
544 "fB''",
545 "Fb''",
546 "FB''",
547 "bf''",
548 "bF''",
549 "Bf''",
550 "BF''",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400551 ])
552
553 def test_leading_trailing_spaces(self):
554 self.assertEqual(f'{ 3}', '3')
555 self.assertEqual(f'{ 3}', '3')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400556 self.assertEqual(f'{3 }', '3')
557 self.assertEqual(f'{3 }', '3')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400558
559 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
560 'expr={1: 2}')
561 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
562 'expr={1: 2}')
563
Eric V. Smith235a6f02015-09-19 14:51:32 -0400564 def test_not_equal(self):
565 # There's a special test for this because there's a special
566 # case in the f-string parser to look for != as not ending an
567 # expression. Normally it would, while looking for !s or !r.
568
569 self.assertEqual(f'{3!=4}', 'True')
570 self.assertEqual(f'{3!=4:}', 'True')
571 self.assertEqual(f'{3!=4!s}', 'True')
572 self.assertEqual(f'{3!=4!s:.3}', 'Tru')
573
574 def test_conversions(self):
575 self.assertEqual(f'{3.14:10.10}', ' 3.14')
576 self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
577 self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
578 self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
579
580 self.assertEqual(f'{"a"}', 'a')
581 self.assertEqual(f'{"a"!r}', "'a'")
582 self.assertEqual(f'{"a"!a}', "'a'")
583
584 # Not a conversion.
585 self.assertEqual(f'{"a!r"}', "a!r")
586
587 # Not a conversion, but show that ! is allowed in a format spec.
588 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
589
Eric V. Smith235a6f02015-09-19 14:51:32 -0400590 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
591 ["f'{3!g}'",
592 "f'{3!A}'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400593 "f'{3!3}'",
594 "f'{3!G}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400595 "f'{3!!}'",
596 "f'{3!:}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400597 "f'{3! s}'", # no space before conversion char
Eric V. Smith235a6f02015-09-19 14:51:32 -0400598 ])
599
600 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
601 ["f'{x!s{y}}'",
602 "f'{3!ss}'",
603 "f'{3!ss:}'",
604 "f'{3!ss:s}'",
605 ])
606
607 def test_assignment(self):
608 self.assertAllRaise(SyntaxError, 'invalid syntax',
609 ["f'' = 3",
610 "f'{0}' = x",
611 "f'{x}' = x",
612 ])
613
614 def test_del(self):
615 self.assertAllRaise(SyntaxError, 'invalid syntax',
616 ["del f''",
617 "del '' f''",
618 ])
619
620 def test_mismatched_braces(self):
621 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
622 ["f'{{}'",
623 "f'{{}}}'",
624 "f'}'",
625 "f'x}'",
626 "f'x}x'",
627
628 # Can't have { or } in a format spec.
629 "f'{3:}>10}'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400630 "f'{3:}}>10}'",
631 ])
632
633 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
634 ["f'{3:{{>10}'",
635 "f'{3'",
636 "f'{3!'",
637 "f'{3:'",
638 "f'{3!s'",
639 "f'{3!s:'",
640 "f'{3!s:3'",
641 "f'x{'",
642 "f'x{x'",
Eric V. Smith451d0e32016-09-09 21:56:20 -0400643 "f'{x'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400644 "f'{3:s'",
645 "f'{{{'",
646 "f'{{}}{'",
647 "f'{'",
648 ])
649
Eric V. Smith235a6f02015-09-19 14:51:32 -0400650 # But these are just normal strings.
651 self.assertEqual(f'{"{"}', '{')
652 self.assertEqual(f'{"}"}', '}')
653 self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
654 self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
655
656 def test_if_conditional(self):
657 # There's special logic in compile.c to test if the
658 # conditional for an if (and while) are constants. Exercise
659 # that code.
660
661 def test_fstring(x, expected):
662 flag = 0
663 if f'{x}':
664 flag = 1
665 else:
666 flag = 2
667 self.assertEqual(flag, expected)
668
669 def test_concat_empty(x, expected):
670 flag = 0
671 if '' f'{x}':
672 flag = 1
673 else:
674 flag = 2
675 self.assertEqual(flag, expected)
676
677 def test_concat_non_empty(x, expected):
678 flag = 0
679 if ' ' f'{x}':
680 flag = 1
681 else:
682 flag = 2
683 self.assertEqual(flag, expected)
684
685 test_fstring('', 2)
686 test_fstring(' ', 1)
687
688 test_concat_empty('', 2)
689 test_concat_empty(' ', 1)
690
691 test_concat_non_empty('', 1)
692 test_concat_non_empty(' ', 1)
693
694 def test_empty_format_specifier(self):
695 x = 'test'
696 self.assertEqual(f'{x}', 'test')
697 self.assertEqual(f'{x:}', 'test')
698 self.assertEqual(f'{x!s:}', 'test')
699 self.assertEqual(f'{x!r:}', "'test'")
700
701 def test_str_format_differences(self):
702 d = {'a': 'string',
703 0: 'integer',
704 }
705 a = 0
706 self.assertEqual(f'{d[0]}', 'integer')
707 self.assertEqual(f'{d["a"]}', 'string')
708 self.assertEqual(f'{d[a]}', 'integer')
709 self.assertEqual('{d[a]}'.format(d=d), 'string')
710 self.assertEqual('{d[0]}'.format(d=d), 'integer')
711
712 def test_invalid_expressions(self):
713 self.assertAllRaise(SyntaxError, 'invalid syntax',
714 [r"f'{a[4)}'",
715 r"f'{a(4]}'",
716 ])
717
Eric V. Smith135d5f42016-02-05 18:23:08 -0500718 def test_errors(self):
719 # see issue 26287
720 self.assertAllRaise(TypeError, 'non-empty',
721 [r"f'{(lambda: 0):x}'",
722 r"f'{(0,):x}'",
723 ])
724 self.assertAllRaise(ValueError, 'Unknown format code',
725 [r"f'{1000:j}'",
726 r"f'{1000:j}'",
727 ])
728
Eric V. Smith235a6f02015-09-19 14:51:32 -0400729 def test_loop(self):
730 for i in range(1000):
731 self.assertEqual(f'i:{i}', 'i:' + str(i))
732
733 def test_dict(self):
734 d = {'"': 'dquote',
735 "'": 'squote',
736 'foo': 'bar',
737 }
Eric V. Smith235a6f02015-09-19 14:51:32 -0400738 self.assertEqual(f'''{d["'"]}''', 'squote')
739 self.assertEqual(f"""{d['"']}""", 'dquote')
740
741 self.assertEqual(f'{d["foo"]}', 'bar')
742 self.assertEqual(f"{d['foo']}", 'bar')
Eric V. Smith235a6f02015-09-19 14:51:32 -0400743
744if __name__ == '__main__':
745 unittest.main()