Skip to content

Commit 1384409

Browse files
authored
gh-125783: Add tests to prevent regressions with the combination of ctypes and metaclasses. (GH-125881)
1 parent 7f6e884 commit 1384409

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import unittest
2+
import ctypes
3+
from ctypes import POINTER, c_void_p
4+
5+
from ._support import PyCSimpleType
6+
7+
8+
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
9+
def tearDown(self):
10+
# to not leak references, we must clean _pointer_type_cache
11+
ctypes._reset_cache()
12+
13+
def test_creating_pointer_in_dunder_new_1(self):
14+
# Test metaclass whose instances are C types; when the type is
15+
# created it automatically creates a pointer type for itself.
16+
# The pointer type is also an instance of the metaclass.
17+
# Such an implementation is used in `IUnknown` of the `comtypes`
18+
# project. See gh-124520.
19+
20+
class ct_meta(type):
21+
def __new__(cls, name, bases, namespace):
22+
self = super().__new__(cls, name, bases, namespace)
23+
24+
# Avoid recursion: don't set up a pointer to
25+
# a pointer (to a pointer...)
26+
if bases == (c_void_p,):
27+
# When creating PtrBase itself, the name
28+
# is not yet available
29+
return self
30+
if issubclass(self, PtrBase):
31+
return self
32+
33+
if bases == (object,):
34+
ptr_bases = (self, PtrBase)
35+
else:
36+
ptr_bases = (self, POINTER(bases[0]))
37+
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {})
38+
ctypes._pointer_type_cache[self] = p
39+
return self
40+
41+
class p_meta(PyCSimpleType, ct_meta):
42+
pass
43+
44+
class PtrBase(c_void_p, metaclass=p_meta):
45+
pass
46+
47+
class CtBase(object, metaclass=ct_meta):
48+
pass
49+
50+
class Sub(CtBase):
51+
pass
52+
53+
class Sub2(Sub):
54+
pass
55+
56+
self.assertIsInstance(POINTER(Sub2), p_meta)
57+
self.assertTrue(issubclass(POINTER(Sub2), Sub2))
58+
self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub)))
59+
self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase)))
60+
61+
def test_creating_pointer_in_dunder_new_2(self):
62+
# A simpler variant of the above, used in `CoClass` of the `comtypes`
63+
# project.
64+
65+
class ct_meta(type):
66+
def __new__(cls, name, bases, namespace):
67+
self = super().__new__(cls, name, bases, namespace)
68+
if isinstance(self, p_meta):
69+
return self
70+
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {})
71+
ctypes._pointer_type_cache[self] = p
72+
return self
73+
74+
class p_meta(PyCSimpleType, ct_meta):
75+
pass
76+
77+
class Core(object):
78+
pass
79+
80+
class CtBase(Core, metaclass=ct_meta):
81+
pass
82+
83+
class Sub(CtBase):
84+
pass
85+
86+
self.assertIsInstance(POINTER(Sub), p_meta)
87+
self.assertTrue(issubclass(POINTER(Sub), Sub))

0 commit comments

Comments
 (0)