import unittest
from collections import OrderedDict
from test import support
from test.support import import_helper

_testcapi = import_helper.import_module('_testcapi')
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX

NULL = None

class StrSubclass(str):
    pass

class BytesSubclass(bytes):
    pass

class WithStr:
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return self.value

class WithRepr:
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return self.value

class WithBytes:
    def __init__(self, value):
        self.value = value
    def __bytes__(self):
        return self.value

class TestObject:
    @property
    def evil(self):
        raise RuntimeError('do not get evil')
    @evil.setter
    def evil(self, value):
        raise RuntimeError('do not set evil')
    @evil.deleter
    def evil(self):
        raise RuntimeError('do not del evil')

class ProxyGetItem:
    def __init__(self, obj):
        self.obj = obj
    def __getitem__(self, key):
        return self.obj[key]

class ProxySetItem:
    def __init__(self, obj):
        self.obj = obj
    def __setitem__(self, key, value):
        self.obj[key] = value

class ProxyDelItem:
    def __init__(self, obj):
        self.obj = obj
    def __delitem__(self, key):
        del self.obj[key]

def gen():
    yield 'a'
    yield 'b'
    yield 'c'


class CAPITest(unittest.TestCase):
    def assertTypedEqual(self, actual, expected):
        self.assertIs(type(actual), type(expected))
        self.assertEqual(actual, expected)

    def test_object_str(self):
        # Test PyObject_Str()
        object_str = _testlimitedcapi.object_str
        self.assertTypedEqual(object_str(''), '')
        self.assertTypedEqual(object_str('abc'), 'abc')
        self.assertTypedEqual(object_str('\U0001f40d'), '\U0001f40d')
        self.assertTypedEqual(object_str(StrSubclass('abc')), 'abc')
        self.assertTypedEqual(object_str(WithStr('abc')), 'abc')
        self.assertTypedEqual(object_str(WithStr(StrSubclass('abc'))), StrSubclass('abc'))
        self.assertTypedEqual(object_str(WithRepr('<abc>')), '<abc>')
        self.assertTypedEqual(object_str(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
        self.assertTypedEqual(object_str(NULL), '<NULL>')

    def test_object_repr(self):
        # Test PyObject_Repr()
        object_repr = _testlimitedcapi.object_repr
        self.assertTypedEqual(object_repr(''), "''")
        self.assertTypedEqual(object_repr('abc'), "'abc'")
        self.assertTypedEqual(object_repr('\U0001f40d'), "'\U0001f40d'")
        self.assertTypedEqual(object_repr(StrSubclass('abc')), "'abc'")
        self.assertTypedEqual(object_repr(WithRepr('<abc>')), '<abc>')
        self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
        self.assertTypedEqual(object_repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>')
        self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>'))
        self.assertTypedEqual(object_repr(NULL), '<NULL>')

    def test_object_ascii(self):
        # Test PyObject_ASCII()
        object_ascii = _testlimitedcapi.object_ascii
        self.assertTypedEqual(object_ascii(''), "''")
        self.assertTypedEqual(object_ascii('abc'), "'abc'")
        self.assertTypedEqual(object_ascii('\U0001f40d'), r"'\U0001f40d'")
        self.assertTypedEqual(object_ascii(StrSubclass('abc')), "'abc'")
        self.assertTypedEqual(object_ascii(WithRepr('<abc>')), '<abc>')
        self.assertTypedEqual(object_ascii(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
        self.assertTypedEqual(object_ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>')
        self.assertTypedEqual(object_ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>')
        self.assertTypedEqual(object_ascii(NULL), '<NULL>')

    def test_object_bytes(self):
        # Test PyObject_Bytes()
        object_bytes = _testlimitedcapi.object_bytes
        self.assertTypedEqual(object_bytes(b''), b'')
        self.assertTypedEqual(object_bytes(b'abc'), b'abc')
        self.assertTypedEqual(object_bytes(BytesSubclass(b'abc')), b'abc')
        self.assertTypedEqual(object_bytes(WithBytes(b'abc')), b'abc')
        self.assertTypedEqual(object_bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc'))
        self.assertTypedEqual(object_bytes(bytearray(b'abc')), b'abc')
        self.assertTypedEqual(object_bytes(memoryview(b'abc')), b'abc')
        self.assertTypedEqual(object_bytes([97, 98, 99]), b'abc')
        self.assertTypedEqual(object_bytes((97, 98, 99)), b'abc')
        self.assertTypedEqual(object_bytes(iter([97, 98, 99])), b'abc')
        self.assertRaises(TypeError, object_bytes, WithBytes(bytearray(b'abc')))
        self.assertRaises(TypeError, object_bytes, WithBytes([97, 98, 99]))
        self.assertRaises(TypeError, object_bytes, 3)
        self.assertRaises(TypeError, object_bytes, 'abc')
        self.assertRaises(TypeError, object_bytes, object())
        self.assertTypedEqual(object_bytes(NULL), b'<NULL>')

    def test_object_getattr(self):
        xgetattr = _testlimitedcapi.object_getattr
        obj = TestObject()
        obj.a = 11
        setattr(obj, '\U0001f40d', 22)
        self.assertEqual(xgetattr(obj, 'a'), 11)
        self.assertRaises(AttributeError, xgetattr, obj, 'b')
        self.assertEqual(xgetattr(obj, '\U0001f40d'), 22)

        self.assertRaises(RuntimeError, xgetattr, obj, 'evil')
        self.assertRaises(TypeError, xgetattr, obj, 1)
        # CRASHES xgetattr(obj, NULL)
        # CRASHES xgetattr(NULL, 'a')

    def test_object_getattrstring(self):
        getattrstring = _testlimitedcapi.object_getattrstring
        obj = TestObject()
        obj.a = 11
        setattr(obj, '\U0001f40d', 22)
        self.assertEqual(getattrstring(obj, b'a'), 11)
        self.assertRaises(AttributeError, getattrstring, obj, b'b')
        self.assertEqual(getattrstring(obj, '\U0001f40d'.encode()), 22)

        self.assertRaises(RuntimeError, getattrstring, obj, b'evil')
        self.assertRaises(UnicodeDecodeError, getattrstring, obj, b'\xff')
        # CRASHES getattrstring(obj, NULL)
        # CRASHES getattrstring(NULL, b'a')

    def test_object_getoptionalattr(self):
        getoptionalattr = _testcapi.object_getoptionalattr
        obj = TestObject()
        obj.a = 11
        setattr(obj, '\U0001f40d', 22)
        self.assertEqual(getoptionalattr(obj, 'a'), 11)
        self.assertIs(getoptionalattr(obj, 'b'), AttributeError)
        self.assertEqual(getoptionalattr(obj, '\U0001f40d'), 22)

        self.assertRaises(RuntimeError, getoptionalattr, obj, 'evil')
        self.assertRaises(TypeError, getoptionalattr, obj, 1)
        # CRASHES getoptionalattr(obj, NULL)
        # CRASHES getoptionalattr(NULL, 'a')

    def test_object_getoptionalattrstring(self):
        getoptionalattrstring = _testcapi.object_getoptionalattrstring
        obj = TestObject()
        obj.a = 11
        setattr(obj, '\U0001f40d', 22)
        self.assertEqual(getoptionalattrstring(obj, b'a'), 11)
        self.assertIs(getoptionalattrstring(obj, b'b'), AttributeError)
        self.assertEqual(getoptionalattrstring(obj, '\U0001f40d'.encode()), 22)

        self.assertRaises(RuntimeError, getoptionalattrstring, obj, b'evil')
        self.assertRaises(UnicodeDecodeError, getoptionalattrstring, obj, b'\xff')
        # CRASHES getoptionalattrstring(obj, NULL)
        # CRASHES getoptionalattrstring(NULL, b'a')

    def test_object_hasattr(self):
        xhasattr = _testlimitedcapi.object_hasattr
        obj = TestObject()
        obj.a = 1
        setattr(obj, '\U0001f40d', 2)
        self.assertTrue(xhasattr(obj, 'a'))
        self.assertFalse(xhasattr(obj, 'b'))
        self.assertTrue(xhasattr(obj, '\U0001f40d'))

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(xhasattr(obj, 'evil'))
            self.assertEqual(cm.unraisable.exc_type, RuntimeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'do not get evil')

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(xhasattr(obj, 1))
            self.assertEqual(cm.unraisable.exc_type, TypeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             "attribute name must be string, not 'int'")

        # CRASHES xhasattr(obj, NULL)
        # CRASHES xhasattr(NULL, 'a')

    def test_object_hasattrstring(self):
        hasattrstring = _testlimitedcapi.object_hasattrstring
        obj = TestObject()
        obj.a = 1
        setattr(obj, '\U0001f40d', 2)
        self.assertTrue(hasattrstring(obj, b'a'))
        self.assertFalse(hasattrstring(obj, b'b'))
        self.assertTrue(hasattrstring(obj, '\U0001f40d'.encode()))

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(hasattrstring(obj, b'evil'))
            self.assertEqual(cm.unraisable.exc_type, RuntimeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'do not get evil')

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(hasattrstring(obj, b'\xff'))
            self.assertEqual(cm.unraisable.exc_type, UnicodeDecodeError)
            self.assertRegex(str(cm.unraisable.exc_value),
                             "'utf-8' codec can't decode")

        # CRASHES hasattrstring(obj, NULL)
        # CRASHES hasattrstring(NULL, b'a')

    def test_object_hasattrwitherror(self):
        xhasattr = _testcapi.object_hasattrwitherror
        obj = TestObject()
        obj.a = 1
        setattr(obj, '\U0001f40d', 2)
        self.assertTrue(xhasattr(obj, 'a'))
        self.assertFalse(xhasattr(obj, 'b'))
        self.assertTrue(xhasattr(obj, '\U0001f40d'))

        self.assertRaises(RuntimeError, xhasattr, obj, 'evil')
        self.assertRaises(TypeError, xhasattr, obj, 1)
        # CRASHES xhasattr(obj, NULL)
        # CRASHES xhasattr(NULL, 'a')

    def test_object_hasattrstringwitherror(self):
        hasattrstring = _testcapi.object_hasattrstringwitherror
        obj = TestObject()
        obj.a = 1
        setattr(obj, '\U0001f40d', 2)
        self.assertTrue(hasattrstring(obj, b'a'))
        self.assertFalse(hasattrstring(obj, b'b'))
        self.assertTrue(hasattrstring(obj, '\U0001f40d'.encode()))

        self.assertRaises(RuntimeError, hasattrstring, obj, b'evil')
        self.assertRaises(UnicodeDecodeError, hasattrstring, obj, b'\xff')
        # CRASHES hasattrstring(obj, NULL)
        # CRASHES hasattrstring(NULL, b'a')

    def test_object_setattr(self):
        xsetattr = _testlimitedcapi.object_setattr
        obj = TestObject()
        xsetattr(obj, 'a', 5)
        self.assertEqual(obj.a, 5)
        xsetattr(obj, '\U0001f40d', 8)
        self.assertEqual(getattr(obj, '\U0001f40d'), 8)

        # PyObject_SetAttr(obj, attr_name, NULL) removes the attribute
        xsetattr(obj, 'a', NULL)
        self.assertNotHasAttr(obj, 'a')
        self.assertRaises(AttributeError, xsetattr, obj, 'b', NULL)
        self.assertRaises(RuntimeError, xsetattr, obj, 'evil', NULL)

        self.assertRaises(RuntimeError, xsetattr, obj, 'evil', 'good')
        self.assertRaises(AttributeError, xsetattr, 42, 'a', 5)
        self.assertRaises(TypeError, xsetattr, obj, 1, 5)
        # CRASHES xsetattr(obj, NULL, 5)
        # CRASHES xsetattr(NULL, 'a', 5)

    def test_object_setattrstring(self):
        setattrstring = _testlimitedcapi.object_setattrstring
        obj = TestObject()
        setattrstring(obj, b'a', 5)
        self.assertEqual(obj.a, 5)
        setattrstring(obj, '\U0001f40d'.encode(), 8)
        self.assertEqual(getattr(obj, '\U0001f40d'), 8)

        # PyObject_SetAttrString(obj, attr_name, NULL) removes the attribute
        setattrstring(obj, b'a', NULL)
        self.assertNotHasAttr(obj, 'a')
        self.assertRaises(AttributeError, setattrstring, obj, b'b', NULL)
        self.assertRaises(RuntimeError, setattrstring, obj, b'evil', NULL)

        self.assertRaises(RuntimeError, setattrstring, obj, b'evil', 'good')
        self.assertRaises(AttributeError, setattrstring, 42, b'a', 5)
        self.assertRaises(TypeError, setattrstring, obj, 1, 5)
        self.assertRaises(UnicodeDecodeError, setattrstring, obj, b'\xff', 5)
        # CRASHES setattrstring(obj, NULL, 5)
        # CRASHES setattrstring(NULL, b'a', 5)

    def test_object_delattr(self):
        xdelattr = _testlimitedcapi.object_delattr
        obj = TestObject()
        obj.a = 1
        setattr(obj, '\U0001f40d', 2)
        xdelattr(obj, 'a')
        self.assertNotHasAttr(obj, 'a')
        self.assertRaises(AttributeError, xdelattr, obj, 'b')
        xdelattr(obj, '\U0001f40d')
        self.assertNotHasAttr(obj, '\U0001f40d')

        self.assertRaises(AttributeError, xdelattr, 42, 'numerator')
        self.assertRaises(RuntimeError, xdelattr, obj, 'evil')
        self.assertRaises(TypeError, xdelattr, obj, 1)
        # CRASHES xdelattr(obj, NULL)
        # CRASHES xdelattr(NULL, 'a')

    def test_object_delattrstring(self):
        delattrstring = _testlimitedcapi.object_delattrstring
        obj = TestObject()
        obj.a = 1
        setattr(obj, '\U0001f40d', 2)
        delattrstring(obj, b'a')
        self.assertNotHasAttr(obj, 'a')
        self.assertRaises(AttributeError, delattrstring, obj, b'b')
        delattrstring(obj, '\U0001f40d'.encode())
        self.assertNotHasAttr(obj, '\U0001f40d')

        self.assertRaises(AttributeError, delattrstring, 42, b'numerator')
        self.assertRaises(RuntimeError, delattrstring, obj, b'evil')
        self.assertRaises(UnicodeDecodeError, delattrstring, obj, b'\xff')
        # CRASHES delattrstring(obj, NULL)
        # CRASHES delattrstring(NULL, b'a')


    def test_mapping_check(self):
        check = _testlimitedcapi.mapping_check
        self.assertTrue(check({1: 2}))
        self.assertTrue(check([1, 2]))
        self.assertTrue(check((1, 2)))
        self.assertTrue(check('abc'))
        self.assertTrue(check(b'abc'))
        self.assertFalse(check(42))
        self.assertFalse(check(object()))
        self.assertFalse(check(NULL))

    def test_mapping_size(self):
        for size in _testlimitedcapi.mapping_size, _testlimitedcapi.mapping_length:
            self.assertEqual(size({1: 2}), 1)
            self.assertEqual(size([1, 2]), 2)
            self.assertEqual(size((1, 2)), 2)
            self.assertEqual(size('abc'), 3)
            self.assertEqual(size(b'abc'), 3)

            self.assertRaises(TypeError, size, 42)
            self.assertRaises(TypeError, size, object())
            self.assertRaises(SystemError, size, NULL)

    def test_object_getitem(self):
        getitem = _testlimitedcapi.object_getitem
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertEqual(getitem(dct, 'a'), 1)
        self.assertRaises(KeyError, getitem, dct, 'b')
        self.assertEqual(getitem(dct, '\U0001f40d'), 2)

        dct2 = ProxyGetItem(dct)
        self.assertEqual(getitem(dct2, 'a'), 1)
        self.assertRaises(KeyError, getitem, dct2, 'b')

        self.assertEqual(getitem(['a', 'b', 'c'], 1), 'b')

        self.assertRaises(TypeError, getitem, 42, 'a')
        self.assertRaises(TypeError, getitem, {}, [])  # unhashable
        self.assertRaises(SystemError, getitem, {}, NULL)
        self.assertRaises(IndexError, getitem, [], 1)
        self.assertRaises(TypeError, getitem, [], 'a')
        self.assertRaises(SystemError, getitem, NULL, 'a')

    def test_mapping_getitemstring(self):
        getitemstring = _testlimitedcapi.mapping_getitemstring
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertEqual(getitemstring(dct, b'a'), 1)
        self.assertRaises(KeyError, getitemstring, dct, b'b')
        self.assertEqual(getitemstring(dct, '\U0001f40d'.encode()), 2)

        dct2 = ProxyGetItem(dct)
        self.assertEqual(getitemstring(dct2, b'a'), 1)
        self.assertRaises(KeyError, getitemstring, dct2, b'b')

        self.assertRaises(TypeError, getitemstring, 42, b'a')
        self.assertRaises(UnicodeDecodeError, getitemstring, {}, b'\xff')
        self.assertRaises(SystemError, getitemstring, {}, NULL)
        self.assertRaises(TypeError, getitemstring, [], b'a')
        self.assertRaises(SystemError, getitemstring, NULL, b'a')

    def test_mapping_getoptionalitem(self):
        getitem = _testcapi.mapping_getoptionalitem
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertEqual(getitem(dct, 'a'), 1)
        self.assertEqual(getitem(dct, 'b'), KeyError)
        self.assertEqual(getitem(dct, '\U0001f40d'), 2)

        dct2 = ProxyGetItem(dct)
        self.assertEqual(getitem(dct2, 'a'), 1)
        self.assertEqual(getitem(dct2, 'b'), KeyError)

        self.assertEqual(getitem(['a', 'b', 'c'], 1), 'b')

        self.assertRaises(TypeError, getitem, 42, 'a')
        self.assertRaises(TypeError, getitem, {}, [])  # unhashable
        self.assertRaises(IndexError, getitem, [], 1)
        self.assertRaises(TypeError, getitem, [], 'a')
        # CRASHES getitem({}, NULL)
        # CRASHES getitem(NULL, 'a')

    def test_mapping_getoptionalitemstring(self):
        getitemstring = _testcapi.mapping_getoptionalitemstring
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertEqual(getitemstring(dct, b'a'), 1)
        self.assertEqual(getitemstring(dct, b'b'), KeyError)
        self.assertEqual(getitemstring(dct, '\U0001f40d'.encode()), 2)

        dct2 = ProxyGetItem(dct)
        self.assertEqual(getitemstring(dct2, b'a'), 1)
        self.assertEqual(getitemstring(dct2, b'b'), KeyError)

        self.assertRaises(TypeError, getitemstring, 42, b'a')
        self.assertRaises(UnicodeDecodeError, getitemstring, {}, b'\xff')
        self.assertRaises(SystemError, getitemstring, {}, NULL)
        self.assertRaises(TypeError, getitemstring, [], b'a')
        # CRASHES getitemstring(NULL, b'a')

    def test_mapping_haskey(self):
        haskey = _testlimitedcapi.mapping_haskey
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertTrue(haskey(dct, 'a'))
        self.assertFalse(haskey(dct, 'b'))
        self.assertTrue(haskey(dct, '\U0001f40d'))

        dct2 = ProxyGetItem(dct)
        self.assertTrue(haskey(dct2, 'a'))
        self.assertFalse(haskey(dct2, 'b'))

        self.assertTrue(haskey(['a', 'b', 'c'], 1))

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskey(42, 'a'))
            self.assertEqual(cm.unraisable.exc_type, TypeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             "'int' object is not subscriptable")

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskey({}, []))
            self.assertEqual(cm.unraisable.exc_type, TypeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             "cannot use 'list' as a dict key "
                             "(unhashable type: 'list')")

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskey([], 1))
            self.assertEqual(cm.unraisable.exc_type, IndexError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'list index out of range')

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskey([], 'a'))
            self.assertEqual(cm.unraisable.exc_type, TypeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'list indices must be integers or slices, not str')

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskey({}, NULL))
            self.assertEqual(cm.unraisable.exc_type, SystemError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'null argument to internal routine')

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskey(NULL, 'a'))
            self.assertEqual(cm.unraisable.exc_type, SystemError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'null argument to internal routine')

    def test_mapping_haskeystring(self):
        haskeystring = _testlimitedcapi.mapping_haskeystring
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertTrue(haskeystring(dct, b'a'))
        self.assertFalse(haskeystring(dct, b'b'))
        self.assertTrue(haskeystring(dct, '\U0001f40d'.encode()))

        dct2 = ProxyGetItem(dct)
        self.assertTrue(haskeystring(dct2, b'a'))
        self.assertFalse(haskeystring(dct2, b'b'))

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskeystring(42, b'a'))
            self.assertEqual(cm.unraisable.exc_type, TypeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             "'int' object is not subscriptable")

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskeystring({}, b'\xff'))
            self.assertEqual(cm.unraisable.exc_type, UnicodeDecodeError)
            self.assertRegex(str(cm.unraisable.exc_value),
                             "'utf-8' codec can't decode")

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskeystring({}, NULL))
            self.assertEqual(cm.unraisable.exc_type, SystemError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             "null argument to internal routine")

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskeystring([], b'a'))
            self.assertEqual(cm.unraisable.exc_type, TypeError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             'list indices must be integers or slices, not str')

        with support.catch_unraisable_exception() as cm:
            self.assertFalse(haskeystring(NULL, b'a'))
            self.assertEqual(cm.unraisable.exc_type, SystemError)
            self.assertEqual(str(cm.unraisable.exc_value),
                             "null argument to internal routine")

    def test_mapping_haskeywitherror(self):
        haskey = _testlimitedcapi.mapping_haskeywitherror
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertTrue(haskey(dct, 'a'))
        self.assertFalse(haskey(dct, 'b'))
        self.assertTrue(haskey(dct, '\U0001f40d'))

        dct2 = ProxyGetItem(dct)
        self.assertTrue(haskey(dct2, 'a'))
        self.assertFalse(haskey(dct2, 'b'))

        self.assertTrue(haskey(['a', 'b', 'c'], 1))

        self.assertRaises(TypeError, haskey, 42, 'a')
        self.assertRaises(TypeError, haskey, {}, [])  # unhashable
        self.assertRaises(IndexError, haskey, [], 1)
        self.assertRaises(TypeError, haskey, [], 'a')

        # CRASHES haskey({}, NULL))
        # CRASHES haskey(NULL, 'a'))

    def test_mapping_haskeystringwitherror(self):
        haskeystring = _testlimitedcapi.mapping_haskeystringwitherror
        dct = {'a': 1, '\U0001f40d': 2}
        self.assertTrue(haskeystring(dct, b'a'))
        self.assertFalse(haskeystring(dct, b'b'))
        self.assertTrue(haskeystring(dct, '\U0001f40d'.encode()))

        dct2 = ProxyGetItem(dct)
        self.assertTrue(haskeystring(dct2, b'a'))
        self.assertFalse(haskeystring(dct2, b'b'))

        self.assertRaises(TypeError, haskeystring, 42, b'a')
        self.assertRaises(UnicodeDecodeError, haskeystring, {}, b'\xff')
        self.assertRaises(SystemError, haskeystring, {}, NULL)
        self.assertRaises(TypeError, haskeystring, [], b'a')
        # CRASHES haskeystring(NULL, b'a')

    def test_object_setitem(self):
        setitem = _testlimitedcapi.object_setitem
        dct = {}
        setitem(dct, 'a', 5)
        self.assertEqual(dct, {'a': 5})
        setitem(dct, '\U0001f40d', 8)
        self.assertEqual(dct, {'a': 5, '\U0001f40d': 8})

        dct = {}
        dct2 = ProxySetItem(dct)
        setitem(dct2, 'a', 5)
        self.assertEqual(dct, {'a': 5})

        lst = ['a', 'b', 'c']
        setitem(lst, 1, 'x')
        self.assertEqual(lst, ['a', 'x', 'c'])

        self.assertRaises(TypeError, setitem, 42, 'a', 5)
        self.assertRaises(TypeError, setitem, {}, [], 5)  # unhashable
        self.assertRaises(SystemError, setitem, {}, NULL, 5)
        self.assertRaises(SystemError, setitem, {}, 'a', NULL)
        self.assertRaises(IndexError, setitem, [], 1, 5)
        self.assertRaises(TypeError, setitem, [], 'a', 5)
        self.assertRaises(TypeError, setitem, (), 1, 5)
        self.assertRaises(SystemError, setitem, NULL, 'a', 5)

    def test_mapping_setitemstring(self):
        setitemstring = _testlimitedcapi.mapping_setitemstring
        dct = {}
        setitemstring(dct, b'a', 5)
        self.assertEqual(dct, {'a': 5})
        setitemstring(dct, '\U0001f40d'.encode(), 8)
        self.assertEqual(dct, {'a': 5, '\U0001f40d': 8})

        dct = {}
        dct2 = ProxySetItem(dct)
        setitemstring(dct2, b'a', 5)
        self.assertEqual(dct, {'a': 5})

        self.assertRaises(TypeError, setitemstring, 42, b'a', 5)
        self.assertRaises(UnicodeDecodeError, setitemstring, {}, b'\xff', 5)
        self.assertRaises(SystemError, setitemstring, {}, NULL, 5)
        self.assertRaises(SystemError, setitemstring, {}, b'a', NULL)
        self.assertRaises(TypeError, setitemstring, [], b'a', 5)
        self.assertRaises(SystemError, setitemstring, NULL, b'a', 5)

    def test_object_delitem(self):
        for delitem in _testlimitedcapi.object_delitem, _testlimitedcapi.mapping_delitem:
            dct = {'a': 1, 'c': 2, '\U0001f40d': 3}
            delitem(dct, 'a')
            self.assertEqual(dct, {'c': 2, '\U0001f40d': 3})
            self.assertRaises(KeyError, delitem, dct, 'b')
            delitem(dct, '\U0001f40d')
            self.assertEqual(dct, {'c': 2})

            dct = {'a': 1, 'c': 2}
            dct2 = ProxyDelItem(dct)
            delitem(dct2, 'a')
            self.assertEqual(dct, {'c': 2})
            self.assertRaises(KeyError, delitem, dct2, 'b')

            lst = ['a', 'b', 'c']
            delitem(lst, 1)
            self.assertEqual(lst, ['a', 'c'])

            self.assertRaises(TypeError, delitem, 42, 'a')
            self.assertRaises(TypeError, delitem, {}, [])  # unhashable
            self.assertRaises(SystemError, delitem, {}, NULL)
            self.assertRaises(IndexError, delitem, [], 1)
            self.assertRaises(TypeError, delitem, [], 'a')
            self.assertRaises(SystemError, delitem, NULL, 'a')

    def test_mapping_delitemstring(self):
        delitemstring = _testlimitedcapi.mapping_delitemstring
        dct = {'a': 1, 'c': 2, '\U0001f40d': 3}
        delitemstring(dct, b'a')
        self.assertEqual(dct, {'c': 2, '\U0001f40d': 3})
        self.assertRaises(KeyError, delitemstring, dct, b'b')
        delitemstring(dct, '\U0001f40d'.encode())
        self.assertEqual(dct, {'c': 2})

        dct = {'a': 1, 'c': 2}
        dct2 = ProxyDelItem(dct)
        delitemstring(dct2, b'a')
        self.assertEqual(dct, {'c': 2})
        self.assertRaises(KeyError, delitemstring, dct2, b'b')

        self.assertRaises(TypeError, delitemstring, 42, b'a')
        self.assertRaises(UnicodeDecodeError, delitemstring, {}, b'\xff')
        self.assertRaises(SystemError, delitemstring, {}, NULL)
        self.assertRaises(TypeError, delitemstring, [], b'a')
        self.assertRaises(SystemError, delitemstring, NULL, b'a')

    def test_mapping_keys_valuesitems(self):
        class Mapping1(dict):
            def keys(self):
                return list(super().keys())
            def values(self):
                return list(super().values())
            def items(self):
                return list(super().items())
        class Mapping2(dict):
            def keys(self):
                return tuple(super().keys())
            def values(self):
                return tuple(super().values())
            def items(self):
                return tuple(super().items())
        dict_obj = {'foo': 1, 'bar': 2, 'spam': 3}

        for mapping in [{}, OrderedDict(), Mapping1(), Mapping2(),
                        dict_obj, OrderedDict(dict_obj),
                        Mapping1(dict_obj), Mapping2(dict_obj)]:
            self.assertListEqual(_testlimitedcapi.mapping_keys(mapping),
                                 list(mapping.keys()))
            self.assertListEqual(_testlimitedcapi.mapping_values(mapping),
                                 list(mapping.values()))
            self.assertListEqual(_testlimitedcapi.mapping_items(mapping),
                                 list(mapping.items()))

    def test_mapping_keys_valuesitems_bad_arg(self):
        self.assertRaises(AttributeError, _testlimitedcapi.mapping_keys, object())
        self.assertRaises(AttributeError, _testlimitedcapi.mapping_values, object())
        self.assertRaises(AttributeError, _testlimitedcapi.mapping_items, object())
        self.assertRaises(AttributeError, _testlimitedcapi.mapping_keys, [])
        self.assertRaises(AttributeError, _testlimitedcapi.mapping_values, [])
        self.assertRaises(AttributeError, _testlimitedcapi.mapping_items, [])
        self.assertRaises(SystemError, _testlimitedcapi.mapping_keys, NULL)
        self.assertRaises(SystemError, _testlimitedcapi.mapping_values, NULL)
        self.assertRaises(SystemError, _testlimitedcapi.mapping_items, NULL)

        class BadMapping:
            def keys(self):
                return None
            def values(self):
                return None
            def items(self):
                return None
        bad_mapping = BadMapping()
        self.assertRaises(TypeError, _testlimitedcapi.mapping_keys, bad_mapping)
        self.assertRaises(TypeError, _testlimitedcapi.mapping_values, bad_mapping)
        self.assertRaises(TypeError, _testlimitedcapi.mapping_items, bad_mapping)

    def test_sequence_check(self):
        check = _testlimitedcapi.sequence_check
        self.assertFalse(check({1: 2}))
        self.assertTrue(check([1, 2]))
        self.assertTrue(check((1, 2)))
        self.assertTrue(check('abc'))
        self.assertTrue(check(b'abc'))
        self.assertFalse(check(42))
        self.assertFalse(check(object()))
        # CRASHES check(NULL)

    def test_sequence_size(self):
        for size in _testlimitedcapi.sequence_size, _testlimitedcapi.sequence_length:
            self.assertEqual(size([1, 2]), 2)
            self.assertEqual(size((1, 2)), 2)
            self.assertEqual(size('abc'), 3)
            self.assertEqual(size(b'abc'), 3)

            self.assertRaises(TypeError, size, {})
            self.assertRaises(TypeError, size, 42)
            self.assertRaises(TypeError, size, object())
            self.assertRaises(SystemError, size, NULL)

    def test_sequence_getitem(self):
        getitem = _testlimitedcapi.sequence_getitem
        lst = ['a', 'b', 'c']
        self.assertEqual(getitem(lst, 1), 'b')
        self.assertEqual(getitem(lst, -1), 'c')
        self.assertRaises(IndexError, getitem, lst, 3)
        self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MAX)
        self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MIN)

        self.assertRaises(TypeError, getitem, 42, 1)
        self.assertRaises(TypeError, getitem, {}, 1)
        self.assertRaises(SystemError, getitem, NULL, 1)

    def test_sequence_concat(self):
        concat = _testlimitedcapi.sequence_concat
        self.assertEqual(concat(['a', 'b'], [1, 2]), ['a', 'b', 1, 2])
        self.assertEqual(concat(('a', 'b'), (1, 2)), ('a', 'b', 1, 2))

        self.assertRaises(TypeError, concat, [], ())
        self.assertRaises(TypeError, concat, (), [])
        self.assertRaises(TypeError, concat, [], 42)
        self.assertRaises(TypeError, concat, 42, [])
        self.assertRaises(TypeError, concat, 42, 43)
        self.assertRaises(SystemError, concat, [], NULL)
        self.assertRaises(SystemError, concat, NULL, [])

    def test_sequence_repeat(self):
        repeat = _testlimitedcapi.sequence_repeat
        self.assertEqual(repeat(['a', 'b'], 2), ['a', 'b', 'a', 'b'])
        self.assertEqual(repeat(('a', 'b'), 2), ('a', 'b', 'a', 'b'))
        self.assertEqual(repeat(['a', 'b'], 0), [])
        self.assertEqual(repeat(['a', 'b'], -1), [])
        self.assertEqual(repeat(['a', 'b'], PY_SSIZE_T_MIN), [])
        self.assertEqual(repeat([], PY_SSIZE_T_MAX), [])
        self.assertRaises(MemoryError, repeat, ['a', 'b'], PY_SSIZE_T_MAX)

        self.assertRaises(TypeError, repeat, set(), 2)
        self.assertRaises(TypeError, repeat, 42, 2)
        self.assertRaises(SystemError, repeat, NULL, 2)

    def test_sequence_inplaceconcat(self):
        inplaceconcat = _testlimitedcapi.sequence_inplaceconcat
        lst = ['a', 'b']
        res = inplaceconcat(lst, [1, 2])
        self.assertEqual(res, ['a', 'b', 1, 2])
        self.assertIs(res, lst)
        lst = ['a', 'b']
        res = inplaceconcat(lst, (1, 2))
        self.assertEqual(res, ['a', 'b', 1, 2])
        self.assertIs(res, lst)
        self.assertEqual(inplaceconcat(('a', 'b'), (1, 2)), ('a', 'b', 1, 2))

        self.assertRaises(TypeError, inplaceconcat, (), [])
        self.assertRaises(TypeError, inplaceconcat, [], 42)
        self.assertRaises(TypeError, inplaceconcat, 42, [])
        self.assertRaises(TypeError, inplaceconcat, 42, 43)
        self.assertRaises(SystemError, inplaceconcat, [], NULL)
        self.assertRaises(SystemError, inplaceconcat, NULL, [])

    def test_sequence_inplacerepeat(self):
        inplacerepeat = _testlimitedcapi.sequence_inplacerepeat
        lst = ['a', 'b']
        res = inplacerepeat(lst, 2)
        self.assertEqual(res, ['a', 'b', 'a', 'b'])
        self.assertIs(res, lst)
        self.assertEqual(inplacerepeat(('a', 'b'), 2), ('a', 'b', 'a', 'b'))
        self.assertEqual(inplacerepeat(['a', 'b'], 0), [])
        self.assertEqual(inplacerepeat(['a', 'b'], -1), [])
        self.assertEqual(inplacerepeat(['a', 'b'], PY_SSIZE_T_MIN), [])
        self.assertEqual(inplacerepeat([], PY_SSIZE_T_MAX), [])
        self.assertRaises(MemoryError, inplacerepeat, ['a', 'b'], PY_SSIZE_T_MAX)

        self.assertRaises(TypeError, inplacerepeat, set(), 2)
        self.assertRaises(TypeError, inplacerepeat, 42, 2)
        self.assertRaises(SystemError, inplacerepeat, NULL, 2)

    def test_sequence_setitem(self):
        setitem = _testlimitedcapi.sequence_setitem
        lst = ['a', 'b', 'c']
        setitem(lst, 1, 'x')
        self.assertEqual(lst, ['a', 'x', 'c'])
        setitem(lst, -1, 'y')
        self.assertEqual(lst, ['a', 'x', 'y'])

        setitem(lst, 0, NULL)
        self.assertEqual(lst, ['x', 'y'])
        self.assertRaises(IndexError, setitem, lst, 3, 'x')
        self.assertRaises(IndexError, setitem, lst, PY_SSIZE_T_MAX, 'x')
        self.assertRaises(IndexError, setitem, lst, PY_SSIZE_T_MIN, 'x')

        self.assertRaises(TypeError, setitem, 42, 1, 'x')
        self.assertRaises(TypeError, setitem, {}, 1, 'x')
        self.assertRaises(SystemError, setitem, NULL, 1, 'x')

    def test_sequence_delitem(self):
        delitem = _testlimitedcapi.sequence_delitem
        lst = ['a', 'b', 'c']
        delitem(lst, 1)
        self.assertEqual(lst, ['a', 'c'])
        delitem(lst, -1)
        self.assertEqual(lst, ['a'])
        self.assertRaises(IndexError, delitem, lst, 3)
        self.assertRaises(IndexError, delitem, lst, PY_SSIZE_T_MAX)
        self.assertRaises(IndexError, delitem, lst, PY_SSIZE_T_MIN)

        self.assertRaises(TypeError, delitem, 42, 1)
        self.assertRaises(TypeError, delitem, {}, 1)
        self.assertRaises(SystemError, delitem, NULL, 1)

    def test_sequence_setslice(self):
        setslice = _testlimitedcapi.sequence_setslice

        # Correct case:
        for start in [*range(-6, 7), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]:
            for stop in [*range(-6, 7), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]:
                data = [1, 2, 3, 4, 5]
                data_copy = [1, 2, 3, 4, 5]
                setslice(data, start, stop, [8, 9])
                data_copy[start:stop] = [8, 9]
                self.assertEqual(data, data_copy)

                data = [1, 2, 3, 4, 5]
                data_copy = [1, 2, 3, 4, 5]
                setslice(data, start, stop, NULL)
                del data_copy[start:stop]
                self.assertEqual(data, data_copy)

        # Custom class:
        class Custom:
            def __setitem__(self, index, value):
                self.index = index
                self.value = value

        c = Custom()
        setslice(c, 0, 5, 'abc')
        self.assertEqual(c.index, slice(0, 5))
        self.assertEqual(c.value, 'abc')

        # Immutable sequences must raise:
        bad_seq1 = (1, 2, 3, 4)
        self.assertRaises(TypeError, setslice, bad_seq1, 1, 3, (8, 9))
        self.assertEqual(bad_seq1, (1, 2, 3, 4))

        bad_seq2 = 'abcd'
        self.assertRaises(TypeError, setslice, bad_seq2, 1, 3, 'xy')
        self.assertEqual(bad_seq2, 'abcd')

        # Not a sequence:
        self.assertRaises(TypeError, setslice, object(), 1, 3, 'xy')
        self.assertRaises(SystemError, setslice, NULL, 1, 3, 'xy')

    def test_sequence_delslice(self):
        delslice = _testlimitedcapi.sequence_delslice

        # Correct case:
        for start in [*range(-6, 7), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]:
            for stop in [*range(-6, 7), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]:
                data = [1, 2, 3, 4, 5]
                data_copy = [1, 2, 3, 4, 5]
                delslice(data, start, stop)
                del data_copy[start:stop]
                self.assertEqual(data, data_copy)

        # Custom class:
        class Custom:
            def __delitem__(self, index):
                self.index = index

        c = Custom()
        delslice(c, 0, 5)
        self.assertEqual(c.index, slice(0, 5))

        # Immutable sequences must raise:
        bad_seq1 = (1, 2, 3, 4)
        self.assertRaises(TypeError, delslice, bad_seq1, 1, 3)
        self.assertEqual(bad_seq1, (1, 2, 3, 4))

        bad_seq2 = 'abcd'
        self.assertRaises(TypeError, delslice, bad_seq2, 1, 3)
        self.assertEqual(bad_seq2, 'abcd')

        # Not a sequence:
        self.assertRaises(TypeError, delslice, object(), 1, 3)
        self.assertRaises(SystemError, delslice, NULL, 1, 3)

        mapping = {1: 'a', 2: 'b', 3: 'c'}
        self.assertRaises(KeyError, delslice, mapping, 1, 3)
        self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'})

    def test_sequence_count(self):
        count = _testlimitedcapi.sequence_count

        lst = ['a', 'b', 'a']
        self.assertEqual(count(lst, 'a'), 2)
        self.assertEqual(count(lst, 'c'), 0)
        self.assertEqual(count(iter(lst), 'a'), 2)
        self.assertEqual(count(iter(lst), 'c'), 0)
        self.assertEqual(count({'a': 2}, 'a'), 1)

        self.assertRaises(TypeError, count, 42, 'a')
        self.assertRaises(SystemError, count, [], NULL)
        self.assertRaises(SystemError, count, [1], NULL)
        self.assertRaises(SystemError, count, NULL, 'a')

    def test_sequence_contains(self):
        contains = _testlimitedcapi.sequence_contains

        lst = ['a', 'b', 'a']
        self.assertEqual(contains(lst, 'a'), 1)
        self.assertEqual(contains(lst, 'c'), 0)
        self.assertEqual(contains(iter(lst), 'a'), 1)
        self.assertEqual(contains(iter(lst), 'c'), 0)
        self.assertEqual(contains({'a': 2}, 'a'), 1)

        # XXX Only for empty sequences. Should be SystemError?
        self.assertEqual(contains([], NULL), 0)

        self.assertRaises(TypeError, contains, 42, 'a')
        self.assertRaises(SystemError, contains, [1], NULL)
        # CRASHES contains({}, NULL)
        # CRASHES contains(set(), NULL)
        # CRASHES contains(NULL, 'a')

    def test_sequence_index(self):
        index = _testlimitedcapi.sequence_index

        lst = ['a', 'b', 'a']
        self.assertEqual(index(lst, 'a'), 0)
        self.assertEqual(index(lst, 'b'), 1)
        self.assertRaises(ValueError, index, lst, 'c')
        self.assertEqual(index(iter(lst), 'a'), 0)
        self.assertEqual(index(iter(lst), 'b'), 1)
        self.assertRaises(ValueError, index, iter(lst), 'c')
        dct = {'a': 2, 'b': 3}
        self.assertEqual(index(dct, 'a'), 0)
        self.assertEqual(index(dct, 'b'), 1)
        self.assertRaises(ValueError, index, dct, 'c')

        self.assertRaises(TypeError, index, 42, 'a')
        self.assertRaises(SystemError, index, [], NULL)
        self.assertRaises(SystemError, index, [1], NULL)
        self.assertRaises(SystemError, index, NULL, 'a')

    def test_sequence_list(self):
        xlist = _testlimitedcapi.sequence_list
        self.assertEqual(xlist(['a', 'b', 'c']), ['a', 'b', 'c'])
        self.assertEqual(xlist(('a', 'b', 'c')), ['a', 'b', 'c'])
        self.assertEqual(xlist(iter(['a', 'b', 'c'])), ['a', 'b', 'c'])
        self.assertEqual(xlist(gen()), ['a', 'b', 'c'])

        self.assertRaises(TypeError, xlist, 42)
        self.assertRaises(SystemError, xlist, NULL)

    def test_sequence_tuple(self):
        xtuple = _testlimitedcapi.sequence_tuple
        self.assertEqual(xtuple(['a', 'b', 'c']), ('a', 'b', 'c'))
        self.assertEqual(xtuple(('a', 'b', 'c')), ('a', 'b', 'c'))
        self.assertEqual(xtuple(iter(['a', 'b', 'c'])), ('a', 'b', 'c'))
        self.assertEqual(xtuple(gen()), ('a', 'b', 'c'))

        self.assertRaises(TypeError, xtuple, 42)
        self.assertRaises(SystemError, xtuple, NULL)

    def test_sequence_fast(self):
        # Test PySequence_Fast()
        sequence_fast = _testlimitedcapi.sequence_fast
        sequence_fast_get_size = _testcapi.sequence_fast_get_size
        sequence_fast_get_item = _testcapi.sequence_fast_get_item

        tpl = ('a', 'b', 'c')
        fast = sequence_fast(tpl, "err_msg")
        self.assertIs(fast, tpl)
        self.assertEqual(sequence_fast_get_size(fast), 3)
        self.assertEqual(sequence_fast_get_item(fast, 2), 'c')

        lst = ['a', 'b', 'c']
        fast = sequence_fast(lst, "err_msg")
        self.assertIs(fast, lst)
        self.assertEqual(sequence_fast_get_size(fast), 3)
        self.assertEqual(sequence_fast_get_item(fast, 2), 'c')

        it = iter(['A', 'B'])
        fast = sequence_fast(it, "err_msg")
        self.assertEqual(fast, ['A', 'B'])
        self.assertEqual(sequence_fast_get_size(fast), 2)
        self.assertEqual(sequence_fast_get_item(fast, 1), 'B')

        text = 'fast'
        fast = sequence_fast(text, "err_msg")
        self.assertEqual(fast, ['f', 'a', 's', 't'])
        self.assertEqual(sequence_fast_get_size(fast), 4)
        self.assertEqual(sequence_fast_get_item(fast, 0), 'f')

        self.assertRaises(TypeError, sequence_fast, 42, "err_msg")
        self.assertRaises(SystemError, sequence_fast, NULL, "err_msg")

        # CRASHES sequence_fast_get_size(NULL)
        # CRASHES sequence_fast_get_item(NULL, 0)

    def test_object_generichash(self):
        # Test PyObject_GenericHash()
        generichash = _testcapi.object_generichash
        for obj in object(), 1, 'string', []:
            self.assertEqual(generichash(obj), object.__hash__(obj))

    def run_iter_api_test(self, next_func):
        for data in (), [], (1, 2, 3), [1 , 2, 3], "123":
            with self.subTest(data=data):
                items = []
                it = iter(data)
                while (item := next_func(it)) is not None:
                    items.append(item)
                self.assertEqual(items, list(data))

        class Broken:
            def __init__(self):
                self.count = 0

            def __next__(self):
                if self.count < 3:
                    self.count += 1
                    return self.count
                else:
                    raise TypeError('bad type')

        it = Broken()
        self.assertEqual(next_func(it), 1)
        self.assertEqual(next_func(it), 2)
        self.assertEqual(next_func(it), 3)
        with self.assertRaisesRegex(TypeError, 'bad type'):
            next_func(it)

    def test_iter_next(self):
        from _testcapi import PyIter_Next
        self.run_iter_api_test(PyIter_Next)
        # CRASHES PyIter_Next(10)

    def test_iter_nextitem(self):
        from _testcapi import PyIter_NextItem
        self.run_iter_api_test(PyIter_NextItem)

        regex = "expected.*iterator.*got.*'int'"
        with self.assertRaisesRegex(TypeError, regex):
            PyIter_NextItem(10)

    def test_object_setattr_null_exc(self):
        class Obj:
            pass
        obj = Obj()
        obj.attr = 123

        exc = ValueError("error")
        with self.assertRaises(SystemError) as cm:
            _testcapi.object_setattr_null_exc(obj, 'attr', exc)
        self.assertIs(cm.exception.__context__, exc)
        self.assertIsNone(cm.exception.__cause__)
        self.assertHasAttr(obj, 'attr')

        with self.assertRaises(SystemError) as cm:
            _testcapi.object_setattrstring_null_exc(obj, 'attr', exc)
        self.assertIs(cm.exception.__context__, exc)
        self.assertIsNone(cm.exception.__cause__)
        self.assertHasAttr(obj, 'attr')

        with self.assertRaises(SystemError) as cm:
            # undecodable name
            _testcapi.object_setattrstring_null_exc(obj, b'\xff', exc)
        self.assertIs(cm.exception.__context__, exc)
        self.assertIsNone(cm.exception.__cause__)


if __name__ == "__main__":
    unittest.main()
