blob: a82dedffe4b32a9d8ef09309636a60e4a4346866 [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' ', ' ')
99 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}',
100 '\N{GREEK CAPITAL LETTER DELTA}')
101 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}',
102 '\u0394')
103 self.assertEqual(f'\N{True}', '\u22a8')
104 self.assertEqual(rf'\N{True}', r'\NTrue')
105
106 def test_escape_order(self):
107 # note that hex(ord('{')) == 0x7b, so this
108 # string becomes f'a{4*10}b'
109 self.assertEqual(f'a\u007b4*10}b', 'a40b')
110 self.assertEqual(f'a\x7b4*10}b', 'a40b')
111 self.assertEqual(f'a\x7b4*10\N{RIGHT CURLY BRACKET}b', 'a40b')
112 self.assertEqual(f'{"a"!\N{LATIN SMALL LETTER R}}', "'a'")
113 self.assertEqual(f'{10\x3a02X}', '0A')
114 self.assertEqual(f'{10:02\N{LATIN CAPITAL LETTER X}}', '0A')
115
116 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
117 [r"""f'a{\u007b4*10}b'""", # mis-matched brackets
118 ])
119 self.assertAllRaise(SyntaxError, 'unexpected character after line continuation character',
120 [r"""f'{"a"\!r}'""",
121 r"""f'{a\!r}'""",
122 ])
123
124 def test_unterminated_string(self):
125 self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
126 [r"""f'{"x'""",
127 r"""f'{"x}'""",
128 r"""f'{("x'""",
129 r"""f'{("x}'""",
130 ])
131
132 def test_mismatched_parens(self):
133 self.assertAllRaise(SyntaxError, 'f-string: mismatched',
134 ["f'{((}'",
135 ])
136
137 def test_double_braces(self):
138 self.assertEqual(f'{{', '{')
139 self.assertEqual(f'a{{', 'a{')
140 self.assertEqual(f'{{b', '{b')
141 self.assertEqual(f'a{{b', 'a{b')
142 self.assertEqual(f'}}', '}')
143 self.assertEqual(f'a}}', 'a}')
144 self.assertEqual(f'}}b', '}b')
145 self.assertEqual(f'a}}b', 'a}b')
146
147 self.assertEqual(f'{{{10}', '{10')
148 self.assertEqual(f'}}{10}', '}10')
149 self.assertEqual(f'}}{{{10}', '}{10')
150 self.assertEqual(f'}}a{{{10}', '}a{10')
151
152 self.assertEqual(f'{10}{{', '10{')
153 self.assertEqual(f'{10}}}', '10}')
154 self.assertEqual(f'{10}}}{{', '10}{')
155 self.assertEqual(f'{10}}}a{{' '}', '10}a{}')
156
157 # Inside of strings, don't interpret doubled brackets.
158 self.assertEqual(f'{"{{}}"}', '{{}}')
159
160 self.assertAllRaise(TypeError, 'unhashable type',
161 ["f'{ {{}} }'", # dict in a set
162 ])
163
164 def test_compile_time_concat(self):
165 x = 'def'
166 self.assertEqual('abc' f'## {x}ghi', 'abc## defghi')
167 self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi')
168 self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ')
169 self.assertEqual('{x}' f'{x}', '{x}def')
170 self.assertEqual('{x' f'{x}', '{xdef')
171 self.assertEqual('{x}' f'{x}', '{x}def')
172 self.assertEqual('{{x}}' f'{x}', '{{x}}def')
173 self.assertEqual('{{x' f'{x}', '{{xdef')
174 self.assertEqual('x}}' f'{x}', 'x}}def')
175 self.assertEqual(f'{x}' 'x}}', 'defx}}')
176 self.assertEqual(f'{x}' '', 'def')
177 self.assertEqual('' f'{x}' '', 'def')
178 self.assertEqual('' f'{x}', 'def')
179 self.assertEqual(f'{x}' '2', 'def2')
180 self.assertEqual('1' f'{x}' '2', '1def2')
181 self.assertEqual('1' f'{x}', '1def')
182 self.assertEqual(f'{x}' f'-{x}', 'def-def')
183 self.assertEqual('' f'', '')
184 self.assertEqual('' f'' '', '')
185 self.assertEqual('' f'' '' f'', '')
186 self.assertEqual(f'', '')
187 self.assertEqual(f'' '', '')
188 self.assertEqual(f'' '' f'', '')
189 self.assertEqual(f'' '' f'' '', '')
190
191 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
192 ["f'{3' f'}'", # can't concat to get a valid f-string
193 ])
194
195 def test_comments(self):
196 # These aren't comments, since they're in strings.
197 d = {'#': 'hash'}
198 self.assertEqual(f'{"#"}', '#')
199 self.assertEqual(f'{d["#"]}', 'hash')
200
201 self.assertAllRaise(SyntaxError, "f-string cannot include '#'",
202 ["f'{1#}'", # error because the expression becomes "(1#)"
203 "f'{3(#)}'",
204 ])
205
206 def test_many_expressions(self):
207 # Create a string with many expressions in it. Note that
208 # because we have a space in here as a literal, we're actually
209 # going to use twice as many ast nodes: one for each literal
210 # plus one for each expression.
211 def build_fstr(n, extra=''):
212 return "f'" + ('{x} ' * n) + extra + "'"
213
214 x = 'X'
215 width = 1
216
217 # Test around 256.
218 for i in range(250, 260):
219 self.assertEqual(eval(build_fstr(i)), (x+' ')*i)
220
221 # Test concatenating 2 largs fstrings.
222 self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256))
223
224 s = build_fstr(253, '{x:{width}} ')
225 self.assertEqual(eval(s), (x+' ')*254)
226
227 # Test lots of expressions and constants, concatenated.
228 s = "f'{1}' 'x' 'y'" * 1024
229 self.assertEqual(eval(s), '1xy' * 1024)
230
231 def test_format_specifier_expressions(self):
232 width = 10
233 precision = 4
234 value = decimal.Decimal('12.34567')
235 self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35')
236 self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35')
237 self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35')
238 self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35')
239 self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35')
240 self.assertEqual(f'{10:#{1}0x}', ' 0xa')
241 self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa')
242 self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa')
243 self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa')
244 self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa')
245
246 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
247 ["""f'{"s"!r{":10"}}'""",
248
249 # This looks like a nested format spec.
250 ])
251
252 self.assertAllRaise(SyntaxError, "invalid syntax",
253 [# Invalid sytax inside a nested spec.
254 "f'{4:{/5}}'",
255 ])
256
257 self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply",
258 [# Can't nest format specifiers.
259 "f'result: {value:{width:{0}}.{precision:1}}'",
260 ])
261
262 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
263 [# No expansion inside conversion or for
264 # the : or ! itself.
265 """f'{"s"!{"r"}}'""",
266 ])
267
268 def test_side_effect_order(self):
269 class X:
270 def __init__(self):
271 self.i = 0
272 def __format__(self, spec):
273 self.i += 1
274 return str(self.i)
275
276 x = X()
277 self.assertEqual(f'{x} {x}', '1 2')
278
279 def test_missing_expression(self):
280 self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
281 ["f'{}'",
282 "f'{ }'"
283 "f' {} '",
284 "f'{!r}'",
285 "f'{ !r}'",
286 "f'{10:{ }}'",
287 "f' { } '",
288 r"f'{\n}'",
289 r"f'{\n \n}'",
Eric V. Smith1d44c412015-09-23 07:49:00 -0400290
291 # Catch the empty expression before the
292 # invalid conversion.
293 "f'{!x}'",
294 "f'{ !xr}'",
295 "f'{!x:}'",
296 "f'{!x:a}'",
297 "f'{ !xr:}'",
298 "f'{ !xr:a}'",
Eric V. Smith548c4d32015-09-23 08:00:01 -0400299
300 "f'{!}'",
301 "f'{:}'",
Eric V. Smithb2080f62015-09-23 10:24:43 -0400302
303 # We find the empty expression before the
304 # missing closing brace.
305 "f'{!'",
306 "f'{!s:'",
307 "f'{:'",
308 "f'{:x'",
Eric V. Smith235a6f02015-09-19 14:51:32 -0400309 ])
310
311 def test_parens_in_expressions(self):
312 self.assertEqual(f'{3,}', '(3,)')
313
314 # Add these because when an expression is evaluated, parens
315 # are added around it. But we shouldn't go from an invalid
316 # expression to a valid one. The added parens are just
317 # supposed to allow whitespace (including newlines).
318 self.assertAllRaise(SyntaxError, 'invalid syntax',
319 ["f'{,}'",
320 "f'{,}'", # this is (,), which is an error
321 ])
322
323 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
324 ["f'{3)+(4}'",
325 ])
326
327 self.assertAllRaise(SyntaxError, 'EOL while scanning string literal',
328 ["f'{\n}'",
329 ])
330
331 def test_newlines_in_expressions(self):
332 self.assertEqual(f'{0}', '0')
333 self.assertEqual(f'{0\n}', '0')
334 self.assertEqual(f'{0\r}', '0')
335 self.assertEqual(f'{\n0\n}', '0')
336 self.assertEqual(f'{\r0\r}', '0')
337 self.assertEqual(f'{\n0\r}', '0')
338 self.assertEqual(f'{\n0}', '0')
339 self.assertEqual(f'{3+\n4}', '7')
340 self.assertEqual(f'{3+\\\n4}', '7')
341 self.assertEqual(rf'''{3+
3424}''', '7')
343 self.assertEqual(f'''{3+\
3444}''', '7')
345
346 self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
347 [r"f'{\n}'",
348 ])
349
350 def test_lambda(self):
351 x = 5
352 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'")
353 self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ")
354 self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ")
355
356 # lambda doesn't work without parens, because the colon
357 # makes the parser think it's a format_spec
358 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
359 ["f'{lambda x:x}'",
360 ])
361
362 def test_yield(self):
363 # Not terribly useful, but make sure the yield turns
364 # a function into a generator
365 def fn(y):
366 f'y:{yield y*2}'
367
368 g = fn(4)
369 self.assertEqual(next(g), 8)
370
371 def test_yield_send(self):
372 def fn(x):
373 yield f'x:{yield (lambda i: x * i)}'
374
375 g = fn(10)
376 the_lambda = next(g)
377 self.assertEqual(the_lambda(4), 40)
378 self.assertEqual(g.send('string'), 'x:string')
379
380 def test_expressions_with_triple_quoted_strings(self):
381 self.assertEqual(f"{'''x'''}", 'x')
382 self.assertEqual(f"{'''eric's'''}", "eric's")
383 self.assertEqual(f'{"""eric\'s"""}', "eric's")
384 self.assertEqual(f"{'''eric\"s'''}", 'eric"s')
385 self.assertEqual(f'{"""eric"s"""}', 'eric"s')
386
387 # Test concatenation within an expression
388 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
389 self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s')
390 self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy')
391 self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy')
392 self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy')
393 self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy')
394
395 def test_multiple_vars(self):
396 x = 98
397 y = 'abc'
398 self.assertEqual(f'{x}{y}', '98abc')
399
400 self.assertEqual(f'X{x}{y}', 'X98abc')
401 self.assertEqual(f'{x}X{y}', '98Xabc')
402 self.assertEqual(f'{x}{y}X', '98abcX')
403
404 self.assertEqual(f'X{x}Y{y}', 'X98Yabc')
405 self.assertEqual(f'X{x}{y}Y', 'X98abcY')
406 self.assertEqual(f'{x}X{y}Y', '98XabcY')
407
408 self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ')
409
410 def test_closure(self):
411 def outer(x):
412 def inner():
413 return f'x:{x}'
414 return inner
415
416 self.assertEqual(outer('987')(), 'x:987')
417 self.assertEqual(outer(7)(), 'x:7')
418
419 def test_arguments(self):
420 y = 2
421 def f(x, width):
422 return f'x={x*y:{width}}'
423
424 self.assertEqual(f('foo', 10), 'x=foofoo ')
425 x = 'bar'
426 self.assertEqual(f(10, 10), 'x= 20')
427
428 def test_locals(self):
429 value = 123
430 self.assertEqual(f'v:{value}', 'v:123')
431
432 def test_missing_variable(self):
433 with self.assertRaises(NameError):
434 f'v:{value}'
435
436 def test_missing_format_spec(self):
437 class O:
438 def __format__(self, spec):
439 if not spec:
440 return '*'
441 return spec
442
443 self.assertEqual(f'{O():x}', 'x')
444 self.assertEqual(f'{O()}', '*')
445 self.assertEqual(f'{O():}', '*')
446
447 self.assertEqual(f'{3:}', '3')
448 self.assertEqual(f'{3!s:}', '3')
449
450 def test_global(self):
451 self.assertEqual(f'g:{a_global}', 'g:global variable')
452 self.assertEqual(f'g:{a_global!r}', "g:'global variable'")
453
454 a_local = 'local variable'
455 self.assertEqual(f'g:{a_global} l:{a_local}',
456 'g:global variable l:local variable')
457 self.assertEqual(f'g:{a_global!r}',
458 "g:'global variable'")
459 self.assertEqual(f'g:{a_global} l:{a_local!r}',
460 "g:global variable l:'local variable'")
461
462 self.assertIn("module 'unittest' from", f'{unittest}')
463
464 def test_shadowed_global(self):
465 a_global = 'really a local'
466 self.assertEqual(f'g:{a_global}', 'g:really a local')
467 self.assertEqual(f'g:{a_global!r}', "g:'really a local'")
468
469 a_local = 'local variable'
470 self.assertEqual(f'g:{a_global} l:{a_local}',
471 'g:really a local l:local variable')
472 self.assertEqual(f'g:{a_global!r}',
473 "g:'really a local'")
474 self.assertEqual(f'g:{a_global} l:{a_local!r}',
475 "g:really a local l:'local variable'")
476
477 def test_call(self):
478 def foo(x):
479 return 'x=' + str(x)
480
481 self.assertEqual(f'{foo(10)}', 'x=10')
482
483 def test_nested_fstrings(self):
484 y = 5
485 self.assertEqual(f'{f"{0}"*3}', '000')
486 self.assertEqual(f'{f"{y}"*3}', '555')
487 self.assertEqual(f'{f"{\'x\'}"*3}', 'xxx')
488
489 self.assertEqual(f"{r'x' f'{\"s\"}'}", 'xs')
490 self.assertEqual(f"{r'x'rf'{\"s\"}'}", 'xs')
491
492 def test_invalid_string_prefixes(self):
493 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
494 ["fu''",
495 "uf''",
496 "Fu''",
497 "fU''",
498 "Uf''",
499 "uF''",
500 "ufr''",
501 "urf''",
502 "fur''",
503 "fru''",
504 "rfu''",
505 "ruf''",
506 "FUR''",
507 "Fur''",
508 ])
509
510 def test_leading_trailing_spaces(self):
511 self.assertEqual(f'{ 3}', '3')
512 self.assertEqual(f'{ 3}', '3')
513 self.assertEqual(f'{\t3}', '3')
514 self.assertEqual(f'{\t\t3}', '3')
515 self.assertEqual(f'{3 }', '3')
516 self.assertEqual(f'{3 }', '3')
517 self.assertEqual(f'{3\t}', '3')
518 self.assertEqual(f'{3\t\t}', '3')
519
520 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
521 'expr={1: 2}')
522 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
523 'expr={1: 2}')
524
525 def test_character_name(self):
526 self.assertEqual(f'{4}\N{GREEK CAPITAL LETTER DELTA}{3}',
527 '4\N{GREEK CAPITAL LETTER DELTA}3')
528 self.assertEqual(f'{{}}\N{GREEK CAPITAL LETTER DELTA}{3}',
529 '{}\N{GREEK CAPITAL LETTER DELTA}3')
530
531 def test_not_equal(self):
532 # There's a special test for this because there's a special
533 # case in the f-string parser to look for != as not ending an
534 # expression. Normally it would, while looking for !s or !r.
535
536 self.assertEqual(f'{3!=4}', 'True')
537 self.assertEqual(f'{3!=4:}', 'True')
538 self.assertEqual(f'{3!=4!s}', 'True')
539 self.assertEqual(f'{3!=4!s:.3}', 'Tru')
540
541 def test_conversions(self):
542 self.assertEqual(f'{3.14:10.10}', ' 3.14')
543 self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
544 self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
545 self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
546
547 self.assertEqual(f'{"a"}', 'a')
548 self.assertEqual(f'{"a"!r}', "'a'")
549 self.assertEqual(f'{"a"!a}', "'a'")
550
551 # Not a conversion.
552 self.assertEqual(f'{"a!r"}', "a!r")
553
554 # Not a conversion, but show that ! is allowed in a format spec.
555 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
556
557 self.assertEqual(f'{"\N{GREEK CAPITAL LETTER DELTA}"}', '\u0394')
558 self.assertEqual(f'{"\N{GREEK CAPITAL LETTER DELTA}"!r}', "'\u0394'")
559 self.assertEqual(f'{"\N{GREEK CAPITAL LETTER DELTA}"!a}', "'\\u0394'")
560
561 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
562 ["f'{3!g}'",
563 "f'{3!A}'",
564 "f'{3!A}'",
565 "f'{3!A}'",
566 "f'{3!!}'",
567 "f'{3!:}'",
568 "f'{3!\N{GREEK CAPITAL LETTER DELTA}}'",
569 "f'{3! s}'", # no space before conversion char
570 "f'{x!\\x00:.<10}'",
571 ])
572
573 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
574 ["f'{x!s{y}}'",
575 "f'{3!ss}'",
576 "f'{3!ss:}'",
577 "f'{3!ss:s}'",
578 ])
579
580 def test_assignment(self):
581 self.assertAllRaise(SyntaxError, 'invalid syntax',
582 ["f'' = 3",
583 "f'{0}' = x",
584 "f'{x}' = x",
585 ])
586
587 def test_del(self):
588 self.assertAllRaise(SyntaxError, 'invalid syntax',
589 ["del f''",
590 "del '' f''",
591 ])
592
593 def test_mismatched_braces(self):
594 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
595 ["f'{{}'",
596 "f'{{}}}'",
597 "f'}'",
598 "f'x}'",
599 "f'x}x'",
600
601 # Can't have { or } in a format spec.
602 "f'{3:}>10}'",
603 r"f'{3:\\}>10}'",
604 "f'{3:}}>10}'",
605 ])
606
607 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
608 ["f'{3:{{>10}'",
609 "f'{3'",
610 "f'{3!'",
611 "f'{3:'",
612 "f'{3!s'",
613 "f'{3!s:'",
614 "f'{3!s:3'",
615 "f'x{'",
616 "f'x{x'",
617 "f'{3:s'",
618 "f'{{{'",
619 "f'{{}}{'",
620 "f'{'",
621 ])
622
623 self.assertAllRaise(SyntaxError, 'invalid syntax',
624 [r"f'{3:\\{>10}'",
625 ])
626
627 # But these are just normal strings.
628 self.assertEqual(f'{"{"}', '{')
629 self.assertEqual(f'{"}"}', '}')
630 self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3')
631 self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2')
632
633 def test_if_conditional(self):
634 # There's special logic in compile.c to test if the
635 # conditional for an if (and while) are constants. Exercise
636 # that code.
637
638 def test_fstring(x, expected):
639 flag = 0
640 if f'{x}':
641 flag = 1
642 else:
643 flag = 2
644 self.assertEqual(flag, expected)
645
646 def test_concat_empty(x, expected):
647 flag = 0
648 if '' f'{x}':
649 flag = 1
650 else:
651 flag = 2
652 self.assertEqual(flag, expected)
653
654 def test_concat_non_empty(x, expected):
655 flag = 0
656 if ' ' f'{x}':
657 flag = 1
658 else:
659 flag = 2
660 self.assertEqual(flag, expected)
661
662 test_fstring('', 2)
663 test_fstring(' ', 1)
664
665 test_concat_empty('', 2)
666 test_concat_empty(' ', 1)
667
668 test_concat_non_empty('', 1)
669 test_concat_non_empty(' ', 1)
670
671 def test_empty_format_specifier(self):
672 x = 'test'
673 self.assertEqual(f'{x}', 'test')
674 self.assertEqual(f'{x:}', 'test')
675 self.assertEqual(f'{x!s:}', 'test')
676 self.assertEqual(f'{x!r:}', "'test'")
677
678 def test_str_format_differences(self):
679 d = {'a': 'string',
680 0: 'integer',
681 }
682 a = 0
683 self.assertEqual(f'{d[0]}', 'integer')
684 self.assertEqual(f'{d["a"]}', 'string')
685 self.assertEqual(f'{d[a]}', 'integer')
686 self.assertEqual('{d[a]}'.format(d=d), 'string')
687 self.assertEqual('{d[0]}'.format(d=d), 'integer')
688
689 def test_invalid_expressions(self):
690 self.assertAllRaise(SyntaxError, 'invalid syntax',
691 [r"f'{a[4)}'",
692 r"f'{a(4]}'",
693 ])
694
Eric V. Smith135d5f42016-02-05 18:23:08 -0500695 def test_errors(self):
696 # see issue 26287
697 self.assertAllRaise(TypeError, 'non-empty',
698 [r"f'{(lambda: 0):x}'",
699 r"f'{(0,):x}'",
700 ])
701 self.assertAllRaise(ValueError, 'Unknown format code',
702 [r"f'{1000:j}'",
703 r"f'{1000:j}'",
704 ])
705
Eric V. Smith235a6f02015-09-19 14:51:32 -0400706 def test_loop(self):
707 for i in range(1000):
708 self.assertEqual(f'i:{i}', 'i:' + str(i))
709
710 def test_dict(self):
711 d = {'"': 'dquote',
712 "'": 'squote',
713 'foo': 'bar',
714 }
715 self.assertEqual(f'{d["\'"]}', 'squote')
716 self.assertEqual(f"{d['\"']}", 'dquote')
717
718 self.assertEqual(f'''{d["'"]}''', 'squote')
719 self.assertEqual(f"""{d['"']}""", 'dquote')
720
721 self.assertEqual(f'{d["foo"]}', 'bar')
722 self.assertEqual(f"{d['foo']}", 'bar')
723 self.assertEqual(f'{d[\'foo\']}', 'bar')
724 self.assertEqual(f"{d[\"foo\"]}", 'bar')
725
726 def test_escaped_quotes(self):
727 d = {'"': 'a',
728 "'": 'b'}
729
730 self.assertEqual(fr"{d['\"']}", 'a')
731 self.assertEqual(fr'{d["\'"]}', 'b')
732 self.assertEqual(fr"{'\"'}", '"')
733 self.assertEqual(fr'{"\'"}', "'")
734 self.assertEqual(f'{"\\"3"}', '"3')
735
736 self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
737 [r'''f'{"""\\}' ''', # Backslash at end of expression
738 ])
739 self.assertAllRaise(SyntaxError, 'unexpected character after line continuation',
740 [r"rf'{3\}'",
741 ])
742
743
744if __name__ == '__main__':
745 unittest.main()