Skip to content

Commit 29837a9

Browse files
authored
gh-155503: Add more PyType C API tests (#155505)
Add tests on functions: * PyType_ClearCache() * PyType_GetFlags() * PyType_IsSubtype() * PyType_Ready() Move PyType limited C API tests from _testcapi to _testlimitedcapi. Add a new Modules/_testlimitedcapi/type.c file.
1 parent 3d6a505 commit 29837a9

10 files changed

Lines changed: 474 additions & 105 deletions

File tree

Include/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ given type object has a specified feature.
490490
#define Py_TPFLAGS_SEQUENCE (1 << 5)
491491
/* Set if instances of the type object are treated as mappings for pattern matching */
492492
#define Py_TPFLAGS_MAPPING (1 << 6)
493-
#endif
493+
#endif // Py_LIMITED_API
494494

495495
/* Disallow creating instances of the type: set tp_new to NULL and don't create
496496
* the "__new__" key in the type dictionary. */

Lib/test/test_capi/test_type.py

Lines changed: 200 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import unittest
33

44
_testcapi = import_helper.import_module('_testcapi')
5+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
6+
7+
NULL = None
58

69

710
class BuiltinStaticTypesTests(unittest.TestCase):
@@ -39,15 +42,18 @@ def test_tp_mro_is_set(self):
3942

4043
class TypeTests(unittest.TestCase):
4144
def test_get_type_name(self):
45+
# Test PyType_GetName(), PyType_GetQualName(),
46+
# PyType_GetFullyQualifiedName() and PyType_GetModuleName().
47+
4248
class MyType:
4349
pass
4450

45-
from _testcapi import (
51+
from _testlimitedcapi import (
4652
get_type_name, get_type_qualname,
4753
get_type_fullyqualname, get_type_module_name)
4854

4955
from collections import OrderedDict
50-
ht = _testcapi.get_heaptype_for_name()
56+
ht = _testlimitedcapi.get_heaptype_for_name()
5157
for cls, fullname, modname, qualname, name in (
5258
(int,
5359
'int',
@@ -107,6 +113,15 @@ class MyType:
107113
MyType.__module__ = 123
108114
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
109115

116+
# CRASHES get_type_name(NULL)
117+
# CRASHES get_type_qualname(NULL)
118+
# CRASHES get_type_fullyqualname(NULL)
119+
# CRASHES get_type_module_name(NULL)
120+
# CRASHES get_type_name(object()): argument must be a type
121+
# CRASHES get_type_qualname(object()): argument must be a type
122+
# CRASHES get_type_fullyqualname(object()): argument must be a type
123+
# CRASHES get_type_module_name(object()): argument must be a type
124+
110125
def test_get_base_by_token(self):
111126
def get_base_by_token(src, key, comparable=True):
112127
def run(use_mro):
@@ -215,7 +230,7 @@ class H2(int): pass
215230

216231
def test_freeze(self):
217232
# test PyType_Freeze()
218-
type_freeze = _testcapi.type_freeze
233+
type_freeze = _testlimitedcapi.type_freeze
219234

220235
# simple case, no inherante
221236
class MyType:
@@ -245,12 +260,15 @@ class D(A, C): pass
245260
# as well
246261
type_freeze(D)
247262

263+
# CRASHES type_freeze(NULL)
264+
# CRASHES type_freeze(object()): argument must be a type
265+
248266
@unittest.skipIf(
249267
Py_GIL_DISABLED and refleak_helper.hunting_for_refleaks(),
250268
"Specialization failure triggers gh-127773")
251269
def test_freeze_meta(self):
252270
"""test PyType_Freeze() with overridden MRO"""
253-
type_freeze = _testcapi.type_freeze
271+
type_freeze = _testlimitedcapi.type_freeze
254272

255273
class Base:
256274
value = 1
@@ -299,3 +317,181 @@ def test_extension_managed_weakref_nogc_type(self):
299317
"flag but not Py_TPFLAGS_HAVE_GC flag")
300318
with self.assertRaisesRegex(SystemError, msg):
301319
_testcapi.create_managed_weakref_nogc_type()
320+
321+
def test_type_ready(self):
322+
# Test PyType_Ready(): calling it on initialized types
323+
# must not raise an exception.
324+
type_ready = _testlimitedcapi.type_ready
325+
326+
class HeapType:
327+
pass
328+
329+
type_ready(int)
330+
type_ready(dict)
331+
type_ready(HeapType)
332+
333+
# CRASHES type_ready(NULL)
334+
# CRASHES type_ready(123): argument must be a type
335+
336+
def test_type_clearcache(self):
337+
# Test PyType_ClearCache()
338+
type_clearcache = _testlimitedcapi.type_clearcache
339+
version_tag = type_clearcache()
340+
self.assertEqual(type(version_tag), int)
341+
self.assertGreaterEqual(version_tag, 0)
342+
343+
def test_type_getflags(self):
344+
# Test PyType_GetFlags()
345+
type_getflags = _testlimitedcapi.type_getflags
346+
347+
from _testlimitedcapi import (
348+
Py_TPFLAGS_HEAPTYPE,
349+
Py_TPFLAGS_HAVE_GC,
350+
Py_TPFLAGS_HAVE_FINALIZE,
351+
Py_TPFLAGS_HAVE_VERSION_TAG,
352+
Py_TPFLAGS_VALID_VERSION_TAG,
353+
Py_TPFLAGS_HAVE_VECTORCALL,
354+
Py_TPFLAGS_DISALLOW_INSTANTIATION,
355+
Py_TPFLAGS_IMMUTABLETYPE,
356+
Py_TPFLAGS_READY,
357+
Py_TPFLAGS_READYING,
358+
Py_TPFLAGS_LONG_SUBCLASS,
359+
Py_TPFLAGS_LIST_SUBCLASS,
360+
Py_TPFLAGS_TUPLE_SUBCLASS,
361+
Py_TPFLAGS_BYTES_SUBCLASS,
362+
Py_TPFLAGS_UNICODE_SUBCLASS,
363+
Py_TPFLAGS_DICT_SUBCLASS,
364+
Py_TPFLAGS_BASE_EXC_SUBCLASS,
365+
Py_TPFLAGS_TYPE_SUBCLASS,
366+
Py_TPFLAGS_IS_ABSTRACT,
367+
Py_TPFLAGS_BASETYPE,
368+
_Py_TPFLAGS_MATCH_SELF,
369+
Py_TPFLAGS_ITEMS_AT_END,
370+
Py_TPFLAGS_METHOD_DESCRIPTOR,
371+
)
372+
from _testcapi import (
373+
_Py_TPFLAGS_STATIC_BUILTIN,
374+
Py_TPFLAGS_SEQUENCE,
375+
Py_TPFLAGS_MAPPING,
376+
Py_TPFLAGS_INLINE_VALUES,
377+
Py_TPFLAGS_MANAGED_WEAKREF,
378+
Py_TPFLAGS_MANAGED_DICT,
379+
)
380+
381+
def check_flag(flags, flag, expected):
382+
self.assertEqual(bool(flags & flag), expected)
383+
384+
def check_subclasses(test_type, flags):
385+
for flag, base_type in (
386+
(Py_TPFLAGS_LONG_SUBCLASS, int),
387+
(Py_TPFLAGS_LIST_SUBCLASS, list),
388+
(Py_TPFLAGS_TUPLE_SUBCLASS, tuple),
389+
(Py_TPFLAGS_BYTES_SUBCLASS, bytes),
390+
(Py_TPFLAGS_UNICODE_SUBCLASS, str),
391+
(Py_TPFLAGS_DICT_SUBCLASS, dict),
392+
(Py_TPFLAGS_BASE_EXC_SUBCLASS, BaseException),
393+
(Py_TPFLAGS_TYPE_SUBCLASS, type),
394+
):
395+
with self.subTest(test_type=test_type, flag=flag, base_type=base_type):
396+
check_flag(flags, flag, issubclass(test_type, base_type))
397+
398+
def check_type(test_type, static_type, have_gc=False, have_vectorcall=False,
399+
is_base_type=True, sequence=False, mapping=False,
400+
match_self=True, items_at_end=False):
401+
heap_type = not static_type
402+
403+
flags = type_getflags(test_type)
404+
check_flag(flags, _Py_TPFLAGS_STATIC_BUILTIN, static_type)
405+
check_flag(flags, Py_TPFLAGS_HEAPTYPE, heap_type)
406+
check_flag(flags, Py_TPFLAGS_HAVE_GC, have_gc)
407+
check_subclasses(test_type, flags)
408+
check_flag(flags, Py_TPFLAGS_HAVE_VECTORCALL, have_vectorcall)
409+
check_flag(flags, Py_TPFLAGS_DISALLOW_INSTANTIATION, False)
410+
check_flag(flags, Py_TPFLAGS_IMMUTABLETYPE, static_type)
411+
check_flag(flags, Py_TPFLAGS_READY, True)
412+
check_flag(flags, Py_TPFLAGS_READYING, False)
413+
check_flag(flags, Py_TPFLAGS_IS_ABSTRACT, False)
414+
check_flag(flags, Py_TPFLAGS_BASETYPE, is_base_type)
415+
check_flag(flags, Py_TPFLAGS_SEQUENCE, sequence)
416+
check_flag(flags, Py_TPFLAGS_MAPPING, mapping)
417+
418+
check_flag(flags, Py_TPFLAGS_INLINE_VALUES, heap_type)
419+
check_flag(flags, Py_TPFLAGS_MANAGED_WEAKREF, heap_type)
420+
check_flag(flags, Py_TPFLAGS_MANAGED_DICT, heap_type)
421+
check_flag(flags, Py_TPFLAGS_ITEMS_AT_END, items_at_end)
422+
check_flag(flags, Py_TPFLAGS_METHOD_DESCRIPTOR, False)
423+
424+
check_flag(flags, _Py_TPFLAGS_MATCH_SELF, match_self)
425+
426+
# Flags kept for backward compatibility
427+
check_flag(flags, Py_TPFLAGS_HAVE_FINALIZE, False)
428+
check_flag(flags, Py_TPFLAGS_HAVE_VERSION_TAG, False)
429+
check_flag(flags, Py_TPFLAGS_VALID_VERSION_TAG, False)
430+
431+
# Scalar types
432+
check_type(int, static_type=True)
433+
check_type(bool, static_type=True,
434+
is_base_type=False)
435+
check_type(float, static_type=True)
436+
check_type(complex, static_type=True,
437+
match_self=False)
438+
check_type(bytes, static_type=True)
439+
check_type(bytearray, static_type=True)
440+
check_type(str, static_type=True)
441+
442+
# Collection types
443+
check_type(tuple, static_type=True, have_gc=True,
444+
sequence=True)
445+
check_type(list, static_type=True, have_gc=True,
446+
sequence=True)
447+
check_type(dict, static_type=True, have_gc=True,
448+
mapping=True)
449+
check_type(frozendict, static_type=True, have_gc=True,
450+
mapping=True)
451+
check_type(set, static_type=True, have_gc=True)
452+
check_type(frozenset, static_type=True, have_gc=True)
453+
454+
# Other types
455+
check_type(BaseException, static_type=True, have_gc=True,
456+
match_self=False)
457+
check_type(type, static_type=True, have_gc=True,
458+
have_vectorcall=True,
459+
match_self=False,
460+
items_at_end=True)
461+
462+
# Heap type
463+
class HeapType:
464+
pass
465+
check_type(HeapType, static_type=False, have_gc=True, match_self=False)
466+
467+
# CRASHES type_getflags(NULL)
468+
469+
def test_type_issubtype(self):
470+
# Test PyType_IsSubtype()
471+
_type_issubtype = _testlimitedcapi.type_issubtype
472+
473+
def type_issubtype(type1, type2):
474+
res = _type_issubtype(type1, type2)
475+
self.assertIn(res, (0, 1))
476+
return bool(res)
477+
478+
class MyList(list):
479+
pass
480+
481+
self.assertTrue(type_issubtype(bool, int))
482+
self.assertTrue(type_issubtype(MyList, list))
483+
484+
self.assertFalse(type_issubtype(int, type))
485+
self.assertFalse(type_issubtype(frozendict, dict))
486+
self.assertFalse(type_issubtype(MyList, tuple))
487+
488+
def test_type_modified(self):
489+
# Test PyType_Modified()
490+
type_modified = _testlimitedcapi.type_modified
491+
492+
class MyType:
493+
pass
494+
type_modified(MyType)
495+
496+
# CRASHES type_modified(NULL)
497+
# CRASHES type_modified({}): argument must be a type

Lib/test/test_type_cache.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
except ImportError:
1212
_clear_type_cache = None
1313

14-
# Skip this test if the _testcapi module isn't available.
14+
# Skip this test if the _testcapi modules are not available.
1515
_testcapi = import_helper.import_module("_testcapi")
16+
_testlimitedcapi = import_helper.import_module("_testlimitedcapi")
1617
_testinternalcapi = import_helper.import_module("_testinternalcapi")
18+
1719
type_get_version = _testcapi.type_get_version
1820
type_assign_specific_version_unsafe = _testinternalcapi.type_assign_specific_version_unsafe
1921
type_assign_version = _testcapi.type_assign_version
20-
type_modified = _testcapi.type_modified
22+
type_modified = _testlimitedcapi.type_modified
2123

2224
def clear_type_cache():
2325
with warnings.catch_warnings():

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
175175
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c
176176
@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/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
177-
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.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/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c
177+
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.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/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c
178178
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
179179
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
180180

0 commit comments

Comments
 (0)