# Python code coverage for Lib/test/test_fstring.py

# | count | content |
---|---|---|

1 | n/a | import ast |

2 | n/a | import types |

3 | n/a | import decimal |

4 | n/a | import unittest |

5 | n/a | |

6 | n/a | a_global = 'global variable' |

7 | n/a | |

8 | n/a | # You could argue that I'm too strict in looking for specific error |

9 | n/a | # values with assertRaisesRegex, but without it it's way too easy to |

10 | n/a | # make a syntax error in the test strings. Especially with all of the |

11 | n/a | # triple quotes, raw strings, backslashes, etc. I think it's a |

12 | n/a | # worthwhile tradeoff. When I switched to this method, I found many |

13 | n/a | # examples where I wasn't testing what I thought I was. |

14 | n/a | |

15 | n/a | class TestCase(unittest.TestCase): |

16 | n/a | def assertAllRaise(self, exception_type, regex, error_strings): |

17 | n/a | for str in error_strings: |

18 | n/a | with self.subTest(str=str): |

19 | n/a | with self.assertRaisesRegex(exception_type, regex): |

20 | n/a | eval(str) |

21 | n/a | |

22 | n/a | def test__format__lookup(self): |

23 | n/a | # Make sure __format__ is looked up on the type, not the instance. |

24 | n/a | class X: |

25 | n/a | def __format__(self, spec): |

26 | n/a | return 'class' |

27 | n/a | |

28 | n/a | x = X() |

29 | n/a | |

30 | n/a | # Add a bound __format__ method to the 'y' instance, but not |

31 | n/a | # the 'x' instance. |

32 | n/a | y = X() |

33 | n/a | y.__format__ = types.MethodType(lambda self, spec: 'instance', y) |

34 | n/a | |

35 | n/a | self.assertEqual(f'{y}', format(y)) |

36 | n/a | self.assertEqual(f'{y}', 'class') |

37 | n/a | self.assertEqual(format(x), format(y)) |

38 | n/a | |

39 | n/a | # __format__ is not called this way, but still make sure it |

40 | n/a | # returns what we expect (so we can make sure we're bypassing |

41 | n/a | # it). |

42 | n/a | self.assertEqual(x.__format__(''), 'class') |

43 | n/a | self.assertEqual(y.__format__(''), 'instance') |

44 | n/a | |

45 | n/a | # This is how __format__ is actually called. |

46 | n/a | self.assertEqual(type(x).__format__(x, ''), 'class') |

47 | n/a | self.assertEqual(type(y).__format__(y, ''), 'class') |

48 | n/a | |

49 | n/a | def test_ast(self): |

50 | n/a | # Inspired by http://bugs.python.org/issue24975 |

51 | n/a | class X: |

52 | n/a | def __init__(self): |

53 | n/a | self.called = False |

54 | n/a | def __call__(self): |

55 | n/a | self.called = True |

56 | n/a | return 4 |

57 | n/a | x = X() |

58 | n/a | expr = """ |

59 | n/a | a = 10 |

60 | n/a | f'{a * x()}'""" |

61 | n/a | t = ast.parse(expr) |

62 | n/a | c = compile(t, '', 'exec') |

63 | n/a | |

64 | n/a | # Make sure x was not called. |

65 | n/a | self.assertFalse(x.called) |

66 | n/a | |

67 | n/a | # Actually run the code. |

68 | n/a | exec(c) |

69 | n/a | |

70 | n/a | # Make sure x was called. |

71 | n/a | self.assertTrue(x.called) |

72 | n/a | |

73 | n/a | def test_docstring(self): |

74 | n/a | def f(): |

75 | n/a | f'''Not a docstring''' |

76 | n/a | self.assertIsNone(f.__doc__) |

77 | n/a | def g(): |

78 | n/a | '''Not a docstring''' \ |

79 | n/a | f'' |

80 | n/a | self.assertIsNone(g.__doc__) |

81 | n/a | |

82 | n/a | def test_literal_eval(self): |

83 | n/a | with self.assertRaisesRegex(ValueError, 'malformed node or string'): |

84 | n/a | ast.literal_eval("f'x'") |

85 | n/a | |

86 | n/a | def test_ast_compile_time_concat(self): |

87 | n/a | x = [''] |

88 | n/a | |

89 | n/a | expr = """x[0] = 'foo' f'{3}'""" |

90 | n/a | t = ast.parse(expr) |

91 | n/a | c = compile(t, '', 'exec') |

92 | n/a | exec(c) |

93 | n/a | self.assertEqual(x[0], 'foo3') |

94 | n/a | |

95 | n/a | def test_compile_time_concat_errors(self): |

96 | n/a | self.assertAllRaise(SyntaxError, |

97 | n/a | 'cannot mix bytes and nonbytes literals', |

98 | n/a | [r"""f'' b''""", |

99 | n/a | r"""b'' f''""", |

100 | n/a | ]) |

