mirror of
https://github.com/micropython/micropython.git
synced 2026-03-11 03:10:17 +01:00
tests: Add full feature and coverage tests for PEP 750 template strings.
Includes corresponding .exp files because this feature is only available in Python 3.14+. Tests for `!a` conversion specifier and space after `!` are not included because they are not supported by MicroPython. Signed-off-by: Koudai Aono <koxudaxi@gmail.com> Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
committed by
Damien George
parent
a989585147
commit
bdef10a92b
246
tests/basics/string_tstring_errors1.py
Normal file
246
tests/basics/string_tstring_errors1.py
Normal file
@@ -0,0 +1,246 @@
|
||||
from string.templatelib import Template, Interpolation
|
||||
|
||||
print("\n=== Edge cases ===")
|
||||
t_empty = Template()
|
||||
print(f"Empty template: '{t_empty}'")
|
||||
|
||||
t_empty_strs = Template("", Interpolation(1, "a"), "", Interpolation(2, "b"), "")
|
||||
print(f"Empty strings: {list(t_empty_strs)}")
|
||||
|
||||
t_adj = t"{1}{2}{3}"
|
||||
print(f"Adjacent: '{t_adj}'")
|
||||
|
||||
t_single = Template("only")
|
||||
print(f"Single iter: {list(t_single)}")
|
||||
|
||||
t_self = t"test"
|
||||
print(f"Self+self: '{(t_self + t_self)}'")
|
||||
|
||||
print("\n=== Additional coverage tests ===")
|
||||
|
||||
t_str_literal = t"{'hello'}"
|
||||
print(f"String literal: {t_str_literal}")
|
||||
|
||||
path = "/usr/local/bin"
|
||||
count = 42
|
||||
raw_path = rt"Path: {path}\n"
|
||||
print(f"Raw path strings: {raw_path.strings}, value={raw_path.interpolations[0].value}")
|
||||
|
||||
raw_regex = rt"Regex: \\d+{count}"
|
||||
print(f"Raw regex strings: {raw_regex.strings}, value={raw_regex.interpolations[0].value}")
|
||||
|
||||
try:
|
||||
t_str_expr = t'{"test"}'
|
||||
print(f"String expr: '{t_str_expr}'")
|
||||
except Exception as e:
|
||||
print(f"String expr error: {e}")
|
||||
|
||||
def raise_error():
|
||||
raise ValueError("Special error")
|
||||
|
||||
try:
|
||||
t_exc = t"{raise_error()}"
|
||||
print(t_exc)
|
||||
except ValueError as e:
|
||||
print(f"Re-raised exception: {e}")
|
||||
|
||||
try:
|
||||
large_str = "x" * 100000
|
||||
exec(f'very_long_name_{large_str} = t"test"')
|
||||
except (ValueError, MemoryError, SyntaxError, RuntimeError) as e:
|
||||
print("Large template: SyntaxError")
|
||||
|
||||
try:
|
||||
exec('''t_triple = t"""Triple "quoted" string"""''')
|
||||
print(f"Triple quoted: '{t_triple}'")
|
||||
except Exception as e:
|
||||
print(f"Triple quoted error: {e}")
|
||||
|
||||
try:
|
||||
exec(r'''t_raw_triple = rt"""Raw triple\n{42}"""''')
|
||||
print(f"Raw triple: '{t_raw_triple}'")
|
||||
except Exception as e:
|
||||
print(f"Raw triple error: {e}")
|
||||
|
||||
t_concat1 = t"a{1}b"
|
||||
t_concat2 = t"c{2}d{3}e"
|
||||
t_concat3 = t_concat1 + t_concat2
|
||||
print(f"Complex concat: strings={t_concat3.strings}, values={t_concat3.values}")
|
||||
|
||||
t_empty = Template()
|
||||
t_nonempty = t"test{42}"
|
||||
t_concat_empty = t_empty + t_nonempty
|
||||
print(f"Empty concat: '{t_concat_empty}'")
|
||||
|
||||
t_self_interp = t"x{1}y"
|
||||
t_self_concat = t_self_interp + t_self_interp
|
||||
print(f"Self interp concat: values={t_self_concat.values}")
|
||||
|
||||
try:
|
||||
exec('t"unterminated')
|
||||
except SyntaxError as e:
|
||||
print(f"Lonely string: {type(e).__name__}")
|
||||
|
||||
try:
|
||||
t_edge = t""
|
||||
print(f"Empty t-string: '{t_edge}'")
|
||||
except Exception as e:
|
||||
print(f"Empty t-string error: {e}")
|
||||
|
||||
|
||||
print("\n=== High byte handling ===")
|
||||
try:
|
||||
result = t'\x7F\x80\x81\xFE\xFF'
|
||||
first_str = result.strings[0]
|
||||
print(f"High bytes: len={len(first_str)}, first=0x{ord(first_str[0]):02x}, last=0x{ord(first_str[-1]):02x}")
|
||||
except Exception as e:
|
||||
print(f"High bytes error: {e}")
|
||||
|
||||
try:
|
||||
result = t'\200\201\377'
|
||||
print(f"Octal high bytes: OK, len={len(result.strings[0])}")
|
||||
except Exception as e:
|
||||
print(f"Octal high bytes error: {e}")
|
||||
|
||||
print("\n=== Deep nesting test ===")
|
||||
try:
|
||||
nested = "1" + " + 1" * 150
|
||||
code = f't"{{{nested}}}"'
|
||||
exec(code)
|
||||
print("Deep nesting: OK")
|
||||
except Exception as e:
|
||||
print(f"Deep nesting error: {type(e).__name__}")
|
||||
|
||||
print("\n=== Trailing whitespace in expression ===")
|
||||
try:
|
||||
x = 42
|
||||
code = 't"{x }"'
|
||||
result = eval(code)
|
||||
print(f"Trailing whitespace: OK")
|
||||
except Exception as e:
|
||||
print(f"Trailing whitespace error: {e}")
|
||||
|
||||
print("\n=== Single closing brace ===")
|
||||
try:
|
||||
exec('t"test }"')
|
||||
print("ERROR: Single } should have raised SyntaxError")
|
||||
except SyntaxError as e:
|
||||
print(f"Single }}: SyntaxError - {e}")
|
||||
|
||||
print("\n=== Escape in string within interpolation ===")
|
||||
|
||||
try:
|
||||
compile('x = 1; t"{x=:"', '<test>', 'exec')
|
||||
except SyntaxError:
|
||||
print('Debug unclosed colon: SyntaxError')
|
||||
|
||||
try:
|
||||
compile('x = 1; t"{x=!"', '<test>', 'exec')
|
||||
except SyntaxError:
|
||||
print('Debug unclosed exclaim: SyntaxError')
|
||||
|
||||
try:
|
||||
compile('t"prefix{incomplete"', '<test>', 'exec')
|
||||
except SyntaxError as e:
|
||||
print('Unclosed brace literal: SyntaxError')
|
||||
|
||||
try:
|
||||
import sys
|
||||
if hasattr(sys.implementation, '_mpy'):
|
||||
compile('f"{x!invalid}"', '<test>', 'exec')
|
||||
except SyntaxError:
|
||||
print('Malformed f-string: SyntaxError')
|
||||
|
||||
# NOTE: Error messages are shortened in MicroPython to avoid stack overflow
|
||||
# during ROM compression on constrained platforms (Windows x86, ASan).
|
||||
# CPython 3.14 message: "Template.__new__ *args need to be of type 'str' or 'Interpolation', got int"
|
||||
# MicroPython message: "Template.__new__ args must be str or Interpolation, got 'int'"
|
||||
|
||||
try:
|
||||
t = Template("string", 123)
|
||||
except TypeError as e:
|
||||
print('Type error int:', e)
|
||||
|
||||
try:
|
||||
t = Template("a", "b", 3.14)
|
||||
except TypeError:
|
||||
print('Type error float: TypeError')
|
||||
|
||||
try:
|
||||
compile('t"text { more text"', '<test>', 'exec')
|
||||
except SyntaxError as e:
|
||||
print('Unmatched brace:', e)
|
||||
|
||||
try:
|
||||
compile('x=1; t"{x= {nested}}"', '<test>', 'exec')
|
||||
except SyntaxError:
|
||||
print('Nested debug braces: SyntaxError')
|
||||
|
||||
try:
|
||||
compile('t"{x!}"', '<test>', 'exec')
|
||||
except SyntaxError:
|
||||
print('Missing conversion: SyntaxError')
|
||||
|
||||
try:
|
||||
compile('t"{x!z}"', '<test>', 'exec')
|
||||
except SyntaxError as e:
|
||||
print('Invalid conversion:', e)
|
||||
|
||||
try:
|
||||
compile('t"{x!r' + chr(0x0b) + ':10}"', '<test>', 'exec')
|
||||
except SyntaxError as e:
|
||||
print('Vertical tab:', e)
|
||||
|
||||
try:
|
||||
compile('t"{x!r@:10}"', '<test>', 'exec')
|
||||
except SyntaxError as e:
|
||||
print('Invalid char after conversion:', e)
|
||||
|
||||
# MicroPython doesn't allow space after conversion.
|
||||
# try:
|
||||
# x = 'test'
|
||||
# exec('result = t"{x!r :10}"')
|
||||
# print('Space after conversion: OK')
|
||||
# except Exception as e:
|
||||
# print(f'Unexpected error: {e}')
|
||||
|
||||
try:
|
||||
x = 'test'
|
||||
exec('result = t"{x!s\\t:10}"')
|
||||
print('Tab after conversion: OK')
|
||||
except Exception as e:
|
||||
print(f'Unexpected error: {e}')
|
||||
|
||||
try:
|
||||
x = 'test'
|
||||
exec('result = t"{x!r\\n:10}"')
|
||||
print('Newline after conversion: OK')
|
||||
except Exception as e:
|
||||
print(f'Unexpected error: {e}')
|
||||
|
||||
try:
|
||||
x = 'test'
|
||||
exec('result = t"{x!r\\r:10}"')
|
||||
print('CR after conversion: OK')
|
||||
except Exception as e:
|
||||
print(f'Unexpected error: {e}')
|
||||
|
||||
try:
|
||||
x = 'test'
|
||||
exec('result = t"{x!s\\f:10}"')
|
||||
print('Form-feed after conversion: OK')
|
||||
except Exception as e:
|
||||
print(f'Unexpected error: {e}')
|
||||
|
||||
print("\n=== Mixed prefixes ===")
|
||||
for src in ('ft"{x}"', 'tf"{x}"', 'frt"{x}"', 'rtf"{x}"', 'trf"{x}"'):
|
||||
try:
|
||||
exec(src)
|
||||
print(src.split('"')[0] + ": OK (BUG!)")
|
||||
except SyntaxError as e:
|
||||
prefix = src.split('"')[0]
|
||||
# Check if we get the specific error message or generic one
|
||||
if "'f' and 't' prefixes are incompatible" in str(e):
|
||||
print(f"{prefix}: incompatible prefixes")
|
||||
else:
|
||||
print(f"{prefix}: invalid syntax")
|
||||
Reference in New Issue
Block a user