https://github.com/python/cpython/commit/e8f644868b3ba350897c05c0d44b5b7081b263eb
commit: e8f644868b3ba350897c05c0d44b5b7081b263eb
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-19T16:08:58Z
summary:
gh-156939: Add tests on the PyUnicode C API (#157798)
Add tests on functions:
* PyUnicode_CHECK_INTERNED()
* PyUnicode_Check()
* PyUnicode_CheckExact()
* PyUnicode_Equal()
* PyUnicode_GET_LENGTH()
* PyUnicode_IS_ASCII()
* PyUnicode_IS_COMPACT()
* PyUnicode_IS_COMPACT_ASCII()
* PyUnicode_KIND()
* PyUnicode_MAX_CHAR_VALUE()
* PyUnicode_READ()
* PyUnicode_READ_CHAR()
* PyUnicode_WRITE()
Changes:
* Add test on PyUnicode_FromOrdinal(0).
* Require _testcapi module in test_capi.test_unicode. Since _testcapi
is specific to CPython, remove @support.cpython_only.
files:
M Lib/test/test_capi/test_unicode.py
M Modules/_testcapi/unicode.c
M Modules/_testcapi/util.h
M Modules/_testlimitedcapi/unicode.c
diff --git a/Lib/test/test_capi/test_unicode.py
b/Lib/test/test_capi/test_unicode.py
index f2b77e3fdb5fc4f..50e807e3e7db083 100644
--- a/Lib/test/test_capi/test_unicode.py
+++ b/Lib/test/test_capi/test_unicode.py
@@ -2,41 +2,72 @@
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 _testcapi
- from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX
-except ImportError:
- _testcapi = None
-try:
- import _testlimitedcapi
-except ImportError:
- _testlimitedcapi = None
-try:
- import _testinternalcapi
-except ImportError:
- _testinternalcapi = None
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
+
NULL = None
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
+
class CAPITest(unittest.TestCase):
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
+ 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 test_new(self):
"""Test PyUnicode_New()"""
- from _testcapi import unicode_new as new
+ new = _testcapi.unicode_new
for maxchar in 0, 0x61, 0xa1, 0x4f60, 0x1f600, 0x10ffff:
self.assertEqual(new(0, maxchar), '')
@@ -53,11 +84,9 @@ def test_new(self):
self.assertRaises(SystemError, new, -1, 0)
self.assertRaises(SystemError, new, PY_SSIZE_T_MIN, 0)
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_fill(self):
"""Test PyUnicode_Fill()"""
- from _testcapi import unicode_fill as fill
+ fill = _testcapi.unicode_fill
strings = [
# all strings have exactly 5 characters
@@ -93,12 +122,7 @@ def test_fill(self):
# CRASHES fill(NULL, 0, 0, 0x78)
# TODO: Test PyUnicode_Fill() with non-modifiable unicode.
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
- def test_writechar(self):
- """Test PyUnicode_WriteChar()"""
- from _testlimitedcapi import unicode_writechar as writechar
-
+ def _test_writechar(self, writechar, *, check):
strings = [
# one string for every kind
'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16',
@@ -111,24 +135,31 @@ def test_writechar(self):
if j <= i:
self.assertEqual(writechar(s, 1, c),
(s[:1] + chr(c) + s[2:], 0))
- else:
+ elif check:
self.assertRaises(ValueError, writechar, s, 1, c)
- 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.
-
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
+ 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()"""
+ self._test_writechar(_testlimitedcapi.unicode_writechar, check=True)
+
+ def test_write_macro(self):
+ """Test PyUnicode_WRITE()"""
+ self._test_writechar(_testcapi.unicode_write, check=False)
+
def test_resize(self):
"""Test PyUnicode_Resize()"""
- from _testlimitedcapi import unicode_resize as resize
+ resize = _testlimitedcapi.unicode_resize
strings = [
# all strings have exactly 3 characters
@@ -150,11 +181,9 @@ def test_resize(self):
# TODO: Test PyUnicode_Resize() with non-modifiable and legacy unicode
# and with NULL as the address.
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_append(self):
"""Test PyUnicode_Append()"""
- from _testlimitedcapi import unicode_append as append
+ append = _testlimitedcapi.unicode_append
strings = [
'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16',
@@ -178,11 +207,9 @@ def test_append(self):
# and with NULL as the address.
# TODO: Check reference counts.
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_appendanddel(self):
"""Test PyUnicode_AppendAndDel()"""
- from _testlimitedcapi import unicode_appendanddel as appendanddel
+ appendanddel = _testlimitedcapi.unicode_appendanddel
strings = [
'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16',
@@ -205,11 +232,9 @@ def test_appendanddel(self):
# and with NULL as the address.
# TODO: Check reference counts.
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_fromstringandsize(self):
"""Test PyUnicode_FromStringAndSize()"""
- from _testlimitedcapi import unicode_fromstringandsize as
fromstringandsize
+ fromstringandsize = _testlimitedcapi.unicode_fromstringandsize
self.assertEqual(fromstringandsize(b'abc'), 'abc')
self.assertEqual(fromstringandsize(b'abc', 2), 'ab')
@@ -230,11 +255,9 @@ def test_fromstringandsize(self):
self.assertRaises(SystemError, fromstringandsize, NULL, 3)
self.assertRaises(SystemError, fromstringandsize, NULL, PY_SSIZE_T_MAX)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_fromstring(self):
"""Test PyUnicode_FromString()"""
- from _testlimitedcapi import unicode_fromstring as fromstring
+ fromstring = _testlimitedcapi.unicode_fromstring
self.assertEqual(fromstring(b'abc'), 'abc')
self.assertEqual(fromstring(b'\xc2\xa1\xc2\xa2'), '\xa1\xa2')
@@ -246,11 +269,9 @@ def test_fromstring(self):
# CRASHES fromstring(NULL)
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_fromkindanddata(self):
"""Test PyUnicode_FromKindAndData()"""
- from _testcapi import unicode_fromkindanddata as fromkindanddata
+ fromkindanddata = _testcapi.unicode_fromkindanddata
strings = [
'abcde', '\xa1\xa2\xa3\xa4\xa5',
@@ -282,11 +303,9 @@ def test_fromkindanddata(self):
# CRASHES fromkindanddata(1, NULL, 1)
# CRASHES fromkindanddata(4, b'\xff\xff\xff\xff')
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_substring(self):
"""Test PyUnicode_Substring()"""
- from _testlimitedcapi import unicode_substring as substring
+ substring = _testlimitedcapi.unicode_substring
strings = [
'ab', 'ab\xa1\xa2',
@@ -306,44 +325,68 @@ def test_substring(self):
# CRASHES substring([], 0, 0)
# CRASHES substring(NULL, 0, 0)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
- def test_getlength(self):
- """Test PyUnicode_GetLength()"""
- from _testlimitedcapi import unicode_getlength as getlength
-
- for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600',
+ 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))
- self.assertRaises(TypeError, getlength, b'abc')
- self.assertRaises(TypeError, getlength, [])
+ if check:
+ self.assertRaises(TypeError, getlength, b'abc')
+ self.assertRaises(TypeError, getlength, [])
# CRASHES getlength(NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
- def test_readchar(self):
- """Test PyUnicode_ReadChar()"""
- from _testlimitedcapi import unicode_readchar as readchar
+ 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))
- self.assertRaises(IndexError, readchar, s, len(s))
- self.assertRaises(IndexError, readchar, s, PY_SSIZE_T_MAX)
- self.assertRaises(IndexError, readchar, s, -1)
- self.assertRaises(IndexError, readchar, s, PY_SSIZE_T_MIN)
+ 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)
- self.assertRaises(TypeError, readchar, b'abc', 0)
- self.assertRaises(TypeError, readchar, [], 0)
- # CRASHES readchar(NULL, 0)
+ 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)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_fromobject(self):
"""Test PyUnicode_FromObject()"""
- from _testlimitedcapi import unicode_fromobject as fromobject
+ fromobject = _testlimitedcapi.unicode_fromobject
for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600',
'a\ud800b\udfffc', '\ud834\udd1e']:
@@ -792,11 +835,9 @@ class LocalType:
self.assertRaisesRegex(SystemError, 'invalid format string',
PyUnicode_FromFormat, b'%+i', c_int(10))
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_interninplace(self):
"""Test PyUnicode_InternInPlace()"""
- from _testlimitedcapi import unicode_interninplace as interninplace
+ interninplace = _testlimitedcapi.unicode_interninplace
s = b'abc'.decode()
r = interninplace(s)
@@ -805,11 +846,9 @@ def test_interninplace(self):
# CRASHES interninplace(b'abc')
# CRASHES interninplace(NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_internfromstring(self):
"""Test PyUnicode_InternFromString()"""
- from _testlimitedcapi import unicode_internfromstring as
internfromstring
+ internfromstring = _testlimitedcapi.unicode_internfromstring
self.assertEqual(internfromstring(b'abc'), 'abc')
self.assertEqual(internfromstring(b'\xf0\x9f\x98\x80'), '\U0001f600')
@@ -819,12 +858,9 @@ def test_internfromstring(self):
# CRASHES internfromstring(NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_fromwidechar(self):
"""Test PyUnicode_FromWideChar()"""
- from _testlimitedcapi import unicode_fromwidechar as fromwidechar
- from _testcapi import SIZEOF_WCHAR_T
+ fromwidechar = _testlimitedcapi.unicode_fromwidechar
if SIZEOF_WCHAR_T == 2:
encoding = 'utf-16le' if sys.byteorder == 'little' else 'utf-16be'
@@ -856,13 +892,10 @@ def test_fromwidechar(self):
#self.assertRaises(MemoryError, fromwidechar, b'', PY_SSIZE_T_MAX)
#self.assertRaises(SystemError, fromwidechar, b'\0'*SIZEOF_WCHAR_T,
PY_SSIZE_T_MIN)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_aswidechar(self):
"""Test PyUnicode_AsWideChar()"""
- from _testlimitedcapi import unicode_aswidechar
- from _testlimitedcapi import unicode_aswidechar_null
- from _testcapi import SIZEOF_WCHAR_T
+ unicode_aswidechar = _testlimitedcapi.unicode_aswidechar
+ unicode_aswidechar_null = _testlimitedcapi.unicode_aswidechar_null
wchar, size = unicode_aswidechar('abcdef', 2)
self.assertEqual(size, 2)
@@ -904,13 +937,10 @@ def test_aswidechar(self):
self.assertRaises(TypeError, unicode_aswidechar_null, [], 10)
self.assertRaises(SystemError, unicode_aswidechar_null, NULL, 10)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_aswidecharstring(self):
"""Test PyUnicode_AsWideCharString()"""
- from _testlimitedcapi import unicode_aswidecharstring
- from _testlimitedcapi import unicode_aswidecharstring_null
- from _testcapi import SIZEOF_WCHAR_T
+ unicode_aswidecharstring = _testlimitedcapi.unicode_aswidecharstring
+ unicode_aswidecharstring_null =
_testlimitedcapi.unicode_aswidecharstring_null
wchar, size = unicode_aswidecharstring('abc')
self.assertEqual(size, 3)
@@ -939,11 +969,9 @@ def test_aswidecharstring(self):
self.assertRaises(TypeError, unicode_aswidecharstring_null, [])
self.assertRaises(SystemError, unicode_aswidecharstring_null, NULL)
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_asucs4(self):
"""Test PyUnicode_AsUCS4()"""
- from _testcapi import unicode_asucs4
+ unicode_asucs4 = _testcapi.unicode_asucs4
for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600',
'a\ud800b\udfffc', '\ud834\udd1e']:
@@ -964,11 +992,9 @@ def test_asucs4(self):
# CRASHES unicode_asucs4(NULL, 1, 0)
# CRASHES unicode_asucs4(NULL, 1, 1)
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_asucs4copy(self):
"""Test PyUnicode_AsUCS4Copy()"""
- from _testcapi import unicode_asucs4copy as asucs4copy
+ asucs4copy = _testcapi.unicode_asucs4copy
for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600',
'a\ud800b\udfffc', '\ud834\udd1e']:
@@ -980,12 +1006,11 @@ def test_asucs4copy(self):
# CRASHES asucs4copy([])
# CRASHES asucs4copy(NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_fromordinal(self):
"""Test PyUnicode_FromOrdinal()"""
- from _testlimitedcapi import unicode_fromordinal as 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')
@@ -993,11 +1018,9 @@ def test_fromordinal(self):
self.assertRaises(ValueError, fromordinal, 0x110000)
self.assertRaises(ValueError, fromordinal, -1)
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_asutf8(self):
"""Test PyUnicode_AsUTF8()"""
- from _testcapi import unicode_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')
@@ -1009,12 +1032,10 @@ def test_asutf8(self):
self.assertRaises(TypeError, unicode_asutf8, [], 0)
# CRASHES unicode_asutf8(NULL, 0)
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
@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
- from threading import Thread
data = "😊"
@@ -1027,12 +1048,10 @@ def worker():
pass
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_asutf8andsize(self):
"""Test PyUnicode_AsUTF8AndSize()"""
- from _testlimitedcapi import unicode_asutf8andsize
- from _testlimitedcapi import unicode_asutf8andsize_null
+ 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))
@@ -1050,19 +1069,15 @@ def test_asutf8andsize(self):
# CRASHES unicode_asutf8andsize(NULL, 0)
# CRASHES unicode_asutf8andsize_null(NULL, 0)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_getdefaultencoding(self):
"""Test PyUnicode_GetDefaultEncoding()"""
- from _testlimitedcapi import unicode_getdefaultencoding as
getdefaultencoding
+ getdefaultencoding = _testlimitedcapi.unicode_getdefaultencoding
self.assertEqual(getdefaultencoding(), b'utf-8')
- @support.cpython_only
- @unittest.skipIf(_testinternalcapi is None, 'need _testinternalcapi
module')
def test_transform_decimal_and_space(self):
"""Test _PyUnicode_TransformDecimalAndSpaceToASCII()"""
- from _testinternalcapi import
_PyUnicode_TransformDecimalAndSpaceToASCII as transform_decimal
+ transform_decimal =
_testinternalcapi._PyUnicode_TransformDecimalAndSpaceToASCII
self.assertEqual(transform_decimal('123'),
'123')
@@ -1078,11 +1093,9 @@ def test_transform_decimal_and_space(self):
self.assertRaises(SystemError, transform_decimal, [])
# CRASHES transform_decimal(NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_concat(self):
"""Test PyUnicode_Concat()"""
- from _testlimitedcapi import unicode_concat as concat
+ concat = _testlimitedcapi.unicode_concat
self.assertEqual(concat('abc', 'def'), 'abcdef')
self.assertEqual(concat('abc', 'где'), 'abcгде')
@@ -1099,11 +1112,9 @@ def test_concat(self):
# CRASHES concat(NULL, 'def')
# CRASHES concat('abc', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_split(self):
"""Test PyUnicode_Split()"""
- from _testlimitedcapi import unicode_split as 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'])
@@ -1127,11 +1138,9 @@ def test_split(self):
self.assertRaises(TypeError, split, [], '|')
# CRASHES split(NULL, '|')
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_rsplit(self):
"""Test PyUnicode_RSplit()"""
- from _testlimitedcapi import unicode_rsplit as 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'])
@@ -1156,11 +1165,9 @@ def test_rsplit(self):
self.assertRaises(TypeError, rsplit, [], '|')
# CRASHES rsplit(NULL, '|')
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_partition(self):
"""Test PyUnicode_Partition()"""
- from _testlimitedcapi import unicode_partition as partition
+ partition = _testlimitedcapi.unicode_partition
self.assertEqual(partition('a|b|c', '|'), ('a', '|', 'b|c'))
self.assertEqual(partition('a||b||c', '||'), ('a', '||', 'b||c'))
@@ -1176,11 +1183,9 @@ def test_partition(self):
# CRASHES partition(NULL, '|')
# CRASHES partition('a|b|c', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_rpartition(self):
"""Test PyUnicode_RPartition()"""
- from _testlimitedcapi import unicode_rpartition as rpartition
+ rpartition = _testlimitedcapi.unicode_rpartition
self.assertEqual(rpartition('a|b|c', '|'), ('a|b', '|', 'c'))
self.assertEqual(rpartition('a||b||c', '||'), ('a||b', '||', 'c'))
@@ -1196,11 +1201,9 @@ def test_rpartition(self):
# CRASHES rpartition(NULL, '|')
# CRASHES rpartition('a|b|c', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_splitlines(self):
"""Test PyUnicode_SplitLines()"""
- from _testlimitedcapi import unicode_splitlines as 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),
@@ -1214,11 +1217,9 @@ def test_splitlines(self):
self.assertRaises(TypeError, splitlines, b'a\nb\rc\r\nd')
# CRASHES splitlines(NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_translate(self):
"""Test PyUnicode_Translate()"""
- from _testlimitedcapi import unicode_translate as 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('в'): '<>'}), 'АБ<>г')
@@ -1239,11 +1240,9 @@ def test_translate(self):
self.assertRaises(LookupError, translate, 'abc', {ord('b'): None},
'foo')
# CRASHES translate(NULL, [])
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_join(self):
"""Test PyUnicode_Join()"""
- from _testlimitedcapi import unicode_join as 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')
@@ -1257,11 +1256,9 @@ def test_join(self):
self.assertRaises(TypeError, join, '|', 123)
self.assertRaises(SystemError, join, '|', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_count(self):
"""Test PyUnicode_Count()"""
- from _testlimitedcapi import unicode_count
+ unicode_count = _testlimitedcapi.unicode_count
for str in "\xa1", "\u8000\u8080", "\ud800\udc02",
"\U0001f100\U0001f1f1":
for i, ch in enumerate(str):
@@ -1288,11 +1285,9 @@ def test_count(self):
# CRASHES unicode_count(NULL, '!', 0, len(str))
# CRASHES unicode_count(str, NULL, 0, len(str))
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_tailmatch(self):
"""Test PyUnicode_Tailmatch()"""
- from _testlimitedcapi import unicode_tailmatch as tailmatch
+ tailmatch = _testlimitedcapi.unicode_tailmatch
str = 'ababahalamaha'
self.assertEqual(tailmatch(str, 'aba', 0, len(str), -1), 1)
@@ -1323,11 +1318,9 @@ def test_tailmatch(self):
# CRASHES tailmatch(NULL, 'aba', 0, len(str), -1)
# CRASHES tailmatch(str, NULL, 0, len(str), -1)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_find(self):
"""Test PyUnicode_Find()"""
- from _testlimitedcapi import unicode_find as find
+ find = _testlimitedcapi.unicode_find
for str in "\xa1", "\u8000\u8080", "\ud800\udc02",
"\U0001f100\U0001f1f1":
for i, ch in enumerate(str):
@@ -1364,11 +1357,9 @@ def test_find(self):
# CRASHES find(NULL, '!', 0, len(str), 1)
# CRASHES find(str, NULL, 0, len(str), 1)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_findchar(self):
"""Test PyUnicode_FindChar()"""
- from _testlimitedcapi import unicode_findchar
+ unicode_findchar = _testlimitedcapi.unicode_findchar
for str in "\xa1", "\u8000\u8080", "\ud800\udc02",
"\U0001f100\U0001f1f1":
for i, ch in enumerate(str):
@@ -1400,11 +1391,9 @@ def test_findchar(self):
# CRASHES unicode_findchar([], ord('!'), 0, len(str), 1)
# CRASHES unicode_findchar(NULL, ord('!'), 0, len(str), 1), 1)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_replace(self):
"""Test PyUnicode_Replace()"""
- from _testlimitedcapi import unicode_replace as replace
+ replace = _testlimitedcapi.unicode_replace
str = 'abracadabra'
self.assertEqual(replace(str, 'a', '='), '=br=c=d=br=')
@@ -1431,11 +1420,9 @@ def test_replace(self):
# CRASHES replace('a', NULL, '=')
# CRASHES replace(NULL, 'a', '=')
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_compare(self):
"""Test PyUnicode_Compare()"""
- from _testlimitedcapi import unicode_compare as compare
+ compare = _testlimitedcapi.unicode_compare
self.assertEqual(compare('abc', 'abc'), 0)
self.assertEqual(compare('abc', 'def'), -1)
@@ -1453,11 +1440,9 @@ def test_compare(self):
# CRASHES compare(NULL, 'abc')
# CRASHES compare('abc', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_comparewithasciistring(self):
"""Test PyUnicode_CompareWithASCIIString()"""
- from _testlimitedcapi import unicode_comparewithasciistring as
comparewithasciistring
+ comparewithasciistring =
_testlimitedcapi.unicode_comparewithasciistring
self.assertEqual(comparewithasciistring('abc', b'abc'), 0)
self.assertEqual(comparewithasciistring('abc', b'def'), -1)
@@ -1470,12 +1455,10 @@ def test_comparewithasciistring(self):
# CRASHES comparewithasciistring([], b'abc')
# CRASHES comparewithasciistring(NULL, b'abc')
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_equaltoutf8(self):
# Test PyUnicode_EqualToUTF8()
- from _testlimitedcapi import unicode_equaltoutf8 as equaltoutf8
- from _testlimitedcapi import unicode_asutf8andsize as asutf8andsize
+ equaltoutf8 = _testlimitedcapi.unicode_equaltoutf8
+ asutf8andsize = _testlimitedcapi.unicode_asutf8andsize
strings = [
'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16',
@@ -1516,12 +1499,10 @@ def test_equaltoutf8(self):
self.assertEqual(equaltoutf8('\ud801',
'\ud801'.encode("utf8", "surrogatepass")), 0)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_equaltoutf8andsize(self):
# Test PyUnicode_EqualToUTF8AndSize()
- from _testlimitedcapi import unicode_equaltoutf8andsize as
equaltoutf8andsize
- from _testlimitedcapi import unicode_asutf8andsize as asutf8andsize
+ equaltoutf8andsize = _testlimitedcapi.unicode_equaltoutf8andsize
+ asutf8andsize = _testlimitedcapi.unicode_asutf8andsize
strings = [
'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16',
@@ -1585,11 +1566,9 @@ def check_not_equal_encoding(text, encoding):
# CRASHES equaltoutf8andsize(NULL, b'abc')
# CRASHES equaltoutf8andsize('abc', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_richcompare(self):
"""Test PyUnicode_RichCompare()"""
- from _testlimitedcapi import unicode_richcompare as richcompare
+ richcompare = _testlimitedcapi.unicode_richcompare
LT, LE, EQ, NE, GT, GE = range(6)
strings = ('abc', 'абв', '\U0001f600', 'abc\0')
@@ -1613,11 +1592,25 @@ def test_richcompare(self):
# CRASHES richcompare(NULL, 'abc', op)
# CRASHES richcompare('abc', NULL, op)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
+ 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()"""
- from _testlimitedcapi import unicode_format as format
+ format = _testlimitedcapi.unicode_format
self.assertEqual(format('x=%d!', 42), 'x=42!')
self.assertEqual(format('x=%d!', (42,)), 'x=42!')
@@ -1626,11 +1619,9 @@ def test_format(self):
self.assertRaises(SystemError, format, 'x=%d!', NULL)
self.assertRaises(SystemError, format, NULL, 42)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_contains(self):
"""Test PyUnicode_Contains()"""
- from _testlimitedcapi import unicode_contains as contains
+ contains = _testlimitedcapi.unicode_contains
self.assertEqual(contains('abcd', ''), 1)
self.assertEqual(contains('abcd', 'b'), 1)
@@ -1648,11 +1639,9 @@ def test_contains(self):
# CRASHES contains(NULL, 'b')
# CRASHES contains('abcd', NULL)
- @support.cpython_only
- @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_isidentifier(self):
"""Test PyUnicode_IsIdentifier()"""
- from _testlimitedcapi import unicode_isidentifier as isidentifier
+ isidentifier = _testlimitedcapi.unicode_isidentifier
self.assertEqual(isidentifier("a"), 1)
self.assertEqual(isidentifier("b0"), 1)
@@ -1670,11 +1659,9 @@ def test_isidentifier(self):
# CRASHES isidentifier([])
# CRASHES isidentifier(NULL)
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_copycharacters(self):
"""Test PyUnicode_CopyCharacters()"""
- from _testcapi import unicode_copycharacters
+ unicode_copycharacters = _testcapi.unicode_copycharacters
strings = [
# all strings have exactly 5 characters
@@ -1725,11 +1712,9 @@ def test_copycharacters(self):
# TODO: Test PyUnicode_CopyCharacters() with non-unicode and
# non-modifiable unicode as "to".
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_pep393_utf8_caching_bug(self):
# Issue #25709: Problem with string concatenation and utf-8 cache
- from _testcapi import getargs_s_hash
+ getargs_s_hash = _testcapi.getargs_s_hash
for k in 0x24, 0xa4, 0x20ac, 0x1f40d:
s = ''
for i in range(5):
@@ -1743,10 +1728,9 @@ def test_pep393_utf8_caching_bug(self):
# Check that the second call returns the same result
self.assertEqual(getargs_s_hash(s), chr(k).encode() * (i + 1))
- @support.cpython_only
- @unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_GET_CACHED_HASH(self):
- from _testcapi import unicode_GET_CACHED_HASH
+ """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')
@@ -1757,9 +1741,86 @@ def test_GET_CACHED_HASH(self):
# 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('\U0010ffff'), 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)
+
class PyUnicodeWriterTest(unittest.TestCase):
def create_writer(self, size):
+ # Test PyUnicodeWriter_Create()
return _testcapi.PyUnicodeWriter(size)
def test_basic(self):
@@ -1782,6 +1843,7 @@ def test_basic(self):
# test PyUnicodeWriter_WriteRepr()
writer.write_repr("repr")
+ # test PyUnicodeWriter_Finish()
self.assertEqual(writer.finish(),
"var=long value 'repr'")
@@ -1818,6 +1880,7 @@ def test_utf8(self):
"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)
@@ -1908,8 +1971,7 @@ def test_decode_utf8_consumed(self):
self.assertEqual(writer.finish(),
"text-\xE9-\u20AC-more-incomplete-default")
def test_widechar(self):
- from _testcapi import SIZEOF_WCHAR_T
-
+ # Test PyUnicodeWriter_WriteWideChar()
if SIZEOF_WCHAR_T == 2:
encoding = 'utf-16le' if sys.byteorder == 'little' else 'utf-16be'
elif SIZEOF_WCHAR_T == 4:
@@ -1940,6 +2002,7 @@ def test_widechar(self):
"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)
@@ -1984,8 +2047,10 @@ def test_substring_empty(self):
self.assertEqual(writer.finish(), '')
def test_singletons(self):
- writer = self.create_writer(5)
- self.assertIs(writer.finish(), '')
+ for size in (0, 123):
+ with self.subTest(size=size):
+ writer = self.create_writer(size)
+ self.assertIs(writer.finish(), '')
for ch in range(256):
with self.subTest(ch=ch):
@@ -2011,6 +2076,7 @@ def test_detect_overflow(self):
self.assertIn(f'at position '.encode(), proc.err)
+# Test PyUnicodeWriter_Format()
@unittest.skipIf(ctypes is None, 'need ctypes')
class PyUnicodeWriterFormatTest(unittest.TestCase):
def create_writer(self, size):
@@ -2079,6 +2145,80 @@ def copy(text):
# CRASHES unicode_equal("abc", NULL)
# CRASHES unicode_equal(NULL, "abc")
+ # 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()
diff --git a/Modules/_testcapi/unicode.c b/Modules/_testcapi/unicode.c
index c23aab385b1d7d5..8c9e3e9b5321d4b 100644
--- a/Modules/_testcapi/unicode.c
+++ b/Modules/_testcapi/unicode.c
@@ -220,6 +220,8 @@ unicode_copycharacters(PyObject *self, PyObject *args)
return Py_BuildValue("(Nn)", to_copy, copied);
}
+
+// Test PyUnstable_Unicode_GET_CACHED_HASH()
static PyObject*
unicode_GET_CACHED_HASH(PyObject *self, PyObject *arg)
{
@@ -295,6 +297,148 @@ corrupt_unicode(PyObject *Py_UNUSED(module), PyObject
*args)
}
+/* Test PyUnicode_READ_CHAR() */
+static PyObject *
+unicode_read_char(PyObject *self, PyObject *args)
+{
+ PyObject *unicode;
+ Py_ssize_t index;
+ if (!PyArg_ParseTuple(args, "On", &unicode, &index)) {
+ return NULL;
+ }
+ NULLABLE(unicode);
+
+ Py_UCS4 result = PyUnicode_READ_CHAR(unicode, index);
+ return PyLong_FromUnsignedLong(result);
+}
+
+
+/* Test PyUnicode_READ() macro */
+static PyObject *
+unicode_read(PyObject *self, PyObject *args)
+{
+ PyObject *unicode;
+ Py_ssize_t index;
+ if (!PyArg_ParseTuple(args, "On", &unicode, &index)) {
+ return NULL;
+ }
+ NULLABLE(unicode);
+
+ int kind = PyUnicode_KIND(unicode);
+ const void *data = PyUnicode_DATA(unicode);
+ Py_UCS4 result = PyUnicode_READ(kind, data, index);
+ return PyLong_FromUnsignedLong(result);
+}
+
+
+/* Test PyUnicode_WRITE() macro */
+static PyObject *
+unicode_write(PyObject *self, PyObject *args)
+{
+ PyObject *unicode;
+ Py_ssize_t index;
+ unsigned int character;
+ if (!PyArg_ParseTuple(args, "OnI", &unicode, &index, &character)) {
+ return NULL;
+ }
+ NULLABLE(unicode);
+
+ PyObject *copy = unicode_copy(unicode);
+ if (copy == NULL) {
+ return NULL;
+ }
+
+ int kind = PyUnicode_KIND(copy);
+ const void *data = PyUnicode_DATA(copy);
+ PyUnicode_WRITE(kind, data, index, character);
+ // Same return value than _testlimitedcapi unicode_writechar():
+ // always use 0 as the function result
+ return Py_BuildValue("(Ni)", copy, 0);
+}
+
+
+/* Test PyUnicode_KIND() macro */
+static PyObject *
+unicode_kind(PyObject *self, PyObject *unicode)
+{
+ NULLABLE(unicode);
+
+ int kind = PyUnicode_KIND(unicode);
+ return PyLong_FromLong(kind);
+}
+
+
+/* Test PyUnicode_MAX_CHAR_VALUE() macro */
+static PyObject *
+unicode_max_char_value(PyObject *self, PyObject *unicode)
+{
+ NULLABLE(unicode);
+
+ Py_UCS4 maxchar = PyUnicode_MAX_CHAR_VALUE(unicode);
+ return PyLong_FromUnsignedLong(maxchar);
+}
+
+
+/* Test PyUnicode_GET_LENGTH() macro */
+static PyObject *
+unicode_getlength_macro(PyObject *self, PyObject *arg)
+{
+ NULLABLE(arg);
+ return PyLong_FromSsize_t(PyUnicode_GET_LENGTH(arg));
+}
+
+
+/* Test PyUnicode_Equal() macro */
+static PyObject *
+unicode_equal(PyObject *self, PyObject *args)
+{
+ PyObject *str1, *str2;
+ if (!PyArg_ParseTuple(args, "OnI", &str1, &str2)) {
+ return NULL;
+ }
+ NULLABLE(str1);
+ NULLABLE(str2);
+
+ RETURN_INT(PyUnicode_Equal(str1, str2));
+}
+
+
+/* Test PyUnicode_CHECK_INTERNED() macro */
+static PyObject *
+unicode_check_interned(PyObject *self, PyObject *arg)
+{
+ NULLABLE(arg);
+ RETURN_UINT(PyUnicode_CHECK_INTERNED(arg));
+}
+
+
+/* Test PyUnicode_IS_ASCII() macro */
+static PyObject *
+unicode_is_ascii(PyObject *self, PyObject *arg)
+{
+ NULLABLE(arg);
+ RETURN_UINT(PyUnicode_IS_ASCII(arg));
+}
+
+
+/* Test PyUnicode_IS_COMPACT() macro */
+static PyObject *
+unicode_is_compact(PyObject *self, PyObject *arg)
+{
+ NULLABLE(arg);
+ RETURN_UINT(PyUnicode_IS_COMPACT(arg));
+}
+
+
+/* Test PyUnicode_IS_COMPACT_ASCII() macro */
+static PyObject *
+unicode_is_compact_ascii(PyObject *self, PyObject *arg)
+{
+ NULLABLE(arg);
+ RETURN_INT(PyUnicode_IS_COMPACT_ASCII(arg));
+}
+
+
// --- PyUnicodeWriter type -------------------------------------------------
typedef struct {
@@ -642,6 +786,17 @@ static PyMethodDef TestMethods[] = {
{"unicode_GET_CACHED_HASH", unicode_GET_CACHED_HASH, METH_O},
{"test_py_identifier", test_py_identifier, METH_NOARGS},
{"corrupt_unicode", corrupt_unicode, METH_VARARGS},
+ {"unicode_read_char", unicode_read_char, METH_VARARGS},
+ {"unicode_read", unicode_read, METH_VARARGS},
+ {"unicode_write", unicode_write, METH_VARARGS},
+ {"unicode_kind", unicode_kind, METH_O},
+ {"unicode_max_char_value", unicode_max_char_value, METH_O},
+ {"unicode_getlength_macro", unicode_getlength_macro, METH_O},
+ {"unicode_equal", unicode_equal, METH_VARARGS},
+ {"unicode_check_interned", unicode_check_interned, METH_O},
+ {"unicode_is_ascii", unicode_is_ascii, METH_O},
+ {"unicode_is_compact", unicode_is_compact, METH_O},
+ {"unicode_is_compact_ascii", unicode_is_compact_ascii, METH_O},
{NULL},
};
diff --git a/Modules/_testcapi/util.h b/Modules/_testcapi/util.h
index 042e522542eddb6..c20472e24f3d465 100644
--- a/Modules/_testcapi/util.h
+++ b/Modules/_testcapi/util.h
@@ -14,6 +14,16 @@
return PyLong_FromLong(_ret); \
} while (0)
+#define RETURN_UINT(value) do { \
+ unsigned int _ret = (value); \
+ if (_ret == (unsigned int)-1) { \
+ assert(PyErr_Occurred()); \
+ return NULL; \
+ } \
+ assert(!PyErr_Occurred()); \
+ return PyLong_FromUnsignedLong(_ret); \
+ } while (0)
+
#define RETURN_SIZE(value) do { \
Py_ssize_t _ret = (value); \
if (_ret == -1) { \
diff --git a/Modules/_testlimitedcapi/unicode.c
b/Modules/_testlimitedcapi/unicode.c
index 980b192fdc3cbb6..a8511a8242fe7a1 100644
--- a/Modules/_testlimitedcapi/unicode.c
+++ b/Modules/_testlimitedcapi/unicode.c
@@ -1855,6 +1855,23 @@ unicode_equal(PyObject *module, PyObject *args)
}
+/* Test PyUnicode_Check() */
+static PyObject *
+unicode_check(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PyUnicode_Check(obj));
+}
+
+
+/* Test PyUnicode_CheckExact() */
+static PyObject *
+unicode_checkexact(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PyUnicode_CheckExact(obj));
+}
+
static PyMethodDef TestMethods[] = {
{"codec_incrementalencoder", codec_incrementalencoder, METH_VARARGS},
@@ -1944,6 +1961,8 @@ static PyMethodDef TestMethods[] = {
{"unicode_contains", unicode_contains, METH_VARARGS},
{"unicode_isidentifier", unicode_isidentifier, METH_O},
{"unicode_equal", unicode_equal, METH_VARARGS},
+ {"unicode_check", unicode_check, METH_O},
+ {"unicode_checkexact", unicode_checkexact, METH_O},
{NULL},
};
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]