101 | n/a | |

102 | n/a | def test_literal(self): |

103 | n/a | self.assertEqual(f'', '') |

104 | n/a | self.assertEqual(f'a', 'a') |

105 | n/a | self.assertEqual(f' ', ' ') |

106 | n/a | |

107 | n/a | def test_unterminated_string(self): |

108 | n/a | self.assertAllRaise(SyntaxError, 'f-string: unterminated string', |

109 | n/a | [r"""f'{"x'""", |

110 | n/a | r"""f'{"x}'""", |

111 | n/a | r"""f'{("x'""", |

112 | n/a | r"""f'{("x}'""", |

113 | n/a | ]) |

114 | n/a | |

115 | n/a | def test_mismatched_parens(self): |

116 | n/a | self.assertAllRaise(SyntaxError, 'f-string: mismatched', |

117 | n/a | ["f'{((}'", |

118 | n/a | ]) |

119 | n/a | |

120 | n/a | def test_double_braces(self): |

121 | n/a | self.assertEqual(f'{{', '{') |

122 | n/a | self.assertEqual(f'a{{', 'a{') |

123 | n/a | self.assertEqual(f'{{b', '{b') |

124 | n/a | self.assertEqual(f'a{{b', 'a{b') |

125 | n/a | self.assertEqual(f'}}', '}') |

126 | n/a | self.assertEqual(f'a}}', 'a}') |

127 | n/a | self.assertEqual(f'}}b', '}b') |

128 | n/a | self.assertEqual(f'a}}b', 'a}b') |

129 | n/a | self.assertEqual(f'{{}}', '{}') |

130 | n/a | self.assertEqual(f'a{{}}', 'a{}') |

131 | n/a | self.assertEqual(f'{{b}}', '{b}') |

132 | n/a | self.assertEqual(f'{{}}c', '{}c') |

133 | n/a | self.assertEqual(f'a{{b}}', 'a{b}') |

134 | n/a | self.assertEqual(f'a{{}}c', 'a{}c') |

135 | n/a | self.assertEqual(f'{{b}}c', '{b}c') |

136 | n/a | self.assertEqual(f'a{{b}}c', 'a{b}c') |

137 | n/a | |

138 | n/a | self.assertEqual(f'{{{10}', '{10') |

139 | n/a | self.assertEqual(f'}}{10}', '}10') |

140 | n/a | self.assertEqual(f'}}{{{10}', '}{10') |

141 | n/a | self.assertEqual(f'}}a{{{10}', '}a{10') |

142 | n/a | |

143 | n/a | self.assertEqual(f'{10}{{', '10{') |

144 | n/a | self.assertEqual(f'{10}}}', '10}') |

145 | n/a | self.assertEqual(f'{10}}}{{', '10}{') |

146 | n/a | self.assertEqual(f'{10}}}a{{' '}', '10}a{}') |

147 | n/a | |

148 | n/a | # Inside of strings, don't interpret doubled brackets. |

149 | n/a | self.assertEqual(f'{"{{}}"}', '{{}}') |

150 | n/a | |

151 | n/a | self.assertAllRaise(TypeError, 'unhashable type', |

152 | n/a | ["f'{ {{}} }'", # dict in a set |

153 | n/a | ]) |

154 | n/a | |

155 | n/a | def test_compile_time_concat(self): |

156 | n/a | x = 'def' |

157 | n/a | self.assertEqual('abc' f'## {x}ghi', 'abc## defghi') |

158 | n/a | self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi') |

159 | n/a | self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ') |

160 | n/a | self.assertEqual('{x}' f'{x}', '{x}def') |

161 | n/a | self.assertEqual('{x' f'{x}', '{xdef') |

162 | n/a | self.assertEqual('{x}' f'{x}', '{x}def') |

163 | n/a | self.assertEqual('{{x}}' f'{x}', '{{x}}def') |

164 | n/a | self.assertEqual('{{x' f'{x}', '{{xdef') |

165 | n/a | self.assertEqual('x}}' f'{x}', 'x}}def') |

166 | n/a | self.assertEqual(f'{x}' 'x}}', 'defx}}') |

167 | n/a | self.assertEqual(f'{x}' '', 'def') |

168 | n/a | self.assertEqual('' f'{x}' '', 'def') |

169 | n/a | self.assertEqual('' f'{x}', 'def') |

170 | n/a | self.assertEqual(f'{x}' '2', 'def2') |

171 | n/a | self.assertEqual('1' f'{x}' '2', '1def2') |

172 | n/a | self.assertEqual('1' f'{x}', '1def') |

173 | n/a | self.assertEqual(f'{x}' f'-{x}', 'def-def') |

174 | n/a | self.assertEqual('' f'', '') |

175 | n/a | self.assertEqual('' f'' '', '') |

176 | n/a | self.assertEqual('' f'' '' f'', '') |

177 | n/a | self.assertEqual(f'', '') |

178 | n/a | self.assertEqual(f'' '', '') |

179 | n/a | self.assertEqual(f'' '' f'', '') |

180 | n/a | self.assertEqual(f'' '' f'' '', '') |

181 | n/a | |

182 | n/a | self.assertAllRaise(SyntaxError, "f-string: expecting '}'", |

183 | n/a | ["f'{3' f'}'", # can't concat to get a valid f-string |

184 | n/a | ]) |

185 | n/a | |

186 | n/a | def test_comments(self): |

187 | n/a | # These aren't comments, since they're in strings. |

188 | n/a | d = {'#': 'hash'} |

189 | n/a | self.assertEqual(f'{"#"}', '#') |

190 | n/a | self.assertEqual(f'{d["#"]}', 'hash') |

191 | n/a | |

192 | n/a | self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'", |

193 | n/a | ["f'{1#}'", # error because the expression becomes "(1#)" |

194 | n/a | "f'{3(#)}'", |

195 | n/a | "f'{#}'", |

196 | n/a | "f'{)#}'", # When wrapped in parens, this becomes |

197 | n/a | # '()#)'. Make sure that doesn't compile. |

198 | n/a | ]) |

199 | n/a | |

200 | n/a | def test_many_expressions(self): |

201 | n/a | # Create a string with many expressions in it. Note that |

202 | n/a | # because we have a space in here as a literal, we're actually |

203 | n/a | # going to use twice as many ast nodes: one for each literal |

204 | n/a | # plus one for each expression. |

205 | n/a | def build_fstr(n, extra=''): |

206 | n/a | return "f'" + ('{x} ' * n) + extra + "'" |

207 | n/a | |

208 | n/a | x = 'X' |

209 | n/a | width = 1 |

210 | n/a | |

211 | n/a | # Test around 256. |

212 | n/a | for i in range(250, 260): |

213 | n/a | self.assertEqual(eval(build_fstr(i)), (x+' ')*i) |

214 | n/a | |

215 | n/a | # Test concatenating 2 largs fstrings. |

216 | n/a | self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256)) |

217 | n/a | |

218 | n/a | s = build_fstr(253, '{x:{width}} ') |

219 | n/a | self.assertEqual(eval(s), (x+' ')*254) |

220 | n/a | |

221 | n/a | # Test lots of expressions and constants, concatenated. |

222 | n/a | s = "f'{1}' 'x' 'y'" * 1024 |

223 | n/a | self.assertEqual(eval(s), '1xy' * 1024) |

224 | n/a | |

225 | n/a | def test_format_specifier_expressions(self): |

226 | n/a | width = 10 |

227 | n/a | precision = 4 |

228 | n/a | value = decimal.Decimal('12.34567') |

229 | n/a | self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35') |

230 | n/a | self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35') |

231 | n/a | self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35') |

232 | n/a | self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35') |

233 | n/a | self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35') |

234 | n/a | self.assertEqual(f'{10:#{1}0x}', ' 0xa') |

235 | n/a | self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa') |

236 | n/a | self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa') |

237 | n/a | self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa') |

238 | n/a | self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa') |

239 | n/a | |

240 | n/a | self.assertAllRaise(SyntaxError, "f-string: expecting '}'", |

241 | n/a | ["""f'{"s"!r{":10"}}'""", |

242 | n/a | |

243 | n/a | # This looks like a nested format spec. |

244 | n/a | ]) |

245 | n/a | |

246 | n/a | self.assertAllRaise(SyntaxError, "invalid syntax", |

247 | n/a | [# Invalid syntax inside a nested spec. |

248 | n/a | "f'{4:{/5}}'", |

249 | n/a | ]) |

250 | n/a | |

251 | n/a | self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply", |

252 | n/a | [# Can't nest format specifiers. |

253 | n/a | "f'result: {value:{width:{0}}.{precision:1}}'", |

254 | n/a | ]) |

255 | n/a | |

256 | n/a | self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', |

257 | n/a | [# No expansion inside conversion or for |

258 | n/a | # the : or ! itself. |

259 | n/a | """f'{"s"!{"r"}}'""", |

260 | n/a | ]) |

261 | n/a | |

262 | n/a | def test_side_effect_order(self): |

263 | n/a | class X: |

264 | n/a | def __init__(self): |

265 | n/a | self.i = 0 |

266 | n/a | def __format__(self, spec): |

267 | n/a | self.i += 1 |

268 | n/a | return str(self.i) |

269 | n/a | |

270 | n/a | x = X() |

271 | n/a | self.assertEqual(f'{x} {x}', '1 2') |

272 | n/a | |

273 | n/a | def test_missing_expression(self): |

274 | n/a | self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed', |

