mirror of
https://github.com/micropython/micropython.git
synced 2026-03-10 10:50: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
@@ -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",
|
||||
]
|
||||
|
||||
6
tests/basics/string_module_tstring.py
Normal file
6
tests/basics/string_module_tstring.py
Normal 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))
|
||||
1
tests/basics/string_module_tstring.py.exp
Normal file
1
tests/basics/string_module_tstring.py.exp
Normal file
@@ -0,0 +1 @@
|
||||
templatelib True
|
||||
338
tests/basics/string_tstring_basic.py
Normal file
338
tests/basics/string_tstring_basic.py
Normal 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!")
|
||||
141
tests/basics/string_tstring_basic.py.exp
Normal file
141
tests/basics/string_tstring_basic.py.exp
Normal 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!
|
||||
131
tests/basics/string_tstring_basic1.py
Normal file
131
tests/basics/string_tstring_basic1.py
Normal 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!")
|
||||
41
tests/basics/string_tstring_basic1.py.exp
Normal file
41
tests/basics/string_tstring_basic1.py.exp
Normal 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!
|
||||
112
tests/basics/string_tstring_constructor.py
Normal file
112
tests/basics/string_tstring_constructor.py
Normal 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!")
|
||||
40
tests/basics/string_tstring_constructor.py.exp
Normal file
40
tests/basics/string_tstring_constructor.py.exp
Normal 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!
|
||||
24
tests/basics/string_tstring_constructor1.py
Normal file
24
tests/basics/string_tstring_constructor1.py
Normal 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!")
|
||||
6
tests/basics/string_tstring_constructor1.py.exp
Normal file
6
tests/basics/string_tstring_constructor1.py.exp
Normal 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!
|
||||
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")
|
||||
60
tests/basics/string_tstring_errors1.py.exp
Normal file
60
tests/basics/string_tstring_errors1.py.exp
Normal 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
|
||||
186
tests/basics/string_tstring_format1.py
Normal file
186
tests/basics/string_tstring_format1.py
Normal 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}")
|
||||
54
tests/basics/string_tstring_format1.py.exp
Normal file
54
tests/basics/string_tstring_format1.py.exp
Normal 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'
|
||||
72
tests/basics/string_tstring_interpolation1.py
Normal file
72
tests/basics/string_tstring_interpolation1.py
Normal 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 ===")
|
||||
20
tests/basics/string_tstring_interpolation1.py.exp
Normal file
20
tests/basics/string_tstring_interpolation1.py.exp
Normal 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 ===
|
||||
55
tests/basics/string_tstring_operations.py
Normal file
55
tests/basics/string_tstring_operations.py
Normal 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 ===")
|
||||
23
tests/basics/string_tstring_operations.py.exp
Normal file
23
tests/basics/string_tstring_operations.py.exp
Normal 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 ===
|
||||
288
tests/basics/string_tstring_parser1.py
Normal file
288
tests/basics/string_tstring_parser1.py
Normal 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}")
|
||||
60
tests/basics/string_tstring_parser1.py.exp
Normal file
60
tests/basics/string_tstring_parser1.py.exp
Normal 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
|
||||
25
tests/basics/string_tstring_whitespace.py
Normal file
25
tests/basics/string_tstring_whitespace.py
Normal 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)")
|
||||
7
tests/basics/string_tstring_whitespace.py.exp
Normal file
7
tests/basics/string_tstring_whitespace.py.exp
Normal 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)
|
||||
8
tests/feature_check/tstring.py
Normal file
8
tests/feature_check/tstring.py
Normal 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")
|
||||
1
tests/feature_check/tstring.py.exp
Normal file
1
tests/feature_check/tstring.py.exp
Normal file
@@ -0,0 +1 @@
|
||||
tstring
|
||||
24
tests/micropython/builtin_tstring.py
Normal file
24
tests/micropython/builtin_tstring.py
Normal 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))
|
||||
9
tests/micropython/builtin_tstring.py.exp
Normal file
9
tests/micropython/builtin_tstring.py.exp
Normal 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",)
|
||||
475
tests/micropython/heapalloc_fail_tstring.py
Normal file
475
tests/micropython/heapalloc_fail_tstring.py
Normal 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 ===")
|
||||
42
tests/micropython/heapalloc_fail_tstring.py.exp
Normal file
42
tests/micropython/heapalloc_fail_tstring.py.exp
Normal 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 ===
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user