| import sys | |
| import textwrap | |
| import unittest | |
| from test import support | |
| from test.support import import_helper | |
| from test.support import threading_helper | |
| from test.support.script_helper import assert_python_failure | |
| from threading import Thread | |
| try: | |
| import ctypes | |
| except ImportError: | |
| ctypes = None | |
| # Skip the test if one these modules is not available | |
| _testcapi = import_helper.import_module('_testcapi') | |
| _testlimitedcapi = import_helper.import_module('_testlimitedcapi') | |
| _testinternalcapi = import_helper.import_module('_testinternalcapi') | |
| from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, SIZEOF_WCHAR_T | |
| MAX_UNICODE = _testinternalcapi._Py_MAX_UNICODE | |
| # The first invalid character after MAX_UNICODE | |
| INVALID_CHAR = MAX_UNICODE + 1 | |
| # Maximum invalid character which fits into 32-bit Py_UCS4 | |
| MAX_INVALID_CHAR = 0xFFFF_FFFF | |
| NULL = None | |
| USED_STR_ERROR = 'Cannot modify a string currently used' | |
| class Str(str): | |
| pass | |
| class StrLike: | |
| def __init__(self, value): | |
| self.value = value | |
| def __str__(self): | |
| return self.value | |
| PyUnicode_1BYTE_KIND = 1 | |
| PyUnicode_2BYTE_KIND = 2 | |
| PyUnicode_4BYTE_KIND = 4 | |
| SSTATE_NOT_INTERNED = 0 | |
| SSTATE_INTERNED_MORTAL = 1 | |
| SSTATE_INTERNED_IMMORTAL = 2 | |
| SSTATE_INTERNED_IMMORTAL_STATIC = 3 | |
| def assert_invalid_string(testcase, text): | |
| # Check that a Unicode string contains invalid characters: | |
| # not in range [U+0000; U+10ffff] | |
| testcase.assertRaises(SystemError, list, text) | |
| class CAPITest(unittest.TestCase): | |
| def _test_check(self, check, *, exact): | |
| # Test PyUnicode_Check() | |
| self.assertTrue(check('')) | |
| self.assertTrue(check('abc')) | |
| self.assertEqual(check(Str('abc')), 0 if exact else 1) | |
| self.assertFalse(check(StrLike('abc'))) | |
| self.assertFalse(check(b'abc')) | |
| self.assertFalse(check(3)) | |
| self.assertFalse(check([])) | |
| self.assertFalse(check(object())) | |
| # CRASHES check(NULL) | |
| def test_check(self): | |
| # Test PyUnicode_Check() | |
| self._test_check(_testlimitedcapi.unicode_check, exact=False) | |
| def test_checkexact(self): | |
| # Test PyUnicode_CheckExact() | |
| self._test_check(_testlimitedcapi.unicode_checkexact, exact=True) | |
| def assert_is_mutable(self, result, refcnt): | |
| # Check that result is a "mutable" Unicode string | |
| self.assertEqual(refcnt, 1) | |
| self.assertFalse(sys._is_immortal(result)) | |
| def assert_is_empty_singleton(self, result): | |
| # Check that result is the empty string singleton | |
| self.assertEqual(result, '') | |
| self.assertTrue(sys._is_immortal(result)) | |
| def test_new(self): | |
| """Test PyUnicode_New()""" | |
| _unicode_new = _testcapi.unicode_new | |
| def new(size, maxchar): | |
| result = _unicode_new(size, maxchar) | |
| if size != 0: | |
| self.assert_is_mutable(result, sys.getrefcount(result)) | |
| else: | |
| self.assert_is_empty_singleton(result) | |
| return result | |
| for maxchar in 0, 0x61, 0xa1, 0x4f60, 0x1f600, 0x10ffff: | |
| self.assertEqual(new(0, maxchar), '') | |
| self.assertEqual(new(5, maxchar), chr(maxchar)*5) | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX, maxchar) | |
| self.assertEqual(new(0, INVALID_CHAR), '') | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX//2, 0x4f60) | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX//2+1, 0x4f60) | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX//2, 0x1f600) | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX//2+1, 0x1f600) | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX//4, 0x1f600) | |
| self.assertRaises(MemoryError, new, PY_SSIZE_T_MAX//4+1, 0x1f600) | |
| self.assertRaises(SystemError, new, 5, INVALID_CHAR) | |
| self.assertRaises(SystemError, new, -1, 0) | |
| self.assertRaises(SystemError, new, PY_SSIZE_T_MIN, 0) | |
| def test_fill(self): | |
| """Test PyUnicode_Fill()""" | |
| fill = _testcapi.unicode_fill | |
| strings = [ | |
| # all strings have exactly 5 characters | |
| 'abcde', '\xa1\xa2\xa3\xa4\xa5', | |
| '\u4f60\u597d\u4e16\u754c\uff01', | |
| '\U0001f600\U0001f601\U0001f602\U0001f603\U0001f604' | |
| ] | |
| chars = [0x78, 0xa9, 0x20ac, 0x1f638] | |
| for idx, fill_char in enumerate(chars): | |
| # wide -> narrow: exceed maxchar limitation | |
| for to in strings[:idx]: | |
| self.assertRaises(ValueError, fill, to, 0, 0, fill_char) | |
| for to in strings[idx:]: | |
| for start in [*range(7), PY_SSIZE_T_MAX]: | |
| for length in [*range(-1, 7 - start), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]: | |
| filled = max(min(length, 5 - start), 0) | |
| if filled == 5 and to != strings[idx]: | |
| # narrow -> wide | |
| # Tests omitted since this creates invalid strings. | |
| continue | |
| expected = to[:start] + chr(fill_char) * filled + to[start + filled:] | |
| self.assertEqual(fill(to, start, length, fill_char), | |
| (expected, filled)) | |
| # A string with 2 references cannot be modified | |
| with self.assertRaisesRegex(SystemError, USED_STR_ERROR): | |
| fill('abc', 0, 3, ord('x'), incref=True) | |
| s = strings[0] | |
| self.assertRaises(IndexError, fill, s, -1, 0, 0x78) | |
| self.assertRaises(IndexError, fill, s, PY_SSIZE_T_MIN, 0, 0x78) | |
| self.assertRaises(ValueError, fill, s, 0, 0, INVALID_CHAR) | |
| self.assertRaises(SystemError, fill, b'abc', 0, 0, 0x78) | |
| self.assertRaises(SystemError, fill, [], 0, 0, 0x78) | |
| # CRASHES fill(s, 0, NULL, 0, 0) | |
| # CRASHES fill(NULL, 0, 0, 0x78) | |
| # TODO: Test PyUnicode_Fill() with non-modifiable unicode. | |
| def _test_writechar(self, writechar, *, check): | |
| strings = [ | |
| # one string for every kind | |
| 'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16', | |
| '\U0001f600\U0001f601\U0001f602' | |
| ] | |
| # one character for every kind + out of range code | |
| chars = [0x78, 0xa9, 0x20ac, 0x1f638, INVALID_CHAR] | |
| for i, s in enumerate(strings): | |
| for j, c in enumerate(chars): | |
| if j <= i: | |
| self.assertEqual(writechar(s, 1, c), | |
| (s[:1] + chr(c) + s[2:], 0)) | |
| elif check: | |
| self.assertRaises(ValueError, writechar, s, 1, c) | |
| if check: | |
| self.assertRaises(IndexError, writechar, 'abc', 3, 0x78) | |
| self.assertRaises(IndexError, writechar, 'abc', -1, 0x78) | |
| self.assertRaises(IndexError, writechar, 'abc', PY_SSIZE_T_MAX, 0x78) | |
| self.assertRaises(IndexError, writechar, 'abc', PY_SSIZE_T_MIN, 0x78) | |
| self.assertRaises(TypeError, writechar, b'abc', 0, 0x78) | |
| self.assertRaises(TypeError, writechar, [], 0, 0x78) | |
| # CRASHES writechar(NULL, 0, 0x78) | |
| # TODO: Test PyUnicode_WriteChar() with non-modifiable and legacy | |
| # unicode. | |
| def test_writechar(self): | |
| """Test PyUnicode_WriteChar()""" | |
| writechar = _testlimitedcapi.unicode_writechar | |
| self._test_writechar(writechar, check=True) | |
| # A string with 2 references cannot be modified | |
| with self.assertRaisesRegex(SystemError, USED_STR_ERROR): | |
| writechar('abc', 1, ord('x'), incref=True) | |
| def test_write_macro(self): | |
| """Test PyUnicode_WRITE()""" | |
| self._test_writechar(_testcapi.unicode_write, check=False) | |
| def test_resize(self): | |
| """Test PyUnicode_Resize()""" | |
| _resize = _testlimitedcapi.unicode_resize | |
| resize_null = _testlimitedcapi.unicode_resize_null | |
| def resize(s, length, new=True, compute_hash=False): | |
| if s is not NULL and isinstance(s, str): | |
| old_length = len(s) | |
| else: | |
| old_length = 0 | |
| result, int_result, refcnt, is_new_obj = _resize(s, length, | |
| new, compute_hash) | |
| self.assertEqual(int_result, 0) | |
| if length == old_length: | |
| # Return the same object unchanged | |
| self.assertFalse(is_new_obj) | |
| elif length == 0: | |
| # Get the empty Unicode string | |
| self.assert_is_empty_singleton(result) | |
| self.assertTrue(is_new_obj) | |
| elif (not new) or compute_hash: | |
| # Get a fresh copy | |
| self.assert_is_mutable(result, refcnt) | |
| self.assertTrue(is_new_obj) | |
| else: | |
| # In-size replace can return the same address, or not. | |
| # So 'is_new_obj' cannot be tested. | |
| self.assert_is_mutable(result, refcnt) | |
| return result | |
| strings = [ | |
| # all strings have exactly 3 characters | |
| 'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16', | |
| '\U0001f600\U0001f601\U0001f602' | |
| ] | |
| for new in (True, False): | |
| for compute_hash in (True, False): | |
| for s in strings: | |
| with self.subTest(new=new, compute_hash=compute_hash, s=s): | |
| self.assertEqual(resize(s, 3, new, compute_hash), | |
| s) | |
| self.assertEqual(resize(s, 2, new, compute_hash), | |
| s[:2]) | |
| self.assertEqual(resize(s, 4, new, compute_hash), | |
| s + '\0') | |
| self.assertEqual(resize(s, 10, new, compute_hash), | |
| s + '\0'*7) | |
| self.assertEqual(resize(s, 0, new, compute_hash), | |
| '') | |
| with self.assertRaises(MemoryError): | |
| resize(s, PY_SSIZE_T_MAX, new, compute_hash) | |
| with self.assertRaises(SystemError): | |
| resize(s, -1, new, compute_hash) | |
| with self.assertRaises(SystemError): | |
| resize(s, PY_SSIZE_T_MIN, new, compute_hash) | |
| self.assertRaises(SystemError, resize, b'abc', 0) | |
| self.assertRaises(SystemError, resize, [], 0) | |
| self.assertRaises(SystemError, resize, NULL, 0) | |
| # TODO: Test PyUnicode_Resize() with non-modifiable and legacy unicode | |
| # and with NULL as the address. | |
| # Test PyUnicode_Resize(NULL, length) | |
| self.assertRaises(SystemError, resize_null, 0) | |
| self.assertRaises(SystemError, resize_null, 123) | |
| def test_append(self): | |
| """Test PyUnicode_Append()""" | |
| append = _testlimitedcapi.unicode_append | |
| strings = [ | |
| 'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16', | |
| '\U0001f600\U0001f601\U0001f602' | |
| ] | |
| for left in strings: | |
| left = left[::-1] | |
| for right in strings: | |
| expected = left + right | |
| self.assertEqual(append(left, right), expected) | |
| self.assertRaises(SystemError, append, 'abc', b'abc') | |
| self.assertRaises(SystemError, append, b'abc', 'abc') | |
| self.assertRaises(SystemError, append, b'abc', b'abc') | |
| self.assertRaises(SystemError, append, 'abc', []) | |
| self.assertRaises(SystemError, append, [], 'abc') | |
| self.assertRaises(SystemError, append, [], []) | |
| self.assertRaises(SystemError, append, NULL, 'abc') | |
| self.assertRaises(SystemError, append, 'abc', NULL) | |
| # TODO: Test PyUnicode_Append() with modifiable unicode | |
| # and with NULL as the address. | |
| # TODO: Check reference counts. | |
| def test_appendanddel(self): | |
| """Test PyUnicode_AppendAndDel()""" | |
| appendanddel = _testlimitedcapi.unicode_appendanddel | |
| strings = [ | |
| 'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16', | |
| '\U0001f600\U0001f601\U0001f602' | |
| ] | |
| for left in strings: | |
| left = left[::-1] | |
| for right in strings: | |
| self.assertEqual(appendanddel(left, right), left + right) | |
| self.assertRaises(SystemError, appendanddel, 'abc', b'abc') | |
| self.assertRaises(SystemError, appendanddel, b'abc', 'abc') | |
| self.assertRaises(SystemError, appendanddel, b'abc', b'abc') | |
| self.assertRaises(SystemError, appendanddel, 'abc', []) | |
| self.assertRaises(SystemError, appendanddel, [], 'abc') | |
| self.assertRaises(SystemError, appendanddel, [], []) | |
| self.assertRaises(SystemError, appendanddel, NULL, 'abc') | |
| self.assertRaises(SystemError, appendanddel, 'abc', NULL) | |
| # TODO: Test PyUnicode_AppendAndDel() with modifiable unicode | |
| # and with NULL as the address. | |
| # TODO: Check reference counts. | |
| def test_fromstringandsize(self): | |
| """Test PyUnicode_FromStringAndSize()""" | |
| fromstringandsize = _testlimitedcapi.unicode_fromstringandsize | |
| self.assertEqual(fromstringandsize(b'abc'), 'abc') | |
| self.assertEqual(fromstringandsize(b'abc', 2), 'ab') | |
| self.assertEqual(fromstringandsize(b'abc\0def'), 'abc\0def') | |
| self.assertEqual(fromstringandsize(b'\xc2\xa1\xc2\xa2'), '\xa1\xa2') | |
| self.assertEqual(fromstringandsize(b'\xe4\xbd\xa0'), '\u4f60') | |
| self.assertEqual(fromstringandsize(b'\xf0\x9f\x98\x80'), '\U0001f600') | |
| self.assertRaises(UnicodeDecodeError, fromstringandsize, b'\xc2\xa1', 1) | |
| self.assertRaises(UnicodeDecodeError, fromstringandsize, b'\xa1', 1) | |
| self.assertEqual(fromstringandsize(b'', 0), '') | |
| self.assertEqual(fromstringandsize(NULL, 0), '') | |
| self.assertRaises(MemoryError, fromstringandsize, b'abc', PY_SSIZE_T_MAX) | |
| self.assertRaises(SystemError, fromstringandsize, b'abc', -1) | |
| self.assertRaises(SystemError, fromstringandsize, b'abc', PY_SSIZE_T_MIN) | |
| self.assertRaises(SystemError, fromstringandsize, NULL, -1) | |
| self.assertRaises(SystemError, fromstringandsize, NULL, PY_SSIZE_T_MIN) | |
| self.assertRaises(SystemError, fromstringandsize, NULL, 3) | |
| self.assertRaises(SystemError, fromstringandsize, NULL, PY_SSIZE_T_MAX) | |
| def test_fromstring(self): | |
| """Test PyUnicode_FromString()""" | |
| fromstring = _testlimitedcapi.unicode_fromstring | |
| self.assertEqual(fromstring(b'abc'), 'abc') | |
| self.assertEqual(fromstring(b'\xc2\xa1\xc2\xa2'), '\xa1\xa2') | |
| self.assertEqual(fromstring(b'\xe4\xbd\xa0'), '\u4f60') | |
| self.assertEqual(fromstring(b'\xf0\x9f\x98\x80'), '\U0001f600') | |
| self.assertRaises(UnicodeDecodeError, fromstring, b'\xc2') | |
| self.assertRaises(UnicodeDecodeError, fromstring, b'\xa1') | |
| self.assertEqual(fromstring(b''), '') | |
| # CRASHES fromstring(NULL) | |
| def test_fromkindanddata(self): | |
| """Test PyUnicode_FromKindAndData()""" | |
| fromkindanddata = _testcapi.unicode_fromkindanddata | |
| strings = [ | |
| 'abcde', '\xa1\xa2\xa3\xa4\xa5', | |
| '\u4f60\u597d\u4e16\u754c\uff01', | |
| '\U0001f600\U0001f601\U0001f602\U0001f603\U0001f604' | |
| ] | |
| enc1 = 'latin1' | |
| for s in strings[:2]: | |
| self.assertEqual(fromkindanddata(1, s.encode(enc1)), s) | |
| enc2 = 'utf-16le' if sys.byteorder == 'little' else 'utf-16be' | |
| for s in strings[:3]: | |
| self.assertEqual(fromkindanddata(2, s.encode(enc2)), s) | |
| enc4 = 'utf-32le' if sys.byteorder == 'little' else 'utf-32be' | |
| for s in strings: | |
| self.assertEqual(fromkindanddata(4, s.encode(enc4)), s) | |
| self.assertEqual(fromkindanddata(2, '\U0001f600'.encode(enc2)), | |
| '\ud83d\ude00') | |
| for kind in 1, 2, 4: | |
| self.assertEqual(fromkindanddata(kind, b''), '') | |
| self.assertEqual(fromkindanddata(kind, b'\0'*kind), '\0') | |
| self.assertEqual(fromkindanddata(kind, NULL, 0), '') | |
| for kind in -1, 0, 3, 5, 8: | |
| self.assertRaises(SystemError, fromkindanddata, kind, b'') | |
| self.assertRaises(ValueError, fromkindanddata, 1, b'abc', -1) | |
| self.assertRaises(ValueError, fromkindanddata, 1, b'abc', PY_SSIZE_T_MIN) | |
| self.assertRaises(ValueError, fromkindanddata, 1, NULL, -1) | |
| self.assertRaises(ValueError, fromkindanddata, 1, NULL, PY_SSIZE_T_MIN) | |
| # CRASHES fromkindanddata(1, NULL, 1) | |
| # Test invalid UCS-4 string. Create an invalid string in release mode, | |
| # or raise SystemError in debug mode. | |
| built_with_assert = support.built_with_c_assertions() | |
| for invalid_char in (INVALID_CHAR, MAX_INVALID_CHAR): | |
| with self.subTest(invalid_char=invalid_char): | |
| # Test single character | |
| ucs4_char = invalid_char.to_bytes(4, byteorder=sys.byteorder) | |
| self.assertRaises(SystemError, fromkindanddata, 4, ucs4_char) | |
| # Test multiple characters | |
| s = 'valid'.encode(enc4) + ucs4_char | |
| if support.Py_DEBUG: | |
| self.assertRaises(SystemError, fromkindanddata, 4, s) | |
| elif not built_with_assert : | |
| result = fromkindanddata(4, s) | |
| assert_invalid_string(self, result) | |
| else: | |
| # PyUnicode_FromKindAndData() fails with an assertion error | |
| pass | |
| def test_substring(self): | |
| """Test PyUnicode_Substring()""" | |
| substring = _testlimitedcapi.unicode_substring | |
| strings = [ | |
| 'ab', 'ab\xa1\xa2', | |
| 'ab\xa1\xa2\u4f60\u597d', | |
| 'ab\xa1\xa2\u4f60\u597d\U0001f600\U0001f601' | |
| ] | |
| for s in strings: | |
| for start in [*range(0, len(s) + 2), PY_SSIZE_T_MAX]: | |
| for end in [*range(max(start-1, 0), len(s) + 2), PY_SSIZE_T_MAX]: | |
| self.assertEqual(substring(s, start, end), s[start:end]) | |
| self.assertRaises(IndexError, substring, 'abc', -1, 0) | |
| self.assertRaises(IndexError, substring, 'abc', PY_SSIZE_T_MIN, 0) | |
| self.assertRaises(IndexError, substring, 'abc', 0, -1) | |
| self.assertRaises(IndexError, substring, 'abc', 0, PY_SSIZE_T_MIN) | |
| # CRASHES substring(b'abc', 0, 0) | |
| # CRASHES substring([], 0, 0) | |
| # CRASHES substring(NULL, 0, 0) | |
| def _test_getlength(self, getlength, *, check): | |
| for s in ['', 'abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600', | |
| 'a\ud800b\udfffc', '\ud834\udd1e']: | |
| self.assertEqual(getlength(s), len(s)) | |
| if check: | |
| self.assertRaises(TypeError, getlength, b'abc') | |
| self.assertRaises(TypeError, getlength, []) | |
| # CRASHES getlength(NULL) | |
| def test_getlength(self): | |
| """Test PyUnicode_GetLength()""" | |
| self._test_getlength(_testlimitedcapi.unicode_getlength, check=True) | |
| def test_getlength_macro(self): | |
| """Test PyUnicode_GET_LENGTH() macro""" | |
| self._test_getlength(_testcapi.unicode_getlength_macro, check=False) | |
| def _test_read_char(self, readchar, *, check, read_null_char): | |
| for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600', | |
| 'a\ud800b\udfffc', '\ud834\udd1e']: | |
| for i, c in enumerate(s): | |
| self.assertEqual(readchar(s, i), ord(c)) | |
| if read_null_char: | |
| self.assertEqual(readchar(s, len(s)), 0) | |
| elif check: | |
| self.assertRaises(IndexError, readchar, s, len(s)) | |
| if check: | |
| self.assertRaises(IndexError, readchar, s, PY_SSIZE_T_MAX) | |
| self.assertRaises(IndexError, readchar, s, -1) | |
| self.assertRaises(IndexError, readchar, s, PY_SSIZE_T_MIN) | |
| else: | |
| # CRASHES on above test | |
| pass | |
| if check: | |
| # invalid type | |
| self.assertRaises(TypeError, readchar, b'abc', 0) | |
| self.assertRaises(TypeError, readchar, [], 0) | |
| # CRASHES readchar(NULL, 0) | |
| else: | |
| # CRASHES on above test | |
| pass | |
| def test_readchar(self): | |
| """Test PyUnicode_ReadChar()""" | |
| self._test_read_char(_testlimitedcapi.unicode_readchar, | |
| check=True, read_null_char=False) | |
| def test_read_char_macro(self): | |
| """Test PyUnicode_READ_CHAR() macro""" | |
| self._test_read_char(_testcapi.unicode_read_char, | |
| check=False, read_null_char=True) | |
| def test_read_macro(self): | |
| """Test PyUnicode_READ() macro""" | |
| self._test_read_char(_testcapi.unicode_read, | |
| check=False, read_null_char=True) | |
| def test_fromobject(self): | |
| """Test PyUnicode_FromObject()""" | |
| fromobject = _testlimitedcapi.unicode_fromobject | |
| for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600', | |
| 'a\ud800b\udfffc', '\ud834\udd1e']: | |
| self.assertEqual(fromobject(s), s) | |
| o = Str(s) | |
| s2 = fromobject(o) | |
| self.assertEqual(s2, s) | |
| self.assertIs(type(s2), str) | |
| self.assertIsNot(s2, s) | |
| self.assertRaises(TypeError, fromobject, b'abc') | |
| self.assertRaises(TypeError, fromobject, []) | |
| # CRASHES fromobject(NULL) | |
| @unittest.skipIf(ctypes is None, 'need ctypes') | |
| def test_from_format(self): | |
| """Test PyUnicode_FromFormat()""" | |
| # Length modifiers "j" and "t" are not tested here because ctypes does | |
| # not expose types for intmax_t and ptrdiff_t. | |
| # _testlimitedcapi.test_string_from_format() has a wider coverage of all | |
| # formats. | |
| from ctypes import ( | |
| c_char_p, | |
| pythonapi, py_object, sizeof, | |
| c_int, c_long, c_longlong, c_ssize_t, | |
| c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p, | |
| c_wchar, c_wchar_p) | |
| name = "PyUnicode_FromFormat" | |
| _PyUnicode_FromFormat = getattr(pythonapi, name) | |
| _PyUnicode_FromFormat.argtypes = (c_char_p,) | |
| _PyUnicode_FromFormat.restype = py_object | |
| def PyUnicode_FromFormat(format, *args): | |
| cargs = tuple( | |
| py_object(arg) if isinstance(arg, str) else arg | |
| for arg in args) | |
| return _PyUnicode_FromFormat(format, *cargs) | |
| def check_format(expected, format, *args): | |
| text = PyUnicode_FromFormat(format, *args) | |
| self.assertEqual(expected, text) | |
| # ascii format, non-ascii argument | |
| check_format('ascii\x7f=unicode\xe9', | |
| b'ascii\x7f=%U', 'unicode\xe9') | |
| # non-ascii format, ascii argument: ensure that PyUnicode_FromFormatV() | |
| # raises an error | |
| self.assertRaisesRegex(ValueError, | |
| r'^PyUnicode_FromFormatV\(\) expects an ASCII-encoded format ' | |
| 'string, got a non-ASCII byte: 0xe9$', | |
| PyUnicode_FromFormat, b'unicode\xe9=%s', 'ascii') | |
| # test "%c" | |
| check_format('\uabcd', | |
| b'%c', c_int(0xabcd)) | |
| check_format('\U0010ffff', | |
| b'%c', c_int(0x10ffff)) | |
| with self.assertRaises(OverflowError): | |
| PyUnicode_FromFormat(b'%c', c_int(INVALID_CHAR)) | |
| # Issue #18183 | |
| check_format('\U00010000\U00100000', | |
| b'%c%c', c_int(0x10000), c_int(0x100000)) | |
| # test "%" | |
| check_format('%', | |
| b'%%') | |
| check_format('%s', | |
| b'%%s') | |
| check_format('[%]', | |
| b'[%%]') | |
| check_format('%abc', | |
| b'%%%s', b'abc') | |
| # test "%s" with empty string | |
| check_format('x=', | |
| b'x=%s', b'') | |
| check_format('x=', | |
| b'x=%0s', b'') | |
| check_format('x=', | |
| b'x=%.3s', b'') | |
| # truncated string | |
| check_format('abc', | |
| b'%.3s', b'abcdef') | |
| check_format('abc[', | |
| b'%.6s', 'abc[\u20ac]'.encode('utf8')) | |
| check_format('abc[\u20ac', | |
| b'%.7s', 'abc[\u20ac]'.encode('utf8')) | |
| check_format('abc[\ufffd', | |
| b'%.5s', b'abc[\xff]') | |
| check_format('abc[', | |
| b'%.6s', b'abc[\xe2\x82]') | |
| check_format('abc[\ufffd]', | |
| b'%.7s', b'abc[\xe2\x82]') | |
| check_format('abc[\ufffd', | |
| b'%.7s', b'abc[\xe2\x82\0') | |
| check_format(' abc[', | |
| b'%10.6s', 'abc[\u20ac]'.encode('utf8')) | |
| check_format(' abc[\u20ac', | |
| b'%10.7s', 'abc[\u20ac]'.encode('utf8')) | |
| check_format(' abc[\ufffd', | |
| b'%10.5s', b'abc[\xff]') | |
| check_format(' abc[', | |
| b'%10.6s', b'abc[\xe2\x82]') | |
| check_format(' abc[\ufffd]', | |
| b'%10.7s', b'abc[\xe2\x82]') | |
| check_format("'\\u20acABC'", | |
| b'%A', '\u20acABC') | |
| check_format("'\\u20", | |
| b'%.5A', '\u20acABCDEF') | |
| check_format("'\u20acABC'", | |
| b'%R', '\u20acABC') | |
| check_format("'\u20acA", | |
| b'%.3R', '\u20acABCDEF') | |
| check_format('\u20acAB', | |
| b'%.3S', '\u20acABCDEF') | |
| check_format('\u20acAB', | |
| b'%.3U', '\u20acABCDEF') | |
| check_format('\u20acAB', | |
| b'%.3V', '\u20acABCDEF', None) | |
| check_format('abc[', | |
| b'%.6V', None, 'abc[\u20ac]'.encode('utf8')) | |
| check_format('abc[\u20ac', | |
| b'%.7V', None, 'abc[\u20ac]'.encode('utf8')) | |
| check_format('abc[\ufffd', | |
| b'%.5V', None, b'abc[\xff]') | |
| check_format('abc[', | |
| b'%.6V', None, b'abc[\xe2\x82]') | |
| check_format('abc[\ufffd]', | |
| b'%.7V', None, b'abc[\xe2\x82]') | |
| check_format(' abc[', | |
| b'%10.6V', None, 'abc[\u20ac]'.encode('utf8')) | |
| check_format(' abc[\u20ac', | |
| b'%10.7V', None, 'abc[\u20ac]'.encode('utf8')) | |
| check_format(' abc[\ufffd', | |
| b'%10.5V', None, b'abc[\xff]') | |
| check_format(' abc[', | |
| b'%10.6V', None, b'abc[\xe2\x82]') | |
| check_format(' abc[\ufffd]', | |
| b'%10.7V', None, b'abc[\xe2\x82]') | |
| check_format(' abc[\ufffd', | |
| b'%10.7V', None, b'abc[\xe2\x82\0') | |
| # following tests comes from #7330 | |
| # test width modifier and precision modifier with %S | |
| check_format("repr= abc", | |
| b'repr=%5S', 'abc') | |
| check_format("repr=ab", | |
| b'repr=%.2S', 'abc') | |
| check_format("repr= ab", | |
| b'repr=%5.2S', 'abc') | |
| # test width modifier and precision modifier with %R | |
| check_format("repr= 'abc'", | |
| b'repr=%8R', 'abc') | |
| check_format("repr='ab", | |
| b'repr=%.3R', 'abc') | |
| check_format("repr= 'ab", | |
| b'repr=%5.3R', 'abc') | |
| # test width modifier and precision modifier with %A | |
| check_format("repr= 'abc'", | |
| b'repr=%8A', 'abc') | |
| check_format("repr='ab", | |
| b'repr=%.3A', 'abc') | |
| check_format("repr= 'ab", | |
| b'repr=%5.3A', 'abc') | |
| # test width modifier and precision modifier with %s | |
| check_format("repr= abc", | |
| b'repr=%5s', b'abc') | |
| check_format("repr=ab", | |
| b'repr=%.2s', b'abc') | |
| check_format("repr= ab", | |
| b'repr=%5.2s', b'abc') | |
| # test width modifier and precision modifier with %U | |
| check_format("repr= abc", | |
| b'repr=%5U', 'abc') | |
| check_format("repr=ab", | |
| b'repr=%.2U', 'abc') | |
| check_format("repr= ab", | |
| b'repr=%5.2U', 'abc') | |
| # test width modifier and precision modifier with %V | |
| check_format("repr= abc", | |
| b'repr=%5V', 'abc', b'123') | |
| check_format("repr=ab", | |
| b'repr=%.2V', 'abc', b'123') | |
| check_format("repr= ab", | |
| b'repr=%5.2V', 'abc', b'123') | |
| check_format("repr= 123", | |
| b'repr=%5V', None, b'123') | |
| check_format("repr=12", | |
| b'repr=%.2V', None, b'123') | |
| check_format("repr= 12", | |
| b'repr=%5.2V', None, b'123') | |
| # test integer formats (%i, %d, %u, %o, %x, %X) | |
| check_format('010', | |
| b'%03i', c_int(10)) | |
| check_format('0010', | |
| b'%0.4i', c_int(10)) | |
| for conv, signed, value, expected in [ | |
| (b'i', True, -123, '-123'), | |
| (b'd', True, -123, '-123'), | |
| (b'u', False, 123, '123'), | |
| (b'o', False, 0o123, '123'), | |
| (b'x', False, 0xabc, 'abc'), | |
| (b'X', False, 0xabc, 'ABC'), | |
| ]: | |
| for mod, ctype in [ | |
| (b'', c_int if signed else c_uint), | |
| (b'l', c_long if signed else c_ulong), | |
| (b'll', c_longlong if signed else c_ulonglong), | |
| (b'z', c_ssize_t if signed else c_size_t), | |
| ]: | |
| with self.subTest(format=b'%' + mod + conv): | |
| check_format(expected, | |
| b'%' + mod + conv, ctype(value)) | |
| # test long output | |
| min_longlong = -(2 ** (8 * sizeof(c_longlong) - 1)) | |
| max_longlong = -min_longlong - 1 | |
| check_format(str(min_longlong), | |
| b'%lld', c_longlong(min_longlong)) | |
| check_format(str(max_longlong), | |
| b'%lld', c_longlong(max_longlong)) | |
| max_ulonglong = 2 ** (8 * sizeof(c_ulonglong)) - 1 | |
| check_format(str(max_ulonglong), | |
| b'%llu', c_ulonglong(max_ulonglong)) | |
| PyUnicode_FromFormat(b'%p', c_void_p(-1)) | |
| # test padding (width and/or precision) | |
| check_format('123', b'%2i', c_int(123)) | |
| check_format(' 123', b'%10i', c_int(123)) | |
| check_format('0000000123', b'%010i', c_int(123)) | |
| check_format('123 ', b'%-10i', c_int(123)) | |
| check_format('123 ', b'%-010i', c_int(123)) | |
| check_format('123', b'%.2i', c_int(123)) | |
| check_format('0000123', b'%.7i', c_int(123)) | |
| check_format(' 123', b'%10.2i', c_int(123)) | |
| check_format(' 0000123', b'%10.7i', c_int(123)) | |
| check_format('0000000123', b'%010.7i', c_int(123)) | |
| check_format('0000123 ', b'%-10.7i', c_int(123)) | |
| check_format('0000123 ', b'%-010.7i', c_int(123)) | |
| check_format('-123', b'%2i', c_int(-123)) | |
| check_format(' -123', b'%10i', c_int(-123)) | |
| check_format('-000000123', b'%010i', c_int(-123)) | |
| check_format('-123 ', b'%-10i', c_int(-123)) | |
| check_format('-123 ', b'%-010i', c_int(-123)) | |
| check_format('-123', b'%.2i', c_int(-123)) | |
| check_format('-0000123', b'%.7i', c_int(-123)) | |
| check_format(' -123', b'%10.2i', c_int(-123)) | |
| check_format(' -0000123', b'%10.7i', c_int(-123)) | |
| check_format('-000000123', b'%010.7i', c_int(-123)) | |
| check_format('-0000123 ', b'%-10.7i', c_int(-123)) | |
| check_format('-0000123 ', b'%-010.7i', c_int(-123)) | |
| check_format('123', b'%2u', c_uint(123)) | |
| check_format(' 123', b'%10u', c_uint(123)) | |
| check_format('0000000123', b'%010u', c_uint(123)) | |
| check_format('123 ', b'%-10u', c_uint(123)) | |
| check_format('123 ', b'%-010u', c_uint(123)) | |
| check_format('123', b'%.2u', c_uint(123)) | |
| check_format('0000123', b'%.7u', c_uint(123)) | |
| check_format(' 123', b'%10.2u', c_uint(123)) | |
| check_format(' 0000123', b'%10.7u', c_uint(123)) | |
| check_format('0000000123', b'%010.7u', c_uint(123)) | |
| check_format('0000123 ', b'%-10.7u', c_uint(123)) | |
| check_format('0000123 ', b'%-010.7u', c_uint(123)) | |
| check_format('123', b'%2o', c_uint(0o123)) | |
| check_format(' 123', b'%10o', c_uint(0o123)) | |
| check_format('0000000123', b'%010o', c_uint(0o123)) | |
| check_format('123 ', b'%-10o', c_uint(0o123)) | |
| check_format('123 ', b'%-010o', c_uint(0o123)) | |
| check_format('123', b'%.2o', c_uint(0o123)) | |
| check_format('0000123', b'%.7o', c_uint(0o123)) | |
| check_format(' 123', b'%10.2o', c_uint(0o123)) | |
| check_format(' 0000123', b'%10.7o', c_uint(0o123)) | |
| check_format('0000000123', b'%010.7o', c_uint(0o123)) | |
| check_format('0000123 ', b'%-10.7o', c_uint(0o123)) | |
| check_format('0000123 ', b'%-010.7o', c_uint(0o123)) | |
| check_format('abc', b'%2x', c_uint(0xabc)) | |
| check_format(' abc', b'%10x', c_uint(0xabc)) | |
| check_format('0000000abc', b'%010x', c_uint(0xabc)) | |
| check_format('abc ', b'%-10x', c_uint(0xabc)) | |
| check_format('abc ', b'%-010x', c_uint(0xabc)) | |
| check_format('abc', b'%.2x', c_uint(0xabc)) | |
| check_format('0000abc', b'%.7x', c_uint(0xabc)) | |
| check_format(' abc', b'%10.2x', c_uint(0xabc)) | |
| check_format(' 0000abc', b'%10.7x', c_uint(0xabc)) | |
| check_format('0000000abc', b'%010.7x', c_uint(0xabc)) | |
| check_format('0000abc ', b'%-10.7x', c_uint(0xabc)) | |
| check_format('0000abc ', b'%-010.7x', c_uint(0xabc)) | |
| check_format('ABC', b'%2X', c_uint(0xabc)) | |
| check_format(' ABC', b'%10X', c_uint(0xabc)) | |
| check_format('0000000ABC', b'%010X', c_uint(0xabc)) | |
| check_format('ABC ', b'%-10X', c_uint(0xabc)) | |
| check_format('ABC ', b'%-010X', c_uint(0xabc)) | |
| check_format('ABC', b'%.2X', c_uint(0xabc)) | |
| check_format('0000ABC', b'%.7X', c_uint(0xabc)) | |
| check_format(' ABC', b'%10.2X', c_uint(0xabc)) | |
| check_format(' 0000ABC', b'%10.7X', c_uint(0xabc)) | |
| check_format('0000000ABC', b'%010.7X', c_uint(0xabc)) | |
| check_format('0000ABC ', b'%-10.7X', c_uint(0xabc)) | |
| check_format('0000ABC ', b'%-010.7X', c_uint(0xabc)) | |
| # test %A | |
| check_format(r"%A:'abc\xe9\uabcd\U0010ffff'", | |
| b'%%A:%A', 'abc\xe9\uabcd\U0010ffff') | |
| # test %V | |
| check_format('abc', | |
| b'%V', 'abc', b'xyz') | |
| check_format('xyz', | |
| b'%V', None, b'xyz') | |
| # test %ls | |
| check_format('abc', b'%ls', c_wchar_p('abc')) | |
| check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11')) | |
| check_format('\U0001f4bb+\U0001f40d', | |
| b'%ls', c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| check_format(' ab', b'%5.2ls', c_wchar_p('abc')) | |
| check_format(' \u4eba\u6c11', b'%5ls', c_wchar_p('\u4eba\u6c11')) | |
| check_format(' \U0001f4bb+\U0001f40d', | |
| b'%5ls', c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| check_format('\u4eba', b'%.1ls', c_wchar_p('\u4eba\u6c11')) | |
| check_format('\U0001f4bb' if sizeof(c_wchar) > 2 else '\ud83d', | |
| b'%.1ls', c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| check_format('\U0001f4bb+' if sizeof(c_wchar) > 2 else '\U0001f4bb', | |
| b'%.2ls', c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| # test %lV | |
| check_format('abc', | |
| b'%lV', 'abc', c_wchar_p('xyz')) | |
| check_format('xyz', | |
| b'%lV', None, c_wchar_p('xyz')) | |
| check_format('\u4eba\u6c11', | |
| b'%lV', None, c_wchar_p('\u4eba\u6c11')) | |
| check_format('\U0001f4bb+\U0001f40d', | |
| b'%lV', None, c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| check_format(' ab', | |
| b'%5.2lV', None, c_wchar_p('abc')) | |
| check_format(' \u4eba\u6c11', | |
| b'%5lV', None, c_wchar_p('\u4eba\u6c11')) | |
| check_format(' \U0001f4bb+\U0001f40d', | |
| b'%5lV', None, c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| check_format('\u4eba', | |
| b'%.1lV', None, c_wchar_p('\u4eba\u6c11')) | |
| check_format('\U0001f4bb' if sizeof(c_wchar) > 2 else '\ud83d', | |
| b'%.1lV', None, c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| check_format('\U0001f4bb+' if sizeof(c_wchar) > 2 else '\U0001f4bb', | |
| b'%.2lV', None, c_wchar_p('\U0001f4bb+\U0001f40d')) | |
| # test %T | |
| check_format('type: str', | |
| b'type: %T', py_object("abc")) | |
| check_format(f'type: st', | |
| b'type: %.2T', py_object("abc")) | |
| check_format(f'type: str', | |
| b'type: %10T', py_object("abc")) | |
| class LocalType: | |
| pass | |
| obj = LocalType() | |
| fullname = f'{__name__}.{LocalType.__qualname__}' | |
| check_format(f'type: {fullname}', | |
| b'type: %T', py_object(obj)) | |
| fullname_alt = f'{__name__}:{LocalType.__qualname__}' | |
| check_format(f'type: {fullname_alt}', | |
| b'type: %#T', py_object(obj)) | |
| # test %N | |
| check_format('type: str', | |
| b'type: %N', py_object(str)) | |
| check_format(f'type: st', | |
| b'type: %.2N', py_object(str)) | |
| check_format(f'type: str', | |
| b'type: %10N', py_object(str)) | |
| check_format(f'type: {fullname}', | |
| b'type: %N', py_object(type(obj))) | |
| check_format(f'type: {fullname_alt}', | |
| b'type: %#N', py_object(type(obj))) | |
| with self.assertRaisesRegex(TypeError, "%N argument must be a type"): | |
| check_format('type: str', | |
| b'type: %N', py_object("abc")) | |
| # test variable width and precision | |
| check_format(' abc', b'%*s', c_int(5), b'abc') | |
| check_format('ab', b'%.*s', c_int(2), b'abc') | |
| check_format(' ab', b'%*.*s', c_int(5), c_int(2), b'abc') | |
| check_format(' abc', b'%*U', c_int(5), 'abc') | |
| check_format('ab', b'%.*U', c_int(2), 'abc') | |
| check_format(' ab', b'%*.*U', c_int(5), c_int(2), 'abc') | |
| check_format(' ab', b'%*.*V', c_int(5), c_int(2), None, b'abc') | |
| check_format(' ab', b'%*.*lV', c_int(5), c_int(2), | |
| None, c_wchar_p('abc')) | |
| check_format(' 123', b'%*i', c_int(8), c_int(123)) | |
| check_format('00123', b'%.*i', c_int(5), c_int(123)) | |
| check_format(' 00123', b'%*.*i', c_int(8), c_int(5), c_int(123)) | |
| # test %p | |
| # We cannot test the exact result, | |
| # because it returns a hex representation of a C pointer, | |
| # which is going to be different each time. But, we can test the format. | |
| p_format_regex = r'^0x[a-zA-Z0-9]{3,}$' | |
| p_format1 = PyUnicode_FromFormat(b'%p', 'abc') | |
| self.assertIsInstance(p_format1, str) | |
| self.assertRegex(p_format1, p_format_regex) | |
| p_format2 = PyUnicode_FromFormat(b'%p %p', '123456', b'xyz') | |
| self.assertIsInstance(p_format2, str) | |
| self.assertRegex(p_format2, | |
| r'0x[a-zA-Z0-9]{3,} 0x[a-zA-Z0-9]{3,}') | |
| # Extra args are ignored: | |
| p_format3 = PyUnicode_FromFormat(b'%p', '123456', None, b'xyz') | |
| self.assertIsInstance(p_format3, str) | |
| self.assertRegex(p_format3, p_format_regex) | |
| # Test string decode from parameter of %s using utf-8. | |
| # b'\xe4\xba\xba\xe6\xb0\x91' is utf-8 encoded byte sequence of | |
| # '\u4eba\u6c11' | |
| check_format('repr=\u4eba\u6c11', | |
| b'repr=%V', None, b'\xe4\xba\xba\xe6\xb0\x91') | |
| #Test replace error handler. | |
| check_format('repr=abc\ufffd', | |
| b'repr=%V', None, b'abc\xff') | |
| # Issue #33817: empty strings | |
| check_format('', | |
| b'') | |
| check_format('', | |
| b'%s', b'') | |
| # test invalid format strings. these tests are just here | |
| # to check for crashes and should not be considered as specifications | |
| for fmt in (b'%', b'%0', b'%01', b'%.', b'%.1', | |
| b'%0%s', b'%1%s', b'%.%s', b'%.1%s', b'%1abc', | |
| b'%l', b'%ll', b'%z', b'%lls', b'%zs'): | |
| with self.subTest(fmt=fmt): | |
| self.assertRaisesRegex(SystemError, 'invalid format string', | |
| PyUnicode_FromFormat, fmt, b'abc') | |
| self.assertRaisesRegex(SystemError, 'invalid format string', | |
| PyUnicode_FromFormat, b'%+i', c_int(10)) | |
| def test_interninplace(self): | |
| """Test PyUnicode_InternInPlace()""" | |
| interninplace = _testlimitedcapi.unicode_interninplace | |
| s = b'abc'.decode() | |
| r = interninplace(s) | |
| self.assertEqual(r, 'abc') | |
| # CRASHES interninplace(b'abc') | |
| # CRASHES interninplace(NULL) | |
| def test_internfromstring(self): | |
| """Test PyUnicode_InternFromString()""" | |
| internfromstring = _testlimitedcapi.unicode_internfromstring | |
| self.assertEqual(internfromstring(b'abc'), 'abc') | |
| self.assertEqual(internfromstring(b'\xf0\x9f\x98\x80'), '\U0001f600') | |
| self.assertRaises(UnicodeDecodeError, internfromstring, b'\xc2') | |
| self.assertRaises(UnicodeDecodeError, internfromstring, b'\xa1') | |
| self.assertEqual(internfromstring(b''), '') | |
| # CRASHES internfromstring(NULL) | |
| def test_fromwidechar(self): | |
| """Test PyUnicode_FromWideChar()""" | |
| fromwidechar = _testlimitedcapi.unicode_fromwidechar | |
| if SIZEOF_WCHAR_T == 2: | |
| encoding = 'utf-16le' if sys.byteorder == 'little' else 'utf-16be' | |
| elif SIZEOF_WCHAR_T == 4: | |
| encoding = 'utf-32le' if sys.byteorder == 'little' else 'utf-32be' | |
| for s in '', 'abc', '\xa1\xa2', '\u4f60', '\U0001f600': | |
| b = s.encode(encoding) | |
| self.assertEqual(fromwidechar(b), s) | |
| self.assertEqual(fromwidechar(b + b'\0'*SIZEOF_WCHAR_T, -1), s) | |
| for s in '\ud83d', '\ude00': | |
| b = s.encode(encoding, 'surrogatepass') | |
| self.assertEqual(fromwidechar(b), s) | |
| self.assertEqual(fromwidechar(b + b'\0'*SIZEOF_WCHAR_T, -1), s) | |
| self.assertEqual(fromwidechar('abc'.encode(encoding), 2), 'ab') | |
| if SIZEOF_WCHAR_T == 2: | |
| self.assertEqual(fromwidechar('a\U0001f600'.encode(encoding), 2), 'a\ud83d') | |
| self.assertRaises(SystemError, fromwidechar, b'\0'*SIZEOF_WCHAR_T, -2) | |
| self.assertEqual(fromwidechar(NULL, 0), '') | |
| self.assertRaises(SystemError, fromwidechar, NULL, 1) | |
| self.assertRaises(SystemError, fromwidechar, NULL, PY_SSIZE_T_MAX) | |
| self.assertRaises(SystemError, fromwidechar, NULL, -1) | |
| self.assertRaises(SystemError, fromwidechar, NULL, -2) | |
| self.assertRaises(SystemError, fromwidechar, NULL, PY_SSIZE_T_MIN) | |
| # The following tests are skipped since they rely on undefined behavior | |
| #self.assertRaises(MemoryError, fromwidechar, b'', PY_SSIZE_T_MAX) | |
| #self.assertRaises(SystemError, fromwidechar, b'\0'*SIZEOF_WCHAR_T, PY_SSIZE_T_MIN) | |
| def test_aswidechar(self): | |
| """Test PyUnicode_AsWideChar()""" | |
| unicode_aswidechar = _testlimitedcapi.unicode_aswidechar | |
| unicode_aswidechar_null = _testlimitedcapi.unicode_aswidechar_null | |
| wchar, size = unicode_aswidechar('abcdef', 2) | |
| self.assertEqual(size, 2) | |
| self.assertEqual(wchar, 'ab') | |
| wchar, size = unicode_aswidechar('abc', 3) | |
| self.assertEqual(size, 3) | |
| self.assertEqual(wchar, 'abc') | |
| self.assertEqual(unicode_aswidechar_null('abc', 10), 4) | |
| self.assertEqual(unicode_aswidechar_null('abc', 0), 4) | |
| wchar, size = unicode_aswidechar('abc', 4) | |
| self.assertEqual(size, 3) | |
| self.assertEqual(wchar, 'abc\0') | |
| wchar, size = unicode_aswidechar('abc', 10) | |
| self.assertEqual(size, 3) | |
| self.assertEqual(wchar, 'abc\0') | |
| wchar, size = unicode_aswidechar('abc\0def', 20) | |
| self.assertEqual(size, 7) | |
| self.assertEqual(wchar, 'abc\0def\0') | |
| self.assertEqual(unicode_aswidechar_null('abc\0def', 20), 8) | |
| nonbmp = chr(0x10ffff) | |
| if SIZEOF_WCHAR_T == 2: | |
| nchar = 2 | |
| else: # SIZEOF_WCHAR_T == 4 | |
| nchar = 1 | |
| wchar, size = unicode_aswidechar(nonbmp, 10) | |
| self.assertEqual(size, nchar) | |
| self.assertEqual(wchar, nonbmp + '\0') | |
| self.assertEqual(unicode_aswidechar_null(nonbmp, 10), nchar + 1) | |
| self.assertRaises(TypeError, unicode_aswidechar, b'abc', 10) | |
| self.assertRaises(TypeError, unicode_aswidechar, [], 10) | |
| self.assertRaises(SystemError, unicode_aswidechar, NULL, 10) | |
| self.assertRaises(TypeError, unicode_aswidechar_null, b'abc', 10) | |
| self.assertRaises(TypeError, unicode_aswidechar_null, [], 10) | |
| self.assertRaises(SystemError, unicode_aswidechar_null, NULL, 10) | |
| def test_aswidecharstring(self): | |
| """Test PyUnicode_AsWideCharString()""" | |
| unicode_aswidecharstring = _testlimitedcapi.unicode_aswidecharstring | |
| unicode_aswidecharstring_null = _testlimitedcapi.unicode_aswidecharstring_null | |
| wchar, size = unicode_aswidecharstring('abc') | |
| self.assertEqual(size, 3) | |
| self.assertEqual(wchar, 'abc\0') | |
| self.assertEqual(unicode_aswidecharstring_null('abc'), 'abc') | |
| wchar, size = unicode_aswidecharstring('abc\0def') | |
| self.assertEqual(size, 7) | |
| self.assertEqual(wchar, 'abc\0def\0') | |
| self.assertRaises(ValueError, unicode_aswidecharstring_null, 'abc\0def') | |
| nonbmp = chr(0x10ffff) | |
| if SIZEOF_WCHAR_T == 2: | |
| nchar = 2 | |
| else: # SIZEOF_WCHAR_T == 4 | |
| nchar = 1 | |
| wchar, size = unicode_aswidecharstring(nonbmp) | |
| self.assertEqual(size, nchar) | |
| self.assertEqual(wchar, nonbmp + '\0') | |
| self.assertEqual(unicode_aswidecharstring_null(nonbmp), nonbmp) | |
| self.assertRaises(TypeError, unicode_aswidecharstring, b'abc') | |
| self.assertRaises(TypeError, unicode_aswidecharstring, []) | |
| self.assertRaises(SystemError, unicode_aswidecharstring, NULL) | |
| self.assertRaises(TypeError, unicode_aswidecharstring_null, b'abc') | |
| self.assertRaises(TypeError, unicode_aswidecharstring_null, []) | |
| self.assertRaises(SystemError, unicode_aswidecharstring_null, NULL) | |
| def test_asucs4(self): | |
| """Test PyUnicode_AsUCS4()""" | |
| unicode_asucs4 = _testcapi.unicode_asucs4 | |
| for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600', | |
| 'a\ud800b\udfffc', '\ud834\udd1e']: | |
| l = len(s) | |
| self.assertEqual(unicode_asucs4(s, l, 1), s+'\0') | |
| self.assertEqual(unicode_asucs4(s, l, 0), s+'\uffff') | |
| self.assertEqual(unicode_asucs4(s, l+1, 1), s+'\0\uffff') | |
| self.assertEqual(unicode_asucs4(s, l+1, 0), s+'\0\uffff') | |
| self.assertRaises(SystemError, unicode_asucs4, s, l-1, 1) | |
| self.assertRaises(SystemError, unicode_asucs4, s, l-2, 0) | |
| s = '\0'.join([s, s]) | |
| self.assertEqual(unicode_asucs4(s, len(s), 1), s+'\0') | |
| self.assertEqual(unicode_asucs4(s, len(s), 0), s+'\uffff') | |
| # CRASHES unicode_asucs4(b'abc', 1, 0) | |
| # CRASHES unicode_asucs4(b'abc', 1, 1) | |
| # CRASHES unicode_asucs4([], 1, 1) | |
| # CRASHES unicode_asucs4(NULL, 1, 0) | |
| # CRASHES unicode_asucs4(NULL, 1, 1) | |
| def test_asucs4copy(self): | |
| """Test PyUnicode_AsUCS4Copy()""" | |
| asucs4copy = _testcapi.unicode_asucs4copy | |
| for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600', | |
| 'a\ud800b\udfffc', '\ud834\udd1e']: | |
| self.assertEqual(asucs4copy(s), s+'\0') | |
| s = '\0'.join([s, s]) | |
| self.assertEqual(asucs4copy(s), s+'\0') | |
| # CRASHES asucs4copy(b'abc') | |
| # CRASHES asucs4copy([]) | |
| # CRASHES asucs4copy(NULL) | |
| def test_fromordinal(self): | |
| """Test PyUnicode_FromOrdinal()""" | |
| fromordinal = _testlimitedcapi.unicode_fromordinal | |
| self.assertEqual(fromordinal(0), '\x00') | |
| self.assertEqual(fromordinal(0x61), 'a') | |
| self.assertEqual(fromordinal(0x20ac), '\u20ac') | |
| self.assertEqual(fromordinal(0x1f600), '\U0001f600') | |
| self.assertRaises(ValueError, fromordinal, INVALID_CHAR) | |
| self.assertRaises(ValueError, fromordinal, -1) | |
| def test_asutf8(self): | |
| """Test PyUnicode_AsUTF8()""" | |
| unicode_asutf8 = _testcapi.unicode_asutf8 | |
| self.assertEqual(unicode_asutf8('abc', 4), b'abc\0') | |
| self.assertEqual(unicode_asutf8('абв', 7), b'\xd0\xb0\xd0\xb1\xd0\xb2\0') | |
| self.assertEqual(unicode_asutf8('\U0001f600', 5), b'\xf0\x9f\x98\x80\0') | |
| self.assertEqual(unicode_asutf8('abc\0def', 8), b'abc\0def\0') | |
| self.assertRaises(UnicodeEncodeError, unicode_asutf8, '\ud8ff', 0) | |
| self.assertRaises(TypeError, unicode_asutf8, b'abc', 0) | |
| self.assertRaises(TypeError, unicode_asutf8, [], 0) | |
| # CRASHES unicode_asutf8(NULL, 0) | |
| @threading_helper.requires_working_threading() | |
| def test_asutf8_race(self): | |
| """Test that there's no race condition in PyUnicode_AsUTF8()""" | |
| unicode_asutf8 = _testcapi.unicode_asutf8 | |
| data = "😊" | |
| def worker(): | |
| for _ in range(1000): | |
| self.assertEqual(unicode_asutf8(data, 5), b'\xf0\x9f\x98\x8a\0') | |
| threads = [Thread(target=worker) for _ in range(10)] | |
| with threading_helper.start_threads(threads): | |
| pass | |
| def test_asutf8andsize(self): | |
| """Test PyUnicode_AsUTF8AndSize()""" | |
| unicode_asutf8andsize = _testlimitedcapi.unicode_asutf8andsize | |
| unicode_asutf8andsize_null = _testlimitedcapi.unicode_asutf8andsize_null | |
| self.assertEqual(unicode_asutf8andsize('abc', 4), (b'abc\0', 3)) | |
| self.assertEqual(unicode_asutf8andsize('абв', 7), (b'\xd0\xb0\xd0\xb1\xd0\xb2\0', 6)) | |
| self.assertEqual(unicode_asutf8andsize('\U0001f600', 5), (b'\xf0\x9f\x98\x80\0', 4)) | |
| self.assertEqual(unicode_asutf8andsize('abc\0def', 8), (b'abc\0def\0', 7)) | |
| self.assertEqual(unicode_asutf8andsize_null('abc', 4), b'abc\0') | |
| self.assertEqual(unicode_asutf8andsize_null('abc\0def', 8), b'abc\0def\0') | |
| self.assertRaises(UnicodeEncodeError, unicode_asutf8andsize, '\ud8ff', 0) | |
| self.assertRaises(TypeError, unicode_asutf8andsize, b'abc', 0) | |
| self.assertRaises(TypeError, unicode_asutf8andsize, [], 0) | |
| self.assertRaises(UnicodeEncodeError, unicode_asutf8andsize_null, '\ud8ff', 0) | |
| self.assertRaises(TypeError, unicode_asutf8andsize_null, b'abc', 0) | |
| self.assertRaises(TypeError, unicode_asutf8andsize_null, [], 0) | |
| # CRASHES unicode_asutf8andsize(NULL, 0) | |
| # CRASHES unicode_asutf8andsize_null(NULL, 0) | |
| def test_getdefaultencoding(self): | |
| """Test PyUnicode_GetDefaultEncoding()""" | |
| getdefaultencoding = _testlimitedcapi.unicode_getdefaultencoding | |
| self.assertEqual(getdefaultencoding(), b'utf-8') | |
| def test_transform_decimal_and_space(self): | |
| """Test _PyUnicode_TransformDecimalAndSpaceToASCII()""" | |
| transform_decimal = _testinternalcapi._PyUnicode_TransformDecimalAndSpaceToASCII | |
| self.assertEqual(transform_decimal('123'), | |
| '123') | |
| self.assertEqual(transform_decimal('\u0663.\u0661\u0664'), | |
| '3.14') | |
| self.assertEqual(transform_decimal("\N{EM SPACE}3.14\N{EN SPACE}"), | |
| " 3.14 ") | |
| self.assertEqual(transform_decimal('12\u20ac3'), | |
| '12?') | |
| self.assertEqual(transform_decimal(''), '') | |
| self.assertRaises(SystemError, transform_decimal, b'123') | |
| self.assertRaises(SystemError, transform_decimal, []) | |
| # CRASHES transform_decimal(NULL) | |
| def test_concat(self): | |
| """Test PyUnicode_Concat()""" | |
| concat = _testlimitedcapi.unicode_concat | |
| self.assertEqual(concat('abc', 'def'), 'abcdef') | |
| self.assertEqual(concat('abc', 'где'), 'abcгде') | |
| self.assertEqual(concat('абв', 'def'), 'абвdef') | |
| self.assertEqual(concat('абв', 'где'), 'абвгде') | |
| self.assertEqual(concat('a\0b', 'c\0d'), 'a\0bc\0d') | |
| self.assertRaises(TypeError, concat, b'abc', 'def') | |
| self.assertRaises(TypeError, concat, 'abc', b'def') | |
| self.assertRaises(TypeError, concat, b'abc', b'def') | |
| self.assertRaises(TypeError, concat, [], 'def') | |
| self.assertRaises(TypeError, concat, 'abc', []) | |
| self.assertRaises(TypeError, concat, [], []) | |
| # CRASHES concat(NULL, 'def') | |
| # CRASHES concat('abc', NULL) | |
| def test_split(self): | |
| """Test PyUnicode_Split()""" | |
| split = _testlimitedcapi.unicode_split | |
| self.assertEqual(split('a|b|c|d', '|'), ['a', 'b', 'c', 'd']) | |
| self.assertEqual(split('a|b|c|d', '|', 2), ['a', 'b', 'c|d']) | |
| self.assertEqual(split('a|b|c|d', '|', PY_SSIZE_T_MAX), | |
| ['a', 'b', 'c', 'd']) | |
| self.assertEqual(split('a|b|c|d', '|', -1), ['a', 'b', 'c', 'd']) | |
| self.assertEqual(split('a|b|c|d', '|', PY_SSIZE_T_MIN), | |
| ['a', 'b', 'c', 'd']) | |
| self.assertEqual(split('a|b|c|d', '\u20ac'), ['a|b|c|d']) | |
| self.assertEqual(split('a||b|c||d', '||'), ['a', 'b|c', 'd']) | |
| self.assertEqual(split('а|б|в|г', '|'), ['а', 'б', 'в', 'г']) | |
| self.assertEqual(split('абабагаламага', 'а'), | |
| ['', 'б', 'б', 'г', 'л', 'м', 'г', '']) | |
| self.assertEqual(split(' a\tb\nc\rd\ve\f', NULL), | |
| ['a', 'b', 'c', 'd', 'e']) | |
| self.assertEqual(split('a\x85b\xa0c\u1680d\u2000e', NULL), | |
| ['a', 'b', 'c', 'd', 'e']) | |
| self.assertRaises(ValueError, split, 'a|b|c|d', '') | |
| self.assertRaises(TypeError, split, 'a|b|c|d', ord('|')) | |
| self.assertRaises(TypeError, split, [], '|') | |
| # CRASHES split(NULL, '|') | |
| def test_rsplit(self): | |
| """Test PyUnicode_RSplit()""" | |
| rsplit = _testlimitedcapi.unicode_rsplit | |
| self.assertEqual(rsplit('a|b|c|d', '|'), ['a', 'b', 'c', 'd']) | |
| self.assertEqual(rsplit('a|b|c|d', '|', 2), ['a|b', 'c', 'd']) | |
| self.assertEqual(rsplit('a|b|c|d', '|', PY_SSIZE_T_MAX), | |
| ['a', 'b', 'c', 'd']) | |
| self.assertEqual(rsplit('a|b|c|d', '|', -1), ['a', 'b', 'c', 'd']) | |
| self.assertEqual(rsplit('a|b|c|d', '|', PY_SSIZE_T_MIN), | |
| ['a', 'b', 'c', 'd']) | |
| self.assertEqual(rsplit('a|b|c|d', '\u20ac'), ['a|b|c|d']) | |
| self.assertEqual(rsplit('a||b|c||d', '||'), ['a', 'b|c', 'd']) | |
| self.assertEqual(rsplit('а|б|в|г', '|'), ['а', 'б', 'в', 'г']) | |
| self.assertEqual(rsplit('абабагаламага', 'а'), | |
| ['', 'б', 'б', 'г', 'л', 'м', 'г', '']) | |
| self.assertEqual(rsplit('aжbжcжd', 'ж'), ['a', 'b', 'c', 'd']) | |
| self.assertEqual(rsplit(' a\tb\nc\rd\ve\f', NULL), | |
| ['a', 'b', 'c', 'd', 'e']) | |
| self.assertEqual(rsplit('a\x85b\xa0c\u1680d\u2000e', NULL), | |
| ['a', 'b', 'c', 'd', 'e']) | |
| self.assertRaises(ValueError, rsplit, 'a|b|c|d', '') | |
| self.assertRaises(TypeError, rsplit, 'a|b|c|d', ord('|')) | |
| self.assertRaises(TypeError, rsplit, [], '|') | |
| # CRASHES rsplit(NULL, '|') | |
| def test_partition(self): | |
| """Test PyUnicode_Partition()""" | |
| partition = _testlimitedcapi.unicode_partition | |
| self.assertEqual(partition('a|b|c', '|'), ('a', '|', 'b|c')) | |
| self.assertEqual(partition('a||b||c', '||'), ('a', '||', 'b||c')) | |
| self.assertEqual(partition('а|б|в', '|'), ('а', '|', 'б|в')) | |
| self.assertEqual(partition('кабан', 'а'), ('к', 'а', 'бан')) | |
| self.assertEqual(partition('aжbжc', 'ж'), ('a', 'ж', 'bжc')) | |
| self.assertRaises(ValueError, partition, 'a|b|c', '') | |
| self.assertRaises(TypeError, partition, b'a|b|c', '|') | |
| self.assertRaises(TypeError, partition, 'a|b|c', b'|') | |
| self.assertRaises(TypeError, partition, 'a|b|c', ord('|')) | |
| self.assertRaises(TypeError, partition, [], '|') | |
| # CRASHES partition(NULL, '|') | |
| # CRASHES partition('a|b|c', NULL) | |
| def test_rpartition(self): | |
| """Test PyUnicode_RPartition()""" | |
| rpartition = _testlimitedcapi.unicode_rpartition | |
| self.assertEqual(rpartition('a|b|c', '|'), ('a|b', '|', 'c')) | |
| self.assertEqual(rpartition('a||b||c', '||'), ('a||b', '||', 'c')) | |
| self.assertEqual(rpartition('а|б|в', '|'), ('а|б', '|', 'в')) | |
| self.assertEqual(rpartition('кабан', 'а'), ('каб', 'а', 'н')) | |
| self.assertEqual(rpartition('aжbжc', 'ж'), ('aжb', 'ж', 'c')) | |
| self.assertRaises(ValueError, rpartition, 'a|b|c', '') | |
| self.assertRaises(TypeError, rpartition, b'a|b|c', '|') | |
| self.assertRaises(TypeError, rpartition, 'a|b|c', b'|') | |
| self.assertRaises(TypeError, rpartition, 'a|b|c', ord('|')) | |
| self.assertRaises(TypeError, rpartition, [], '|') | |
| # CRASHES rpartition(NULL, '|') | |
| # CRASHES rpartition('a|b|c', NULL) | |
| def test_splitlines(self): | |
| """Test PyUnicode_SplitLines()""" | |
| splitlines = _testlimitedcapi.unicode_splitlines | |
| self.assertEqual(splitlines('a\nb\rc\r\nd'), ['a', 'b', 'c', 'd']) | |
| self.assertEqual(splitlines('a\nb\rc\r\nd', True), | |
| ['a\n', 'b\r', 'c\r\n', 'd']) | |
| self.assertEqual(splitlines('a\x85b\u2028c\u2029d'), | |
| ['a', 'b', 'c', 'd']) | |
| self.assertEqual(splitlines('a\x85b\u2028c\u2029d', True), | |
| ['a\x85', 'b\u2028', 'c\u2029', 'd']) | |
| self.assertEqual(splitlines('а\nб\rв\r\nг'), ['а', 'б', 'в', 'г']) | |
| self.assertRaises(TypeError, splitlines, b'a\nb\rc\r\nd') | |
| # CRASHES splitlines(NULL) | |
| def test_translate(self): | |
| """Test PyUnicode_Translate()""" | |
| translate = _testlimitedcapi.unicode_translate | |
| self.assertEqual(translate('abcd', {ord('a'): 'A', ord('b'): ord('B'), ord('c'): '<>'}), 'AB<>d') | |
| self.assertEqual(translate('абвг', {ord('а'): 'А', ord('б'): ord('Б'), ord('в'): '<>'}), 'АБ<>г') | |
| self.assertEqual(translate('abc', {}), 'abc') | |
| self.assertEqual(translate('abc', []), 'abc') | |
| self.assertRaises(UnicodeTranslateError, translate, 'abc', {ord('b'): None}) | |
| self.assertRaises(UnicodeTranslateError, translate, 'abc', {ord('b'): None}, 'strict') | |
| self.assertRaises(LookupError, translate, 'abc', {ord('b'): None}, 'foo') | |
| self.assertEqual(translate('abc', {ord('b'): None}, 'ignore'), 'ac') | |
| self.assertEqual(translate('abc', {ord('b'): None}, 'replace'), 'a\ufffdc') | |
| self.assertEqual(translate('abc', {ord('b'): None}, 'backslashreplace'), r'a\x62c') | |
| # XXX Other error handlers do not support UnicodeTranslateError | |
| self.assertRaises(TypeError, translate, b'abc', []) | |
| self.assertRaises(TypeError, translate, 123, []) | |
| self.assertRaises(TypeError, translate, 'abc', {ord('a'): b'A'}) | |
| self.assertRaises(TypeError, translate, 'abc', 123) | |
| self.assertRaises(TypeError, translate, 'abc', NULL) | |
| self.assertRaises(LookupError, translate, 'abc', {ord('b'): None}, 'foo') | |
| # CRASHES translate(NULL, []) | |
| def test_join(self): | |
| """Test PyUnicode_Join()""" | |
| join = _testlimitedcapi.unicode_join | |
| self.assertEqual(join('|', ['a', 'b', 'c']), 'a|b|c') | |
| self.assertEqual(join('|', ['a', '', 'c']), 'a||c') | |
| self.assertEqual(join('', ['a', 'b', 'c']), 'abc') | |
| self.assertEqual(join(NULL, ['a', 'b', 'c']), 'a b c') | |
| self.assertEqual(join('|', ['а', 'б', 'в']), 'а|б|в') | |
| self.assertEqual(join('ж', ['а', 'б', 'в']), 'ажбжв') | |
| self.assertRaises(TypeError, join, b'|', ['a', 'b', 'c']) | |
| self.assertRaises(TypeError, join, '|', [b'a', b'b', b'c']) | |
| self.assertRaises(TypeError, join, NULL, [b'a', b'b', b'c']) | |
| self.assertRaises(TypeError, join, '|', b'123') | |
| self.assertRaises(TypeError, join, '|', 123) | |
| self.assertRaises(SystemError, join, '|', NULL) | |
| def test_count(self): | |
| """Test PyUnicode_Count()""" | |
| unicode_count = _testlimitedcapi.unicode_count | |
| for str in "\xa1", "\u8000\u8080", "\ud800\udc02", "\U0001f100\U0001f1f1": | |
| for i, ch in enumerate(str): | |
| self.assertEqual(unicode_count(str, ch, 0, len(str)), 1) | |
| str = "!>_<!" | |
| self.assertEqual(unicode_count(str, 'z', 0, len(str)), 0) | |
| self.assertEqual(unicode_count(str, '', 0, len(str)), len(str)+1) | |
| # start < end | |
| self.assertEqual(unicode_count(str, '!', 1, len(str)+1), 1) | |
| self.assertEqual(unicode_count(str, '!', 1, PY_SSIZE_T_MAX), 1) | |
| # start >= end | |
| self.assertEqual(unicode_count(str, '!', 0, 0), 0) | |
| self.assertEqual(unicode_count(str, '!', len(str), 0), 0) | |
| # negative | |
| self.assertEqual(unicode_count(str, '!', -len(str), -1), 1) | |
| self.assertEqual(unicode_count(str, '!', -len(str)-1, -1), 1) | |
| self.assertEqual(unicode_count(str, '!', PY_SSIZE_T_MIN, -1), 1) | |
| # bad arguments | |
| self.assertRaises(TypeError, unicode_count, str, b'!', 0, len(str)) | |
| self.assertRaises(TypeError, unicode_count, b"!>_<!", '!', 0, len(str)) | |
| self.assertRaises(TypeError, unicode_count, str, ord('!'), 0, len(str)) | |
| self.assertRaises(TypeError, unicode_count, [], '!', 0, len(str), 1) | |
| # CRASHES unicode_count(NULL, '!', 0, len(str)) | |
| # CRASHES unicode_count(str, NULL, 0, len(str)) | |
| def test_tailmatch(self): | |
| """Test PyUnicode_Tailmatch()""" | |
| tailmatch = _testlimitedcapi.unicode_tailmatch | |
| str = 'ababahalamaha' | |
| self.assertEqual(tailmatch(str, 'aba', 0, len(str), -1), 1) | |
| self.assertEqual(tailmatch(str, 'aha', 0, len(str), 1), 1) | |
| self.assertEqual(tailmatch(str, 'aba', 0, PY_SSIZE_T_MAX, -1), 1) | |
| self.assertEqual(tailmatch(str, 'aba', -len(str), PY_SSIZE_T_MAX, -1), 1) | |
| self.assertEqual(tailmatch(str, 'aba', PY_SSIZE_T_MIN, len(str), -1), 1) | |
| self.assertEqual(tailmatch(str, 'aha', 0, PY_SSIZE_T_MAX, 1), 1) | |
| self.assertEqual(tailmatch(str, 'aha', PY_SSIZE_T_MIN, len(str), 1), 1) | |
| self.assertEqual(tailmatch(str, 'z', 0, len(str), 1), 0) | |
| self.assertEqual(tailmatch(str, 'z', 0, len(str), -1), 0) | |
| self.assertEqual(tailmatch(str, '', 0, len(str), 1), 1) | |
| self.assertEqual(tailmatch(str, '', 0, len(str), -1), 1) | |
| self.assertEqual(tailmatch(str, 'ba', 0, len(str)-1, -1), 0) | |
| self.assertEqual(tailmatch(str, 'ba', 1, len(str)-1, -1), 1) | |
| self.assertEqual(tailmatch(str, 'aba', 1, len(str)-1, -1), 0) | |
| self.assertEqual(tailmatch(str, 'ba', -len(str)+1, -1, -1), 1) | |
| self.assertEqual(tailmatch(str, 'ah', 0, len(str), 1), 0) | |
| self.assertEqual(tailmatch(str, 'ah', 0, len(str)-1, 1), 1) | |
| self.assertEqual(tailmatch(str, 'ah', -len(str), -1, 1), 1) | |
| # bad arguments | |
| self.assertRaises(TypeError, tailmatch, str, ('aba', 'aha'), 0, len(str), -1) | |
| self.assertRaises(TypeError, tailmatch, str, ('aba', 'aha'), 0, len(str), 1) | |
| # CRASHES tailmatch(NULL, 'aba', 0, len(str), -1) | |
| # CRASHES tailmatch(str, NULL, 0, len(str), -1) | |
| def test_find(self): | |
| """Test PyUnicode_Find()""" | |
| find = _testlimitedcapi.unicode_find | |
| for str in "\xa1", "\u8000\u8080", "\ud800\udc02", "\U0001f100\U0001f1f1": | |
| for i, ch in enumerate(str): | |
| self.assertEqual(find(str, ch, 0, len(str), 1), i) | |
| self.assertEqual(find(str, ch, 0, len(str), -1), i) | |
| str = "!>_<!" | |
| self.assertEqual(find(str, 'z', 0, len(str), 1), -1) | |
| self.assertEqual(find(str, 'z', 0, len(str), -1), -1) | |
| self.assertEqual(find(str, '', 0, len(str), 1), 0) | |
| self.assertEqual(find(str, '', 0, len(str), -1), len(str)) | |
| # start < end | |
| self.assertEqual(find(str, '!', 1, len(str)+1, 1), 4) | |
| self.assertEqual(find(str, '!', 1, PY_SSIZE_T_MAX, 1), 4) | |
| self.assertEqual(find(str, '!', 0, len(str)+1, -1), 4) | |
| self.assertEqual(find(str, '!', 0, PY_SSIZE_T_MAX, -1), 4) | |
| # start >= end | |
| self.assertEqual(find(str, '!', 0, 0, 1), -1) | |
| self.assertEqual(find(str, '!', 0, 0, -1), -1) | |
| self.assertEqual(find(str, '!', len(str), 0, 1), -1) | |
| self.assertEqual(find(str, '!', len(str), 0, -1), -1) | |
| # negative | |
| self.assertEqual(find(str, '!', -len(str), -1, 1), 0) | |
| self.assertEqual(find(str, '!', -len(str), -1, -1), 0) | |
| self.assertEqual(find(str, '!', PY_SSIZE_T_MIN, -1, 1), 0) | |
| self.assertEqual(find(str, '!', PY_SSIZE_T_MIN, -1, -1), 0) | |
| self.assertEqual(find(str, '!', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, 1), 0) | |
| self.assertEqual(find(str, '!', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, -1), 4) | |
| # bad arguments | |
| self.assertRaises(TypeError, find, str, b'!', 0, len(str), 1) | |
| self.assertRaises(TypeError, find, b"!>_<!", '!', 0, len(str), 1) | |
| self.assertRaises(TypeError, find, str, ord('!'), 0, len(str), 1) | |
| self.assertRaises(TypeError, find, [], '!', 0, len(str), 1) | |
| # CRASHES find(NULL, '!', 0, len(str), 1) | |
| # CRASHES find(str, NULL, 0, len(str), 1) | |
| def test_findchar(self): | |
| """Test PyUnicode_FindChar()""" | |
| unicode_findchar = _testlimitedcapi.unicode_findchar | |
| for str in "\xa1", "\u8000\u8080", "\ud800\udc02", "\U0001f100\U0001f1f1": | |
| for i, ch in enumerate(str): | |
| self.assertEqual(unicode_findchar(str, ord(ch), 0, len(str), 1), i) | |
| self.assertEqual(unicode_findchar(str, ord(ch), 0, len(str), -1), i) | |
| str = "!>_<!" | |
| self.assertEqual(unicode_findchar(str, INVALID_CHAR, 0, len(str), 1), -1) | |
| self.assertEqual(unicode_findchar(str, INVALID_CHAR, 0, len(str), -1), -1) | |
| # start < end | |
| self.assertEqual(unicode_findchar(str, ord('!'), 1, len(str)+1, 1), 4) | |
| self.assertEqual(unicode_findchar(str, ord('!'), 1, PY_SSIZE_T_MAX, 1), 4) | |
| self.assertEqual(unicode_findchar(str, ord('!'), 0, len(str)+1, -1), 4) | |
| self.assertEqual(unicode_findchar(str, ord('!'), 0, PY_SSIZE_T_MAX, -1), 4) | |
| # start >= end | |
| self.assertEqual(unicode_findchar(str, ord('!'), 0, 0, 1), -1) | |
| self.assertEqual(unicode_findchar(str, ord('!'), 0, 0, -1), -1) | |
| self.assertEqual(unicode_findchar(str, ord('!'), len(str), 0, 1), -1) | |
| self.assertEqual(unicode_findchar(str, ord('!'), len(str), 0, -1), -1) | |
| # negative | |
| self.assertEqual(unicode_findchar(str, ord('!'), -len(str), -1, 1), 0) | |
| self.assertEqual(unicode_findchar(str, ord('!'), -len(str), -1, -1), 0) | |
| self.assertEqual(unicode_findchar(str, ord('!'), PY_SSIZE_T_MIN, -1, 1), 0) | |
| self.assertEqual(unicode_findchar(str, ord('!'), PY_SSIZE_T_MIN, -1, -1), 0) | |
| self.assertEqual(unicode_findchar(str, ord('!'), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, 1), 0) | |
| self.assertEqual(unicode_findchar(str, ord('!'), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, -1), 4) | |
| # bad arguments | |
| # CRASHES unicode_findchar(b"!>_<!", ord('!'), 0, len(str), 1) | |
| # CRASHES unicode_findchar([], ord('!'), 0, len(str), 1) | |
| # CRASHES unicode_findchar(NULL, ord('!'), 0, len(str), 1), 1) | |
| def test_replace(self): | |
| """Test PyUnicode_Replace()""" | |
| replace = _testlimitedcapi.unicode_replace | |
| str = 'abracadabra' | |
| self.assertEqual(replace(str, 'a', '='), '=br=c=d=br=') | |
| self.assertEqual(replace(str, 'a', '<>'), '<>br<>c<>d<>br<>') | |
| self.assertEqual(replace(str, 'abra', '='), '=cad=') | |
| self.assertEqual(replace(str, 'a', '=', 2), '=br=cadabra') | |
| self.assertEqual(replace(str, 'a', '=', 0), str) | |
| self.assertEqual(replace(str, 'a', '=', PY_SSIZE_T_MAX), '=br=c=d=br=') | |
| self.assertEqual(replace(str, 'a', '=', -1), '=br=c=d=br=') | |
| self.assertEqual(replace(str, 'a', '=', PY_SSIZE_T_MIN), '=br=c=d=br=') | |
| self.assertEqual(replace(str, 'z', '='), str) | |
| self.assertEqual(replace(str, '', '='), '=a=b=r=a=c=a=d=a=b=r=a=') | |
| self.assertEqual(replace(str, 'a', 'ж'), 'жbrжcжdжbrж') | |
| self.assertEqual(replace('абабагаламага', 'а', '='), '=б=б=г=л=м=г=') | |
| self.assertEqual(replace('Баден-Баден', 'Баден', 'Baden'), 'Baden-Baden') | |
| # bad arguments | |
| self.assertRaises(TypeError, replace, 'a', 'a', b'=') | |
| self.assertRaises(TypeError, replace, 'a', b'a', '=') | |
| self.assertRaises(TypeError, replace, b'a', 'a', '=') | |
| self.assertRaises(TypeError, replace, 'a', 'a', ord('=')) | |
| self.assertRaises(TypeError, replace, 'a', ord('a'), '=') | |
| self.assertRaises(TypeError, replace, [], 'a', '=') | |
| # CRASHES replace('a', 'a', NULL) | |
| # CRASHES replace('a', NULL, '=') | |
| # CRASHES replace(NULL, 'a', '=') | |
| def test_compare(self): | |
| """Test PyUnicode_Compare()""" | |
| compare = _testlimitedcapi.unicode_compare | |
| self.assertEqual(compare('abc', 'abc'), 0) | |
| self.assertEqual(compare('abc', 'def'), -1) | |
| self.assertEqual(compare('def', 'abc'), 1) | |
| self.assertEqual(compare('abc', 'abc\0def'), -1) | |
| self.assertEqual(compare('abc\0def', 'abc\0def'), 0) | |
| self.assertEqual(compare('абв', 'abc'), 1) | |
| self.assertRaises(TypeError, compare, b'abc', 'abc') | |
| self.assertRaises(TypeError, compare, 'abc', b'abc') | |
| self.assertRaises(TypeError, compare, b'abc', b'abc') | |
| self.assertRaises(TypeError, compare, [], 'abc') | |
| self.assertRaises(TypeError, compare, 'abc', []) | |
| self.assertRaises(TypeError, compare, [], []) | |
| # CRASHES compare(NULL, 'abc') | |
| # CRASHES compare('abc', NULL) | |
| def test_comparewithasciistring(self): | |
| """Test PyUnicode_CompareWithASCIIString()""" | |
| comparewithasciistring = _testlimitedcapi.unicode_comparewithasciistring | |
| self.assertEqual(comparewithasciistring('abc', b'abc'), 0) | |
| self.assertEqual(comparewithasciistring('abc', b'def'), -1) | |
| self.assertEqual(comparewithasciistring('def', b'abc'), 1) | |
| self.assertEqual(comparewithasciistring('abc', b'abc\0def'), 0) | |
| self.assertEqual(comparewithasciistring('abc\0def', b'abc\0def'), 1) | |
| self.assertEqual(comparewithasciistring('абв', b'abc'), 1) | |
| # CRASHES comparewithasciistring(b'abc', b'abc') | |
| # CRASHES comparewithasciistring([], b'abc') | |
| # CRASHES comparewithasciistring(NULL, b'abc') | |
| def test_equaltoutf8(self): | |
| # Test PyUnicode_EqualToUTF8() | |
| equaltoutf8 = _testlimitedcapi.unicode_equaltoutf8 | |
| asutf8andsize = _testlimitedcapi.unicode_asutf8andsize | |
| strings = [ | |
| 'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16', | |
| '\U0001f600\U0001f601\U0001f602', | |
| '\U0010ffff', | |
| ] | |
| for s in strings: | |
| # Call PyUnicode_AsUTF8AndSize() which creates the UTF-8 | |
| # encoded string cached in the Unicode object. | |
| asutf8andsize(s, 0) | |
| b = s.encode() | |
| self.assertEqual(equaltoutf8(s, b), 1) # Use the UTF-8 cache. | |
| s2 = b.decode() # New Unicode object without the UTF-8 cache. | |
| self.assertEqual(equaltoutf8(s2, b), 1) | |
| self.assertEqual(equaltoutf8(s + 'x', b + b'x'), 1) | |
| self.assertEqual(equaltoutf8(s + 'x', b + b'y'), 0) | |
| self.assertEqual(equaltoutf8(s, b + b'\0'), 1) | |
| self.assertEqual(equaltoutf8(s2, b + b'\0'), 1) | |
| self.assertEqual(equaltoutf8(s + '\0', b + b'\0'), 0) | |
| self.assertEqual(equaltoutf8(s + '\0', b), 0) | |
| self.assertEqual(equaltoutf8(s2, b + b'x'), 0) | |
| self.assertEqual(equaltoutf8(s2, b[:-1]), 0) | |
| self.assertEqual(equaltoutf8(s2, b[:-1] + b'x'), 0) | |
| self.assertEqual(equaltoutf8('', b''), 1) | |
| self.assertEqual(equaltoutf8('', b'\0'), 1) | |
| # embedded null chars/bytes | |
| self.assertEqual(equaltoutf8('abc', b'abc\0def\0'), 1) | |
| self.assertEqual(equaltoutf8('a\0bc', b'abc'), 0) | |
| self.assertEqual(equaltoutf8('abc', b'a\0bc'), 0) | |
| # Surrogate characters are always treated as not equal | |
| self.assertEqual(equaltoutf8('\udcfe', | |
| '\udcfe'.encode("utf8", "surrogateescape")), 0) | |
| self.assertEqual(equaltoutf8('\udcfe', | |
| '\udcfe'.encode("utf8", "surrogatepass")), 0) | |
| self.assertEqual(equaltoutf8('\ud801', | |
| '\ud801'.encode("utf8", "surrogatepass")), 0) | |
| def test_equaltoutf8andsize(self): | |
| # Test PyUnicode_EqualToUTF8AndSize() | |
| equaltoutf8andsize = _testlimitedcapi.unicode_equaltoutf8andsize | |
| asutf8andsize = _testlimitedcapi.unicode_asutf8andsize | |
| strings = [ | |
| 'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16', | |
| '\U0001f600\U0001f601\U0001f602', | |
| '\U0010ffff', | |
| ] | |
| for s in strings: | |
| # Call PyUnicode_AsUTF8AndSize() which creates the UTF-8 | |
| # encoded string cached in the Unicode object. | |
| asutf8andsize(s, 0) | |
| b = s.encode() | |
| self.assertEqual(equaltoutf8andsize(s, b), 1) # Use the UTF-8 cache. | |
| s2 = b.decode() # New Unicode object without the UTF-8 cache. | |
| self.assertEqual(equaltoutf8andsize(s2, b), 1) | |
| self.assertEqual(equaltoutf8andsize(s + 'x', b + b'x'), 1) | |
| self.assertEqual(equaltoutf8andsize(s + 'x', b + b'y'), 0) | |
| self.assertEqual(equaltoutf8andsize(s, b + b'\0'), 0) | |
| self.assertEqual(equaltoutf8andsize(s2, b + b'\0'), 0) | |
| self.assertEqual(equaltoutf8andsize(s + '\0', b + b'\0'), 1) | |
| self.assertEqual(equaltoutf8andsize(s + '\0', b), 0) | |
| self.assertEqual(equaltoutf8andsize(s2, b + b'x'), 0) | |
| self.assertEqual(equaltoutf8andsize(s2, b[:-1]), 0) | |
| self.assertEqual(equaltoutf8andsize(s2, b[:-1] + b'x'), 0) | |
| # Not null-terminated, | |
| self.assertEqual(equaltoutf8andsize(s, b + b'x', len(b)), 1) | |
| self.assertEqual(equaltoutf8andsize(s2, b + b'x', len(b)), 1) | |
| self.assertEqual(equaltoutf8andsize(s + '\0', b + b'\0x', len(b) + 1), 1) | |
| self.assertEqual(equaltoutf8andsize(s2, b, len(b) - 1), 0) | |
| self.assertEqual(equaltoutf8andsize(s, b, -1), 0) | |
| self.assertEqual(equaltoutf8andsize(s, b, PY_SSIZE_T_MAX), 0) | |
| self.assertEqual(equaltoutf8andsize(s, b, PY_SSIZE_T_MIN), 0) | |
| self.assertEqual(equaltoutf8andsize('', b''), 1) | |
| self.assertEqual(equaltoutf8andsize('', b'\0'), 0) | |
| self.assertEqual(equaltoutf8andsize('', b'x', 0), 1) | |
| # embedded null chars/bytes | |
| self.assertEqual(equaltoutf8andsize('abc\0def', b'abc\0def'), 1) | |
| self.assertEqual(equaltoutf8andsize('abc\0def\0', b'abc\0def\0'), 1) | |
| # Surrogate characters are always treated as not equal | |
| self.assertEqual(equaltoutf8andsize('\udcfe', | |
| '\udcfe'.encode("utf8", "surrogateescape")), 0) | |
| self.assertEqual(equaltoutf8andsize('\udcfe', | |
| '\udcfe'.encode("utf8", "surrogatepass")), 0) | |
| self.assertEqual(equaltoutf8andsize('\ud801', | |
| '\ud801'.encode("utf8", "surrogatepass")), 0) | |
| def check_not_equal_encoding(text, encoding): | |
| self.assertEqual(equaltoutf8andsize(text, text.encode(encoding)), 0) | |
| self.assertNotEqual(text.encode(encoding), text.encode("utf8")) | |
| # Strings encoded to other encodings are not equal to expected UTF8-encoding string | |
| check_not_equal_encoding('Stéphane', 'latin1') | |
| check_not_equal_encoding('Stéphane', 'utf-16-le') # embedded null characters | |
| check_not_equal_encoding('北京市', 'gbk') | |
| # CRASHES equaltoutf8andsize('abc', b'abc', -1) | |
| # CRASHES equaltoutf8andsize(b'abc', b'abc') | |
| # CRASHES equaltoutf8andsize([], b'abc') | |
| # CRASHES equaltoutf8andsize(NULL, b'abc') | |
| # CRASHES equaltoutf8andsize('abc', NULL) | |
| def test_richcompare(self): | |
| """Test PyUnicode_RichCompare()""" | |
| richcompare = _testlimitedcapi.unicode_richcompare | |
| LT, LE, EQ, NE, GT, GE = range(6) | |
| strings = ('abc', 'абв', '\U0001f600', 'abc\0') | |
| for s1 in strings: | |
| for s2 in strings: | |
| self.assertIs(richcompare(s1, s2, LT), s1 < s2) | |
| self.assertIs(richcompare(s1, s2, LE), s1 <= s2) | |
| self.assertIs(richcompare(s1, s2, EQ), s1 == s2) | |
| self.assertIs(richcompare(s1, s2, NE), s1 != s2) | |
| self.assertIs(richcompare(s1, s2, GT), s1 > s2) | |
| self.assertIs(richcompare(s1, s2, GE), s1 >= s2) | |
| for op in LT, LE, EQ, NE, GT, GE: | |
| self.assertIs(richcompare(b'abc', 'abc', op), NotImplemented) | |
| self.assertIs(richcompare('abc', b'abc', op), NotImplemented) | |
| self.assertIs(richcompare(b'abc', b'abc', op), NotImplemented) | |
| self.assertIs(richcompare([], 'abc', op), NotImplemented) | |
| self.assertIs(richcompare('abc', [], op), NotImplemented) | |
| self.assertIs(richcompare([], [], op), NotImplemented) | |
| # CRASHES richcompare(NULL, 'abc', op) | |
| # CRASHES richcompare('abc', NULL, op) | |
| def test_equal(self): | |
| """Test PyUnicode_Equal()""" | |
| equal = _testlimitedcapi.unicode_equal | |
| strings = ('abc', 'абв', '\U0001f600', 'abc\0') | |
| for s1 in strings: | |
| for s2 in strings: | |
| self.assertEqual(equal(s1, s2), int(s1 == s2)) | |
| self.assertRaises(TypeError, equal, 'str', b'bytes') | |
| self.assertRaises(TypeError, equal, b'bytes', 'str') | |
| self.assertRaises(TypeError, equal, 12, 34) | |
| # CRASHES equal(NULL, 'abc') | |
| # CRASHES equal('abc', NULL) | |
| def test_format(self): | |
| """Test PyUnicode_Format()""" | |
| format = _testlimitedcapi.unicode_format | |
| self.assertEqual(format('x=%d!', 42), 'x=42!') | |
| self.assertEqual(format('x=%d!', (42,)), 'x=42!') | |
| self.assertEqual(format('x=%d y=%s!', (42, [])), 'x=42 y=[]!') | |
| self.assertRaises(SystemError, format, 'x=%d!', NULL) | |
| self.assertRaises(SystemError, format, NULL, 42) | |
| def test_contains(self): | |
| """Test PyUnicode_Contains()""" | |
| contains = _testlimitedcapi.unicode_contains | |
| self.assertEqual(contains('abcd', ''), 1) | |
| self.assertEqual(contains('abcd', 'b'), 1) | |
| self.assertEqual(contains('abcd', 'x'), 0) | |
| self.assertEqual(contains('abcd', 'ж'), 0) | |
| self.assertEqual(contains('abcd', '\0'), 0) | |
| self.assertEqual(contains('abc\0def', '\0'), 1) | |
| self.assertEqual(contains('abcd', 'bc'), 1) | |
| self.assertRaises(TypeError, contains, b'abcd', 'b') | |
| self.assertRaises(TypeError, contains, 'abcd', b'b') | |
| self.assertRaises(TypeError, contains, b'abcd', b'b') | |
| self.assertRaises(TypeError, contains, [], 'b') | |
| self.assertRaises(TypeError, contains, 'abcd', ord('b')) | |
| # CRASHES contains(NULL, 'b') | |
| # CRASHES contains('abcd', NULL) | |
| def test_isidentifier(self): | |
| """Test PyUnicode_IsIdentifier()""" | |
| isidentifier = _testlimitedcapi.unicode_isidentifier | |
| self.assertEqual(isidentifier("a"), 1) | |
| self.assertEqual(isidentifier("b0"), 1) | |
| self.assertEqual(isidentifier("µ"), 1) | |
| self.assertEqual(isidentifier("𝔘𝔫𝔦𝔠𝔬𝔡𝔢"), 1) | |
| self.assertEqual(isidentifier(""), 0) | |
| self.assertEqual(isidentifier(" "), 0) | |
| self.assertEqual(isidentifier("["), 0) | |
| self.assertEqual(isidentifier("©"), 0) | |
| self.assertEqual(isidentifier("0"), 0) | |
| self.assertEqual(isidentifier("32M"), 0) | |
| # CRASHES isidentifier(b"a") | |
| # CRASHES isidentifier([]) | |
| # CRASHES isidentifier(NULL) | |
| def test_copycharacters(self): | |
| """Test PyUnicode_CopyCharacters()""" | |
| unicode_copycharacters = _testcapi.unicode_copycharacters | |
| strings = [ | |
| # all strings have exactly 5 characters | |
| 'abcde', '\xa1\xa2\xa3\xa4\xa5', | |
| '\u4f60\u597d\u4e16\u754c\uff01', | |
| '\U0001f600\U0001f601\U0001f602\U0001f603\U0001f604' | |
| ] | |
| for idx, from_ in enumerate(strings): | |
| # wide -> narrow: exceed maxchar limitation | |
| for to in strings[:idx]: | |
| self.assertRaises( | |
| SystemError, | |
| unicode_copycharacters, to, 0, from_, 0, 5 | |
| ) | |
| # same kind | |
| for from_start in range(5): | |
| self.assertEqual( | |
| unicode_copycharacters(from_, 0, from_, from_start, 5), | |
| (from_[from_start:from_start+5].ljust(5, '\0'), | |
| 5-from_start) | |
| ) | |
| for to_start in range(5): | |
| self.assertEqual( | |
| unicode_copycharacters(from_, to_start, from_, to_start, 5), | |
| (from_[to_start:to_start+5].rjust(5, '\0'), | |
| 5-to_start) | |
| ) | |
| # narrow -> wide | |
| # Tests omitted since this creates invalid strings. | |
| s = strings[0] | |
| self.assertRaises(IndexError, unicode_copycharacters, s, 6, s, 0, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, PY_SSIZE_T_MAX, s, 0, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, -1, s, 0, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, PY_SSIZE_T_MIN, s, 0, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, 0, s, 6, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, 0, s, PY_SSIZE_T_MAX, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, 0, s, -1, 5) | |
| self.assertRaises(IndexError, unicode_copycharacters, s, 0, s, PY_SSIZE_T_MIN, 5) | |
| self.assertRaises(SystemError, unicode_copycharacters, s, 1, s, 0, 5) | |
| self.assertRaises(SystemError, unicode_copycharacters, s, 1, s, 0, PY_SSIZE_T_MAX) | |
| self.assertRaises(SystemError, unicode_copycharacters, s, 0, s, 0, -1) | |
| self.assertRaises(SystemError, unicode_copycharacters, s, 0, s, 0, PY_SSIZE_T_MIN) | |
| self.assertRaises(SystemError, unicode_copycharacters, s, 0, b'', 0, 0) | |
| self.assertRaises(SystemError, unicode_copycharacters, s, 0, [], 0, 0) | |
| # A string with 2 references cannot be modified | |
| with self.assertRaisesRegex(SystemError, USED_STR_ERROR): | |
| unicode_copycharacters('abc', 0, 'abc', 0, 1, incref=True) | |
| # CRASHES unicode_copycharacters(s, 0, NULL, 0, 0) | |
| # TODO: Test PyUnicode_CopyCharacters() with non-unicode and | |
| # non-modifiable unicode as "to". | |
| def test_pep393_utf8_caching_bug(self): | |
| # Issue #25709: Problem with string concatenation and utf-8 cache | |
| getargs_s_hash = _testcapi.getargs_s_hash | |
| for k in 0x24, 0xa4, 0x20ac, 0x1f40d: | |
| s = '' | |
| for i in range(5): | |
| # Due to CPython specific optimization the 's' string can be | |
| # resized in-place. | |
| s += chr(k) | |
| # Parsing with the "s#" format code calls indirectly | |
| # PyUnicode_AsUTF8AndSize() which creates the UTF-8 | |
| # encoded string cached in the Unicode object. | |
| self.assertEqual(getargs_s_hash(s), chr(k).encode() * (i + 1)) | |
| # Check that the second call returns the same result | |
| self.assertEqual(getargs_s_hash(s), chr(k).encode() * (i + 1)) | |
| def test_GET_CACHED_HASH(self): | |
| """Test PyUnstable_Unicode_GET_CACHED_HASH()""" | |
| unicode_GET_CACHED_HASH = _testcapi.unicode_GET_CACHED_HASH | |
| content_bytes = b'some new string' | |
| # avoid parser interning & constant folding | |
| obj = str(content_bytes, 'ascii') | |
| # impl detail: fresh strings do not have cached hash | |
| self.assertEqual(unicode_GET_CACHED_HASH(obj), -1) | |
| # impl detail: adding string to a dict caches its hash | |
| {obj: obj} | |
| # impl detail: ASCII string hashes are equal to bytes ones | |
| self.assertEqual(unicode_GET_CACHED_HASH(obj), hash(content_bytes)) | |
| def test_kind(self): | |
| """Test PyUnicode_KIND()""" | |
| unicode_kind = _testcapi.unicode_kind | |
| self.assertEqual(unicode_kind('ascii'), PyUnicode_1BYTE_KIND) | |
| self.assertEqual(unicode_kind('latin1:\xe9'), PyUnicode_1BYTE_KIND) | |
| self.assertEqual(unicode_kind('bmp:\u20ac'), PyUnicode_2BYTE_KIND) | |
| self.assertEqual(unicode_kind('\U0010ffff'), PyUnicode_4BYTE_KIND) | |
| # CRASHES unicode_kind(NULL) | |
| def test_max_char_value(self): | |
| """Test PyUnicode_MAX_CHAR_VALUE()""" | |
| max_char_value = _testcapi.unicode_max_char_value | |
| self.assertEqual(max_char_value('ascii'), 0x7f) | |
| self.assertEqual(max_char_value('latin1:\xe9'), 0xff) | |
| self.assertEqual(max_char_value('bmp:\u20ac'), 0xffff) | |
| self.assertEqual(max_char_value(chr(0x10_0000)), 0x10_ffff) | |
| # CRASHES max_char_value(NULL) | |
| def test_check_interned(self): | |
| """Test PyUnicode_CHECK_INTERNED() macro""" | |
| check_interned = _testcapi.unicode_check_interned | |
| def fresh_string(text): | |
| # Create a new string object by decoding a bytes string. | |
| return text.encode().decode() | |
| s = fresh_string('hello') | |
| self.assertEqual(check_interned(s), SSTATE_NOT_INTERNED) | |
| s = fresh_string('long string unlikely to be used by Python') | |
| s = sys.intern(s) | |
| if support.Py_GIL_DISABLED: | |
| self.assertEqual(check_interned(s), SSTATE_INTERNED_IMMORTAL) | |
| else: | |
| self.assertEqual(check_interned(s), SSTATE_INTERNED_MORTAL) | |
| # 'x' is a singleton: immortal static interned string | |
| self.assertEqual(check_interned('x'), SSTATE_INTERNED_IMMORTAL_STATIC) | |
| # CRASHES check_interned(NULL) | |
| def test_is_ascii(self): | |
| """Test PyUnicode_IS_ASCII() macro""" | |
| is_ascii = _testcapi.unicode_is_ascii | |
| self.assertEqual(is_ascii('abc'), 1) | |
| self.assertEqual(is_ascii('\u20ac'), 0) | |
| # CRASHES is_ascii(NULL) | |
| def test_is_compact(self): | |
| """Test PyUnicode_IS_COMPACT() macro""" | |
| is_compact = _testcapi.unicode_is_compact | |
| self.assertEqual(is_compact('ascii'), 1) | |
| self.assertEqual(is_compact('latin1:\xe9'), 1) | |
| self.assertEqual(is_compact('bmp:\u20ac'), 1) | |
| self.assertEqual(is_compact('\U0010ffff'), 1) | |
| # str subclasses are not compact | |
| self.assertEqual(is_compact(Str('abc')), 0) | |
| self.assertEqual(is_compact(Str('\u20ac')), 0) | |
| # CRASHES is_compact(NULL) | |
| def test_is_compact_ascii(self): | |
| """Test PyUnicode_IS_COMPACT_ASCII() macro""" | |
| is_compact_ascii = _testcapi.unicode_is_compact_ascii | |
| self.assertEqual(is_compact_ascii('ascii'), 1) | |
| self.assertEqual(is_compact_ascii('latin1:\xe9'), 0) | |
| self.assertEqual(is_compact_ascii('bmp:\u20ac'), 0) | |
| self.assertEqual(is_compact_ascii('\U0010ffff'), 0) | |
| # str subclasses are not compact | |
| self.assertEqual(is_compact_ascii(Str('abc')), 0) | |
| self.assertEqual(is_compact_ascii(Str('\u20ac')), 0) | |
| # CRASHES is_compact_ascii(NULL) | |
| def test_unicode_equal(self): | |
| unicode_equal = _testlimitedcapi.unicode_equal | |
| def copy(text): | |
| return text.encode().decode() | |
| self.assertTrue(unicode_equal("", "")) | |
| self.assertTrue(unicode_equal("abc", "abc")) | |
| self.assertTrue(unicode_equal("abc", copy("abc"))) | |
| self.assertTrue(unicode_equal("\u20ac", copy("\u20ac"))) | |
| self.assertTrue(unicode_equal("\U0010ffff", copy("\U0010ffff"))) | |
| self.assertFalse(unicode_equal("abc", "abcd")) | |
| self.assertFalse(unicode_equal("\u20ac", "\u20ad")) | |
| self.assertFalse(unicode_equal("\U0010ffff", "\U0010fffe")) | |
| # str subclass | |
| self.assertTrue(unicode_equal("abc", Str("abc"))) | |
| self.assertTrue(unicode_equal(Str("abc"), "abc")) | |
| self.assertFalse(unicode_equal("abc", Str("abcd"))) | |
| self.assertFalse(unicode_equal(Str("abc"), "abcd")) | |
| # invalid type | |
| for invalid_type in (b'bytes', 123, ("tuple",)): | |
| with self.subTest(invalid_type=invalid_type): | |
| with self.assertRaises(TypeError): | |
| unicode_equal("abc", invalid_type) | |
| with self.assertRaises(TypeError): | |
| unicode_equal(invalid_type, "abc") | |
| # CRASHES unicode_equal("abc", NULL) | |
| # CRASHES unicode_equal(NULL, "abc") | |
| class PyUnicodeWriterTest(unittest.TestCase): | |
| def create_writer(self, size): | |
| # Test PyUnicodeWriter_Create() | |
| return _testcapi.PyUnicodeWriter(size) | |
| def test_basic(self): | |
| writer = self.create_writer(100) | |
| # test PyUnicodeWriter_WriteUTF8() | |
| writer.write_utf8(b'var', -1) | |
| # test PyUnicodeWriter_WriteChar() | |
| writer.write_char(ord('=')) | |
| # test PyUnicodeWriter_WriteSubstring() | |
| writer.write_substring("[long]", 1, 5) | |
| # CRASHES writer.write_substring(NULL, 0, 0) | |
| # test PyUnicodeWriter_WriteStr() | |
| writer.write_str(" value ") | |
| # CRASHES writer.write_str(NULL) | |
| # test PyUnicodeWriter_WriteRepr() | |
| writer.write_repr("repr") | |
| # test PyUnicodeWriter_Finish() | |
| self.assertEqual(writer.finish(), | |
| "var=long value 'repr'") | |
| def test_create(self): | |
| # Test PyUnicodeWriter_Create() with non-zero size | |
| s = 'Monty Python' | |
| # Preallocate the exact length. Use 2 writes to force the creation | |
| # of a buffer: | |
| # 1. Use the read-only optimization. | |
| # 2. Allocate a buffer of length character. | |
| # No resize needed in finish(). | |
| writer = self.create_writer(len(s)) | |
| writer.write_str(s[:5]) | |
| self.assertEqual(writer.get_buffer(), (5, 127, True)) | |
| writer.write_str(s[5:]) | |
| self.assertEqual(writer.get_buffer(), (len(s), 127, False)) | |
| self.assertEqual(writer.finish(), s) | |
| # Preallocate len(s)-1 characters. Use 3 writes: | |
| # 1. Use read-only optimization. | |
| # 2. Allocate a buffer of len-1 characters. | |
| # 3. Resize the buffer with overallocation. | |
| # finish() has to truncate the buffer. | |
| writer = self.create_writer(len(s) - 1) | |
| writer.write_str(s[:2]) | |
| self.assertEqual(writer.get_buffer(), (2, 127, True)) | |
| writer.write_str(s[2:5]) | |
| self.assertEqual(writer.get_buffer(), (len(s) - 1, 127, False)) | |
| writer.write_str(s[5:]) | |
| self.assertGreater(writer.get_buffer()[0], len(s)) | |
| self.assertEqual(writer.finish(), s) | |
| def test_str_subclass(self): | |
| # The read-only optimization must not return a str subclass | |
| class MyStr(str): | |
| def __str__(self): | |
| return self | |
| writer = self.create_writer(0) | |
| writer.write_str(MyStr('abc')) | |
| result = writer.finish() | |
| self.assertEqual(result, 'abc') | |
| self.assertIs(type(result), str) | |
| def test_repr_null(self): | |
| writer = self.create_writer(0) | |
| writer.write_utf8(b'var=', -1) | |
| writer.write_repr(NULL) | |
| self.assertEqual(writer.finish(), | |
| "var=<NULL>") | |
| def test_write_char(self): | |
| writer = self.create_writer(0) | |
| writer.write_char(0) | |
| writer.write_char(ord('$')) | |
| writer.write_char(0x20ac) | |
| writer.write_char(0x10_ffff) | |
| self.assertRaises(ValueError, writer.write_char, INVALID_CHAR) | |
| self.assertRaises(ValueError, writer.write_char, MAX_INVALID_CHAR) | |
| self.assertEqual(writer.finish(), | |
| "\0$\u20AC\U0010FFFF") | |
| writer = self.create_writer(0) | |
| for ch in 'hello': | |
| writer.write_char(ord(ch)) | |
| self.assertEqual(writer.finish(), 'hello') | |
| def test_utf8(self): | |
| writer = self.create_writer(0) | |
| writer.write_utf8(b"ascii", -1) | |
| writer.write_char(ord('-')) | |
| writer.write_utf8(b"latin1=\xC3\xA9", -1) | |
| writer.write_char(ord('-')) | |
| writer.write_utf8(b"euro=\xE2\x82\xAC", -1) | |
| writer.write_char(ord('.')) | |
| writer.write_utf8(NULL, 0) | |
| # CRASHES writer.write_utf8(NULL, 1) | |
| # CRASHES writer.write_utf8(NULL, -1) | |
| self.assertEqual(writer.finish(), | |
| "ascii-latin1=\xE9-euro=\u20AC.") | |
| def test_ascii(self): | |
| # Test PyUnicodeWriter_WriteASCII() | |
| writer = self.create_writer(0) | |
| writer.write_ascii(b"Hello ", -1) | |
| writer.write_ascii(b"", 0) | |
| writer.write_ascii(NULL, 0) | |
| # CRASHES writer.write_ascii(NULL, 1) | |
| # CRASHES writer.write_ascii(NULL, -1) | |
| writer.write_ascii(b"Python! <truncated>", 6) | |
| self.assertEqual(writer.finish(), "Hello Python") | |
| def test_write_latin1(self): | |
| # Test _PyUnicodeWriter_WriteLatin1String() | |
| writer = self.create_writer(0) | |
| # Start with ASCII buffer | |
| writer.write_latin1(b"abc IGNORED", 3) | |
| writer.write_latin1(b"IGNORED", 0) | |
| # Change buffer kind to UCS-1 | |
| writer.write_latin1(b"\xe9", 1) | |
| # Change buffer kind to UCS-2 | |
| writer.write_str('[\u20ac]') | |
| writer.write_latin1(b"def\xa0", 4) | |
| # Change buffer kind to UCS-4 | |
| writer.write_str('[\U0010ffff]') | |
| writer.write_latin1(b"ghi\xff.", 5) | |
| writer.write_latin1(b"IGNORED", 0) | |
| self.assertEqual(writer.finish(), | |
| "abc\xe9[\u20ac]def\xa0[\U0010ffff]ghi\xff.") | |
| def test_invalid_utf8(self): | |
| writer = self.create_writer(0) | |
| with self.assertRaises(UnicodeDecodeError): | |
| writer.write_utf8(b"invalid=\xFF", -1) | |
| def test_recover_utf8_error(self): | |
| # Recover from PyUnicodeWriter_WriteUTF8() errors. A temporary write | |
| # changes the buffer kind to UCS-2 before raising UnicodeDecodeError. | |
| # Then, PyUnicodeWriter_Finish() has to change the buffer kind back to | |
| # ASCII. | |
| writer = self.create_writer(0) | |
| writer.write_utf8(b"value=", -1) | |
| # write fails with an invalid string | |
| with self.assertRaises(UnicodeDecodeError): | |
| writer.write_utf8(b"invalid\xFF", -1) | |
| with self.assertRaises(UnicodeDecodeError): | |
| s = "truncated\u20AC".encode() | |
| writer.write_utf8(s, len(s) - 1) | |
| with self.assertRaises(UnicodeDecodeError): | |
| # Change buffer kind to UCS-2 then raise UnicodeDecodeError | |
| s = "\u20AC\u20AC".encode() | |
| writer.write_utf8(s, len(s) - 1) | |
| writer.write_utf8(b"valid", -1) | |
| self.assertEqual(writer.finish(), "value=valid") | |
| def test_decode_utf8(self): | |
| # test PyUnicodeWriter_DecodeUTF8Stateful() | |
| writer = self.create_writer(0) | |
| writer.decodeutf8stateful(b"ign\xFFore", -1, b"ignore") | |
| writer.write_char(ord('-')) | |
| writer.decodeutf8stateful(b"replace\xFF", -1, b"replace") | |
| writer.write_char(ord('-')) | |
| # incomplete trailing UTF-8 sequence | |
| writer.decodeutf8stateful(b"incomplete\xC3", -1, b"replace") | |
| writer.decodeutf8stateful(NULL, 0, b"replace") | |
| # CRASHES writer.decodeutf8stateful(NULL, 1, b"replace") | |
| # CRASHES writer.decodeutf8stateful(NULL, -1, b"replace") | |
| with self.assertRaises(UnicodeDecodeError): | |
| writer.decodeutf8stateful(b"default\xFF", -1, NULL) | |
| self.assertEqual(writer.finish(), | |
| "ignore-replace\uFFFD-incomplete\uFFFD") | |
| def test_decode_utf8_consumed(self): | |
| # test PyUnicodeWriter_DecodeUTF8Stateful() with consumed | |
| writer = self.create_writer(0) | |
| # valid string | |
| consumed = writer.decodeutf8stateful(b"text", -1, b"strict", True) | |
| self.assertEqual(consumed, 4) | |
| writer.write_char(ord('-')) | |
| # non-ASCII | |
| consumed = writer.decodeutf8stateful(b"\xC3\xA9-\xE2\x82\xAC", 6, b"strict", True) | |
| self.assertEqual(consumed, 6) | |
| writer.write_char(ord('-')) | |
| # invalid UTF-8 (consumed is 0 on error) | |
| with self.assertRaises(UnicodeDecodeError): | |
| writer.decodeutf8stateful(b"invalid\xFF", -1, b"strict", True) | |
| # ignore error handler | |
| consumed = writer.decodeutf8stateful(b"more\xFF", -1, b"ignore", True) | |
| self.assertEqual(consumed, 5) | |
| writer.write_char(ord('-')) | |
| # incomplete trailing UTF-8 sequence | |
| consumed = writer.decodeutf8stateful(b"incomplete\xC3", -1, b"ignore", True) | |
| self.assertEqual(consumed, 10) | |
| writer.write_char(ord('-')) | |
| consumed = writer.decodeutf8stateful(NULL, 0, b"replace", True) | |
| self.assertEqual(consumed, 0) | |
| # CRASHES writer.decodeutf8stateful(NULL, 1, b"replace", True) | |
| # CRASHES writer.decodeutf8stateful(NULL, -1, b"replace", True) | |
| consumed = writer.decodeutf8stateful(b"default\xC3", -1, NULL, True) | |
| self.assertEqual(consumed, 7) | |
| self.assertEqual(writer.finish(), "text-\xE9-\u20AC-more-incomplete-default") | |
| def test_widechar(self): | |
| # Test PyUnicodeWriter_WriteWideChar() | |
| if SIZEOF_WCHAR_T == 2: | |
| encoding = 'utf-16le' if sys.byteorder == 'little' else 'utf-16be' | |
| elif SIZEOF_WCHAR_T == 4: | |
| encoding = 'utf-32le' if sys.byteorder == 'little' else 'utf-32be' | |
| writer = self.create_writer(0) | |
| writer.write_widechar("latin1=\xE9".encode(encoding)) | |
| writer.write_char(ord("-")) | |
| writer.write_widechar("euro=\u20AC".encode(encoding)) | |
| writer.write_char(ord("-")) | |
| writer.write_widechar("max=\U0010ffff".encode(encoding)) | |
| writer.write_char(ord("-")) | |
| writer.write_widechar("zeroes=".encode(encoding).ljust(SIZEOF_WCHAR_T * 10, b'\0'), | |
| 10) | |
| writer.write_char(ord('.')) | |
| if SIZEOF_WCHAR_T == 4: | |
| invalid = (b'\x00\x00\x11\x00' if sys.byteorder == 'little' else | |
| b'\x00\x11\x00\x00') | |
| with self.assertRaises(ValueError): | |
| writer.write_widechar("invalid=".encode(encoding) + invalid) | |
| writer.write_widechar(b'', -5) | |
| writer.write_widechar(NULL, 0) | |
| # CRASHES writer.write_widechar(NULL, 1) | |
| # CRASHES writer.write_widechar(NULL, -1) | |
| self.assertEqual(writer.finish(), | |
| "latin1=\xE9-euro=\u20AC-max=\U0010ffff-zeroes=\0\0\0.") | |
| def test_ucs4(self): | |
| # Test PyUnicodeWriter_WriteUCS4() | |
| encoding = 'utf-32le' if sys.byteorder == 'little' else 'utf-32be' | |
| writer = self.create_writer(0) | |
| writer.write_ucs4("ascii IGNORED".encode(encoding), 5) | |
| writer.write_char(ord("-")) | |
| writer.write_ucs4("latin1=\xe9".encode(encoding)) | |
| writer.write_char(ord("-")) | |
| writer.write_ucs4("euro=\u20ac".encode(encoding)) | |
| writer.write_char(ord("-")) | |
| writer.write_ucs4("max=\U0010ffff".encode(encoding)) | |
| writer.write_char(ord(".")) | |
| self.assertEqual(writer.finish(), | |
| "ascii-latin1=\xE9-euro=\u20AC-max=\U0010ffff.") | |
| # Test some special characters | |
| writer = self.create_writer(0) | |
| # Lone surrogate character | |
| writer.write_ucs4("lone\uDC80".encode(encoding, 'surrogatepass')) | |
| writer.write_char(ord("-")) | |
| # Surrogate pair | |
| writer.write_ucs4("pair\uD83D\uDC0D".encode(encoding, 'surrogatepass')) | |
| writer.write_char(ord("-")) | |
| writer.write_ucs4("null[\0]".encode(encoding), 7) | |
| writer.write_ucs4(NULL, 0) | |
| # CRASHES writer.write_ucs4(NULL, 1) | |
| self.assertEqual(writer.finish(), | |
| "lone\udc80-pair\ud83d\udc0d-null[\x00]") | |
| # Invalid UCS-4 characters. Create an invalid string in release mode, | |
| # or raise SystemError in debug mode. | |
| built_with_assert = support.built_with_c_assertions() | |
| writer = self.create_writer(0) | |
| for invalid_char in (INVALID_CHAR, MAX_INVALID_CHAR): | |
| with self.subTest(invalid_char=invalid_char): | |
| # Test single character | |
| ucs4_char = invalid_char.to_bytes(4, byteorder=sys.byteorder) | |
| if support.Py_DEBUG: | |
| self.assertRaises(SystemError, writer.write_ucs4, ucs4_char) | |
| else: | |
| writer.write_ucs4(ucs4_char) | |
| # Test multiple characters | |
| s = 'valid'.encode(encoding) + ucs4_char | |
| if support.Py_DEBUG: | |
| self.assertRaises(SystemError, writer.write_ucs4, s) | |
| else: | |
| writer.write_ucs4(s) | |
| if support.Py_DEBUG: | |
| self.assertEqual(writer.finish(), '') | |
| elif not built_with_assert: | |
| result = writer.finish() | |
| assert_invalid_string(self, result) | |
| else: | |
| # PyUnicodeWriter_Finish() fails with an assertion error | |
| pass | |
| # Invalid size | |
| writer = self.create_writer(0) | |
| with self.assertRaises(ValueError): | |
| writer.write_ucs4("text".encode(encoding), -1) | |
| self.assertRaises(ValueError, writer.write_ucs4, b'', -1) | |
| self.assertRaises(ValueError, writer.write_ucs4, NULL, -1) | |
| def test_substring_empty(self): | |
| writer = self.create_writer(0) | |
| writer.write_substring("abc", 1, 1) | |
| self.assertEqual(writer.finish(), '') | |
| def test_singletons(self): | |
| for size in (0, 123): | |
| with self.subTest(size=size): | |
| # PyUnicodeWriter_Finish() returns the empty string singleton | |
| # if no character has been written. | |
| writer = self.create_writer(size) | |
| writer.write_utf8(b'utf8', 0) | |
| writer.write_ascii(b'ascii', 0) | |
| writer.write_widechar(b'wstr', 0) | |
| writer.write_ucs4(b'ucs4', 0) | |
| writer.write_substring('text', 2, 2) | |
| self.assertEqual(writer.get_buffer(), (None, 127, False)) | |
| self.assertIs(writer.finish(), '') | |
| for size in (0, 123): | |
| for ch in range(256): | |
| with self.subTest(size=size, ch=ch): | |
| ch = chr(ch) | |
| maxchar = (255 if ord(ch) >= 128 else 127) | |
| # PyUnicodeWriter_WriteChar(ch) uses the read-only | |
| # optimization with the character singleton if ch is a | |
| # Latin1 character and no buffer was allocated yet. | |
| writer = self.create_writer(size) | |
| writer.write_char(ord(ch)) | |
| self.assertEqual(writer.get_buffer(), | |
| (1, maxchar, True)) | |
| self.assertIs(writer.finish(), ch) | |
| # PyUnicodeWriter_Finish() replaces the buffer with the | |
| # singleton. Use PyUnicodeWriter_WriteSubstring() to avoid | |
| # the read-only buffer optimization. | |
| writer = self.create_writer(size) | |
| writer.write_substring('xxx' + ch + 'y', 3, 4) | |
| self.assertEqual(writer.get_buffer(), | |
| (size or 1, maxchar, False)) | |
| self.assertIs(writer.finish(), ch) | |
| @unittest.skipUnless(support.Py_DEBUG, 'need debug build (Py_DEBUG)') | |
| def test_detect_overflow(self): | |
| # Test detection of buffer overflow | |
| code = textwrap.dedent(''' | |
| from test.support import SuppressCrashReport | |
| import _testinternalcapi | |
| SuppressCrashReport().__enter__() | |
| _testinternalcapi.unicodewriter_overflow() | |
| ''') | |
| proc = assert_python_failure('-c', code) | |
| self.assertIn(b'Buffer overflow detected in PyUnicodeWriter', proc.err) | |
| # Do not test the position value since it depends on the overallocation | |
| # strategy which depends on the operating system | |
| self.assertIn(f'at position '.encode(), proc.err) | |
| @support.nomemtest | |
| def test_memory_error(self): | |
| # Inject MemoryError in PyUnicodeWriter_WriteStr() | |
| writer = self.create_writer(0) | |
| writer.write_utf8(b"start", -1) | |
| self.assertEqual(writer.get_buffer(), (5, 127, False)) | |
| with self.assertRaises(MemoryError): | |
| with support.inject_memory_error_cm(): | |
| # Resize the internal str object | |
| writer.write_str("s" * 1024) | |
| writer.write_str(" end") | |
| self.assertEqual(writer.finish(), "start end") | |
| # Inject MemoryError in PyUnicodeWriter_Finish(). Use write_utf8() to | |
| # allocate a buffer of 1024 character. finish() needs to truncate the | |
| # buffer to 3 characters. | |
| writer = self.create_writer(1024) | |
| writer.write_utf8(b"abc", -1) | |
| self.assertEqual(writer.get_buffer(), (1024, 127, False)) | |
| with self.assertRaises(MemoryError): | |
| with support.inject_memory_error_cm(): | |
| writer.finish() | |
| def test_change_kind(self): | |
| writer = self.create_writer(0) | |
| # Create an ASCII buffer | |
| writer.write_str('ascii ') | |
| self.assertEqual(writer.get_buffer()[1], 127) | |
| # Change the buffer to UCS1 | |
| writer.write_str('latin1:\xe9 ') | |
| self.assertEqual(writer.get_buffer()[1], 255) | |
| # Change the buffer to UCS2 | |
| writer.write_str('ucs2:\u20ac ') | |
| self.assertEqual(writer.get_buffer()[1], 0xffff) | |
| # Change the buffer to UCS4 | |
| writer.write_str('ucs4:\U0010ffff') | |
| self.assertEqual(writer.get_buffer()[1], 0x10_ffff) | |
| self.assertEqual(writer.finish(), | |
| 'ascii latin1:\xe9 ucs2:\u20ac ucs4:\U0010ffff') | |
| def test_readonly_optim(self): | |
| # Read-only optimization: if the first and only write is a Python str | |
| # object and no buffer was allocated yet, return the object unchanged | |
| unique_string = 'unique string' | |
| expected = (len(unique_string), 127, True) | |
| for size in (0, 123): | |
| with self.subTest(size=size): | |
| # PyUnicodeWriter_WriteStr() optimization | |
| writer = self.create_writer(size) | |
| writer.write_str(unique_string) | |
| self.assertEqual(writer.get_buffer(), expected) | |
| self.assertIs(writer.finish(), unique_string) | |
| # PyUnicodeWriter_WriteSubstring() optimization | |
| writer = self.create_writer(size) | |
| writer.write_substring(unique_string, 0, len(unique_string)) | |
| self.assertEqual(writer.get_buffer(), expected) | |
| self.assertIs(writer.finish(), unique_string) | |
| # PyUnicodeWriter_WriteStr() optimization | |
| class MyStr: | |
| def __str__(self): | |
| return unique_string | |
| writer = self.create_writer(size) | |
| writer.write_str(MyStr()) | |
| self.assertEqual(writer.get_buffer(), expected) | |
| self.assertIs(writer.finish(), unique_string) | |
| # PyUnicodeWriter_WriteRepr() optimization | |
| class MyRepr: | |
| def __repr__(self): | |
| return unique_string | |
| writer = self.create_writer(size) | |
| writer.write_repr(MyRepr()) | |
| self.assertEqual(writer.get_buffer(), expected) | |
| self.assertIs(writer.finish(), unique_string) | |
| def test_readonly_optim_large_int(self): | |
| # Read-only optimization in _PyLong_FormatWriter() for large integer: | |
| # use _pylong.int_to_decimal_string() result as a read-only string. | |
| # See pylong_int_to_decimal_string(). | |
| self.addCleanup(sys.set_int_max_str_digits, | |
| sys.get_int_max_str_digits()) | |
| sys.set_int_max_str_digits(0) | |
| # _PyLong_FormatWriter() calls _pylong.int_to_decimal_string() for | |
| # integer with Py_SIZE() > 1000. | |
| large_int = 1 << (sys.int_info.bits_per_digit * 1020) | |
| large_int_str = str(large_int) | |
| expected = (len(large_int_str), 127, True) | |
| for size in (0, 123): | |
| with self.subTest(size=size): | |
| # Test PyUnicodeWriter_WriteStr() | |
| writer = self.create_writer(size) | |
| writer.write_str(large_int) | |
| self.assertEqual(writer.get_buffer(), expected) | |
| self.assertEqual(writer.finish(), large_int_str) | |
| # Test PyUnicodeWriter_WriteRepr() | |
| writer = self.create_writer(size) | |
| writer.write_repr(large_int) | |
| self.assertEqual(writer.get_buffer(), expected) | |
| self.assertEqual(writer.finish(), large_int_str) | |
| def test_finish_with_size(self): | |
| # Test _PyUnicodeWriter_FinishWithSize(). Truncate text requires | |
| # to change the buffer kind. | |
| text = 'a\xff\u20ac\U0010ffff' | |
| expected = (0x10ffff, False) # do not test the buffer size | |
| for size in range(len(text) + 1): | |
| writer = self.create_writer(0) | |
| writer.write_utf8(text.encode(), -1) | |
| self.assertEqual(writer.get_buffer()[1:], expected) | |
| self.assertEqual(writer.finish_with_size(size), text[:size]) | |
| # CRASHES writer.finish_with_size(len(text) + 1) | |
| # Test PyUnicodeWriter_Format() | |
| @unittest.skipIf(ctypes is None, 'need ctypes') | |
| class PyUnicodeWriterFormatTest(unittest.TestCase): | |
| def create_writer(self, size): | |
| return _testcapi.PyUnicodeWriter(size) | |
| def writer_format(self, writer, *args): | |
| from ctypes import c_char_p, pythonapi, c_int, c_void_p | |
| _PyUnicodeWriter_Format = getattr(pythonapi, "PyUnicodeWriter_Format") | |
| _PyUnicodeWriter_Format.argtypes = (c_void_p, c_char_p,) | |
| _PyUnicodeWriter_Format.restype = c_int | |
| if _PyUnicodeWriter_Format(writer.get_pointer(), *args) < 0: | |
| raise ValueError("PyUnicodeWriter_Format failed") | |
| def test_format(self): | |
| from ctypes import c_int | |
| writer = self.create_writer(0) | |
| self.writer_format(writer, b'%s %i', b'abc', c_int(123)) | |
| writer.write_char(ord('.')) | |
| self.assertEqual(writer.finish(), 'abc 123.') | |
| def test_recover_error(self): | |
| # test recovering from PyUnicodeWriter_Format() error | |
| writer = self.create_writer(0) | |
| self.writer_format(writer, b"%s ", b"Hello") | |
| # PyUnicodeWriter_Format() fails with an invalid format string | |
| with self.assertRaises(ValueError): | |
| self.writer_format(writer, b"%s\xff", b"World") | |
| # Retry PyUnicodeWriter_Format() with a valid format string | |
| self.writer_format(writer, b"%s.", b"World") | |
| self.assertEqual(writer.finish(), 'Hello World.') | |
| def test_recheck_maxchar(self): | |
| # PyUnicodeWriter_Format() changes buffer kind to UCS-2 before raising | |
| # an exception. Then, PyUnicodeWriter_Finish() has to change the buffer | |
| # kind back to ASCII. | |
| from ctypes import py_object | |
| class StrError: | |
| def __str__(self): | |
| raise RuntimeError("bug") | |
| writer = self.create_writer(0) | |
| # Allocate ASCII buffer | |
| writer.write_str('ascii') | |
| obj = StrError() | |
| ucs2_utf8 = '\u20ac'.encode() | |
| with self.assertRaises(RuntimeError): | |
| # Change buffer kind to UCS-2, but then raise RuntimeError | |
| self.writer_format(writer, b"%s%S", ucs2_utf8, py_object(obj)) | |
| writer.write_str('.') | |
| self.assertEqual(writer.finish(), 'ascii.') | |
| def test_readonly_optim(self): | |
| # Read-only optimization: if the first and only write is a Python str | |
| # object and no buffer was allocated yet, return the object unchanged | |
| from ctypes import py_object | |
| unique_string = 'unique string' | |
| for format in (b'%S', b'%U'): | |
| with self.subTest(format=format): | |
| writer = self.create_writer(0) | |
| self.writer_format(writer, format, py_object(unique_string)) | |
| self.assertIs(writer.finish(), unique_string) | |
| class MyStr: | |
| def __str__(self): | |
| return unique_string | |
| writer = self.create_writer(0) | |
| self.writer_format(writer, b'%S', py_object(MyStr())) | |
| self.assertIs(writer.finish(), unique_string) | |
| class MyRepr: | |
| def __repr__(self): | |
| return unique_string | |
| writer = self.create_writer(0) | |
| self.writer_format(writer, b'%R', py_object(MyRepr())) | |
| self.assertIs(writer.finish(), unique_string) | |
| # TODO: Add tests to the following codec functions: | |
| # - PyUnicode_AsASCIIString | |
| # - PyUnicode_AsCharmapString | |
| # - PyUnicode_AsEncodedString | |
| # - PyUnicode_AsLatin1String | |
| # - PyUnicode_AsMBCSString | |
| # - PyUnicode_AsRawUnicodeEscapeString | |
| # - PyUnicode_AsUTF16String | |
| # - PyUnicode_AsUTF32String | |
| # - PyUnicode_AsUTF8String | |
| # - PyUnicode_AsUnicodeEscapeString | |
| # - PyUnicode_BuildEncodingMap | |
| # - PyUnicode_Decode | |
| # - PyUnicode_DecodeASCII | |
| # - PyUnicode_DecodeCharmap | |
| # - PyUnicode_DecodeCodePageStateful | |
| # - PyUnicode_DecodeFSDefault | |
| # - PyUnicode_DecodeFSDefaultAndSize | |
| # - PyUnicode_DecodeLatin1 | |
| # - PyUnicode_DecodeLocale | |
| # - PyUnicode_DecodeLocaleAndSize | |
| # - PyUnicode_DecodeMBCS | |
| # - PyUnicode_DecodeMBCSStateful | |
| # - PyUnicode_DecodeRawUnicodeEscape | |
| # - PyUnicode_DecodeUTF16 | |
| # - PyUnicode_DecodeUTF16Stateful | |
| # - PyUnicode_DecodeUTF32 | |
| # - PyUnicode_DecodeUTF32Stateful | |
| # - PyUnicode_DecodeUTF7 | |
| # - PyUnicode_DecodeUTF7Stateful | |
| # - PyUnicode_DecodeUTF8 | |
| # - PyUnicode_DecodeUTF8Stateful | |
| # - PyUnicode_DecodeUnicodeEscape | |
| # - PyUnicode_EncodeCodePage | |
| # - PyUnicode_EncodeFSDefault | |
| # - PyUnicode_EncodeLocale | |
| # - PyUnicode_FSConverter | |
| # - PyUnicode_FSDecoder | |
| # - PyUnicode_FromEncodedObject | |
| # - PyUnicode_Splitlines | |
| # TODO: Add tests to the following character functions: | |
| # - Py_UNICODE_ISALNUM | |
| # - Py_UNICODE_ISALPHA | |
| # - Py_UNICODE_ISDECIMAL | |
| # - Py_UNICODE_ISDIGIT | |
| # - Py_UNICODE_ISLINEBREAK | |
| # - Py_UNICODE_ISLOWER | |
| # - Py_UNICODE_ISNUMERIC | |
| # - Py_UNICODE_ISPRINTABLE | |
| # - Py_UNICODE_ISSPACE | |
| # - Py_UNICODE_ISTITLE | |
| # - Py_UNICODE_ISUPPER | |
| # - Py_UNICODE_TODECIMAL | |
| # - Py_UNICODE_TODIGIT | |
| # - Py_UNICODE_TOLOWER | |
| # - Py_UNICODE_TONUMERIC | |
| # - Py_UNICODE_TOTITLE | |
| # - Py_UNICODE_TOUPPER | |
| # TODO: Maybe add tests to the following less important functions: | |
| # - PyUnicode_1BYTE_DATA | |
| # - PyUnicode_2BYTE_DATA | |
| # - PyUnicode_4BYTE_DATA | |
| # - PyUnicode_DATA | |
| # - PyUnicode_IS_READY | |
| # - PyUnicode_READY | |
| # - Py_UNICODE_HIGH_SURROGATE | |
| # - Py_UNICODE_IS_HIGH_SURROGATE | |
| # - Py_UNICODE_IS_LOW_SURROGATE | |
| # - Py_UNICODE_IS_SURROGATE | |
| # - Py_UNICODE_JOIN_SURROGATES | |
| # - Py_UNICODE_LOW_SURROGATE | |
| if __name__ == "__main__": | |
| unittest.main() |