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