Skip to content

Commit

Permalink
pythongh-93649: Add Modules/_testcapi/type.c file
Browse files Browse the repository at this point in the history
Move PyType C API tests to a new file.

Move following tests from test_capi.test_misc to test_capi.test_type:

* BuiltinStaticTypesTests
* test_get_type_name()
* test_get_base_by_token()
  • Loading branch information
vstinner committed Jan 31, 2025
1 parent 8b70ff5 commit 2795aca
Show file tree
Hide file tree
Showing 8 changed files with 434 additions and 393 deletions.
174 changes: 0 additions & 174 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,147 +1111,6 @@ class Data(_testcapi.ObjExtraData):
del d.extra
self.assertIsNone(d.extra)

def test_get_type_name(self):
class MyType:
pass

from _testcapi import (
get_type_name, get_type_qualname,
get_type_fullyqualname, get_type_module_name)

from collections import OrderedDict
ht = _testcapi.get_heaptype_for_name()
for cls, fullname, modname, qualname, name in (
(int,
'int',
'builtins',
'int',
'int'),
(OrderedDict,
'collections.OrderedDict',
'collections',
'OrderedDict',
'OrderedDict'),
(ht,
'_testcapi.HeapTypeNameType',
'_testcapi',
'HeapTypeNameType',
'HeapTypeNameType'),
(MyType,
f'{__name__}.CAPITest.test_get_type_name.<locals>.MyType',
__name__,
'CAPITest.test_get_type_name.<locals>.MyType',
'MyType'),
):
with self.subTest(cls=repr(cls)):
self.assertEqual(get_type_fullyqualname(cls), fullname)
self.assertEqual(get_type_module_name(cls), modname)
self.assertEqual(get_type_qualname(cls), qualname)
self.assertEqual(get_type_name(cls), name)

# override __module__
ht.__module__ = 'test_module'
self.assertEqual(get_type_fullyqualname(ht), 'test_module.HeapTypeNameType')
self.assertEqual(get_type_module_name(ht), 'test_module')
self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType')
self.assertEqual(get_type_name(ht), 'HeapTypeNameType')

# override __name__ and __qualname__
MyType.__name__ = 'my_name'
MyType.__qualname__ = 'my_qualname'
self.assertEqual(get_type_fullyqualname(MyType), f'{__name__}.my_qualname')
self.assertEqual(get_type_module_name(MyType), __name__)
self.assertEqual(get_type_qualname(MyType), 'my_qualname')
self.assertEqual(get_type_name(MyType), 'my_name')

# override also __module__
MyType.__module__ = 'my_module'
self.assertEqual(get_type_fullyqualname(MyType), 'my_module.my_qualname')
self.assertEqual(get_type_module_name(MyType), 'my_module')
self.assertEqual(get_type_qualname(MyType), 'my_qualname')
self.assertEqual(get_type_name(MyType), 'my_name')

# PyType_GetFullyQualifiedName() ignores the module if it's "builtins"
# or "__main__" of it is not a string
MyType.__module__ = 'builtins'
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
MyType.__module__ = '__main__'
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
MyType.__module__ = 123
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')

def test_get_base_by_token(self):
def get_base_by_token(src, key, comparable=True):
def run(use_mro):
find_first = _testcapi.pytype_getbasebytoken
ret1, result = find_first(src, key, use_mro, True)
ret2, no_result = find_first(src, key, use_mro, False)
self.assertIn(ret1, (0, 1))
self.assertEqual(ret1, result is not None)
self.assertEqual(ret1, ret2)
self.assertIsNone(no_result)
return result

found_in_mro = run(True)
found_in_bases = run(False)
if comparable:
self.assertIs(found_in_mro, found_in_bases)
return found_in_mro
return found_in_mro, found_in_bases

create_type = _testcapi.create_type_with_token
get_token = _testcapi.get_tp_token

Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC
self.assertEqual(Py_TP_USE_SPEC, 0)

A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC)
self.assertTrue(get_token(A1) != Py_TP_USE_SPEC)

B1 = create_type('_testcapi.B1', id(self))
self.assertTrue(get_token(B1) == id(self))

tokenA1 = get_token(A1)
# find A1 from A1
found = get_base_by_token(A1, tokenA1)
self.assertIs(found, A1)

