Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ env:
test_subclassinit
test_super
test_syntax
test_tstring
test_tuple
test_types
test_unary
Expand Down
296 changes: 296 additions & 0 deletions Lib/test/test_tstring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
import unittest

from test.test_string._support import TStringBaseCase, fstring


class TestTString(unittest.TestCase, TStringBaseCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; + Template(strings=('Hello',), interpolations=())
def test_string_representation(self):
# Test __repr__
t = t"Hello"
self.assertEqual(repr(t), "Template(strings=('Hello',), interpolations=())")

name = "Python"
t = t"Hello, {name}"
self.assertEqual(repr(t),
"Template(strings=('Hello, ', ''), "
"interpolations=(Interpolation('Python', 'name', None, ''),))"
)

def test_interpolation_basics(self):
# Test basic interpolation
name = "Python"
t = t"Hello, {name}"
self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")])
self.assertEqual(fstring(t), "Hello, Python")

# Multiple interpolations
first = "Python"
last = "Developer"
t = t"{first} {last}"
self.assertTStringEqual(
t, ("", " ", ""), [(first, 'first'), (last, 'last')]
)
self.assertEqual(fstring(t), "Python Developer")

# Interpolation with expressions
a = 10
b = 20
t = t"Sum: {a + b}"
self.assertTStringEqual(t, ("Sum: ", ""), [(a + b, "a + b")])
self.assertEqual(fstring(t), "Sum: 30")

# Interpolation with function
def square(x):
return x * x
t = t"Square: {square(5)}"
self.assertTStringEqual(
t, ("Square: ", ""), [(square(5), "square(5)")]
)
self.assertEqual(fstring(t), "Square: 25")

# Test attribute access in expressions
class Person:
def __init__(self, name):
self.name = name

def upper(self):
return self.name.upper()

person = Person("Alice")
t = t"Name: {person.name}"
self.assertTStringEqual(
t, ("Name: ", ""), [(person.name, "person.name")]
)
self.assertEqual(fstring(t), "Name: Alice")

# Test method calls
t = t"Name: {person.upper()}"
self.assertTStringEqual(
t, ("Name: ", ""), [(person.upper(), "person.upper()")]
)
self.assertEqual(fstring(t), "Name: ALICE")

# Test dictionary access
data = {"name": "Bob", "age": 30}
t = t"Name: {data['name']}, Age: {data['age']}"
self.assertTStringEqual(
t, ("Name: ", ", Age: ", ""),
[(data["name"], "data['name']"), (data["age"], "data['age']")],
)
self.assertEqual(fstring(t), "Name: Bob, Age: 30")

def test_format_specifiers(self):
# Test basic format specifiers
value = 3.14159
t = t"Pi: {value:.2f}"
self.assertTStringEqual(
t, ("Pi: ", ""), [(value, "value", None, ".2f")]
)
self.assertEqual(fstring(t), "Pi: 3.14")

def test_conversions(self):
# Test !s conversion (str)
obj = object()
t = t"Object: {obj!s}"
self.assertTStringEqual(t, ("Object: ", ""), [(obj, "obj", "s")])
self.assertEqual(fstring(t), f"Object: {str(obj)}")

# Test !r conversion (repr)
t = t"Data: {obj!r}"
self.assertTStringEqual(t, ("Data: ", ""), [(obj, "obj", "r")])
self.assertEqual(fstring(t), f"Data: {repr(obj)}")

# Test !a conversion (ascii)
text = "Café"
t = t"ASCII: {text!a}"
self.assertTStringEqual(t, ("ASCII: ", ""), [(text, "text", "a")])
self.assertEqual(fstring(t), f"ASCII: {ascii(text)}")

# Test !z conversion (error)
num = 1
with self.assertRaises(SyntaxError):
eval("t'{num!z}'")

@unittest.expectedFailure # TODO: RUSTPYTHON; ? ++++++
def test_debug_specifier(self):
# Test debug specifier
value = 42
t = t"Value: {value=}"
self.assertTStringEqual(
t, ("Value: value=", ""), [(value, "value", "r")]
)
self.assertEqual(fstring(t), "Value: value=42")

# Test debug specifier with format (conversion default to !r)
t = t"Value: {value=:.2f}"
self.assertTStringEqual(
t, ("Value: value=", ""), [(value, "value", None, ".2f")]
)
self.assertEqual(fstring(t), "Value: value=42.00")

# Test debug specifier with conversion
t = t"Value: {value=!s}"
self.assertTStringEqual(
t, ("Value: value=", ""), [(value, "value", "s")]
)

# Test white space in debug specifier
t = t"Value: {value = }"
self.assertTStringEqual(
t, ("Value: value = ", ""), [(value, "value", "r")]
)
self.assertEqual(fstring(t), "Value: value = 42")

def test_raw_tstrings(self):
path = r"C:\Users"
t = rt"{path}\Documents"
self.assertTStringEqual(t, ("", r"\Documents"), [(path, "path")])
self.assertEqual(fstring(t), r"C:\Users\Documents")

# Test alternative prefix
t = tr"{path}\Documents"
self.assertTStringEqual(t, ("", r"\Documents"), [(path, "path")])

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "can only concatenate string.templatelib.Template \(not "str"\) to string.templatelib.Template" does not match "can only concatenate Template (not 'str') to Template"
def test_template_concatenation(self):
# Test template + template
t1 = t"Hello, "
t2 = t"world"
combined = t1 + t2
self.assertTStringEqual(combined, ("Hello, world",), ())
self.assertEqual(fstring(combined), "Hello, world")

# Test template + string
t1 = t"Hello"
expected_msg = 'can only concatenate string.templatelib.Template ' \
'\\(not "str"\\) to string.templatelib.Template'
with self.assertRaisesRegex(TypeError, expected_msg):
t1 + ", world"

# Test template + template with interpolation
name = "Python"
t1 = t"Hello, "
t2 = t"{name}"
combined = t1 + t2
self.assertTStringEqual(combined, ("Hello, ", ""), [(name, "name")])
self.assertEqual(fstring(combined), "Hello, Python")

# Test string + template
expected_msg = 'can only concatenate str ' \
'\\(not "string.templatelib.Template"\\) to str'
with self.assertRaisesRegex(TypeError, expected_msg):
"Hello, " + t"{name}"

def test_nested_templates(self):
# Test a template inside another template expression
name = "Python"
inner = t"{name}"
t = t"Language: {inner}"

t_interp = t.interpolations[0]
self.assertEqual(t.strings, ("Language: ", ""))
self.assertEqual(t_interp.value.strings, ("", ""))
self.assertEqual(t_interp.value.interpolations[0].value, name)
self.assertEqual(t_interp.value.interpolations[0].expression, "name")
self.assertEqual(t_interp.value.interpolations[0].conversion, None)
self.assertEqual(t_interp.value.interpolations[0].format_spec, "")
self.assertEqual(t_interp.expression, "inner")
self.assertEqual(t_interp.conversion, None)
self.assertEqual(t_interp.format_spec, "")

@unittest.expectedFailure # TODO: RUSTPYTHON multiple instances of AssertionError
def test_syntax_errors(self):
for case, err in (
("t'", "unterminated t-string literal"),
("t'''", "unterminated triple-quoted t-string literal"),
("t''''", "unterminated triple-quoted t-string literal"),
("t'{", "'{' was never closed"),
("t'{'", "t-string: expecting '}'"),
("t'{a'", "t-string: expecting '}'"),
("t'}'", "t-string: single '}' is not allowed"),
("t'{}'", "t-string: valid expression required before '}'"),
("t'{=x}'", "t-string: valid expression required before '='"),
("t'{!x}'", "t-string: valid expression required before '!'"),
("t'{:x}'", "t-string: valid expression required before ':'"),
("t'{x;y}'", "t-string: expecting '=', or '!', or ':', or '}'"),
("t'{x=y}'", "t-string: expecting '!', or ':', or '}'"),
("t'{x!s!}'", "t-string: expecting ':' or '}'"),
("t'{x!s:'", "t-string: expecting '}', or format specs"),
("t'{x!}'", "t-string: missing conversion character"),
("t'{x=!}'", "t-string: missing conversion character"),
("t'{x!z}'", "t-string: invalid conversion character 'z': "
"expected 's', 'r', or 'a'"),
("t'{lambda:1}'", "t-string: lambda expressions are not allowed "
"without parentheses"),
("t'{x:{;}}'", "t-string: expecting a valid expression after '{'"),
("t'{1:d\n}'", "t-string: newlines are not allowed in format specifiers")
):
with self.subTest(case), self.assertRaisesRegex(SyntaxError, err):
eval(case)

def test_runtime_errors(self):
# Test missing variables
with self.assertRaises(NameError):
eval("t'Hello, {name}'")

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_literal_concatenation(self):
# Test concatenation of t-string literals
t = t"Hello, " t"world"
self.assertTStringEqual(t, ("Hello, world",), ())
self.assertEqual(fstring(t), "Hello, world")

# Test concatenation with interpolation
name = "Python"
t = t"Hello, " t"{name}"
self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")])
self.assertEqual(fstring(t), "Hello, Python")

# Test disallowed mix of t-string and string/f-string (incl. bytes)
what = 't'
expected_msg = 'cannot mix t-string literals with string or bytes literals'
for case in (
"t'{what}-string literal' 'str literal'",
"t'{what}-string literal' u'unicode literal'",
"t'{what}-string literal' f'f-string literal'",
"t'{what}-string literal' r'raw string literal'",
"t'{what}-string literal' rf'raw f-string literal'",
"t'{what}-string literal' b'bytes literal'",
"t'{what}-string literal' br'raw bytes literal'",
"'str literal' t'{what}-string literal'",
"u'unicode literal' t'{what}-string literal'",
"f'f-string literal' t'{what}-string literal'",
"r'raw string literal' t'{what}-string literal'",
"rf'raw f-string literal' t'{what}-string literal'",
"b'bytes literal' t'{what}-string literal'",
"br'raw bytes literal' t'{what}-string literal'",
):
with self.subTest(case):
with self.assertRaisesRegex(SyntaxError, expected_msg):
eval(case)

def test_triple_quoted(self):
# Test triple-quoted t-strings
t = t"""
Hello,
world
"""
self.assertTStringEqual(
t, ("\n Hello,\n world\n ",), ()
)
self.assertEqual(fstring(t), "\n Hello,\n world\n ")

# Test triple-quoted with interpolation
name = "Python"
t = t"""
Hello,
{name}
"""
self.assertTStringEqual(
t, ("\n Hello,\n ", "\n "), [(name, "name")]
)
self.assertEqual(fstring(t), "\n Hello,\n Python\n ")

if __name__ == '__main__':
unittest.main()
Loading