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:
Koudai Aono
2025-11-10 17:15:23 +00:00
committed by Damien George
parent a989585147
commit bdef10a92b
30 changed files with 2517 additions and 0 deletions

View File

@@ -36,6 +36,15 @@ target-version = "py38"
[tool.ruff.lint]
exclude = [ # Ruff finds Python SyntaxError in these files
"tests/basics/string_module_tstring.py",
"tests/basics/string_tstring_basic.py",
"tests/basics/string_tstring_basic1.py",
"tests/basics/string_tstring_constructor.py",
"tests/basics/string_tstring_errors1.py",
"tests/basics/string_tstring_format1.py",
"tests/basics/string_tstring_interpolation1.py",
"tests/basics/string_tstring_operations.py",
"tests/basics/string_tstring_parser1.py",
"tests/cmdline/cmd_compile_only_error.py",
"tests/cmdline/repl_autocomplete.py",
"tests/cmdline/repl_autocomplete_underscore.py",
@@ -47,6 +56,8 @@ exclude = [ # Ruff finds Python SyntaxError in these files
"tests/cmdline/repl_words_move.py",
"tests/feature_check/repl_emacs_check.py",
"tests/feature_check/repl_words_move_check.py",
"tests/feature_check/tstring.py",
"tests/micropython/heapalloc_fail_tstring.py",
"tests/micropython/viper_args.py",
]
extend-select = ["C9", "PLC"]
@@ -78,11 +89,14 @@ mccabe.max-complexity = 40
# Exclude third-party code, and exclude the following tests:
# basics: needs careful attention before applying automatic formatting
# repl_: not real python files
# tstring: ruff does not support template strings
# viper_args: uses f(*)
exclude = [
"tests/basics/*.py",
"tests/*/repl_*.py",
"tests/cmdline/cmd_compile_only_error.py",
"tests/feature_check/tstring.py",
"tests/micropython/heapalloc_fail_tstring.py",
"tests/micropython/test_normalize_newlines.py",
"tests/micropython/viper_args.py",
]

View File

@@ -0,0 +1,6 @@
# Test basic templatelib functionality.
# This test requires t-strings support.
from string.templatelib import Template
print("templatelib", isinstance(t"hi", Template))

View File

@@ -0,0 +1 @@
templatelib True

View File

@@ -0,0 +1,338 @@
from string.templatelib import Template, Interpolation
print("=== Basic functionality ===")
t = t"Hello World"
print(type(t).__name__)
name = "World"
t2 = t"Hello {name}"
print(f"Strings: {t2.strings}")
print(f"Value: {t2.interpolations[0].value}")
print(f"str(): {str(t2)}")
t_raw = rt"Path: C:\test\{name}"
print(f"Raw: '{t_raw.strings[0]}'")
t_tr = tr"Path: C:\test\{name}"
print(f"tr: '{t_tr.strings[0]}'")
print("\n=== Parser tests ===")
data = {"a": {"b": [1, 2, 3]}}
t_complex = t"{data['a']['b'][0]}"
print(f"Complex: {str(t_complex)}")
print(f"None: {str(t'{None}')}")
print(f"True: {str(t'{True}')}")
print(f"False: {str(t'{False}')}")
print(f"Ellipsis: {str(t'{...}')}")
obj = type('Obj', (), {
'__getattr__': lambda s, n: s,
'__getitem__': lambda s, k: s,
'__call__': lambda s, *a: 42,
'__str__': lambda s: "42"
})()
print(f"Deep nest: {str(t'{obj.a.b[0].c()}')}")
print("\n=== Conversions and formatting ===")
val = {"key": "value"}
print(f"repr: {str(t'{val!r}')}")
print(f"str: {str(t'{val!s}')}")
# print(f"ascii: {str(t'{val!a}')}")
print(f"Width: '{str(t'{42:10d}')}'")
print(f"Precision: {str(t'{3.14159:.2f}')}")
x = 42
t_debug = t"{x=}"
assert t_debug.strings == ("x=", "")
assert t_debug.interpolations[0].expression == "x"
assert t_debug.interpolations[0].conversion == "r"
assert t_debug.interpolations[0].value == 42
print(f"Debug: {str(t_debug)}")
y = 10
t_debug2 = t"{x + y=}"
assert t_debug2.strings == ("x + y=", "")
assert t_debug2.interpolations[0].expression == "x + y"
assert t_debug2.interpolations[0].conversion == "r"
assert t_debug2.interpolations[0].value == 52
pi = 3.14159
t_debug3 = t"{pi=:.2f}"
assert t_debug3.strings == ("pi=", "")
assert t_debug3.interpolations[0].expression == "pi"
assert t_debug3.interpolations[0].conversion is None
assert t_debug3.interpolations[0].format_spec == ".2f"
assert t_debug3.interpolations[0].value == 3.14159
print("\n=== Constructor tests ===")
t_empty = Template()
print(f"Empty: {t_empty.strings}")
t_single = Template("single")
print(f"Single: {t_single.strings}")
t_multi = Template("a", "b", "c")
print(f"Multi: {t_multi.strings}")
i1 = Interpolation(1, "x")
i2 = Interpolation(2, "y")
t_mixed = Template("start", i1, "middle", i2, "end")
print(f"Mixed: strings={t_mixed.strings}, values={t_mixed.values}")
print("\n=== Operations ===")
t1 = t"Hello"
t2 = t" World"
t_concat = t1 + t2
print(f"Concat: '{str(t_concat)}'")
items = list(t"a{1}b{2}c")
print(f"Iterator: {[type(x).__name__ for x in items]}")
t_attr = t"test{42}"
print(f"strings attr: {t_attr.strings}")
print(f"interpolations attr: {len(t_attr.interpolations)}")
print(f"values attr: {t_attr.values}")
print(f"repr: {repr(t2)[:50]}...")
print("\n=== Escaped braces evaluation ===")
t_escaped = Template("{", Interpolation(42, "x"), "}", Interpolation(42, "x"), "{{", Interpolation(42, "x"), "}}")
print(f"Escaped eval: '{str(t_escaped)}'")
t_braces = Template("{{hello}}", Interpolation(1, "a"), " {", Interpolation(2, "b"), "} ", Interpolation(3, "c"), "{{world}}")
print(f"Braces in strings: '{str(t_braces)}'")
print("\n=== Memory stress test ===")
for n in [10, 20, 30]:
args = []
for i in range(n):
args.append("s")
args.append(Interpolation(i, f"var{i}"))
args.append("s")
t_mem = Template(*args)
result = str(t_mem)
print(f"Memory test [{n}]: {len(result)} chars")
large_args = []
for i in range(20):
large_args.append("")
large_args.append(Interpolation(i, f"v{i}"))
large_args.append("")
t_large = Template(*large_args)
print(f"Large values: {len(t_large.values)} values")
print("\n=== Nested quotes and braces tests ===")
t_nested1 = t"{"{}"}"
print(f"Nested quotes 1: {str(t_nested1)}")
print(f" Value: {t_nested1.interpolations[0].value}")
print(f" Expression: {t_nested1.interpolations[0].expression}")
t_nested2 = t"{'hello'}"
print(f"Nested quotes 2: {str(t_nested2)}")
print(f" Value: {t_nested2.interpolations[0].value}")
t_nested3 = t'{"world"}'
print(f"Nested quotes 3: {str(t_nested3)}")
print(f" Value: {t_nested3.interpolations[0].value}")
t_nested4 = t"{['a', 'b', 'c'][1]}"
print(f"Nested quotes 4: {str(t_nested4)}")
print(f" Value: {t_nested4.interpolations[0].value}")
d = {"key": "value", "x": 123}
t_nested5 = t"{d['key']}"
print(f"Nested quotes 5: {str(t_nested5)}")
print(f" Value: {t_nested5.interpolations[0].value}")
t_escaped1 = t'{{""}}'
print(f"Escaped braces 1: {str(t_escaped1)}")
print(f" Strings: {t_escaped1.strings}")
t_escaped2 = t"{{}}}}"
print(f"Escaped braces 2: {str(t_escaped2)}")
print(f" Strings: {t_escaped2.strings}")
x = "test"
t_mixed = t"{{before}} {x} {{after}}"
print(f"Mixed escaped: {str(t_mixed)}")
print(f" Strings: {t_mixed.strings}")
print(f" Value: {t_mixed.interpolations[0].value}")
t_escape = t"{'\n\t'}"
print(f"Escape sequences: {str(t_escape)}")
print(f" Value repr: {repr(t_escape.interpolations[0].value)}")
inner = "{}"
t_nested_expr = t"{inner}"
print(f"Nested expr: {str(t_nested_expr)}")
print(f" Value: {t_nested_expr.interpolations[0].value}")
print("\n=== Interpolation attribute tests ===")
i_basic = Interpolation(42, "x")
print(f"Basic conversion: {i_basic.conversion}")
print(f"Basic format_spec: {i_basic.format_spec}")
i_with_conv = Interpolation(42, "x", "s")
print(f"With conversion: {i_with_conv.conversion}")
i_with_fmt = Interpolation(42, "x", None, ":>10")
print(f"With format_spec: {i_with_fmt.format_spec}")
i_full = Interpolation(42, "x", "r", ":>10")
print(f"Full conversion: {i_full.conversion}")
print(f"Full format_spec: {i_full.format_spec}")
t_conv = t"{42!s}"
print(f"Template conversion: {t_conv.interpolations[0].conversion}")
t_fmt = t"{42:>10}"
print(f"Template format_spec: {t_fmt.interpolations[0].format_spec}")
t_both = t"{42!r:>10}"
print(f"Both conversion: {t_both.interpolations[0].conversion}")
print(f"Both format_spec: {t_both.interpolations[0].format_spec}")
print("\n=== Escape sequence tests ===")
print(repr(t"Line1\nLine2"))
print(repr(t"Path\\to\\file"))
print(repr(t"It\'s working"))
print(repr(t"She said \"Hello\""))
print(repr(t"Bell\a"))
print(repr(t"Back\bspace"))
print(repr(t"Tab\there"))
print(repr(t"Vertical\vtab"))
print(repr(t"Form\ffeed"))
print(repr(t"Carriage\rreturn"))
# Valid hex escapes
print(repr(t"\x41"))
print(repr(t"\x7F"))
# Valid octal escapes
print(repr(t"\0"))
print(repr(t"\7"))
print(repr(t"\77"))
print(repr(t"\101"))
print(repr(t"\1a"))
print(repr(t"\123c"))
# Line continuation
print(repr(t"First \
second"))
print(repr(t"One\
Two\
Three"))
# Multiple escapes
print(repr(t"\n\t\r"))
# Raw strings
print(repr(rt"\n\t\x41"))
print(repr(rt"C:\new\test"))
# Triple quoted strings
print(repr(t"""\n\t\x41"""))
print(repr(rt"""\n\t\x41"""))
print("\n=== Triple quote edge cases ===")
doc = '''This is a
multi-line
docstring'''
result1 = t"""Documentation:
{doc}
End of doc"""
print(f"Triple in interpolation: {str(result1)}")
print(f" Value: {repr(result1.interpolations[0].value)}")
data = {
"users": [
{"name": "Alice", "msg": 'Say "Hello"'},
{"name": "Bob", "msg": "It's great!"}
]
}
complex_nested = t"""Users:
{data['users'][0]['name']}: {data['users'][0]['msg']}
{data['users'][1]['name']}: {data['users'][1]['msg']}"""
print(f"Complex nested: {str(complex_nested)}")
print(f" Values: {complex_nested.values}")
import os
path_sep = os.sep
raw_with_interp = rt"""Path: C:\Users\{path_sep}Documents
Raw newline: \n
Raw tab: \t"""
print(f"Raw with interpolation: {str(raw_with_interp)}")
print(f" String 0 repr: {repr(raw_with_interp.strings[0])}")
long_line = "x" * 50
long_triple = t"""Start
{long_line}
End"""
print(f"Long line: strings={long_triple.strings}")
print(f" Value length: {len(long_triple.values[0])}")
inner = t"inner {42}"
outer = t"""Outer template:
{inner}
End"""
print(f"Nested templates: {str(outer)}")
print(f" Inner value: {outer.values[0]}")
formatted = t"""Math constants:
Pi: {314:.2f}
E: {271:.1e}
Sqrt(2): {141:.0f}"""
print(f"Formatted values: {repr(formatted)}")
print(f" Interpolation count: {len(formatted.interpolations)}")
print(f" Format specs: {[i.format_spec for i in formatted.interpolations]}")
x, y, z = 1, 2, 3
debug_complex = t"""Debug info:
{x + y=} {x * y * z=} {x < y < z=}"""
print(f"Debug complex: {repr(debug_complex)}")
print(f" Expressions: {[i.expression for i in debug_complex.interpolations]}")
part1 = t"""Part 1
with newline"""
part2 = t'''Part 2
also multiline'''
concatenated = part1 + part2
print(f"Concatenated triple: {repr(concatenated)}")
print(f" Strings: {concatenated.strings}")
print("\n=== PEP 701 brace compliance ===")
t_brace1 = t"{{"
print(f"Escaped {{{{: {t_brace1.strings}")
assert t_brace1.strings == ('{',)
t_brace2 = t"}}"
print(f"Escaped }}}}: {t_brace2.strings}")
assert t_brace2.strings == ('}',)
t_brace3 = t"test{{escape}}here"
print(f"Mixed: {t_brace3.strings}")
assert t_brace3.strings == ('test{escape}here',)
print("\n=== Format spec scope resolution ===")
def test_format_scope():
width = 10
x = 42
t_scope = t"{x:{width}}"
print(f"Local vars: width={width}, x={x}")
print(f"Format spec: {t_scope.interpolations[0].format_spec}")
return t_scope.interpolations[0].format_spec == '10'
result = test_format_scope()
print(f"Scope test: {'PASS' if result else 'FAIL'}")
assert result, "Format spec scope resolution failed"
precision = 2
value = 3.14159
t_prec = t"{value:.{precision}f}"
print(f"Precision: format_spec={t_prec.interpolations[0].format_spec}")
assert t_prec.interpolations[0].format_spec == '.2f'
print("\nBasic tests completed!")

View File

@@ -0,0 +1,141 @@
=== Basic functionality ===
Template
Strings: ('Hello ', '')
Value: World
str(): Template(strings=('Hello ', ''), interpolations=(Interpolation('World', 'name', None, ''),))
Raw: 'Path: C:\test\'
tr: 'Path: C:\test\'
=== Parser tests ===
Complex: Template(strings=('', ''), interpolations=(Interpolation(1, "data['a']['b'][0]", None, ''),))
None: Template(strings=('', ''), interpolations=(Interpolation(None, 'None', None, ''),))
True: Template(strings=('', ''), interpolations=(Interpolation(True, 'True', None, ''),))
False: Template(strings=('', ''), interpolations=(Interpolation(False, 'False', None, ''),))
Ellipsis: Template(strings=('', ''), interpolations=(Interpolation(Ellipsis, '...', None, ''),))
Deep nest: Template(strings=('', ''), interpolations=(Interpolation(42, 'obj.a.b[0].c()', None, ''),))
=== Conversions and formatting ===
repr: Template(strings=('', ''), interpolations=(Interpolation({'key': 'value'}, 'val', 'r', ''),))
str: Template(strings=('', ''), interpolations=(Interpolation({'key': 'value'}, 'val', 's', ''),))
Width: 'Template(strings=('', ''), interpolations=(Interpolation(42, '42', None, '10d'),))'
Precision: Template(strings=('', ''), interpolations=(Interpolation(3.14159, '3.14159', None, '.2f'),))
Debug: Template(strings=('x=', ''), interpolations=(Interpolation(42, 'x', 'r', ''),))
=== Constructor tests ===
Empty: ('',)
Single: ('single',)
Multi: ('abc',)
Mixed: strings=('start', 'middle', 'end'), values=(1, 2)
=== Operations ===
Concat: 'Template(strings=('Hello World',), interpolations=())'
Iterator: ['str', 'Interpolation', 'str', 'Interpolation', 'str']
strings attr: ('test', '')
interpolations attr: 1
values attr: (42,)
repr: Template(strings=(' World',), interpolations=())...
=== Escaped braces evaluation ===
Escaped eval: 'Template(strings=('{', '}', '{{', '}}'), interpolations=(Interpolation(42, 'x', None, ''), Interpolation(42, 'x', None, ''), Interpolation(42, 'x', None, '')))'
Braces in strings: 'Template(strings=('{{hello}}', ' {', '} ', '{{world}}'), interpolations=(Interpolation(1, 'a', None, ''), Interpolation(2, 'b', None, ''), Interpolation(3, 'c', None, '')))'
=== Memory stress test ===
Memory test [10]: 450 chars
Memory test [20]: 880 chars
Memory test [30]: 1310 chars
Large values: 20 values
=== Nested quotes and braces tests ===
Nested quotes 1: Template(strings=('', ''), interpolations=(Interpolation('{}', '"{}"', None, ''),))
Value: {}
Expression: "{}"
Nested quotes 2: Template(strings=('', ''), interpolations=(Interpolation('hello', "'hello'", None, ''),))
Value: hello
Nested quotes 3: Template(strings=('', ''), interpolations=(Interpolation('world', '"world"', None, ''),))
Value: world
Nested quotes 4: Template(strings=('', ''), interpolations=(Interpolation('b', "['a', 'b', 'c'][1]", None, ''),))
Value: b
Nested quotes 5: Template(strings=('', ''), interpolations=(Interpolation('value', "d['key']", None, ''),))
Value: value
Escaped braces 1: Template(strings=('{""}',), interpolations=())
Strings: ('{""}',)
Escaped braces 2: Template(strings=('{}}',), interpolations=())
Strings: ('{}}',)
Mixed escaped: Template(strings=('{before} ', ' {after}'), interpolations=(Interpolation('test', 'x', None, ''),))
Strings: ('{before} ', ' {after}')
Value: test
Escape sequences: Template(strings=('', ''), interpolations=(Interpolation('\n\t', "'\\n\\t'", None, ''),))
Value repr: '\n\t'
Nested expr: Template(strings=('', ''), interpolations=(Interpolation('{}', 'inner', None, ''),))
Value: {}
=== Interpolation attribute tests ===
Basic conversion: None
Basic format_spec:
With conversion: s
With format_spec: :>10
Full conversion: r
Full format_spec: :>10
Template conversion: s
Template format_spec: >10
Both conversion: r
Both format_spec: >10
=== Escape sequence tests ===
Template(strings=('Line1\nLine2',), interpolations=())
Template(strings=('Path\\to\\file',), interpolations=())
Template(strings=("It's working",), interpolations=())
Template(strings=('She said "Hello"',), interpolations=())
Template(strings=('Bell\x07',), interpolations=())
Template(strings=('Back\x08space',), interpolations=())
Template(strings=('Tab\there',), interpolations=())
Template(strings=('Vertical\x0btab',), interpolations=())
Template(strings=('Form\x0cfeed',), interpolations=())
Template(strings=('Carriage\rreturn',), interpolations=())
Template(strings=('A',), interpolations=())
Template(strings=('\x7f',), interpolations=())
Template(strings=('\x00',), interpolations=())
Template(strings=('\x07',), interpolations=())
Template(strings=('?',), interpolations=())
Template(strings=('A',), interpolations=())
Template(strings=('\x01a',), interpolations=())
Template(strings=('Sc',), interpolations=())
Template(strings=('First second',), interpolations=())
Template(strings=('OneTwoThree',), interpolations=())
Template(strings=('\n\t\r',), interpolations=())
Template(strings=('\\n\\t\\x41',), interpolations=())
Template(strings=('C:\\new\\test',), interpolations=())
Template(strings=('\n\tA',), interpolations=())
Template(strings=('\\n\\t\\x41',), interpolations=())
=== Triple quote edge cases ===
Triple in interpolation: Template(strings=('Documentation:\n', '\nEnd of doc'), interpolations=(Interpolation('This is a\nmulti-line\ndocstring', 'doc', None, ''),))
Value: 'This is a\nmulti-line\ndocstring'
Complex nested: Template(strings=('Users:\n', ': ', '\n', ': ', ''), interpolations=(Interpolation('Alice', "data['users'][0]['name']", None, ''), Interpolation('Say "Hello"', "data['users'][0]['msg']", None, ''), Interpolation('Bob', "data['users'][1]['name']", None, ''), Interpolation("It's great!", "data['users'][1]['msg']", None, '')))
Values: ('Alice', 'Say "Hello"', 'Bob', "It's great!")
Raw with interpolation: Template(strings=('Path: C:\\Users\\', 'Documents\nRaw newline: \\n\nRaw tab: \\t'), interpolations=(Interpolation('/', 'path_sep', None, ''),))
String 0 repr: 'Path: C:\\Users\\'
Long line: strings=('Start\n', '\nEnd')
Value length: 50
Nested templates: Template(strings=('Outer template:\n', '\nEnd'), interpolations=(Interpolation(Template(strings=('inner ', ''), interpolations=(Interpolation(42, '42', None, ''),)), 'inner', None, ''),))
Inner value: Template(strings=('inner ', ''), interpolations=(Interpolation(42, '42', None, ''),))
Formatted values: Template(strings=('Math constants:\nPi: ', '\nE: ', '\nSqrt(2): ', ''), interpolations=(Interpolation(314, '314', None, '.2f'), Interpolation(271, '271', None, '.1e'), Interpolation(141, '141', None, '.0f')))
Interpolation count: 3
Format specs: ['.2f', '.1e', '.0f']
Debug complex: Template(strings=('Debug info:\nx + y=', ' x * y * z=', ' x < y < z=', ''), interpolations=(Interpolation(3, 'x + y', 'r', ''), Interpolation(6, 'x * y * z', 'r', ''), Interpolation(True, 'x < y < z', 'r', '')))
Expressions: ['x + y', 'x * y * z', 'x < y < z']
Concatenated triple: Template(strings=('Part 1\nwith newlinePart 2\nalso multiline',), interpolations=())
Strings: ('Part 1\nwith newlinePart 2\nalso multiline',)
=== PEP 701 brace compliance ===
Escaped {{: ('{',)
Escaped }}: ('}',)
Mixed: ('test{escape}here',)
=== Format spec scope resolution ===
Local vars: width=10, x=42
Format spec: 10
Scope test: PASS
Precision: format_spec=.2f
Basic tests completed!

View File

@@ -0,0 +1,131 @@
print("=== Parser error tests ===")
try:
exec('t_empty = t"{}"')
print(f"Empty expr: {t_empty.interpolations[0].value}")
except SyntaxError as e:
print(f"Empty expr: SyntaxError - {e}")
# Whitespace in expression (Python semantics are to strip trailing)
t_ws = t"{ 42 }"
print(f"Whitespace: {str(t_ws)}")
print("\n=== Error cases ===")
try:
exec('t"{@}"')
except SyntaxError:
print("Invalid syntax: SyntaxError")
try:
long_expr = "x" * 10001
exec(f't"{{{long_expr}}}"')
except (ValueError, SyntaxError, RuntimeError, NameError):
print("Long expr: Error")
try:
exec('t"hello" "world"')
except SyntaxError:
print("Mixed concat: SyntaxError")
try:
exec('bt"test"')
except SyntaxError:
print("bt prefix: SyntaxError")
print("\n=== Escape sequence coverage tests ===")
def expect_invalid_escape(label, expr):
try:
eval(expr)
except SyntaxError:
print(f"{label}: SyntaxError")
else:
print(f"{label}: ERROR (expected SyntaxError)")
# Deprecated escape sequences (CPython warns, MicroPython accepts as literal)
print(repr(t"\8"))
print(repr(t"\9"))
# Unknown escape chars
print(repr(t"\z"))
print(repr(t"\k"))
# Invalid escapes
expect_invalid_escape("Invalid \\x escape", 't"\\xGG"')
expect_invalid_escape("Invalid \\u escape", 't"\\uGGGG"')
expect_invalid_escape("Invalid \\U escape", 't"\\UGGGGGGGG"')
# Unicode escapes (display format differs)
print(repr(t"\x00\x01\xFF"))
print(repr(t"\u0041"))
print(repr(t"\u03B1"))
print(repr(t"\u2764"))
print(repr(t"\U00000041"))
print(repr(t"\U0001F600"))
print(repr(t"\x41\u0042\103"))
# Unicode in triple-quoted strings
unicode_test = t"""Unicode test:
Emoji: {'\U0001f40d'}
Special: {'\u03b1 \u03b2 \u03b3'}"""
print(f"Unicode: {str(unicode_test)}")
print("\n=== Trailing whitespace preservation (PEP 750) ===")
x = 42
tmpl_trail = t"{x }"
expr = tmpl_trail.interpolations[0].expression
print(f"Expression with trailing spaces: |{expr}|")
assert expr == "x", f"Expected 'x' but got '{expr}'"
assert len(expr) == 1, f"Expected length 1 but got {len(expr)}"
tmpl_both = t"{ x }"
expr2 = tmpl_both.interpolations[0].expression
print(f"Expression with both spaces: |{expr2}|")
assert expr2 == " x", f"Expected ' x' but got '{expr2}'"
assert len(expr2) == 4, f"Expected length 4 but got {len(expr2)}"
tmpl_lead = t"{ x}"
expr3 = tmpl_lead.interpolations[0].expression
print(f"Expression with leading spaces: |{expr3}|")
assert expr3 == " x", f"Expected ' x' but got '{expr3}'"
# Debug specifiers: leading space preserved, trailing space stripped from expression
# PEP 750: "Whitespace is preserved in the debug specifier" (in strings part)
z = 99
t_debug4 = t"{z =}"
assert t_debug4.strings == ("z =", "")
assert t_debug4.interpolations[0].expression == "z" # Trailing space stripped
assert t_debug4.interpolations[0].conversion == "r"
t_debug5 = t"{ z=}"
assert t_debug5.strings == (" z=", "")
assert t_debug5.interpolations[0].expression == " z" # Leading space preserved
assert t_debug5.interpolations[0].conversion == "r"
t_debug6 = t"{ z =}"
assert t_debug6.strings == (" z =", "")
assert t_debug6.interpolations[0].expression == " z" # Leading preserved, trailing stripped
assert t_debug6.interpolations[0].conversion == "r"
print("Trailing whitespace: PASS")
print("\n=== Error message tests ===")
try:
exec('t"}"')
except SyntaxError:
print("Lone }} rejected (correct)")
try:
exec('t"{"')
except SyntaxError:
print("Unterminated {{ rejected (correct)")
print("\n=== Triple quote empty interpolation ===")
try:
exec('''empty_interp = t"""Start
{}
End"""''')
print(f"Empty interpolation: {str(empty_interp)}")
except Exception as e:
print(f"Empty interpolation error: {type(e).__name__}: {e}")
print("\nIncompatible tests completed!")

View File

@@ -0,0 +1,41 @@
=== Parser error tests ===
Empty expr: SyntaxError - malformed f-string
Whitespace: Template(strings=('', ''), interpolations=(Interpolation(42, ' 42', None, ''),))
=== Error cases ===
Invalid syntax: SyntaxError
Long expr: Error
Mixed concat: SyntaxError
bt prefix: SyntaxError
=== Escape sequence coverage tests ===
Template(strings=('\\8',), interpolations=())
Template(strings=('\\9',), interpolations=())
Template(strings=('\\z',), interpolations=())
Template(strings=('\\k',), interpolations=())
Invalid \x escape: SyntaxError
Invalid \u escape: SyntaxError
Invalid \U escape: SyntaxError
Template(strings=('\x00\x01\xff',), interpolations=())
Template(strings=('A',), interpolations=())
Template(strings=('\u03b1',), interpolations=())
Template(strings=('\u2764',), interpolations=())
Template(strings=('A',), interpolations=())
Template(strings=('\U0001f600',), interpolations=())
Template(strings=('ABC',), interpolations=())
Unicode: Template(strings=('Unicode test:\nEmoji: ', '\nSpecial: ', ''), interpolations=(Interpolation('\U0001f40d', "'\\U0001f40d'", None, ''), Interpolation('\u03b1 \u03b2 \u03b3', "'\\u03b1 \\u03b2 \\u03b3'", None, '')))
=== Trailing whitespace preservation (PEP 750) ===
Expression with trailing spaces: |x|
Expression with both spaces: | x|
Expression with leading spaces: | x|
Trailing whitespace: PASS
=== Error message tests ===
Lone }} rejected (correct)
Unterminated {{ rejected (correct)
=== Triple quote empty interpolation ===
Empty interpolation error: SyntaxError: malformed f-string
Incompatible tests completed!

View File

@@ -0,0 +1,112 @@
from string.templatelib import Template, Interpolation
print("=== Constructor basic usage ===")
t = Template("hello ", Interpolation(42, "x"), "world")
print(f"Template repr: {repr(t)}")
t_varargs = Template("Hello ", Interpolation("World", "name"), "!")
print(f"Varargs constructor: strings={t_varargs.strings}, values={t_varargs.values}")
t_concat = Template("A", "B", Interpolation(1, "value"), "C", "D")
print(f"Varargs merged strings: {t_concat.strings}")
t_leading = Template(Interpolation(1, "x"), " tail")
print(f"Leading interpolation strings: {t_leading.strings}")
t_trailing = Template("head ", Interpolation(2, "y"))
print(f"Trailing interpolation strings: {t_trailing.strings}")
t_interps_only = Template(Interpolation(1, "x"), Interpolation(2, "y"))
print(f"Interpolation only strings: {t_interps_only.strings}")
print("\n=== Special cases ===")
i = Interpolation(42, "x", "s", ":>10")
try:
i.value = 100
except AttributeError:
print("Interp read-only: AttributeError")
t_ws_trim = Template("", Interpolation(None, " ", None, ""), "")
print(f"Whitespace trim: '{t_ws_trim}'")
t_debug = Template("", Interpolation(42, "x=", None, ""), "")
print(f"Debug =: {t_debug}")
class Custom:
def __repr__(self):
return "CustomRepr"
def __str__(self):
return "CustomStr"
obj = Custom()
print(f"Custom !r: {t'{obj!r}'}")
print(f"Custom !s: {t'{obj!s}'}")
t_empty_start = Template("", Interpolation(1, "1"), "text")
print(f"Empty start iter: {[type(x).__name__ for x in t_empty_start]}")
t_iter_edge = Template("", Interpolation(1, "1"), "", Interpolation(2, "2"), "")
iter_items = []
for item in t_iter_edge:
iter_items.append(type(item).__name__)
print(f"Iterator edge: {iter_items}")
print("\n=== Values property ===")
for n in range(7):
args = []
for i in range(n):
args.append("")
args.append(Interpolation(i, str(i)))
args.append("")
t = Template(*args)
print(f"Values[{n}]: {t.values}")
print("\n=== Multiple consecutive strings ===")
try:
t = Template("first", "second", "third", Interpolation(42, "x"), "fourth", "fifth")
if t.strings == ("firstsecondthird", "fourthfifth"):
print("Multiple strings concatenated: OK")
else:
print(f"Multiple strings: strings={t.strings}")
except Exception as e:
print(f"Multiple strings error: {e}")
print("\n=== Template() constructor with many interpolations ===")
try:
exprs = ["a", "b", "c", "d", "e"]
interps = [Interpolation(i, exprs[i % len(exprs)]) for i in range(20)]
strings = [""] * 21
t = Template(*strings, *interps)
print(f"Template() constructor: OK ({len(t.interpolations)} interpolations)")
except Exception as e:
print(f"Template() constructor: {type(e).__name__}")
print("\n=== vstr string concatenation ===")
try:
t1 = Template("part1", "part2", "part3", "part4", Interpolation(1, "x"), "end")
result = str(t1)
print(f"vstr concat: '{result}'")
except Exception as e:
print(f"vstr concat 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("\nConstructor tests completed!")

View File

@@ -0,0 +1,40 @@
=== Constructor basic usage ===
Template repr: Template(strings=('hello ', 'world'), interpolations=(Interpolation(42, 'x', None, ''),))
Varargs constructor: strings=('Hello ', '!'), values=('World',)
Varargs merged strings: ('AB', 'CD')
Leading interpolation strings: ('', ' tail')
Trailing interpolation strings: ('head ', '')
Interpolation only strings: ('', '', '')
=== Special cases ===
Interp read-only: AttributeError
Whitespace trim: 'Template(strings=('', ''), interpolations=(Interpolation(None, ' ', None, ''),))'
Debug =: Template(strings=('', ''), interpolations=(Interpolation(42, 'x=', None, ''),))
Custom !r: Template(strings=('', ''), interpolations=(Interpolation(CustomRepr, 'obj', 'r', ''),))
Custom !s: Template(strings=('', ''), interpolations=(Interpolation(CustomRepr, 'obj', 's', ''),))
Empty start iter: ['Interpolation', 'str']
Iterator edge: ['Interpolation', 'Interpolation']
=== Values property ===
Values[0]: ()
Values[1]: (0,)
Values[2]: (0, 1)
Values[3]: (0, 1, 2)
Values[4]: (0, 1, 2, 3)
Values[5]: (0, 1, 2, 3, 4)
Values[6]: (0, 1, 2, 3, 4, 5)
=== Multiple consecutive strings ===
Multiple strings concatenated: OK
=== Template() constructor with many interpolations ===
Template() constructor: OK (20 interpolations)
=== vstr string concatenation ===
vstr concat: 'Template(strings=('part1part2part3part4', 'end'), interpolations=(Interpolation(1, 'x', None, ''),))'
=== High byte handling ===
High bytes: len=5, first=0x7f, last=0xff
Octal high bytes: OK, len=3
Constructor tests completed!

View File

@@ -0,0 +1,24 @@
# 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'"
from string.templatelib import Template
print("=== Constructor error messages ===")
try:
Template(strings=("test",))
except TypeError as e:
print(f"Keyword args: {e}")
try:
Template("hello", 42, "world")
except TypeError as e:
print(f"Invalid type: {e}")
try:
Template("a", 42, "b")
except TypeError as e:
print(f"Invalid type in varargs: {e}")
print("\nConstructor error tests completed!")

View File

@@ -0,0 +1,6 @@
=== Constructor error messages ===
Keyword args: function doesn't take keyword arguments
Invalid type: expected str or Interpolation
Invalid type in varargs: expected str or Interpolation
Constructor error tests completed!

View 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")

View File

@@ -0,0 +1,60 @@
=== Edge cases ===
Empty template: 'Template(strings=('',), interpolations=())'
Empty strings: [Interpolation(1, 'a', None, ''), Interpolation(2, 'b', None, '')]
Adjacent: 'Template(strings=('', '', '', ''), interpolations=(Interpolation(1, '1', None, ''), Interpolation(2, '2', None, ''), Interpolation(3, '3', None, '')))'
Single iter: ['only']
Self+self: 'Template(strings=('testtest',), interpolations=())'
=== Additional coverage tests ===
String literal: Template(strings=('', ''), interpolations=(Interpolation('hello', "'hello'", None, ''),))
Raw path strings: ('Path: ', '\\n'), value=/usr/local/bin
Raw regex strings: ('Regex: \\\\d+', ''), value=42
String expr: 'Template(strings=('', ''), interpolations=(Interpolation('test', '"test"', None, ''),))'
Re-raised exception: Special error
Large template: SyntaxError
Triple quoted: 'Template(strings=('Triple "quoted" string',), interpolations=())'
Raw triple: 'Template(strings=('Raw triple\\n', ''), interpolations=(Interpolation(42, '42', None, ''),))'
Complex concat: strings=('a', 'bc', 'd', 'e'), values=(1, 2, 3)
Empty concat: 'Template(strings=('test', ''), interpolations=(Interpolation(42, '42', None, ''),))'
Self interp concat: values=(1, 1)
Lonely string: SyntaxError
Empty t-string: 'Template(strings=('',), interpolations=())'
=== High byte handling ===
High bytes: len=5, first=0x7f, last=0xff
Octal high bytes: OK, len=3
=== Deep nesting test ===
Deep nesting: OK
=== Trailing whitespace in expression ===
Trailing whitespace: OK
=== Single closing brace ===
Single }: SyntaxError - malformed f-string
=== Escape in string within interpolation ===
Debug unclosed colon: SyntaxError
Debug unclosed exclaim: SyntaxError
Unclosed brace literal: SyntaxError
Malformed f-string: SyntaxError
Type error int: expected str or Interpolation
Type error float: TypeError
Unmatched brace: malformed f-string
Nested debug braces: SyntaxError
Missing conversion: SyntaxError
Invalid conversion: invalid syntax
Vertical tab: invalid syntax
Invalid char after conversion: invalid syntax
Unexpected error: invalid syntax
Unexpected error: invalid syntax
Unexpected error: invalid syntax
Unexpected error: invalid syntax
=== Mixed prefixes ===
ft: invalid syntax
tf: invalid syntax
frt: invalid syntax
rtf: invalid syntax
trf: invalid syntax

View File

@@ -0,0 +1,186 @@
print("\n=== Format spec edge cases ===")
print(f"Empty fmt: {t'{42:}'}")
print(f"Width: '{t'{42:10}'}'")
print(f"Conv+fmt: '{t'{42!r:>10}'}'")
width = 10
print(f"Interp fmt: '{t'{3.14:{width}.2f}'}'")
try:
t_escaped = t"Hello {{name}} and {{{{value}}}}"
print(f"Escaped braces: '{t_escaped}'")
except Exception as e:
print(f"Escaped braces error: {e}")
try:
t_conv_fmt = t"{42!r:}"
print(f"Conv empty fmt: '{t_conv_fmt}'")
except Exception as e:
print(f"Conv fmt error: {e}")
print("\n=== Format spec edge cases ===")
try:
x = 42
result = t'{x!r:0>+#10.5}'
print(f"Full format: '{result}'")
except Exception as e:
print(f"Full format error: {e}")
print("\n=== Format spec with special characters ===")
try:
x = 42
result = t"{x:!r}"
print(f"Format spec '!r': {result.interpolations[0].format_spec == '!r'}")
print(f"Conversion is None: {result.interpolations[0].conversion is None}")
except Exception as e:
print(f"Format spec error: {type(e).__name__}")
print("\nCoverage tests completed!")
print("\n=== Debug format edge cases ===")
try:
x = 42
result = t'{x=!r}'
print(f"Debug with conv: '{result}'")
except SyntaxError as e:
print(f"Debug conv: SyntaxError - {e}")
print("\n=== Debug specifier tests ===")
try:
value = 42
t1 = t'{value=}'
print(f"Debug basic: strings={t1.strings}, conv={t1.interpolations[0].conversion}")
t2 = t'{value=!s}'
print(f"Debug !s: strings={t2.strings}, conv={t2.interpolations[0].conversion}")
t3 = t'{value=:>10}'
print(f"Debug fmt: strings={t3.strings}, fmt_spec='{t3.interpolations[0].format_spec}'")
except Exception as e:
print(f"Debug format error: {e}")
print("\n=== Large number of interpolations ===")
for N in (62, 63, 64, 65):
x = 1
code = 'result = str(t"' + '{x}' * N + '")'
exec(code)
expected = "Template(strings=" + repr(tuple("" for _ in range(N + 1))) + ", interpolations=(" + ", ".join("Interpolation(1, 'x', None, '')" for _ in range(N)) + "))"
print(N, result == expected)
print("\n=== Malformed format specs (valid in CPython) ===")
x = 42
value = 42
result = t'{x:10!r}'
print(f"Malformed 1: format_spec: '{result.interpolations[0].format_spec}', conversion: {result.interpolations[0].conversion}")
result = t'{value:^10!s}'
print(f"Malformed 2: format_spec: '{result.interpolations[0].format_spec}', conversion: {result.interpolations[0].conversion}")
result = t'{x:>!10}'
print(f"Malformed 3: format_spec: '{result.interpolations[0].format_spec}', conversion: {result.interpolations[0].conversion}")
try:
exec(r"t'\U00110000'")
print("ERROR: Should have failed")
except SyntaxError as e:
print(f"Invalid unicode: SyntaxError - {e}")
class CustomException(Exception):
pass
class BadClass:
def __getattr__(self, name):
raise CustomException("Test exception")
try:
bad_obj = BadClass()
result = t"{bad_obj.attr}"
print("ERROR: Should have raised exception")
except CustomException as e:
print(f"Custom exception re-raised: {e}")
except Exception as e:
print(f"Other exception: {type(e).__name__} - {e}")
print("\nNULL lexer: Tested in heapalloc_fail_tstring.py")
try:
exec(r"t'\N{LATIN SMALL LETTER A}'")
print("ERROR: \\N{{}} should not be supported")
except (SyntaxError, NotImplementedError) as e:
print(f"\\N{{}} escape: SyntaxError")
try:
result = rt'\u0041\U00000042'
print(f"Raw Unicode escapes: '{result.strings[0]}'")
except Exception as e:
print(f"Raw Unicode error: {e}")
try:
code_with_crlf = 't"line1\\r\\nline2"'
exec(code_with_crlf)
print("CR LF handling: OK")
except Exception as e:
print(f"CR LF error: {e}")
try:
code = ""
for i in range(20):
code += " " * i + "if True:\n"
code += " " * 20 + "x = t'deep indent'"
exec(code)
print("Deep indent: OK")
except Exception as e:
print(f"Deep indent error: {e}")
try:
exec("t'test\\")
print("ERROR: Trailing backslash should fail")
except SyntaxError as e:
print(f"Trailing backslash: SyntaxError")
print("\n=== Empty format spec node ===")
try:
x = 42
result = t'{x:}'
print(f"Empty format after colon: OK")
except Exception as e:
print(f"Empty format spec error: {e}")
print("\n=== Format spec with special characters (coverage) ===")
try:
align = "<"
width = 5
value = "test"
result = t'{value:{"^"*width}}'
print(f"Double quotes expr: format_spec={repr(result.interpolations[0].format_spec)}")
except Exception as e:
print(f"Double quotes expr error: {type(e).__name__}: {e}")
try:
align = "<"
width = 5
value = "test"
result = rt"{value:{align}{width}\\}"
print(f"Backslash raw: format_spec={repr(result.interpolations[0].format_spec)}")
except Exception as e:
print(f"Backslash raw error: {type(e).__name__}: {e}")
try:
align = "<"
width = 5
value = "test"
result = t"{value:{align}{width}\\}"
print(f"Backslash non-raw: format_spec={repr(result.interpolations[0].format_spec)}")
except Exception as e:
print(f"Backslash non-raw error: {type(e).__name__}: {e}")
try:
align = "<"
width = 5
value = "test"
result = t"{value:{align}{width}\n\t\r}"
print(f"Multiple escapes: format_spec={repr(result.interpolations[0].format_spec)}")
except Exception as e:
print(f"Multiple escapes error: {type(e).__name__}: {e}")

View File

@@ -0,0 +1,54 @@
=== Format spec edge cases ===
Empty fmt: Template(strings=('', ''), interpolations=(Interpolation(42, '42', None, ''),))
Width: 'Template(strings=('', ''), interpolations=(Interpolation(42, '42', None, '10'),))'
Conv+fmt: 'Template(strings=('', ''), interpolations=(Interpolation(42, '42', 'r', '>10'),))'
Interp fmt: 'Template(strings=('', ''), interpolations=(Interpolation(3.14, '3.14', None, '10.2f'),))'
Escaped braces: 'Template(strings=('Hello {name} and {{value}}',), interpolations=())'
Conv empty fmt: 'Template(strings=('', ''), interpolations=(Interpolation(42, '42', 'r', ''),))'
=== Format spec edge cases ===
Full format: 'Template(strings=('', ''), interpolations=(Interpolation(42, 'x', 'r', '0>+#10.5'),))'
=== Format spec with special characters ===
Format spec '!r': True
Conversion is None: True
Coverage tests completed!
=== Debug format edge cases ===
Debug with conv: 'Template(strings=('x=', ''), interpolations=(Interpolation(42, 'x', 'r', ''),))'
=== Debug specifier tests ===
Debug basic: strings=('value=', ''), conv=r
Debug !s: strings=('value=', ''), conv=s
Debug fmt: strings=('value=', ''), fmt_spec='>10'
=== Large number of interpolations ===
62 True
63 True
64 True
65 True
=== Malformed format specs (valid in CPython) ===
Malformed 1: format_spec: '10!r', conversion: None
Malformed 2: format_spec: '^10!s', conversion: None
Malformed 3: format_spec: '>!10', conversion: None
Invalid unicode: SyntaxError - invalid syntax
Custom exception re-raised: Test exception
NULL lexer: Tested in heapalloc_fail_tstring.py
\N{} escape: SyntaxError
Raw Unicode escapes: '\u0041\U00000042'
CR LF handling: OK
Deep indent: OK
Trailing backslash: SyntaxError
=== Empty format spec node ===
Empty format after colon: OK
=== Format spec with special characters (coverage) ===
Double quotes expr: format_spec='^^^^^'
Backslash raw: format_spec='<5\\\\'
Backslash non-raw: format_spec='<5\\'
Multiple escapes: format_spec='<5\n\t\r'

View File

@@ -0,0 +1,72 @@
print("\n=== Bracket/paren depth tracking ===")
d = {'a': 1, 'b:c': 2}
items = [10, 20, 30]
colon_key = t"{d['b:c']}"
print(f"Colon in key: expr={colon_key.interpolations[0].expression}, value={colon_key.interpolations[0].value}")
slice_expr = t"{items[1:3]}"
print(f"Slice colon: expr={slice_expr.interpolations[0].expression}, value={slice_expr.interpolations[0].value}")
def pair(x, y=':'):
return (x, y)
func_expr = t"{pair(1, 2)}"
print(f"Function call: expr={func_expr.interpolations[0].expression}, value={func_expr.interpolations[0].value}")
default_expr = t"{pair(3)}"
print(f"Default arg colon: expr={default_expr.interpolations[0].expression}, value={default_expr.interpolations[0].value}")
matrix = [[1, 2], [3, 4]]
nested_expr = t"{matrix[0][1]}"
print(f"Nested brackets: expr={nested_expr.interpolations[0].expression}, value={nested_expr.interpolations[0].value}")
print("\n=== Inline template literal tests ===")
try:
result = t"outer { t'' }"
print(f"Empty nested: {type(result.values[0]).__name__}")
except Exception as e:
print(f"Empty nested: FAIL - {type(e).__name__}: {e}")
try:
result = t"outer { t'inner' }"
print(f"With content: {type(result.values[0]).__name__}")
except Exception as e:
print(f"With content: FAIL - {type(e).__name__}: {e}")
try:
result = t"result: { t'a' + t'b' }"
print(f"Concatenation: {type(result.values[0]).__name__}")
except Exception as e:
print(f"Concatenation: FAIL - {type(e).__name__}: {e}")
try:
result = t'Single { t"double" }'
print(f"Mixed quotes: {type(result.values[0]).__name__}")
except Exception as e:
print(f"Mixed quotes: FAIL - {type(e).__name__}: {e}")
try:
x = 42
result = t"outer { t'inner {x}' }"
print(f"Nested interp: {type(result.values[0]).__name__}")
except Exception as e:
print(f"Nested interp: FAIL - {type(e).__name__}: {e}")
print("\n=== Backslashes not allowed in expressions (PEP 498/750) ===")
try:
code = r"result = t'{\"test\"value\"}'"
exec(code)
print(f"ERROR: Backslash in expression should raise SyntaxError")
except SyntaxError as e:
print(f"Backslash in expression: SyntaxError")
try:
code = r"result = t'{\"\\n\"}'"
exec(code)
print(f"ERROR: Backslash in expression should raise SyntaxError")
except SyntaxError as e:
print(f"Escaped newline: SyntaxError")
print("\n=== Format spec with special characters ===")

View File

@@ -0,0 +1,20 @@
=== Bracket/paren depth tracking ===
Colon in key: expr=d['b:c'], value=2
Slice colon: expr=items[1:3], value=[20, 30]
Function call: expr=pair(1, 2), value=(1, 2)
Default arg colon: expr=pair(3), value=(3, ':')
Nested brackets: expr=matrix[0][1], value=2
=== Inline template literal tests ===
Empty nested: Template
With content: Template
Concatenation: Template
Mixed quotes: Template
Nested interp: Template
=== Backslashes not allowed in expressions (PEP 498/750) ===
Backslash in expression: SyntaxError
Escaped newline: SyntaxError
=== Format spec with special characters ===

View File

@@ -0,0 +1,55 @@
from string.templatelib import Template, Interpolation
print("\n=== Binary operations ===")
t1 = t"template"
try:
t1 + "string"
except TypeError as e:
print(f"Template+str: TypeError")
try:
"string" + t1
except TypeError as e:
print(f"str+Template: TypeError")
try:
42 + t1
except TypeError:
print("int+Template: TypeError")
for op in ["-", "*", "/", "%", "**", "&", "|", "^", "<<", ">>"]:
try:
eval(f"t1 {op} t1")
except TypeError:
print(f"{op}: unsupported")
print("\n=== Template.__add__ with multiple interpolations ===")
try:
exprs = ["a", "b", "c", "d", "e"]
interps1 = [Interpolation(i, exprs[i % len(exprs)]) for i in range(20)]
strings1 = [""] * 21
t1 = Template(*strings1, *interps1)
interps2 = [Interpolation(i + 20, exprs[i % len(exprs)]) for i in range(20)]
strings2 = [""] * 21
t2 = Template(*strings2, *interps2)
result = t1 + t2
print(f"Template.__add__: OK ({len(result.interpolations)} interpolations)")
except Exception as e:
print(f"Template.__add__: {type(e).__name__}")
print("\n=== Template + non-string object ===")
try:
class CustomObj:
pass
t1 = t"hello"
result = t1 + CustomObj()
print("ERROR: Should have raised TypeError")
except TypeError as e:
print(f"Template + custom object: TypeError (correct)")
print("\n=== 2-tuple constructor overflow ===")

View File

@@ -0,0 +1,23 @@
=== Binary operations ===
Template+str: TypeError
str+Template: TypeError
int+Template: TypeError
-: unsupported
*: unsupported
/: unsupported
%: unsupported
**: unsupported
&: unsupported
|: unsupported
^: unsupported
<<: unsupported
>>: unsupported
=== Template.__add__ with multiple interpolations ===
Template.__add__: OK (40 interpolations)
=== Template + non-string object ===
Template + custom object: TypeError (correct)
=== 2-tuple constructor overflow ===

View File

@@ -0,0 +1,288 @@
from string.templatelib import Template
print("\n=== Empty expression tests ===")
try:
exec("t'{}'")
print("ERROR: Empty expression should have raised SyntaxError")
except SyntaxError as e:
print("Empty expr SyntaxError:", e)
try:
exec("t'{ }'")
print("ERROR: Whitespace-only expression should have raised SyntaxError")
except SyntaxError as e:
print("Whitespace expr SyntaxError:", e)
try:
exec("t'{\\n\\t \\n}'")
print("ERROR: Whitespace-only expr should fail")
except SyntaxError as e:
print(f"Whitespace expr: SyntaxError")
try:
exec("t'{value'")
print("ERROR: Unterminated expr should fail")
except SyntaxError as e:
print(f"Unterminated expr: {type(e).__name__}")
try:
exec("t'{ !r}'")
print("ERROR: Whitespace-conversion expr should fail")
except SyntaxError as e:
print(f"Whitespace + conversion: {type(e).__name__}")
try:
long_expr = 'x' * 104
code = f"t'{{{long_expr}}}'"
exec(f"{long_expr} = 'test'; result = {code}")
print("Long expression: OK")
except Exception as e:
print(f"Long expr error: {type(e).__name__}: {e}")
try:
large_str = 'x' * 1099
tmpl = Template(large_str)
result = str(tmpl)
print("Template.__str__ with large string: OK")
except Exception as e:
print(f"Template.__str__ error: {type(e).__name__}: {e}")
try:
x = 'a'
result = t'{x}'
print(f"Basic t-string: OK, result={result}")
except Exception as e:
print(f"Basic t-string error: {type(e).__name__}: {e}")
try:
code = 't"' + 'text{nested{x}}more' * 50 + '"'
x = 'test'
exec(f"x = 'test'; result = {code}")
print("Nested interpolations: OK")
except Exception as e:
print(f"Nested interpolations error: {type(e).__name__}: {e}")
width = 5
x = 42
try:
tmpl = t'{x:{width}{{literal}}}'
result = str(tmpl)
print(f'Escaped braces result: {result}')
except ValueError as e:
print(f'Escaped braces ValueError: {e}')
print("\n=== Expression parser tests ===")
try:
exec("t'{[{(x,y):[1,2,3]} for x,y in [(1,2),(3,4)]]}'")
print("Complex expr: OK")
except Exception as e:
print(f"Complex expr error: {type(e).__name__}")
print("\n=== Lexer edge cases ===")
print("Lexer NULL case: Tested via heapalloc_fail_tstring.py")
print("\n=== Parser allocation edge cases ===")
try:
deep_expr = "(" * 50 + "x" + ")" * 50
exec(f"x = 1; result = t'{{{deep_expr}}}'")
print("Deep nesting: OK")
except Exception as e:
print(f"Deep nesting error: {type(e).__name__} - {e}")
try:
long_expr = "(" * 100 + "x" + ")" * 100
exec(f"x = 1; result = t'{{{long_expr}}}'")
print("Very long expression: OK")
except Exception as e:
print(f"Long expression error: {e}")
print("\n=== Additional parser regression tests ===")
try:
fmt = '"QUOTED"'
value = 'raw'
tmpl = rt"{value:{fmt}}"
print(f"Raw nested spec: {tmpl.interpolations[0].format_spec}")
except Exception as e:
print(f"Raw nested spec error: {type(e).__name__}: {e}")
try:
value = 'fmt'
tmpl = t'{value:"QUOTED"}'
print(f"Escaped quote spec: {tmpl.interpolations[0].format_spec}")
except Exception as e:
print(f"Escaped quote spec error: {type(e).__name__}: {e}")
try:
value = 'fmt'
tmpl = t"{value:\\n}"
print(f"Escaped backslash spec: {tmpl.interpolations[0].format_spec}")
except Exception as e:
print(f"Escaped backslash spec error: {type(e).__name__}: {e}")
try:
ns = {}
exec("value = 'fmt'\nresult = t\"{value:{'\\\\'}}\"", globals(), ns)
tmpl = ns['result']
print(f"Nested quoted spec: {tmpl.interpolations[0].format_spec}")
except Exception as e:
print(f"Nested quoted spec error: {type(e).__name__}: {e}")
try:
value = 'brace'
exec('tmpl = t"{value}}}}tail"')
print(f"Literal brace strings: {tmpl.strings}")
except Exception as e:
print(f"Literal brace error: {type(e).__name__}: {e}")
try:
exec('t"{value"')
print("ERROR: Unterminated field should have raised")
except SyntaxError as e:
print(f"Unterminated field: {e}")
print("\n=== Complex expression stress test ===")
try:
vars_dict = {f"x{i}": i for i in range(60)}
exec_globals = globals().copy()
exec_globals.update(vars_dict)
expr_parts = [f"x{i}" for i in range(60)]
expr_str = " + ".join(expr_parts)
code = f't"{{{expr_str}}}"'
result = eval(code, exec_globals)
expected = sum(range(60))
actual = result.interpolations[0].value
if actual == expected:
print(f"60 terms: PASS")
else:
print(f"60 terms: FAIL (expected={expected}, got={actual})")
except Exception as e:
print(f"60 terms: ERROR - {type(e).__name__}: {e}")
try:
vars_dict = {f"x{i}": i for i in range(100)}
exec_globals = globals().copy()
exec_globals.update(vars_dict)
expr_parts = [f"x{i}" for i in range(100)]
expr_str = " + ".join(expr_parts)
code = f't"{{{expr_str}}}"'
result = eval(code, exec_globals)
expected = sum(range(100))
actual = result.interpolations[0].value
if actual == expected:
print(f"100 terms: PASS")
else:
print(f"100 terms: FAIL (expected={expected}, got={actual})")
except Exception as e:
print(f"100 terms: ERROR - {type(e).__name__}: {e}")
print("\n=== Stack expansion test (copy_parse_node) ===")
try:
depth = 20
nested_expr = "[" * depth + "1" + "]" * depth
code = f't"{{{nested_expr}}}"'
result = eval(code)
if len(str(result.interpolations[0].value)) > 30:
print("Stack expansion: OK")
else:
print(f"Stack expansion: FAIL")
except Exception as e:
print(f"Stack expansion error: {type(e).__name__}")
print("\n=== Format spec with triple-quoted strings ===")
try:
x = 42
code = '''t'{x:{"""test"""}}' '''
result = eval(code)
print(f"Triple-quote in format spec: OK")
except Exception as e:
print(f"Triple-quote error: {type(e).__name__}: {e}")
try:
x = 42
code = """t"{x:{'''test'''}}" """
result = eval(code)
print(f"Triple single-quote in format spec: OK")
except Exception as e:
print(f"Triple single-quote error: {type(e).__name__}: {e}")
print("\n=== Format spec parse error (exception handler) ===")
try:
x = 42
code = "t\"{x:{1 + (}}\" " # Unclosed parenthesis
result = eval(code)
print("ERROR: Should have raised SyntaxError")
except SyntaxError as e:
print(f"Parse error handler: OK")
except Exception as e:
print(f"Unexpected error: {type(e).__name__}: {e}")
try:
x = 42
code = "t\"{x:{1 2 3}}\" " # Invalid syntax
result = eval(code)
print("ERROR: Should have raised SyntaxError")
except SyntaxError as e:
print(f"Parse error handler 2: OK")
except Exception as e:
print(f"Unexpected error 2: {type(e).__name__}: {e}")
print("\n=== Large integer in t-string ===")
try:
# Use eval() to avoid compile-time overflow in longlong variant
large_int = eval("10**30")
tmpl = t"{large_int}"
if tmpl.interpolations[0].value == large_int:
print("Large integer: OK")
else:
print(f"Large integer: FAIL")
except OverflowError:
# longlong variant doesn't support arbitrary precision integers
print("Large integer: OK")
except Exception as e:
print(f"Large integer error: {type(e).__name__}: {e}")
print("\n=== Bytes in expression ===")
try:
data = b"test"
tmpl = t"{data}"
if tmpl.interpolations[0].value == b"test":
print("Bytes in expression: OK")
else:
print(f"Bytes: FAIL")
except Exception as e:
print(f"Bytes error: {type(e).__name__}: {e}")
print("\n=== Boolean constants ===")
try:
tmpl = t"{True} {False}"
if tmpl.values == (True, False):
print("Boolean constants: OK")
else:
print(f"Booleans: FAIL")
except Exception as e:
print(f"Boolean error: {type(e).__name__}: {e}")
print("\n=== None constant ===")
try:
tmpl = t"{None}"
if tmpl.values[0] is None:
print("None constant: OK")
else:
print(f"None: FAIL")
except Exception as e:
print(f"None error: {type(e).__name__}: {e}")
print("\n=== Deep nesting for rule stack expansion ===")
try:
depth = 80
code = '(' * depth + '1' + ')' * depth
result = eval(code)
if result == 1:
print(f"Deep nesting (depth={depth}): OK")
else:
print(f"Deep nesting: FAIL (result={result})")
except Exception as e:
print(f"Deep nesting error: {type(e).__name__}: {e}")

View File

@@ -0,0 +1,60 @@
=== Empty expression tests ===
Empty expr SyntaxError: malformed f-string
Whitespace expr SyntaxError: malformed f-string
Whitespace expr: SyntaxError
Unterminated expr: SyntaxError
Whitespace + conversion: SyntaxError
Long expression: OK
Template.__str__ with large string: OK
Basic t-string: OK, result=Template(strings=('', ''), interpolations=(Interpolation('a', 'x', None, ''),))
Nested interpolations error: SyntaxError: invalid syntax
Escaped braces result: Template(strings=('', ''), interpolations=(Interpolation(42, 'x', None, '5{literal}'),))
=== Expression parser tests ===
Complex expr: OK
=== Lexer edge cases ===
Lexer NULL case: Tested via heapalloc_fail_tstring.py
=== Parser allocation edge cases ===
Deep nesting: OK
Very long expression: OK
=== Additional parser regression tests ===
Raw nested spec: "QUOTED"
Escaped quote spec: "QUOTED"
Escaped backslash spec: \n
Nested quoted spec: \
Literal brace error: SyntaxError: malformed f-string
Unterminated field: malformed f-string
=== Complex expression stress test ===
60 terms: PASS
100 terms: PASS
=== Stack expansion test (copy_parse_node) ===
Stack expansion: OK
=== Format spec with triple-quoted strings ===
Triple-quote in format spec: OK
Triple single-quote in format spec: OK
=== Format spec parse error (exception handler) ===
Parse error handler: OK
Parse error handler 2: OK
=== Large integer in t-string ===
Large integer: OK
=== Bytes in expression ===
Bytes in expression: OK
=== Boolean constants ===
Boolean constants: OK
=== None constant ===
None constant: OK
=== Deep nesting for rule stack expansion ===
Deep nesting (depth=80): OK

View File

@@ -0,0 +1,25 @@
print("# Empty expression (whitespace only)")
try:
exec('t"{ }"')
print("ERROR: Should have raised SyntaxError")
except SyntaxError as e:
print("Empty expr (space): SyntaxError (correct)")
try:
exec('t"{\t}"')
print("ERROR: Should have raised SyntaxError")
except SyntaxError as e:
print("Empty expr (tab): SyntaxError (correct)")
try:
exec('t"{\n}"')
print("ERROR: Should have raised SyntaxError")
except SyntaxError as e:
print("Empty expr (newline): SyntaxError (correct)")
print("\n# Lexer escape sequence: invalid then valid")
try:
exec("t'\\U00200000\\x41'")
print("ERROR: Should have raised SyntaxError")
except SyntaxError as e:
print("Invalid escape sequence: SyntaxError (correct)")

View File

@@ -0,0 +1,7 @@
# Empty expression (whitespace only)
Empty expr (space): SyntaxError (correct)
Empty expr (tab): SyntaxError (correct)
Empty expr (newline): SyntaxError (correct)
# Lexer escape sequence: invalid then valid
Invalid escape sequence: SyntaxError (correct)

View File

@@ -0,0 +1,8 @@
# check whether t-strings (PEP-750) are supported
# TODO remove this check when micropython-lib's string extends ustring
from string.templatelib import Template, Interpolation
a = 1
t = t"a={a}"
print("tstring")

View File

@@ -0,0 +1 @@
tstring

View File

@@ -0,0 +1,24 @@
# This tests the built-in __template__ (which is MicroPython specific).
# Test a varying number of arguments.
print(__template__(()))
print(__template__((), ()))
print(__template__((), None, None))
print(__template__((), None, None, None))
print(__template__((), None, None, None, None))
print(__template__((), None, None, None, None, None))
# Test two strings and one interpolation.
print(__template__(("Hello ", "!"), (42, "x", None, "")))
# Test not enough arguments.
try:
print(__template__())
except TypeError as er:
print(repr(er))
# Test two arguments with second not being a tuple/list.
try:
print(__template__((), None))
except TypeError as er:
print(repr(er))

View File

@@ -0,0 +1,9 @@
Template(strings=(), interpolations=())
Template(strings=(), interpolations=())
Template(strings=(), interpolations=())
Template(strings=(), interpolations=())
Template(strings=(), interpolations=(Interpolation(None, None, None, None),))
Template(strings=(), interpolations=(Interpolation(None, None, None, None),))
Template(strings=('Hello ', '!'), interpolations=(Interpolation(42, 'x', None, ''),))
TypeError('function missing 1 required positional arguments',)
TypeError("object 'NoneType' isn't a tuple or list",)

View File

@@ -0,0 +1,475 @@
# Test template string (t-string) operations with heap allocation failure
import micropython
from string.templatelib import Template, Interpolation
i1 = Interpolation(1, "1")
i2 = Interpolation(2, "2")
micropython.heap_lock()
try:
args = []
for i in range(9):
args.append("x" * 100)
args.append(Interpolation(i, "x" + str(i)))
args.append("x" * 100)
Template(*args)
print("FAIL: Template creation")
except MemoryError:
print("OK: Template creation")
micropython.heap_unlock()
# Multiple string concatenation
micropython.heap_lock()
try:
Template("first", "second", "third", "fourth")
print("FAIL: Multi string concat")
except MemoryError:
print("OK: Multi string concat")
micropython.heap_unlock()
# Mixed constructor
micropython.heap_lock()
try:
Template("a", i1, "b", i2, "c", "d", "e")
print("FAIL: Mixed constructor")
except MemoryError:
print("OK: Mixed constructor")
micropython.heap_unlock()
# Template.__str__()
t = t"Hello {42} world {99}"
micropython.heap_lock()
try:
str(t)
print("FAIL: Template.__str__()")
except MemoryError:
print("OK: Template.__str__()")
micropython.heap_unlock()
# Template.values property
vals = list(range(10))
t_many = t"{vals[0]}{vals[1]}{vals[2]}{vals[3]}{vals[4]}"
micropython.heap_lock()
try:
t_many.values
print("FAIL: Template.values")
except MemoryError:
print("OK: Template.values")
micropython.heap_unlock()
n = 20
args_large = []
for i in range(n):
args_large.append("")
args_large.append(Interpolation(i, "x" + str(i)))
args_large.append("")
t_large = Template(*args_large)
micropython.heap_lock()
try:
t_large.values
print("FAIL: Large values array")
except MemoryError:
print("OK: Large values array")
micropython.heap_unlock()
# Template concatenation
t1 = t"Hello"
t2 = t"World"
micropython.heap_lock()
try:
t1 + t2
print("FAIL: Template concatenation")
except MemoryError:
print("OK: Template concatenation")
micropython.heap_unlock()
# Template iterator
t_iter = t"a{1}b{2}c"
micropython.heap_lock()
try:
iter(t_iter)
print("FAIL: Template iterator")
except MemoryError:
print("OK: Template iterator")
micropython.heap_unlock()
# Iterator next
t_iter2 = t"a{1}b{2}c{3}d"
it = iter(t_iter2)
micropython.heap_lock()
try:
list(it)
print("FAIL: Iterator next")
except MemoryError:
print("OK: Iterator next")
micropython.heap_unlock()
# __template__ builtin
strings2 = ("test",) * 5
interps2 = ((42, "x", None, ""),) * 4
micropython.heap_lock()
try:
__template__(strings2, interps2)
print("FAIL: __template__ builtin")
except MemoryError:
print("OK: __template__ builtin")
micropython.heap_unlock()
# Format spec interpolation
width = 10
t_fmt = t"{42:{width}d}"
micropython.heap_lock()
try:
str(t_fmt)
print("FAIL: Format spec interpolation")
except MemoryError:
print("OK: Format spec interpolation")
micropython.heap_unlock()
# Debug format
x = 42
t_debug = t"{x=}"
micropython.heap_lock()
try:
str(t_debug)
print("FAIL: Debug format")
except MemoryError:
print("OK: Debug format")
micropython.heap_unlock()
# Conversion with format
obj = "test"
t_conv = t"{obj!r:>10}"
micropython.heap_lock()
try:
str(t_conv)
print("FAIL: Conversion + format")
except MemoryError:
print("OK: Conversion + format")
micropython.heap_unlock()
# String conversion (s)
t_s = t"{'test'!s}"
micropython.heap_lock()
try:
str(t_s)
print("FAIL: s conversion")
except MemoryError:
print("OK: s conversion")
micropython.heap_unlock()
# ASCII conversion (a)
# t_a = t"{'test'!a}"
# micropython.heap_lock()
# try:
# str(t_a)
# print("FAIL: a conversion")
# except MemoryError:
# print("OK: a conversion")
# micropython.heap_unlock()
# Complex format spec
fill = "*"
align = ">"
width = 10
precision = 2
t_complex = t"{3.14159:{fill}{align}{width}.{precision}f}"
micropython.heap_lock()
try:
str(t_complex)
print("FAIL: Complex format spec")
except MemoryError:
print("OK: Complex format spec")
micropython.heap_unlock()
# Simple expression
x = 42
t_simple = t"{x}"
micropython.heap_lock()
try:
str(t_simple)
print("FAIL: Simple expression")
except MemoryError:
print("OK: Simple expression")
micropython.heap_unlock()
# Interpolation creation
micropython.heap_lock()
try:
Interpolation("value", "expr", "r", ".2f")
print("FAIL: Interpolation creation")
except MemoryError:
print("OK: Interpolation creation")
micropython.heap_unlock()
# Template repr
t = t"test"
micropython.heap_lock()
try:
repr(t)
print("FAIL: Template repr")
except MemoryError:
print("OK: Template repr")
micropython.heap_unlock()
# Interpolation repr
i = Interpolation(42, "x")
micropython.heap_lock()
try:
repr(i)
print("FAIL: Interpolation repr")
except MemoryError:
print("OK: Interpolation repr")
micropython.heap_unlock()
def exhaust_heap(limit):
allocations = []
try:
for _ in range(limit):
allocations.append("x" * 1024)
except MemoryError:
pass
return allocations
def test_many_interpolations_heap():
# Pre-create variables before exhausting heap
x1, x2, x3, x4, x5, x6, x7, x8, x9 = 1, 2, 3, 4, 5, 6, 7, 8, 9
micropython.heap_lock()
try:
t = t"{x1}{x2}{x3}{x4}{x5}{x6}{x7}{x8}{x9}"
print("FAIL: Many interpolations heap test")
except MemoryError:
print("OK: Many interpolations heap test")
micropython.heap_unlock()
def test_template_str_heap():
t = t"x{1}x{2}x{3}x{4}x"
micropython.heap_lock()
try:
s = str(t)
print("FAIL: Template str heap test")
except MemoryError:
print("OK: Template str heap test")
micropython.heap_unlock()
def test_template_iter_heap():
t = t"a{1}b{2}c"
micropython.heap_lock()
try:
parts = list(iter(t))
print("FAIL: Template iter heap test")
except MemoryError:
print("OK: Template iter heap test")
micropython.heap_unlock()
def test_template_concat_heap():
t1 = t"first"
t2 = t"second"
micropython.heap_lock()
try:
t3 = t1 + t2
print("FAIL: Template concat heap test")
except MemoryError:
print("OK: Template concat heap test")
micropython.heap_unlock()
def test_format_spec_heap():
width = 10
t = t"{42:{width}d}"
micropython.heap_lock()
try:
s = str(t)
print("FAIL: Format spec heap test")
except MemoryError:
print("OK: Format spec heap test")
micropython.heap_unlock()
def test_debug_format_heap():
value = 42
t = t"{value}"
micropython.heap_lock()
try:
s = str(t)
print("FAIL: Debug format heap test")
except MemoryError:
print("OK: Debug format heap test")
micropython.heap_unlock()
print("\n=== Heap allocation failure tests ===")
test_many_interpolations_heap()
test_template_str_heap()
test_template_iter_heap()
test_template_concat_heap()
test_format_spec_heap()
test_debug_format_heap()
# Additional tests from coverage.c
print("\n=== Coverage.c heap tests ===")
# Test creating interpolation with heap locked (from coverage.c)
micropython.heap_lock()
try:
i = Interpolation(42, "x", None, None)
print("FAIL: Interpolation with heap locked")
except MemoryError:
print("OK: Interpolation creation with heap locked")
micropython.heap_unlock()
# Test parsing expression with heap locked (from coverage.c)
micropython.heap_lock()
try:
t = eval('t"{x + 1}"')
print("FAIL: Parse with heap locked")
except Exception as e:
if type(e).__name__ == "MemoryError":
print("OK: Parse with heap locked")
else:
print(f"Parse with heap locked: {type(e).__name__}")
micropython.heap_unlock()
# Test lexer allocation failure for t-string expression parsing
print("\n=== Lexer/Parser allocation tests ===")
# Wrap all tests in a try-catch to handle any unexpected errors
try:
# Test 1: Empty expression (tests tstring_expr_parser.c empty check)
try:
micropython.heap_lock()
try:
# This tests the empty expression path
exec("t'{}'")
print("FAIL: Empty expression with heap locked")
except Exception as e:
print(f"OK: Empty expression - {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 2: Whitespace-only expression
try:
micropython.heap_lock()
try:
exec("t'{ }'")
print("FAIL: Whitespace expression with heap locked")
except Exception as e:
print(f"OK: Whitespace expression - {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 3: Very complex expression to stress parser allocation
# Pre-create variables
x, y, z = 1, 2, 3
try:
micropython.heap_lock()
try:
# Complex nested expression that requires many parse nodes
result = t'{[x*y for x in range(10) for y in range(10) if x+y > 5]}'
print("FAIL: Complex expression with heap locked")
except MemoryError:
print("OK: Complex expression parser allocation")
except Exception as e:
print(f"Complex expression: {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 4: Expression parser lexer failure (simulates NULL lexer)
# This happens when memory is exhausted during lexer creation
try:
micropython.heap_lock()
try:
# Try to create t-string with expression during low memory
long_expr = "x" * 100 # Use a moderately long expression
exec(f"{long_expr} = 42; result = t'{{{long_expr}}}'")
print("FAIL: Lexer creation with heap locked")
except MemoryError:
print("OK: Lexer creation failure")
except Exception as e:
print(f"Lexer creation: {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 5: Format spec and conversion parsing under memory pressure
val = 42
try:
micropython.heap_lock()
try:
# This tests format spec node creation
result = t'{val!r:>10.2f}'
print("FAIL: Format spec with heap locked")
except MemoryError:
print("OK: Format spec allocation")
except Exception as e:
print(f"Format spec: {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 6: Debug format with memory pressure
x = 100
try:
micropython.heap_lock()
try:
result = t'{x=:.2f}'
print("FAIL: Debug format with heap locked")
except MemoryError:
print("OK: Debug format allocation")
except Exception as e:
print(f"Debug format: {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 7: Multiple interpolations causing allocation failure
vals = list(range(20))
try:
micropython.heap_lock()
try:
# Many interpolations to stress allocation
result = t'{vals[0]}{vals[1]}{vals[2]}{vals[3]}{vals[4]}{vals[5]}{vals[6]}{vals[7]}{vals[8]}{vals[9]}'
print("FAIL: Many interpolations with heap locked")
except MemoryError:
print("OK: Many interpolations allocation")
except Exception as e:
print(f"Many interpolations: {type(e).__name__}")
finally:
micropython.heap_unlock()
except:
pass
# Test 8: Template string size limit (tests overflow check)
# Note: We can't directly test integer overflow, but we can test large templates
print("Template size limit: Tested via exec in coverage tests")
# Test 9: Compile-time parsing under allocation failure
try:
micropython.heap_lock()
try:
compile("t'{x}'", "<tstring>", "exec")
print("FAIL: Compile-time parsing under heap lock")
except MemoryError:
print("OK: Compile-time parsing under heap lock")
finally:
micropython.heap_unlock()
except:
pass
except Exception as e:
# Catch any unexpected errors from the tests
print(f"\nTest error: {type(e).__name__}: {e}")
# Ensure heap is unlocked
try:
micropython.heap_unlock()
except:
pass
print("\n=== Tests completed ===")

View File

@@ -0,0 +1,42 @@
OK: Template creation
OK: Multi string concat
OK: Mixed constructor
OK: Template.__str__()
OK: Template.values
OK: Large values array
OK: Template concatenation
OK: Template iterator
OK: Iterator next
OK: __template__ builtin
OK: Format spec interpolation
OK: Debug format
OK: Conversion + format
OK: s conversion
OK: Complex format spec
OK: Simple expression
OK: Interpolation creation
OK: Template repr
OK: Interpolation repr
=== Heap allocation failure tests ===
OK: Many interpolations heap test
OK: Template str heap test
OK: Template iter heap test
OK: Template concat heap test
OK: Format spec heap test
OK: Debug format heap test
=== Coverage.c heap tests ===
OK: Interpolation creation with heap locked
OK: Parse with heap locked
=== Lexer/Parser allocation tests ===
OK: Complex expression parser allocation
OK: Lexer creation failure
OK: Format spec allocation
OK: Debug format allocation
OK: Many interpolations allocation
Template size limit: Tested via exec in coverage tests
OK: Compile-time parsing under heap lock
=== Tests completed ===

View File

@@ -650,6 +650,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
skip_const = False
skip_revops = False
skip_fstring = False
skip_tstring = False
skip_endian = False
skip_inlineasm = False
has_complex = True
@@ -710,6 +711,11 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
if output != b"a=1\n":
skip_fstring = True
# Check if tstring feature is enabled, and skip such tests if it doesn't
output = run_feature_check(pyb, args, "tstring.py")
if output != b"tstring\n":
skip_tstring = True
if args.inlineasm_arch == "thumb":
# Check if @micropython.asm_thumb supports Thumb2 instructions, and skip such tests if it doesn't
output = run_feature_check(pyb, args, "inlineasm_thumb2.py")
@@ -881,6 +887,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
is_async = test_name.startswith(("async_", "asyncio_")) or test_name.endswith("_async")
is_const = test_name.startswith("const")
is_fstring = test_name.startswith("string_fstring")
is_tstring = test_name.startswith("string_tstring") or test_name.endswith("_tstring")
is_inlineasm = test_name.startswith("asm")
skip_it = os.path.realpath(test_file) in skip_tests
@@ -895,6 +902,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
skip_it |= skip_const and is_const
skip_it |= skip_revops and "reverse_op" in test_name
skip_it |= skip_fstring and is_fstring
skip_it |= skip_tstring and is_tstring
skip_it |= skip_inlineasm and is_inlineasm
if skip_it: