Skip to content

Commit 1a238a6

Browse files
committed
gh-155503: Add more PyType C API tests
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 e4b22ad commit 1a238a6

10 files changed

Lines changed: 467 additions & 96 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: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unittest
33

44
_testcapi = import_helper.import_module('_testcapi')
5+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
56

67

78
class BuiltinStaticTypesTests(unittest.TestCase):
@@ -39,15 +40,18 @@ def test_tp_mro_is_set(self):
3940

4041
class TypeTests(unittest.TestCase):
4142
def test_get_type_name(self):
43+
# Test PyType_GetName(), PyType_GetQualName(),
44+
# PyType_GetFullyQualifiedName() and PyType_GetModuleName().
45+
4246
class MyType:
4347
pass
4448

45-
from _testcapi import (
49+
from _testlimitedcapi import (
4650
get_type_name, get_type_qualname,
4751
get_type_fullyqualname, get_type_module_name)
4852

4953
from collections import OrderedDict
50-
ht = _testcapi.get_heaptype_for_name()
54+
ht = _testlimitedcapi.get_heaptype_for_name()
5155
for cls, fullname, modname, qualname, name in (
5256
(int,
5357
'int',
@@ -215,7 +219,7 @@ class H2(int): pass
215219

216220
def test_freeze(self):
217221
# test PyType_Freeze()
218-
type_freeze = _testcapi.type_freeze
222+
type_freeze = _testlimitedcapi.type_freeze
219223

220224
# simple case, no inherante
221225
class MyType:
@@ -250,7 +254,7 @@ class D(A, C): pass
250254
"Specialization failure triggers gh-127773")
251255
def test_freeze_meta(self):
252256
"""test PyType_Freeze() with overridden MRO"""
253-
type_freeze = _testcapi.type_freeze
257+
type_freeze = _testlimitedcapi.type_freeze
254258

255259
class Base:
256260
value = 1
@@ -299,3 +303,160 @@ def test_extension_managed_weakref_nogc_type(self):
299303
"flag but not Py_TPFLAGS_HAVE_GC flag")
300304
with self.assertRaisesRegex(SystemError, msg):
301305
_testcapi.create_managed_weakref_nogc_type()
306+
307+
def test_type_ready(self):
308+
# Test PyType_Ready(): calling it on initialized types
309+
# must not raise an exception.
310+
type_ready = _testlimitedcapi.type_ready
311+
312+
class HeapType:
313+
pass
314+
315+
type_ready(int)
316+
type_ready(dict)
317+
type_ready(HeapType)
318+
319+
def test_type_clearcache(self):
320+
# Test PyType_ClearCache()
321+
type_clearcache = _testlimitedcapi.type_clearcache
322+
version_tag = type_clearcache()
323+
self.assertEqual(type(version_tag), int)
324+
self.assertGreaterEqual(version_tag, 0)
325+
326+
def test_type_getflags(self):
327+
# Test PyType_GetFlags()
328+
type_getflags = _testlimitedcapi.type_getflags
329+
330+
from _testlimitedcapi import (
331+
Py_TPFLAGS_HEAPTYPE,
332+
Py_TPFLAGS_HAVE_GC,
333+
Py_TPFLAGS_HAVE_FINALIZE,
334+
Py_TPFLAGS_HAVE_VERSION_TAG,
335+
Py_TPFLAGS_VALID_VERSION_TAG,
336+
Py_TPFLAGS_HAVE_VECTORCALL,
337+
Py_TPFLAGS_DISALLOW_INSTANTIATION,
338+
Py_TPFLAGS_IMMUTABLETYPE,
339+
Py_TPFLAGS_READY,
340+
Py_TPFLAGS_READYING,
341+
Py_TPFLAGS_LONG_SUBCLASS,
342+
Py_TPFLAGS_LIST_SUBCLASS,
343+
Py_TPFLAGS_TUPLE_SUBCLASS,
344+
Py_TPFLAGS_BYTES_SUBCLASS,
345+
Py_TPFLAGS_UNICODE_SUBCLASS,
346+
Py_TPFLAGS_DICT_SUBCLASS,
347+
Py_TPFLAGS_BASE_EXC_SUBCLASS,
348+
Py_TPFLAGS_TYPE_SUBCLASS,
349+
Py_TPFLAGS_IS_ABSTRACT,
350+
Py_TPFLAGS_BASETYPE,
351+
_Py_TPFLAGS_MATCH_SELF,
352+
Py_TPFLAGS_ITEMS_AT_END,
353+
Py_TPFLAGS_METHOD_DESCRIPTOR,
354+
)
355+
from _testcapi import (
356+
_Py_TPFLAGS_STATIC_BUILTIN,
357+
Py_TPFLAGS_SEQUENCE,
358+
Py_TPFLAGS_MAPPING,
359+
Py_TPFLAGS_INLINE_VALUES,
360+
Py_TPFLAGS_MANAGED_WEAKREF,
361+
Py_TPFLAGS_MANAGED_DICT,
362+
)
363+
364+
def check_flag(flags, flag, expected):
365+
self.assertEqual(bool(flags & flag), expected)
366+
367+
def check_subclasses(test_type, flags):
368+
for flag, base_type in (
369+
(Py_TPFLAGS_LONG_SUBCLASS, int),
370+
(Py_TPFLAGS_LIST_SUBCLASS, list),
371+
(Py_TPFLAGS_TUPLE_SUBCLASS, tuple),
372+
(Py_TPFLAGS_BYTES_SUBCLASS, bytes),
373+
(Py_TPFLAGS_UNICODE_SUBCLASS, str),
374+
(Py_TPFLAGS_DICT_SUBCLASS, dict),
375+
(Py_TPFLAGS_BASE_EXC_SUBCLASS, BaseException),
376+
(Py_TPFLAGS_TYPE_SUBCLASS, type),
377+
):
378+
with self.subTest(test_type=test_type, flag=flag, base_type=base_type):
379+
check_flag(flags, flag, issubclass(test_type, base_type))
380+
381+
def check_type(test_type, static_type, have_gc=False, have_vectorcall=False,
382+
is_base_type=True, sequence=False, mapping=False,
383+
match_self=True, items_at_end=False):
384+
heap_type = not static_type
385+
386+
flags = type_getflags(test_type)
387+
check_flag(flags, _Py_TPFLAGS_STATIC_BUILTIN, static_type)
388+
check_flag(flags, Py_TPFLAGS_HEAPTYPE, heap_type)
389+
check_flag(flags, Py_TPFLAGS_HAVE_GC, have_gc)
390+
check_subclasses(test_type, flags)
391+
check_flag(flags, Py_TPFLAGS_HAVE_VECTORCALL, have_vectorcall)
392+
check_flag(flags, Py_TPFLAGS_DISALLOW_INSTANTIATION, False)
393+
check_flag(flags, Py_TPFLAGS_IMMUTABLETYPE, static_type)
394+
check_flag(flags, Py_TPFLAGS_READY, True)
395+
check_flag(flags, Py_TPFLAGS_READYING, False)
396+
check_flag(flags, Py_TPFLAGS_IS_ABSTRACT, False)
397+
check_flag(flags, Py_TPFLAGS_BASETYPE, is_base_type)
398+
check_flag(flags, Py_TPFLAGS_SEQUENCE, sequence)
399+
check_flag(flags, Py_TPFLAGS_MAPPING, mapping)
400+
401+
check_flag(flags, Py_TPFLAGS_INLINE_VALUES, heap_type)
402+
check_flag(flags, Py_TPFLAGS_MANAGED_WEAKREF, heap_type)
403+
check_flag(flags, Py_TPFLAGS_MANAGED_DICT, heap_type)
404+
check_flag(flags, Py_TPFLAGS_ITEMS_AT_END, items_at_end)
405+
check_flag(flags, Py_TPFLAGS_METHOD_DESCRIPTOR, False)
406+
407+
check_flag(flags, _Py_TPFLAGS_MATCH_SELF, match_self)
408+
409+
# Flags kept for backward compatibility
410+
check_flag(flags, Py_TPFLAGS_HAVE_FINALIZE, False)
411+
check_flag(flags, Py_TPFLAGS_HAVE_VERSION_TAG, False)
412+
check_flag(flags, Py_TPFLAGS_VALID_VERSION_TAG, False)
413+
414+
# Scalar types
415+
check_type(int, static_type=True)
416+
check_type(bool, static_type=True,
417+
is_base_type=False)
418+
check_type(float, static_type=True)
419+
check_type(complex, static_type=True,
420+
match_self=False)
421+
check_type(bytes, static_type=True)
422+
check_type(bytearray, static_type=True)
423+
check_type(str, static_type=True)
424+
425+
# Collection types
426+
check_type(tuple, static_type=True, have_gc=True,
427+
sequence=True)
428+
check_type(list, static_type=True, have_gc=True,
429+
sequence=True)
430+
check_type(dict, static_type=True, have_gc=True,
431+
mapping=True)
432+
check_type(frozendict, static_type=True, have_gc=True,
433+
mapping=True)
434+
check_type(set, static_type=True, have_gc=True)
435+
check_type(frozenset, static_type=True, have_gc=True)
436+
437+
# Other types
438+
check_type(BaseException, static_type=True, have_gc=True,
439+
match_self=False)
440+
check_type(type, static_type=True, have_gc=True,
441+
have_vectorcall=True,
442+
match_self=False,
443+
items_at_end=True)
444+
445+
# Heap type
446+
class HeapType:
447+
pass
448+
check_type(HeapType, static_type=False, have_gc=True, match_self=False)
449+
450+
def test_type_issubtype(self):
451+
# Test PyType_IsSubtype()
452+
type_issubtype = _testlimitedcapi.type_issubtype
453+
454+
class MyList(list):
455+
pass
456+
457+
self.assertTrue(type_issubtype(bool, int))
458+
self.assertTrue(type_issubtype(MyList, list))
459+
460+
self.assertFalse(type_issubtype(int, type))
461+
self.assertFalse(type_issubtype(frozendict, dict))
462+
self.assertFalse(type_issubtype(MyList, tuple))

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)