275 | n/a | ["f'{}'", |

276 | n/a | "f'{ }'" |

277 | n/a | "f' {} '", |

278 | n/a | "f'{!r}'", |

279 | n/a | "f'{ !r}'", |

280 | n/a | "f'{10:{ }}'", |

281 | n/a | "f' { } '", |

282 | n/a | |

283 | n/a | # Catch the empty expression before the |

284 | n/a | # invalid conversion. |

285 | n/a | "f'{!x}'", |

286 | n/a | "f'{ !xr}'", |

287 | n/a | "f'{!x:}'", |

288 | n/a | "f'{!x:a}'", |

289 | n/a | "f'{ !xr:}'", |

290 | n/a | "f'{ !xr:a}'", |

291 | n/a | |

292 | n/a | "f'{!}'", |

293 | n/a | "f'{:}'", |

294 | n/a | |

295 | n/a | # We find the empty expression before the |

296 | n/a | # missing closing brace. |

297 | n/a | "f'{!'", |

298 | n/a | "f'{!s:'", |

299 | n/a | "f'{:'", |

300 | n/a | "f'{:x'", |

301 | n/a | ]) |

302 | n/a | |

303 | n/a | def test_parens_in_expressions(self): |

304 | n/a | self.assertEqual(f'{3,}', '(3,)') |

305 | n/a | |

306 | n/a | # Add these because when an expression is evaluated, parens |

307 | n/a | # are added around it. But we shouldn't go from an invalid |

308 | n/a | # expression to a valid one. The added parens are just |

309 | n/a | # supposed to allow whitespace (including newlines). |

310 | n/a | self.assertAllRaise(SyntaxError, 'invalid syntax', |

311 | n/a | ["f'{,}'", |

312 | n/a | "f'{,}'", # this is (,), which is an error |

313 | n/a | ]) |

314 | n/a | |

315 | n/a | self.assertAllRaise(SyntaxError, "f-string: expecting '}'", |

316 | n/a | ["f'{3)+(4}'", |

317 | n/a | ]) |

318 | n/a | |

319 | n/a | self.assertAllRaise(SyntaxError, 'EOL while scanning string literal', |

320 | n/a | ["f'{\n}'", |

321 | n/a | ]) |

322 | n/a | |

323 | n/a | def test_backslashes_in_string_part(self): |

324 | n/a | self.assertEqual(f'\t', '\t') |

325 | n/a | self.assertEqual(r'\t', '\\t') |

326 | n/a | self.assertEqual(rf'\t', '\\t') |

327 | n/a | self.assertEqual(f'{2}\t', '2\t') |

328 | n/a | self.assertEqual(f'{2}\t{3}', '2\t3') |

329 | n/a | self.assertEqual(f'\t{3}', '\t3') |

330 | n/a | |

331 | n/a | self.assertEqual(f'\u0394', '\u0394') |

332 | n/a | self.assertEqual(r'\u0394', '\\u0394') |

333 | n/a | self.assertEqual(rf'\u0394', '\\u0394') |

334 | n/a | self.assertEqual(f'{2}\u0394', '2\u0394') |

335 | n/a | self.assertEqual(f'{2}\u0394{3}', '2\u03943') |

336 | n/a | self.assertEqual(f'\u0394{3}', '\u03943') |

337 | n/a | |

338 | n/a | self.assertEqual(f'\U00000394', '\u0394') |

339 | n/a | self.assertEqual(r'\U00000394', '\\U00000394') |

340 | n/a | self.assertEqual(rf'\U00000394', '\\U00000394') |

341 | n/a | self.assertEqual(f'{2}\U00000394', '2\u0394') |

342 | n/a | self.assertEqual(f'{2}\U00000394{3}', '2\u03943') |

343 | n/a | self.assertEqual(f'\U00000394{3}', '\u03943') |

344 | n/a | |

345 | n/a | self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394') |

346 | n/a | self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') |

347 | n/a | self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943') |

348 | n/a | self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943') |

349 | n/a | self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') |

350 | n/a | self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943') |

351 | n/a | self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943') |

352 | n/a | |

353 | n/a | self.assertEqual(f'\x20', ' ') |

354 | n/a | self.assertEqual(r'\x20', '\\x20') |

355 | n/a | self.assertEqual(rf'\x20', '\\x20') |

356 | n/a | self.assertEqual(f'{2}\x20', '2 ') |

357 | n/a | self.assertEqual(f'{2}\x20{3}', '2 3') |

358 | n/a | self.assertEqual(f'\x20{3}', ' 3') |

359 | n/a | |

360 | n/a | self.assertEqual(f'2\x20', '2 ') |

361 | n/a | self.assertEqual(f'2\x203', '2 3') |

362 | n/a | self.assertEqual(f'\x203', ' 3') |

363 | n/a | |

