| [case testLibrtStrings_librt_experimental] |
| from typing import Any |
| import base64 |
| import binascii |
| import random |
| |
| from librt.strings import BytesWriter, StringWriter |
| |
| from testutil import assertRaises |
| |
| def test_bytes_writer_basics() -> None: |
| w = BytesWriter() |
| assert w.getvalue() == b"" |
| assert len(w) == 0 |
| assert repr(w) == "BytesWriter(b'')" |
| |
| w = BytesWriter() |
| w.append(ord('a')) |
| w.write(b'bc') |
| assert w.getvalue() == b"abc" |
| assert repr(w) == "BytesWriter(b'abc')" |
| |
| def test_bytes_writer_get_item() -> None: |
| w = BytesWriter() |
| w.write(b"foobar") |
| assert w[0] == ord("f") |
| assert w[5] == ord("r") |
| assert w[-1] == ord("r") |
| assert w[-2] == ord("a") |
| assert w[-6] == ord("f") |
| |
| with assertRaises(IndexError): |
| w[6] |
| with assertRaises(IndexError): |
| w[-7] |
| with assertRaises(IndexError): |
| w[1 << 50] |
| with assertRaises(IndexError): |
| w[-(1 << 50)] |
| |
| def test_bytes_writer_set_item() -> None: |
| w = BytesWriter() |
| w.write(b"foobar") |
| w[0] = 255 |
| assert w[0] == 255 |
| w[5] = 0 |
| assert w[5] == 0 |
| assert w.getvalue() == b"\xffooba\x00" |
| |
| with assertRaises(IndexError): |
| w[6] = 0 |
| with assertRaises(IndexError): |
| w[-7] = 0 |
| with assertRaises(IndexError): |
| w[1 << 50] = 0 |
| with assertRaises(IndexError): |
| w[-(1 << 50)] = 0 |
| |
| with assertRaises(ValueError): |
| w[0] = int() - 1 |
| with assertRaises(ValueError): |
| w[0] = int() + 256 |
| |
| # Grow BytesWriter |
| w.write(b"xy" * 512) |
| w[1024 + 5] = 66 |
| assert w[1024 + 5] == 66 |
| assert w[0] == 255 |
| |
| def test_bytes_writer_append_grow() -> None: |
| w = BytesWriter() |
| for i in range(16384): |
| w.append((i ^ (i >> 8)) & 255) |
| assert len(w) == i + 1 |
| b = w.getvalue() |
| for i in range(16384): |
| assert b[i] == (i ^ (i >> 8)) & 255 |
| |
| def test_bytes_writer_write_grow() -> None: |
| w = BytesWriter() |
| for i in range(16384): |
| w.write(bytes([(i ^ (i >> 8)) & 255])) |
| b = w.getvalue() |
| for i in range(16384): |
| assert b[i] == (i ^ (i >> 8)) & 255 |
| assert b[i] == w[i] |
| |
| w = BytesWriter() |
| a = [] |
| for i in range(16384): |
| w.write(bytes()) |
| if i & 1 == 0: |
| segment = b"foobarz" |
| else: |
| segment = b"\x7f\x00ab!" |
| w.write(segment) |
| a.append(segment) |
| assert w.getvalue() == b"".join(a) |
| |
| def test_bytes_writer_truncate() -> None: |
| b = BytesWriter() |
| b.write(b"foobar") |
| b.truncate(6) # No-op |
| assert len(b) == 6 |
| assert b.getvalue() == b"foobar" |
| b.truncate(5) |
| assert len(b) == 5 |
| assert b.getvalue() == b"fooba" |
| b.truncate(0) |
| assert len(b) == 0 |
| assert b.getvalue() == b"" |
| |
| with assertRaises(ValueError): |
| b.truncate(1) |
| |
| b = BytesWriter() |
| b.write(b"foobar") |
| with assertRaises(ValueError): |
| b.truncate(-1) |
| |
| def test_write_bytearray() -> None: |
| w = BytesWriter() |
| w.write(bytearray(b"foobar")) |
| w.write(bytearray(b"")) |
| w.write(bytearray(b"\x00\xf8")) |
| assert w.getvalue() == b"foobar\x00\xf8" |
| |
| def test_cast_bytes_writer() -> None: |
| a: Any = BytesWriter() |
| b: BytesWriter = a |
| assert b.getvalue() == b"" |
| |
| a2: Any = "x" |
| with assertRaises(TypeError): |
| b = a2 |
| |
| def test_bytes_writer_wrapper_functions() -> None: |
| cls: Any = BytesWriter |
| b: Any = cls() |
| assert repr(b) == "BytesWriter(b'')" |
| assert len(b) == 0 |
| b.append(ord('a')) |
| b.append(0) |
| b.append(255) |
| b.write(b"foo") |
| b.write(bytearray(b"bar")) |
| assert b.getvalue() == b"a\x00\xfffoobar" |
| assert len(b) == 9 |
| |
| assert b[0] == ord('a') |
| assert b[8] == ord('r') |
| assert b[-1] == ord('r') |
| assert b[-9] == ord('a') |
| |
| b[0] = 215 |
| b[8] = 0 |
| assert b[0] == 215 |
| assert b[8] == 0 |
| b[-1] = 1 |
| assert b[8] == 1 |
| b[-9] = 2 |
| assert b[0] == 2 |
| |
| assert isinstance(b, cls) |
| |
| with assertRaises(TypeError): |
| b.append("x") |
| |
| with assertRaises(TypeError): |
| b.write("foo") |
| |
| with assertRaises(TypeError): |
| b.append(256) |
| |
| with assertRaises(TypeError): |
| b.append(-1) |
| |
| with assertRaises(IndexError): |
| b[9] |
| with assertRaises(IndexError): |
| b[-10] |
| with assertRaises(IndexError): |
| b[9] = 0 |
| with assertRaises(IndexError): |
| b[-10] = 0 |
| |
| with assertRaises(TypeError): |
| b[0] = -1 |
| with assertRaises(TypeError): |
| b[0] = 256 |
| |
| def test_string_writer_basics() -> None: |
| w = StringWriter() |
| assert w.getvalue() == "" |
| |
| def test_string_writer_repr() -> None: |
| # Kind 1 (ASCII) |
| w = StringWriter() |
| assert repr(w) == "StringWriter('')" |
| w.append(ord('h')) |
| w.append(ord('i')) |
| assert repr(w) == "StringWriter('hi')" |
| |
| # Kind 2 (UCS-2) |
| w2 = StringWriter() |
| w2.append(0x100) |
| w2.append(0x200) |
| assert repr(w2) == "StringWriter('" + chr(0x100) + chr(0x200) + "')" |
| |
| # Kind 4 (UCS-4) |
| w3 = StringWriter() |
| w3.append(0x10000) |
| expected = "StringWriter('" + chr(0x10000) + "')" |
| assert repr(w3) == expected |
| |
| def test_string_writer_repr_escaping() -> None: |
| # Kind 1: Test escaping of newline, nul, tab, backslash |
| w = StringWriter() |
| w.append(ord('a')) |
| w.append(ord('\n')) |
| w.append(0) |
| w.append(ord('\t')) |
| w.append(ord('\\')) |
| assert repr(w) == "StringWriter('a\\n\\x00\\t\\\\')" |
| |
| # Kind 2: escaping with UCS-2 |
| w2 = StringWriter() |
| w2.append(0x100) |
| w2.append(ord('\n')) |
| assert repr(w2) == "StringWriter('" + chr(0x100) + "\\n')" |
| |
| # Kind 4: escaping with UCS-4 |
| w3 = StringWriter() |
| w3.append(0x10000) |
| w3.append(0) |
| assert repr(w3) == "StringWriter('" + chr(0x10000) + "\\x00')" |
| |
| def test_string_writer_len() -> None: |
| # Kind 1 (ASCII) |
| w = StringWriter() |
| assert len(w) == 0 |
| w.append(ord('a')) |
| assert len(w) == 1 |
| w.append(ord('b')) |
| w.append(ord('c')) |
| assert len(w) == 3 |
| |
| # Kind 2 (UCS-2) |
| w2 = StringWriter() |
| w2.append(0x100) |
| assert len(w2) == 1 |
| for i in range(10): |
| w2.append(0x200 + i) |
| assert len(w2) == 11 |
| |
| # Kind 4 (UCS-4) |
| w3 = StringWriter() |
| w3.append(0x10000) |
| assert len(w3) == 1 |
| w3.append(0x10001) |
| w3.append(0x10002) |
| assert len(w3) == 3 |
| |
| # Test len after growing buffer |
| w4 = StringWriter() |
| for i in range(500): |
| w4.append(ord('X')) |
| assert len(w4) == 500 |
| |
| def test_string_writer_get_item() -> None: |
| # Kind 1 (ASCII) |
| w = StringWriter() |
| w.append(ord('f')) |
| w.append(ord('o')) |
| w.append(ord('o')) |
| assert w[0 + int()] == ord('f') |
| assert w[1 + int()] == ord('o') |
| assert w[2 + int()] == ord('o') |
| assert w[-1 + int()] == ord('o') |
| assert w[-2 + int()] == ord('o') |
| assert w[-3 + int()] == ord('f') |
| |
| with assertRaises(IndexError): |
| w[3 + int()] |
| with assertRaises(IndexError): |
| w[-4 + int()] |
| with assertRaises(IndexError): |
| w[1 << 50] |
| with assertRaises(IndexError): |
| w[-(1 << 50)] |
| |
| # Kind 2 (UCS-2) |
| w2 = StringWriter() |
| w2.append(0x100) |
| w2.append(0x200) |
| w2.append(0x300) |
| assert w2[0 + int()] == 0x100 |
| assert w2[1 + int()] == 0x200 |
| assert w2[2 + int()] == 0x300 |
| assert w2[-1 + int()] == 0x300 |
| assert w2[-2 + int()] == 0x200 |
| assert w2[-3 + int()] == 0x100 |
| |
| with assertRaises(IndexError): |
| w2[3 + int()] |
| with assertRaises(IndexError): |
| w2[-4 + int()] |
| |
| # Kind 4 (UCS-4) |
| w3 = StringWriter() |
| w3.append(0x10000) |
| w3.append(0x10001) |
| w3.append(0x10002) |
| assert w3[0 + int()] == 0x10000 |
| assert w3[1 + int()] == 0x10001 |
| assert w3[2 + int()] == 0x10002 |
| assert w3[-1 + int()] == 0x10002 |
| assert w3[-2 + int()] == 0x10001 |
| assert w3[-3 + int()] == 0x10000 |
| |
| with assertRaises(IndexError): |
| w3[3 + int()] |
| with assertRaises(IndexError): |
| w3[-4 + int()] |
| |
| # Test get_item after buffer growth |
| w4 = StringWriter() |
| for i in range(1000): |
| w4.append(ord('a') + (i % 26)) |
| assert w4[0 + int()] == ord('a') |
| assert w4[999 + int()] == ord('a') + (999 % 26) |
| assert w4[500 + int()] == ord('a') + (500 % 26) |
| assert w4[-1 + int()] == ord('a') + (999 % 26) |
| assert w4[-1000 + int()] == ord('a') |
| |
| def test_string_writer_append() -> None: |
| w = StringWriter() |
| w.append(ord('a')) |
| assert w.getvalue() == "a" |
| w.append(0xff) |
| assert w.getvalue() == "a\xff" |
| |
| # Switch kind 1->2 |
| w.append(0x100) |
| assert w.getvalue() == "a\xff\u0100", w.getvalue() |
| w.append(0xffff) |
| assert w.getvalue() == "a\xff\u0100\uffff" |
| |
| # Switch kind 2->4 |
| w.append(0x10000) |
| assert w.getvalue() == "a\xff\u0100\uffff" + chr(0x10000) |
| |
| # Maximum valid Unicode code point (0x10FFFF = 1114111) |
| w2 = StringWriter() |
| w2.append(0x10FFFF) |
| assert w2.getvalue() == chr(0x10FFFF) |
| |
| # Invalid code points |
| w3 = StringWriter() |
| with assertRaises(ValueError, "code point 1114112 is outside valid Unicode range (0-1114111)"): |
| w3.append(0x110000) |
| |
| w4 = StringWriter() |
| with assertRaises(ValueError, "code point -1 is outside valid Unicode range (0-1114111)"): |
| w4.append(-1) |
| |
| w5 = StringWriter() |
| with assertRaises(ValueError, "code point 2097152 is outside valid Unicode range (0-1114111)"): |
| w5.append(0x200000) |
| |
| def test_string_writer_write() -> None: |
| # Kind 1: Write ASCII strings |
| w = StringWriter() |
| w.write("hello") |
| assert w.getvalue() == "hello" |
| w.write(" world") |
| assert w.getvalue() == "hello world" |
| |
| # Write empty string |
| w.write("") |
| assert w.getvalue() == "hello world" |
| |
| # Kind 1 -> Kind 2: Write string with UCS-2 characters |
| w2 = StringWriter() |
| w2.write("abc") |
| assert w2.getvalue() == "abc" |
| w2.write(chr(0x100) + chr(0x200)) |
| assert w2.getvalue() == "abc" + chr(0x100) + chr(0x200) |
| w2.write("xyz") |
| assert w2.getvalue() == "abc" + chr(0x100) + chr(0x200) + "xyz" |
| |
| # Kind 2: Write all UCS-2 |
| w3 = StringWriter() |
| w3.append(0x100) |
| w3.write(chr(0x200) + chr(0x300)) |
| assert w3.getvalue() == chr(0x100) + chr(0x200) + chr(0x300) |
| |
| # Kind 2 -> Kind 4: Write string with UCS-4 characters |
| w4 = StringWriter() |
| w4.write(chr(0x100)) |
| w4.write(chr(0x10000)) |
| assert w4.getvalue() == chr(0x100) + chr(0x10000) |
| |
| # Kind 4: Write mixed |
| w5 = StringWriter() |
| w5.append(0x10000) |
| w5.write("abc") |
| w5.write(chr(0x200)) |
| w5.write(chr(0x10001)) |
| assert w5.getvalue() == chr(0x10000) + "abc" + chr(0x200) + chr(0x10001) |
| |
| # Test with longer strings to trigger buffer growth |
| w6 = StringWriter() |
| for _ in range(100): |
| w6.write("hello") |
| assert w6.getvalue() == "hello" * 100 |
| assert len(w6) == 500 |
| |
| def test_string_writer_append_grow_same_kind() -> None: |
| # Test growing buffer while staying in kind 1 (ASCII) |
| w = StringWriter() |
| # Append enough ASCII characters to grow beyond embedded buffer |
| for i in range(1000): |
| w.append(ord('a') + (i % 26)) |
| assert len(w) == i + 1 |
| result = w.getvalue() |
| assert len(result) == 1000 |
| for i in range(1000): |
| assert result[i] == chr(ord('a') + (i % 26)) |
| |
| # Test growing buffer while staying in kind 2 |
| w2 = StringWriter() |
| w2.append(0x100) # Switch to kind 2 |
| for i in range(1000): |
| w2.append(0x100 + (i % 100)) |
| assert len(w2) == i + 2 |
| result2 = w2.getvalue() |
| assert len(result2) == 1001 |
| assert result2[0] == chr(0x100) |
| for i in range(1000): |
| assert result2[i + 1] == chr(0x100 + (i % 100)) |
| |
| # Test growing buffer while staying in kind 4 |
| w3 = StringWriter() |
| w3.append(0x10000) # Switch to kind 4 |
| for i in range(500): |
| w3.append(0x10000 + (i % 100)) |
| assert len(w3) == i + 2 |
| result3 = w3.getvalue() |
| assert len(result3) == 501 |
| assert result3[0] == chr(0x10000) |
| for i in range(500): |
| assert result3[i + 1] == chr(0x10000 + (i % 100)) |
| |
| def test_string_writer_append_grow_and_switch_kind() -> None: |
| # Test growing buffer AND switching from kind 1 to kind 2 |
| w = StringWriter() |
| # Fill with ASCII to grow buffer |
| for i in range(500): |
| w.append(ord('A')) |
| assert len(w) == 500 |
| # Now append non-ASCII that requires kind 2, triggering both grow and kind switch |
| for i in range(500): |
| w.append(0x100 + i) |
| result = w.getvalue() |
| assert len(result) == 1000 |
| for i in range(500): |
| assert result[i] == 'A' |
| for i in range(500): |
| assert result[500 + i] == chr(0x100 + i) |
| |
| # Test growing buffer AND switching from kind 2 to kind 4 |
| w2 = StringWriter() |
| w2.append(0x100) # Switch to kind 2 |
| # Fill with kind 2 characters to grow buffer |
| for i in range(300): |
| w2.append(0x200 + (i % 100)) |
| assert len(w2) == 301 |
| # Now append characters that require kind 4, triggering both grow and kind switch |
| for i in range(300): |
| w2.append(0x10000 + i) |
| result2 = w2.getvalue() |
| assert len(result2) == 601 |
| assert result2[0] == chr(0x100) |
| for i in range(300): |
| assert result2[1 + i] == chr(0x200 + (i % 100)) |
| for i in range(300): |
| assert result2[301 + i] == chr(0x10000 + i) |
| |
| # Test switching kind 1->4 with buffer growth |
| w3 = StringWriter() |
| for i in range(300): |
| w3.append(ord('X')) |
| # Jump directly to kind 4 |
| w3.append(0x10000) |
| result3 = w3.getvalue() |
| assert len(result3) == 301 |
| for i in range(300): |
| assert result3[i] == 'X' |
| assert result3[300] == chr(0x10000) |
| |
| def test_string_writer_wrapper_functions() -> None: |
| cls: Any = StringWriter |
| s: Any = cls() |
| assert repr(s) == "StringWriter('')" |
| assert len(s) == 0 |
| s.append(ord('a')) |
| s.append(0x100) |
| s.append(0x10000) |
| s.write("foo") |
| assert s.getvalue() == "a" + chr(0x100) + chr(0x10000) + "foo" |
| assert len(s) == 6 |
| |
| assert s[0] == ord('a') |
| assert s[1] == 0x100 |
| assert s[2] == 0x10000 |
| assert s[-1] == ord('o') |
| assert s[-6] == ord('a') |
| |
| assert isinstance(s, cls) |
| |
| with assertRaises(TypeError): |
| s.append("x") |
| |
| with assertRaises(TypeError): |
| s.write(b"foo") |
| |
| with assertRaises(ValueError): |
| s.append(0x110000) |
| |
| with assertRaises(ValueError): |
| s.append(-1) |
| |
| with assertRaises(IndexError): |
| s[6] |
| with assertRaises(IndexError): |
| s[-7] |
| |
| [case testStringsFeaturesNotAvailableInNonExperimentalBuild_librt] |
| # This also ensures librt.strings can be built without experimental features |
| import librt.strings |
| |
| def test_bytes_writer_not_available() -> None: |
| assert not hasattr(librt.strings, "BytesWriter") |