# no token in static types
STATIC = type(1)
self.assertEqual(get_token(STATIC), 0)
found = get_base_by_token(STATIC, tokenA1)
self.assertIs(found, None)

# no token in pure subtypes
class A2(A1): pass
self.assertEqual(get_token(A2), 0)
# find A1
class Z(STATIC, B1, A2): pass
found = get_base_by_token(Z, tokenA1)
self.assertIs(found, A1)

# searching for NULL token is an error
with self.assertRaises(SystemError):
get_base_by_token(Z, 0)
with self.assertRaises(SystemError):
get_base_by_token(STATIC, 0)

# share the token with A1
C1 = create_type('_testcapi.C1', tokenA1)
self.assertTrue(get_token(C1) == tokenA1)

# find C1 first by shared token
class Z(C1, A2): pass
found = get_base_by_token(Z, tokenA1)
self.assertIs(found, C1)
# B1 not found
found = get_base_by_token(Z, get_token(B1))
self.assertIs(found, None)

with self.assertRaises(TypeError):
_testcapi.pytype_getbasebytoken(
'not a type', id(self), True, False)

def test_gen_get_code(self):
def genf(): yield
gen = genf()
Expand Down Expand Up @@ -2922,39 +2781,6 @@ def test_linked_lifecycle_link_incref_unlink_decref(self):
0, get_refcount(interpid))


class BuiltinStaticTypesTests(unittest.TestCase):

TYPES = [
object,
type,
int,
str,
dict,
type(None),
bool,
BaseException,
Exception,
Warning,
DeprecationWarning, # Warning subclass
]

def test_tp_bases_is_set(self):
# PyTypeObject.tp_bases is documented as public API.
# See https://github.com/python/cpython/issues/105020.
for typeobj in self.TYPES:
with self.subTest(typeobj):
bases = _testcapi.type_get_tp_bases(typeobj)
self.assertIsNot(bases, None)

def test_tp_mro_is_set(self):
# PyTypeObject.tp_bases is documented as public API.
# See https://github.com/python/cpython/issues/105020.
for typeobj in self.TYPES:
with self.subTest(typeobj):
mro = _testcapi.type_get_tp_mro(typeobj)
self.assertIsNot(mro, None)


class TestStaticTypes(unittest.TestCase):

_has_run = False
Expand Down
174 changes: 174 additions & 0 deletions Lib/test/test_capi/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,181 @@
_testcapi = import_helper.import_module('_testcapi')


class BuiltinStaticTypesTests(unittest.TestCase):

TYPES = [
object,
type,
int,
str,
dict,
type(None),
bool,
BaseException,
Exception,
Warning,
DeprecationWarning, # Warning subclass
]

def test_tp_bases_is_set(self):
# PyTypeObject.tp_bases is documented as public API.
# See https://github.com/python/cpython/issues/105020.
for typeobj in self.TYPES:
with self.subTest(typeobj):
bases = _testcapi.type_get_tp_bases(typeobj)
self.assertIsNot(bases, None)

def test_tp_mro_is_set(self):
# PyTypeObject.tp_bases is documented as public API.
# See https://github.com/python/cpython/issues/105020.
for typeobj in self.TYPES:
with self.subTest(typeobj):
mro = _testcapi.type_get_tp_mro(typeobj)
self.assertIsNot(mro, None)


class TypeTests(unittest.TestCase):
def test_get_type_name(self):
class MyType:
pass

from _testcapi import (
get_type_name, get_type_qualname,
get_type_fullyqualname, get_type_module_name)

from collections import OrderedDict
ht = _testcapi.get_heaptype_for_name()
for cls, fullname, modname, qualname, name in (
(int,
'int',
'builtins',
'int',
'int'),
(OrderedDict,
'collections.OrderedDict',
'collections',
'OrderedDict',
'OrderedDict'),
(ht,
'_testcapi.HeapTypeNameType',
'_testcapi',
'HeapTypeNameType',
'HeapTypeNameType'),
(MyType,
f'{__name__}.TypeTests.test_get_type_name.<locals>.MyType',
__name__,
'TypeTests.test_get_type_name.<locals>.MyType',
'MyType'),
):
with self.subTest(cls=repr(cls)):
self.assertEqual(get_type_fullyqualname(cls), fullname)
self.assertEqual(get_type_module_name(cls), modname)
self.assertEqual(get_type_qualname(cls), qualname)
self.assertEqual(get_type_name(cls), name)