364 | n/a | def test_misformed_unicode_character_name(self): |

365 | n/a | # These test are needed because unicode names are parsed |

366 | n/a | # differently inside f-strings. |

367 | n/a | self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape", |

368 | n/a | [r"f'\N'", |

369 | n/a | r"f'\N{'", |

370 | n/a | r"f'\N{GREEK CAPITAL LETTER DELTA'", |

371 | n/a | |

372 | n/a | # Here are the non-f-string versions, |

373 | n/a | # which should give the same errors. |

374 | n/a | r"'\N'", |

375 | n/a | r"'\N{'", |

376 | n/a | r"'\N{GREEK CAPITAL LETTER DELTA'", |

377 | n/a | ]) |

378 | n/a | |

379 | n/a | def test_no_backslashes_in_expression_part(self): |

380 | n/a | self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash', |

381 | n/a | [r"f'{\'a\'}'", |

382 | n/a | r"f'{\t3}'", |

383 | n/a | r"f'{\}'", |

384 | n/a | r"rf'{\'a\'}'", |

385 | n/a | r"rf'{\t3}'", |

386 | n/a | r"rf'{\}'", |

387 | n/a | r"""rf'{"\N{LEFT CURLY BRACKET}"}'""", |

388 | n/a | r"f'{\n}'", |

389 | n/a | ]) |

390 | n/a | |

391 | n/a | def test_no_escapes_for_braces(self): |

392 | n/a | """ |

393 | n/a | Only literal curly braces begin an expression. |

394 | n/a | """ |

395 | n/a | # \x7b is '{'. |

396 | n/a | self.assertEqual(f'\x7b1+1}}', '{1+1}') |

397 | n/a | self.assertEqual(f'\x7b1+1', '{1+1') |

398 | n/a | self.assertEqual(f'\u007b1+1', '{1+1') |

399 | n/a | self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}') |

400 | n/a | |

401 | n/a | def test_newlines_in_expressions(self): |

402 | n/a | self.assertEqual(f'{0}', '0') |

403 | n/a | self.assertEqual(rf'''{3+ |

404 | n/a | 4}''', '7') |

405 | n/a | |

406 | n/a | def test_lambda(self): |

407 | n/a | x = 5 |

408 | n/a | self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'") |

409 | n/a | self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ") |

410 | n/a | self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ") |

411 | n/a | |

412 | n/a | # lambda doesn't work without parens, because the colon |

413 | n/a | # makes the parser think it's a format_spec |

414 | n/a | self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', |

415 | n/a | ["f'{lambda x:x}'", |

416 | n/a | ]) |

417 | n/a | |

418 | n/a | def test_yield(self): |

419 | n/a | # Not terribly useful, but make sure the yield turns |

420 | n/a | # a function into a generator |

421 | n/a | def fn(y): |

422 | n/a | f'y:{yield y*2}' |

423 | n/a | |

424 | n/a | g = fn(4) |

425 | n/a | self.assertEqual(next(g), 8) |

426 | n/a | |

427 | n/a | def test_yield_send(self): |

428 | n/a | def fn(x): |

429 | n/a | yield f'x:{yield (lambda i: x * i)}' |

430 | n/a | |

431 | n/a | g = fn(10) |

432 | n/a | the_lambda = next(g) |

433 | n/a | self.assertEqual(the_lambda(4), 40) |

434 | n/a | self.assertEqual(g.send('string'), 'x:string') |

435 | n/a | |

436 | n/a | def test_expressions_with_triple_quoted_strings(self): |

437 | n/a | self.assertEqual(f"{'''x'''}", 'x') |

438 | n/a | self.assertEqual(f"{'''eric's'''}", "eric's") |

439 | n/a | |

440 | n/a | # Test concatenation within an expression |

441 | n/a | self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy') |

442 | n/a | self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s') |

443 | n/a | self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy') |

444 | n/a | self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy') |

445 | n/a | self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy') |

446 | n/a | self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy') |

447 | n/a | |

448 | n/a | def test_multiple_vars(self): |

449 | n/a | x = 98 |

450 | n/a | y = 'abc' |

451 | n/a | self.assertEqual(f'{x}{y}', '98abc') |

452 | n/a | |

453 | n/a | self.assertEqual(f'X{x}{y}', 'X98abc') |

454 | n/a | self.assertEqual(f'{x}X{y}', '98Xabc') |

455 | n/a | self.assertEqual(f'{x}{y}X', '98abcX') |

456 | n/a | |

457 | n/a | self.assertEqual(f'X{x}Y{y}', 'X98Yabc') |

458 | n/a | self.assertEqual(f'X{x}{y}Y', 'X98abcY') |

459 | n/a | self.assertEqual(f'{x}X{y}Y', '98XabcY') |

460 | n/a | |

461 | n/a | self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ') |

462 | n/a | |

463 | n/a | def test_closure(self): |

