diff options
Diffstat (limited to 'Lib/test/test_tstring.py')
-rw-r--r-- | Lib/test/test_tstring.py | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/Lib/test/test_tstring.py b/Lib/test/test_tstring.py new file mode 100644 index 00000000000..e72a1ea5417 --- /dev/null +++ b/Lib/test/test_tstring.py @@ -0,0 +1,313 @@ +import unittest + +from test.test_string._support import TStringBaseCase, fstring + + +class TestTString(unittest.TestCase, TStringBaseCase): + 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}'") + + 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")]) + + + 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" + combined = t1 + ", world" + self.assertTStringEqual(combined, ("Hello, world",), ()) + self.assertEqual(fstring(combined), "Hello, 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 + t = "Hello, " + t"{name}" + self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")]) + self.assertEqual(fstring(t), "Hello, Python") + + 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, "") + + 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 '{'"), + ): + 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}'") + + 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 concatenation with string literal + name = "Python" + t = t"Hello, {name}" "and welcome!" + self.assertTStringEqual( + t, ("Hello, ", "and welcome!"), [(name, "name")] + ) + self.assertEqual(fstring(t), "Hello, Pythonand welcome!") + + # Test concatenation with Unicode literal + name = "Python" + t = t"Hello, {name}" u"and welcome!" + self.assertTStringEqual( + t, ("Hello, ", "and welcome!"), [(name, "name")] + ) + self.assertEqual(fstring(t), "Hello, Pythonand welcome!") + + # Test concatenation with f-string literal + tab = '\t' + t = t"Tab: {tab}. " f"f-tab: {tab}." + self.assertTStringEqual(t, ("Tab: ", ". f-tab: \t."), [(tab, "tab")]) + self.assertEqual(fstring(t), "Tab: \t. f-tab: \t.") + + # Test concatenation with raw string literal + tab = '\t' + t = t"Tab: {tab}. " r"Raw tab: \t." + self.assertTStringEqual( + t, ("Tab: ", r". Raw tab: \t."), [(tab, "tab")] + ) + self.assertEqual(fstring(t), "Tab: \t. Raw tab: \\t.") + + # Test concatenation with raw f-string literal + tab = '\t' + t = t"Tab: {tab}. " rf"f-tab: {tab}. Raw tab: \t." + self.assertTStringEqual( + t, ("Tab: ", ". f-tab: \t. Raw tab: \\t."), [(tab, "tab")] + ) + self.assertEqual(fstring(t), "Tab: \t. f-tab: \t. Raw tab: \\t.") + + what = 't' + expected_msg = 'cannot mix bytes and nonbytes literals' + for case in ( + "t'{what}-string literal' b'bytes literal'", + "t'{what}-string literal' br'raw bytes literal'", + ): + 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() |