# override __module__
ht.__module__ = 'test_module'
self.assertEqual(get_type_fullyqualname(ht), 'test_module.HeapTypeNameType')
self.assertEqual(get_type_module_name(ht), 'test_module')
self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType')
self.assertEqual(get_type_name(ht), 'HeapTypeNameType')

# override __name__ and __qualname__
MyType.__name__ = 'my_name'
MyType.__qualname__ = 'my_qualname'
self.assertEqual(get_type_fullyqualname(MyType), f'{__name__}.my_qualname')
self.assertEqual(get_type_module_name(MyType), __name__)
self.assertEqual(get_type_qualname(MyType), 'my_qualname')
self.assertEqual(get_type_name(MyType), 'my_name')

# override also __module__
MyType.__module__ = 'my_module'
self.assertEqual(get_type_fullyqualname(MyType), 'my_module.my_qualname')
self.assertEqual(get_type_module_name(MyType), 'my_module')
self.assertEqual(get_type_qualname(MyType), 'my_qualname')
self.assertEqual(get_type_name(MyType), 'my_name')

# PyType_GetFullyQualifiedName() ignores the module if it's "builtins"
# or "__main__" of it is not a string
MyType.__module__ = 'builtins'
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
MyType.__module__ = '__main__'
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
MyType.__module__ = 123
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')

def test_get_base_by_token(self):
def get_base_by_token(src, key, comparable=True):
def run(use_mro):
find_first = _testcapi.pytype_getbasebytoken
ret1, result = find_first(src, key, use_mro, True)
ret2, no_result = find_first(src, key, use_mro, False)
self.assertIn(ret1, (0, 1))
self.assertEqual(ret1, result is not None)
self.assertEqual(ret1, ret2)
self.assertIsNone(no_result)
return result

found_in_mro = run(True)
found_in_bases = run(False)
if comparable:
self.assertIs(found_in_mro, found_in_bases)
return found_in_mro
return found_in_mro, found_in_bases

create_type = _testcapi.create_type_with_token
get_token = _testcapi.get_tp_token

Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC
self.assertEqual(Py_TP_USE_SPEC, 0)

A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC)
self.assertTrue(get_token(A1) != Py_TP_USE_SPEC)

B1 = create_type('_testcapi.B1', id(self))
self.assertTrue(get_token(B1) == id(self))

tokenA1 = get_token(A1)
# find A1 from A1
found = get_base_by_token(A1, tokenA1)
self.assertIs(found, A1)

# no token in static types
STATIC = type(1)
self.assertEqual(get_token(STATIC), 0)
found = get_base_by_token(STATIC, tokenA1)
self.assertIs(found, None)

# no token in pure subtypes
class A2(A1): pass
self.assertEqual(get_token(A2), 0)
# find A1
class Z(STATIC, B1, A2): pass
found = get_base_by_token(Z, tokenA1)
self.assertIs(found, A1)

# searching for NULL token is an error
with self.assertRaises(SystemError):
get_base_by_token(Z, 0)
with self.assertRaises(SystemError):
get_base_by_token(STATIC, 0)

# share the token with A1
C1 = create_type('_testcapi.C1', tokenA1)
self.assertTrue(get_token(C1) == tokenA1)

# find C1 first by shared token
class Z(C1, A2): pass
found = get_base_by_token(Z, tokenA1)
self.assertIs(found, C1)
# B1 not found
found = get_base_by_token(Z, get_token(B1))
self.assertIs(found, None)

with self.assertRaises(TypeError):
_testcapi.pytype_getbasebytoken(
'not a type', id(self), True, False)

def test_freeze(self):
# test PyType_Freeze()
type_freeze = _testcapi.type_freeze
Expand Down
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
Expand Down
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ int _PyTestCapi_Init_Object(PyObject *module);
int _PyTestCapi_Init_Config(PyObject *mod);
int _PyTestCapi_Init_Import(PyObject *mod);
int _PyTestCapi_Init_Frame(PyObject *mod);
int _PyTestCapi_Init_Type(PyObject *mod);

#endif // Py_TESTCAPI_PARTS_H
Loading

0 comments on commit 2795aca

Please sign in to comment.