464 | n/a | def outer(x): |

465 | n/a | def inner(): |

466 | n/a | return f'x:{x}' |

467 | n/a | return inner |

468 | n/a | |

469 | n/a | self.assertEqual(outer('987')(), 'x:987') |

470 | n/a | self.assertEqual(outer(7)(), 'x:7') |

471 | n/a | |

472 | n/a | def test_arguments(self): |

473 | n/a | y = 2 |

474 | n/a | def f(x, width): |

475 | n/a | return f'x={x*y:{width}}' |

476 | n/a | |

477 | n/a | self.assertEqual(f('foo', 10), 'x=foofoo ') |

478 | n/a | x = 'bar' |

479 | n/a | self.assertEqual(f(10, 10), 'x= 20') |

480 | n/a | |

481 | n/a | def test_locals(self): |

482 | n/a | value = 123 |

483 | n/a | self.assertEqual(f'v:{value}', 'v:123') |

484 | n/a | |

485 | n/a | def test_missing_variable(self): |

486 | n/a | with self.assertRaises(NameError): |

487 | n/a | f'v:{value}' |

488 | n/a | |

489 | n/a | def test_missing_format_spec(self): |

490 | n/a | class O: |

491 | n/a | def __format__(self, spec): |

492 | n/a | if not spec: |

493 | n/a | return '*' |

494 | n/a | return spec |

495 | n/a | |

496 | n/a | self.assertEqual(f'{O():x}', 'x') |

497 | n/a | self.assertEqual(f'{O()}', '*') |

498 | n/a | self.assertEqual(f'{O():}', '*') |

499 | n/a | |

500 | n/a | self.assertEqual(f'{3:}', '3') |

501 | n/a | self.assertEqual(f'{3!s:}', '3') |

502 | n/a | |

503 | n/a | def test_global(self): |

504 | n/a | self.assertEqual(f'g:{a_global}', 'g:global variable') |

505 | n/a | self.assertEqual(f'g:{a_global!r}', "g:'global variable'") |

506 | n/a | |

507 | n/a | a_local = 'local variable' |

508 | n/a | self.assertEqual(f'g:{a_global} l:{a_local}', |

509 | n/a | 'g:global variable l:local variable') |

510 | n/a | self.assertEqual(f'g:{a_global!r}', |

511 | n/a | "g:'global variable'") |

512 | n/a | self.assertEqual(f'g:{a_global} l:{a_local!r}', |

513 | n/a | "g:global variable l:'local variable'") |

514 | n/a | |

515 | n/a | self.assertIn("module 'unittest' from", f'{unittest}') |

516 | n/a | |

517 | n/a | def test_shadowed_global(self): |

518 | n/a | a_global = 'really a local' |

519 | n/a | self.assertEqual(f'g:{a_global}', 'g:really a local') |

520 | n/a | self.assertEqual(f'g:{a_global!r}', "g:'really a local'") |

521 | n/a | |

522 | n/a | a_local = 'local variable' |

523 | n/a | self.assertEqual(f'g:{a_global} l:{a_local}', |

524 | n/a | 'g:really a local l:local variable') |

525 | n/a | self.assertEqual(f'g:{a_global!r}', |

526 | n/a | "g:'really a local'") |

527 | n/a | self.assertEqual(f'g:{a_global} l:{a_local!r}', |

528 | n/a | "g:really a local l:'local variable'") |

529 | n/a | |

530 | n/a | def test_call(self): |

531 | n/a | def foo(x): |

532 | n/a | return 'x=' + str(x) |

533 | n/a | |

534 | n/a | self.assertEqual(f'{foo(10)}', 'x=10') |

535 | n/a | |

536 | n/a | def test_nested_fstrings(self): |

537 | n/a | y = 5 |

538 | n/a | self.assertEqual(f'{f"{0}"*3}', '000') |

539 | n/a | self.assertEqual(f'{f"{y}"*3}', '555') |

540 | n/a | |

541 | n/a | def test_invalid_string_prefixes(self): |

542 | n/a | self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', |

543 | n/a | ["fu''", |

544 | n/a | "uf''", |

545 | n/a | "Fu''", |

546 | n/a | "fU''", |

547 | n/a | "Uf''", |

548 | n/a | "uF''", |

549 | n/a | "ufr''", |

550 | n/a | "urf''", |

551 | n/a | "fur''", |

552 | n/a | "fru''", |

553 | n/a | "rfu''", |

554 | n/a | "ruf''", |

555 | n/a | "FUR''", |

556 | n/a | "Fur''", |

557 | n/a | "fb''", |

558 | n/a | "fB''", |

559 | n/a | "Fb''", |

560 | n/a | "FB''", |

561 | n/a | "bf''", |

562 | n/a | "bF''", |

563 | n/a | "Bf''", |

564 | n/a | "BF''", |

565 | n/a | ]) |

