Skip to content

Commit a7c57fe

Browse files
committed
Add tests
1 parent 98e5d83 commit a7c57fe

File tree

2 files changed

+398
-6
lines changed

2 files changed

+398
-6
lines changed

Lib/test/test_tstring.py

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
import ast
2+
import unittest
3+
4+
from string.templatelib import Template, Interpolation
5+
6+
7+
def convert(value, conversion) -> object:
8+
if conversion == "a":
9+
return ascii(value)
10+
elif conversion == "r":
11+
return repr(value)
12+
elif conversion == "s":
13+
return str(value)
14+
return value
15+
16+
17+
def f(template) -> str:
18+
parts = []
19+
for item in template:
20+
match item:
21+
case str() as s:
22+
parts.append(s)
23+
case Interpolation(value, _, conversion, format_spec):
24+
value = convert(value, conversion)
25+
value = format(value, format_spec)
26+
parts.append(value)
27+
return "".join(parts)
28+
29+
30+
class TestTString(unittest.TestCase):
31+
def assertAllRaise(self, exception_type, regex, error_strings):
32+
for s in error_strings:
33+
with self.subTest(s=s):
34+
with self.assertRaisesRegex(exception_type, regex):
35+
eval(s)
36+
37+
def test_template_basic_creation(self):
38+
# Simple t-string creation
39+
t = t"Hello, world"
40+
self.assertTrue(isinstance(t, Template))
41+
self.assertEqual(t.strings, ("Hello, world",))
42+
self.assertEqual(len(t.interpolations), 0)
43+
self.assertEqual(f(t), "Hello, world")
44+
45+
# Empty t-string
46+
t = t""
47+
self.assertEqual(t.strings, ("",))
48+
self.assertEqual(len(t.interpolations), 0)
49+
self.assertEqual(f(t), "")
50+
51+
# Multi-line t-string
52+
t = t"""Hello,
53+
world"""
54+
self.assertEqual(t.strings, ("Hello,\nworld",))
55+
self.assertEqual(len(t.interpolations), 0)
56+
self.assertEqual(f(t), "Hello,\nworld")
57+
58+
def test_string_representation(self):
59+
# Test __repr__
60+
t = t"Hello"
61+
self.assertEqual(repr(t), "Template(strings=('Hello',), interpolations=())")
62+
63+
name = "Python"
64+
t = t"Hello, {name}"
65+
self.assertEqual(repr(t),
66+
"Template(strings=('Hello, ', ''), "
67+
"interpolations=(Interpolation('Python', 'name', None, ''),))"
68+
)
69+
70+
def test_interpolation_basics(self):
71+
# Test basic interpolation
72+
name = "Python"
73+
t = t"Hello, {name}"
74+
self.assertEqual(t.strings, ("Hello, ", ""))
75+
self.assertEqual(t.interpolations[0].value, name)
76+
self.assertEqual(t.interpolations[0].expression, "name")
77+
self.assertEqual(t.interpolations[0].conversion, None)
78+
self.assertEqual(t.interpolations[0].format_spec, "")
79+
self.assertEqual(f(t), "Hello, Python")
80+
81+
# Multiple interpolations
82+
first = "Python"
83+
last = "Developer"
84+
t = t"{first} {last}"
85+
self.assertEqual(t.strings, ("", " ", ""))
86+
self.assertEqual(t.interpolations[0].value, first)
87+
self.assertEqual(t.interpolations[0].expression, "first")
88+
self.assertEqual(t.interpolations[0].conversion, None)
89+
self.assertEqual(t.interpolations[0].format_spec, "")
90+
self.assertEqual(t.interpolations[1].value, last)
91+
self.assertEqual(t.interpolations[1].expression, "last")
92+
self.assertEqual(t.interpolations[1].conversion, None)
93+
self.assertEqual(t.interpolations[1].format_spec, "")
94+
self.assertEqual(f(t), "Python Developer")
95+
96+
# Interpolation with expressions
97+
a = 10
98+
b = 20
99+
t = t"Sum: {a + b}"
100+
self.assertEqual(t.strings, ("Sum: ", ""))
101+
self.assertEqual(t.interpolations[0].value, a + b)
102+
self.assertEqual(t.interpolations[0].expression, "a + b")
103+
self.assertEqual(t.interpolations[0].conversion, None)
104+
self.assertEqual(t.interpolations[0].format_spec, "")
105+
self.assertEqual(f(t), "Sum: 30")
106+
107+
# Interpolation with function
108+
def square(x):
109+
return x * x
110+
t = t"Square: {square(5)}"
111+
self.assertEqual(t.strings, ("Square: ", ""))
112+
self.assertEqual(t.interpolations[0].value, square(5))
113+
self.assertEqual(t.interpolations[0].expression, "square(5)")
114+
self.assertEqual(t.interpolations[0].conversion, None)
115+
self.assertEqual(t.interpolations[0].format_spec, "")
116+
self.assertEqual(f(t), "Square: 25")
117+
118+
# Test attribute access in expressions
119+
class Person:
120+
def __init__(self, name):
121+
self.name = name
122+
123+
def upper(self):
124+
return self.name.upper()
125+
126+
person = Person("Alice")
127+
t = t"Name: {person.name}"
128+
self.assertEqual(t.strings, ("Name: ", ""))
129+
self.assertEqual(t.interpolations[0].value, person.name)
130+
self.assertEqual(t.interpolations[0].expression, "person.name")
131+
self.assertEqual(t.interpolations[0].conversion, None)
132+
self.assertEqual(t.interpolations[0].format_spec, "")
133+
self.assertEqual(f(t), "Name: Alice")
134+
135+
# Test method calls
136+
t = t"Name: {person.upper()}"
137+
self.assertEqual(t.strings, ("Name: ", ""))
138+
self.assertEqual(t.interpolations[0].value, person.upper())
139+
self.assertEqual(t.interpolations[0].expression, "person.upper()")
140+
self.assertEqual(t.interpolations[0].conversion, None)
141+
self.assertEqual(t.interpolations[0].format_spec, "")
142+
self.assertEqual(f(t), "Name: ALICE")
143+
144+
# Test dictionary access
145+
data = {"name": "Bob", "age": 30}
146+
t = t"Name: {data['name']}, Age: {data['age']}"
147+
self.assertEqual(t.strings, ("Name: ", ", Age: ", ""))
148+
self.assertEqual(t.interpolations[0].value, data["name"])
149+
self.assertEqual(t.interpolations[0].expression, "data['name']")
150+
self.assertEqual(t.interpolations[0].conversion, None)
151+
self.assertEqual(t.interpolations[0].format_spec, "")
152+
self.assertEqual(t.interpolations[1].value, data["age"])
153+
self.assertEqual(t.interpolations[1].expression, "data['age']")
154+
self.assertEqual(t.interpolations[1].conversion, None)
155+
self.assertEqual(t.interpolations[1].format_spec, "")
156+
self.assertEqual(f(t), "Name: Bob, Age: 30")
157+
158+
def test_format_specifiers(self):
159+
# Test basic format specifiers
160+
value = 3.14159
161+
t = t"Pi: {value:.2f}"
162+
self.assertEqual(t.strings, ("Pi: ", ""))
163+
self.assertEqual(t.interpolations[0].value, value)
164+
self.assertEqual(t.interpolations[0].expression, "value")
165+
self.assertEqual(t.interpolations[0].conversion, None)
166+
self.assertEqual(t.interpolations[0].format_spec, ".2f")
167+
self.assertEqual(f(t), "Pi: 3.14")
168+
169+
def test_conversions(self):
170+
# Test !s conversion (str)
171+
obj = object()
172+
t = t"Object: {obj!s}"
173+
self.assertEqual(t.strings, ("Object: ", ""))
174+
self.assertEqual(t.interpolations[0].value, obj)
175+
self.assertEqual(t.interpolations[0].expression, "obj")
176+
self.assertEqual(t.interpolations[0].conversion, "s")
177+
self.assertEqual(t.interpolations[0].format_spec, "")
178+
self.assertEqual(f(t), f"Object: {str(obj)}")
179+
180+
# Test !r conversion (repr)
181+
t = t"Data: {obj!r}"
182+
self.assertEqual(t.strings, ("Data: ", ""))
183+
self.assertEqual(t.interpolations[0].value, obj)
184+
self.assertEqual(t.interpolations[0].expression, "obj")
185+
self.assertEqual(t.interpolations[0].conversion, "r")
186+
self.assertEqual(t.interpolations[0].format_spec, "")
187+
self.assertEqual(f(t), f"Data: {repr(obj)}")
188+
189+
# Test !a conversion (ascii)
190+
text = "Café"
191+
t = t"ASCII: {text!a}"
192+
self.assertEqual(t.strings, ("ASCII: ", ""))
193+
self.assertEqual(t.interpolations[0].value, text)
194+
self.assertEqual(t.interpolations[0].expression, "text")
195+
self.assertEqual(t.interpolations[0].conversion, "a")
196+
self.assertEqual(t.interpolations[0].format_spec, "")
197+
self.assertEqual(f(t), f"ASCII: {ascii(text)}")
198+
199+
# Test !z conversion (error)
200+
num = 1
201+
with self.assertRaises(SyntaxError):
202+
eval("t'{num!z}'")
203+
204+
def test_debug_specifier(self):
205+
# Test debug specifier
206+
value = 42
207+
t = t"Value: {value=}"
208+
self.assertEqual(t.strings, ("Value: value=", ""))
209+
self.assertEqual(t.interpolations[0].value, value)
210+
self.assertEqual(t.interpolations[0].expression, "value")
211+
self.assertEqual(t.interpolations[0].conversion, "r")
212+
self.assertEqual(t.interpolations[0].format_spec, "")
213+
self.assertEqual(f(t), "Value: value=42")
214+
215+
# Test debug specifier with format (conversion default to !r)
216+
t = t"Value: {value=:.2f}"
217+
self.assertEqual(t.strings, ("Value: value=", ""))
218+
self.assertEqual(t.interpolations[0].value, value)
219+
self.assertEqual(t.interpolations[0].expression, "value")
220+
self.assertEqual(t.interpolations[0].conversion, None)
221+
self.assertEqual(t.interpolations[0].format_spec, ".2f")
222+
self.assertEqual(f(t), "Value: value=42.00")
223+
224+
# Test debug specifier with conversion
225+
t = t"Value: {value=!s}"
226+
self.assertEqual(t.strings, ("Value: value=", ""))
227+
self.assertEqual(t.interpolations[0].value, value)
228+
self.assertEqual(t.interpolations[0].expression, "value")
229+
self.assertEqual(t.interpolations[0].conversion, "s")
230+
self.assertEqual(t.interpolations[0].format_spec, "")
231+
232+
# Test white space in debug specifier
233+
t = t"Value: {value = }"
234+
self.assertEqual(t.strings, ("Value: value = ", ""))
235+
self.assertEqual(t.interpolations[0].value, value)
236+
self.assertEqual(t.interpolations[0].expression, "value")
237+
self.assertEqual(t.interpolations[0].conversion, "r")
238+
self.assertEqual(t.interpolations[0].format_spec, "")
239+
self.assertEqual(f(t), "Value: value = 42")
240+
241+
def test_raw_tstrings(self):
242+
path = r"C:\Users"
243+
t = rt"{path}\Documents"
244+
self.assertEqual(t.strings, ("", r"\Documents",))
245+
self.assertEqual(t.interpolations[0].value, path)
246+
self.assertEqual(t.interpolations[0].expression, "path")
247+
self.assertEqual(f(t), r"C:\Users\Documents")
248+
249+
# Test alternative prefix
250+
t = tr"{path}\Documents"
251+
self.assertEqual(t.strings, ("", r"\Documents",))
252+
self.assertEqual(t.interpolations[0].value, path)
253+
254+
255+
def test_template_concatenation(self):
256+
# Test template + template
257+
t1 = t"Hello, "
258+
t2 = t"world"
259+
combined = t1 + t2
260+
self.assertEqual(combined.strings, ("Hello, world",))
261+
self.assertEqual(len(combined.interpolations), 0)
262+
self.assertEqual(f(combined), "Hello, world")
263+
264+
# Test template + string
265+
t1 = t"Hello"
266+
combined = t1 + ", world"
267+
self.assertEqual(combined.strings, ("Hello, world",))
268+
self.assertEqual(len(combined.interpolations), 0)
269+
self.assertEqual(f(combined), "Hello, world")
270+
271+
# Test template + template with interpolation
272+
name = "Python"
273+
t1 = t"Hello, "
274+
t2 = t"{name}"
275+
combined = t1 + t2
276+
self.assertEqual(combined.strings, ("Hello, ", ""))
277+
self.assertEqual(combined.interpolations[0].value, name)
278+
self.assertEqual(combined.interpolations[0].expression, "name")
279+
self.assertEqual(combined.interpolations[0].conversion, None)
280+
self.assertEqual(combined.interpolations[0].format_spec, "")
281+
self.assertEqual(f(combined), "Hello, Python")
282+
283+
# Test string + template
284+
t = "Hello, " + t"{name}"
285+
self.assertEqual(t.strings, ("Hello, ", ""))
286+
self.assertEqual(t.interpolations[0].value, name)
287+
self.assertEqual(t.interpolations[0].expression, "name")
288+
self.assertEqual(t.interpolations[0].conversion, None)
289+
self.assertEqual(t.interpolations[0].format_spec, "")
290+
self.assertEqual(f(t), "Hello, Python")
291+
292+
def test_nested_templates(self):
293+
# Test a template inside another template expression
294+
name = "Python"
295+
inner = t"{name}"
296+
t = t"Language: {inner}"
297+
298+
self.assertEqual(t.strings, ("Language: ", ""))
299+
self.assertEqual(t.interpolations[0].value.strings, ("", ""))
300+
self.assertEqual(t.interpolations[0].value.interpolations[0].value, name)
301+
self.assertEqual(t.interpolations[0].value.interpolations[0].expression, "name")
302+
self.assertEqual(t.interpolations[0].value.interpolations[0].conversion, None)
303+
self.assertEqual(t.interpolations[0].value.interpolations[0].format_spec, "")
304+
self.assertEqual(t.interpolations[0].expression, "inner")
305+
self.assertEqual(t.interpolations[0].conversion, None)
306+
self.assertEqual(t.interpolations[0].format_spec, "")
307+
308+
def test_ast_structure(self):
309+
# Test AST structure for simple t-string
310+
tree = ast.parse('t"Hello"')
311+
self.assertIsInstance(tree.body[0].value, ast.TemplateStr)
312+
self.assertIsInstance(tree.body[0].value.values[0], ast.Constant)
313+
314+
# Test AST for t-string with interpolation
315+
tree = ast.parse('t"Hello {name}"')
316+
self.assertIsInstance(tree.body[0].value, ast.TemplateStr)
317+
self.assertIsInstance(tree.body[0].value.values[0], ast.Constant)
318+
self.assertIsInstance(tree.body[0].value.values[1], ast.Interpolation)
319+
320+
def test_error_conditions(self):
321+
# Test syntax errors
322+
with self.assertRaisesRegex(SyntaxError, "'{' was never closed"):
323+
eval("t'{")
324+
325+
with self.assertRaisesRegex(SyntaxError, "t-string: expecting '}'"):
326+
eval("t'{a'")
327+
328+
with self.assertRaisesRegex(SyntaxError, "t-string: single '}' is not allowed"):
329+
eval("t'}'")
330+
331+
# Test missing variables
332+
with self.assertRaises(NameError):
333+
eval("t'Hello, {name}'")
334+
335+
# Test invalid conversion
336+
num = 1
337+
with self.assertRaises(SyntaxError):
338+
eval("t'{num!z}'")
339+
340+
def test_literal_concatenation(self):
341+
# Test concatenation of t-string literals
342+
t = t"Hello, " t"world"
343+
self.assertEqual(t.strings, ("Hello, world",))
344+
self.assertEqual(len(t.interpolations), 0)
345+
self.assertEqual(f(t), "Hello, world")
346+
347+
# Test concatenation with interpolation
348+
name = "Python"
349+
t = t"Hello, " t"{name}"
350+
self.assertEqual(t.strings, ("Hello, ", ""))
351+
self.assertEqual(t.interpolations[0].value, name)
352+
self.assertEqual(t.interpolations[0].expression, "name")
353+
self.assertEqual(t.interpolations[0].conversion, None)
354+
self.assertEqual(t.interpolations[0].format_spec, "")
355+
self.assertEqual(f(t), "Hello, Python")
356+
357+
# Test concatenation with string literal
358+
name = "Python"
359+
t = t"Hello, {name}" "and welcome!"
360+
self.assertEqual(t.strings, ("Hello, ", "and welcome!"))
361+
self.assertEqual(t.interpolations[0].value, name)
362+
self.assertEqual(t.interpolations[0].expression, "name")
363+
self.assertEqual(t.interpolations[0].conversion, None)
364+
self.assertEqual(t.interpolations[0].format_spec, "")
365+
self.assertEqual(f(t), "Hello, Pythonand welcome!")
366+
367+
def test_triple_quoted(self):
368+
# Test triple-quoted t-strings
369+
t = t"""
370+
Hello,
371+
world
372+
"""
373+
self.assertEqual(t.strings, ("\n Hello,\n world\n ",))
374+
self.assertEqual(len(t.interpolations), 0)
375+
self.assertEqual(f(t), "\n Hello,\n world\n ")
376+
377+
# Test triple-quoted with interpolation
378+
name = "Python"
379+
t = t"""
380+
Hello,
381+
{name}
382+
"""
383+
self.assertEqual(t.strings, ("\n Hello,\n ", "\n "))
384+
self.assertEqual(t.interpolations[0].value, name)
385+
self.assertEqual(t.interpolations[0].expression, "name")
386+
self.assertEqual(t.interpolations[0].conversion, None)
387+
self.assertEqual(t.interpolations[0].format_spec, "")
388+
self.assertEqual(f(t), "\n Hello,\n Python\n ")
389+
390+
if __name__ == '__main__':
391+
unittest.main()

0 commit comments

Comments
 (0)