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]

Reply via email to