566 | n/a | |

567 | n/a | def test_leading_trailing_spaces(self): |

568 | n/a | self.assertEqual(f'{ 3}', '3') |

569 | n/a | self.assertEqual(f'{ 3}', '3') |

570 | n/a | self.assertEqual(f'{3 }', '3') |

571 | n/a | self.assertEqual(f'{3 }', '3') |

572 | n/a | |

573 | n/a | self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}', |

574 | n/a | 'expr={1: 2}') |

575 | n/a | self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }', |

576 | n/a | 'expr={1: 2}') |

577 | n/a | |

578 | n/a | def test_not_equal(self): |

579 | n/a | # There's a special test for this because there's a special |

580 | n/a | # case in the f-string parser to look for != as not ending an |

581 | n/a | # expression. Normally it would, while looking for !s or !r. |

582 | n/a | |

583 | n/a | self.assertEqual(f'{3!=4}', 'True') |

584 | n/a | self.assertEqual(f'{3!=4:}', 'True') |

585 | n/a | self.assertEqual(f'{3!=4!s}', 'True') |

586 | n/a | self.assertEqual(f'{3!=4!s:.3}', 'Tru') |

587 | n/a | |

588 | n/a | def test_conversions(self): |

589 | n/a | self.assertEqual(f'{3.14:10.10}', ' 3.14') |

590 | n/a | self.assertEqual(f'{3.14!s:10.10}', '3.14 ') |

591 | n/a | self.assertEqual(f'{3.14!r:10.10}', '3.14 ') |

592 | n/a | self.assertEqual(f'{3.14!a:10.10}', '3.14 ') |

593 | n/a | |

594 | n/a | self.assertEqual(f'{"a"}', 'a') |

595 | n/a | self.assertEqual(f'{"a"!r}', "'a'") |

596 | n/a | self.assertEqual(f'{"a"!a}', "'a'") |

597 | n/a | |

598 | n/a | # Not a conversion. |

599 | n/a | self.assertEqual(f'{"a!r"}', "a!r") |

600 | n/a | |

601 | n/a | # Not a conversion, but show that ! is allowed in a format spec. |

602 | n/a | self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!') |

603 | n/a | |

604 | n/a | self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', |

605 | n/a | ["f'{3!g}'", |

606 | n/a | "f'{3!A}'", |

607 | n/a | "f'{3!3}'", |

608 | n/a | "f'{3!G}'", |

609 | n/a | "f'{3!!}'", |

610 | n/a | "f'{3!:}'", |

611 | n/a | "f'{3! s}'", # no space before conversion char |

612 | n/a | ]) |

613 | n/a | |

614 | n/a | self.assertAllRaise(SyntaxError, "f-string: expecting '}'", |

615 | n/a | ["f'{x!s{y}}'", |

616 | n/a | "f'{3!ss}'", |

617 | n/a | "f'{3!ss:}'", |

618 | n/a | "f'{3!ss:s}'", |

619 | n/a | ]) |

620 | n/a | |

621 | n/a | def test_assignment(self): |

622 | n/a | self.assertAllRaise(SyntaxError, 'invalid syntax', |

623 | n/a | ["f'' = 3", |

624 | n/a | "f'{0}' = x", |

625 | n/a | "f'{x}' = x", |

626 | n/a | ]) |

627 | n/a | |

628 | n/a | def test_del(self): |

629 | n/a | self.assertAllRaise(SyntaxError, 'invalid syntax', |

630 | n/a | ["del f''", |

631 | n/a | "del '' f''", |

632 | n/a | ]) |

633 | n/a | |

634 | n/a | def test_mismatched_braces(self): |

635 | n/a | self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed", |

636 | n/a | ["f'{{}'", |

637 | n/a | "f'{{}}}'", |

638 | n/a | "f'}'", |

639 | n/a | "f'x}'", |

640 | n/a | "f'x}x'", |

641 | n/a | r"f'\u007b}'", |

642 | n/a | |

643 | n/a | # Can't have { or } in a format spec. |

644 | n/a | "f'{3:}>10}'", |

645 | n/a | "f'{3:}}>10}'", |

646 | n/a | ]) |

647 | n/a | |

648 | n/a | self.assertAllRaise(SyntaxError, "f-string: expecting '}'", |

649 | n/a | ["f'{3:{{>10}'", |

650 | n/a | "f'{3'", |

651 | n/a | "f'{3!'", |

652 | n/a | "f'{3:'", |

653 | n/a | "f'{3!s'", |

654 | n/a | "f'{3!s:'", |

655 | n/a | "f'{3!s:3'", |

656 | n/a | "f'x{'", |

657 | n/a | "f'x{x'", |

658 | n/a | "f'{x'", |

659 | n/a | "f'{3:s'", |

660 | n/a | "f'{{{'", |

661 | n/a | "f'{{}}{'", |

662 | n/a | "f'{'", |

663 | n/a | ]) |

664 | n/a | |

665 | n/a | # But these are just normal strings. |

666 | n/a | self.assertEqual(f'{"{"}', '{') |

667 | n/a | self.assertEqual(f'{"}"}', '}') |

668 | n/a | self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3') |

669 | n/a | self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2') |

670 | n/a | |

671 | n/a | def test_if_conditional(self): |

672 | n/a | # There's special logic in compile.c to test if the |

673 | n/a | # conditional for an if (and while) are constants. Exercise |

674 | n/a | # that code. |

675 | n/a | |

676 | n/a | def test_fstring(x, expected): |

677 | n/a | flag = 0 |

678 | n/a | if f'{x}': |

679 | n/a | flag = 1 |

680 | n/a | else: |

681 | n/a | flag = 2 |

682 | n/a | self.assertEqual(flag, expected) |

683 | n/a | |

684 | n/a | def test_concat_empty(x, expected): |

685 | n/a | flag = 0 |

686 | n/a | if '' f'{x}': |

687 | n/a | flag = 1 |

688 | n/a | else: |

689 | n/a | flag = 2 |

690 | n/a | self.assertEqual(flag, expected) |

691 | n/a | |

692 | n/a | def test_concat_non_empty(x, expected): |

693 | n/a | flag = 0 |

694 | n/a | if ' ' f'{x}': |

695 | n/a | flag = 1 |

696 | n/a | else: |

697 | n/a | flag = 2 |

698 | n/a | self.assertEqual(flag, expected) |

699 | n/a | |

700 | n/a | test_fstring('', 2) |

701 | n/a | test_fstring(' ', 1) |

702 | n/a | |

703 | n/a | test_concat_empty('', 2) |

704 | n/a | test_concat_empty(' ', 1) |

705 | n/a | |

706 | n/a | test_concat_non_empty('', 1) |

707 | n/a | test_concat_non_empty(' ', 1) |

708 | n/a | |

709 | n/a | def test_empty_format_specifier(self): |

710 | n/a | x = 'test' |

711 | n/a | self.assertEqual(f'{x}', 'test') |

712 | n/a | self.assertEqual(f'{x:}', 'test') |

713 | n/a | self.assertEqual(f'{x!s:}', 'test') |

714 | n/a | self.assertEqual(f'{x!r:}', "'test'") |

715 | n/a | |

716 | n/a | def test_str_format_differences(self): |

717 | n/a | d = {'a': 'string', |

718 | n/a | 0: 'integer', |

719 | n/a | } |

720 | n/a | a = 0 |

721 | n/a | self.assertEqual(f'{d[0]}', 'integer') |

722 | n/a | self.assertEqual(f'{d["a"]}', 'string') |

723 | n/a | self.assertEqual(f'{d[a]}', 'integer') |

724 | n/a | self.assertEqual('{d[a]}'.format(d=d), 'string') |

725 | n/a | self.assertEqual('{d[0]}'.format(d=d), 'integer') |

726 | n/a | |

727 | n/a | def test_invalid_expressions(self): |

728 | n/a | self.assertAllRaise(SyntaxError, 'invalid syntax', |

729 | n/a | [r"f'{a[4)}'", |

730 | n/a | r"f'{a(4]}'", |

731 | n/a | ]) |

732 | n/a | |

733 | n/a | def test_errors(self): |

734 | n/a | # see issue 26287 |

735 | n/a | self.assertAllRaise(TypeError, 'unsupported', |

736 | n/a | [r"f'{(lambda: 0):x}'", |

737 | n/a | r"f'{(0,):x}'", |

738 | n/a | ]) |

739 | n/a | self.assertAllRaise(ValueError, 'Unknown format code', |

740 | n/a | [r"f'{1000:j}'", |

741 | n/a | r"f'{1000:j}'", |

742 | n/a | ]) |

743 | n/a | |

744 | n/a | def test_loop(self): |

745 | n/a | for i in range(1000): |

746 | n/a | self.assertEqual(f'i:{i}', 'i:' + str(i)) |

747 | n/a | |

748 | n/a | def test_dict(self): |

749 | n/a | d = {'"': 'dquote', |

750 | n/a | "'": 'squote', |

751 | n/a | 'foo': 'bar', |

752 | n/a | } |

753 | n/a | self.assertEqual(f'''{d["'"]}''', 'squote') |

754 | n/a | self.assertEqual(f"""{d['"']}""", 'dquote') |

755 | n/a | |

756 | n/a | self.assertEqual(f'{d["foo"]}', 'bar') |

757 | n/a | self.assertEqual(f"{d['foo']}", 'bar') |

758 | n/a | |

759 | n/a | if __name__ == '__main__': |

760 | n/a | unittest.main() |