-
-
Notifications
You must be signed in to change notification settings - Fork 31.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[3.12] gh-125783: Add tests to prevent regressions with the combinati…
…on of `ctypes` and metaclasses. (GH-125881) (GH-125988) cherry picked from commit 1384409 by Jun Komoda Also: Add test_ctypes/_support.py from 3.13+ This partially backports be89ee5 (#113727) by AN Long Co-authored-by: Jun Komoda <[email protected]> Co-authored-by: AN Long <[email protected]> Co-authored-by: Erlend E. Aasland <[email protected]>
- Loading branch information
1 parent
bc9ae4a
commit bce9df9
Showing
2 changed files
with
111 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Some classes and types are not export to _ctypes module directly. | ||
|
||
import ctypes | ||
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr | ||
|
||
|
||
_CData = Structure.__base__ | ||
assert _CData.__name__ == "_CData" | ||
|
||
class _X(Structure): | ||
_fields_ = [("x", ctypes.c_int)] | ||
CField = type(_X.x) | ||
|
||
# metaclasses | ||
PyCStructType = type(Structure) | ||
UnionType = type(Union) | ||
PyCPointerType = type(_Pointer) | ||
PyCArrayType = type(Array) | ||
PyCSimpleType = type(_SimpleCData) | ||
PyCFuncPtrType = type(CFuncPtr) | ||
|
||
# type flags | ||
Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7 | ||
Py_TPFLAGS_IMMUTABLETYPE = 1 << 8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import unittest | ||
import ctypes | ||
from ctypes import POINTER, c_void_p | ||
|
||
from ._support import PyCSimpleType | ||
|
||
|
||
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase): | ||
def tearDown(self): | ||
# to not leak references, we must clean _pointer_type_cache | ||
ctypes._reset_cache() | ||
|
||
def test_creating_pointer_in_dunder_new_1(self): | ||
# Test metaclass whose instances are C types; when the type is | ||
# created it automatically creates a pointer type for itself. | ||
# The pointer type is also an instance of the metaclass. | ||
# Such an implementation is used in `IUnknown` of the `comtypes` | ||
# project. See gh-124520. | ||
|
||
class ct_meta(type): | ||
def __new__(cls, name, bases, namespace): | ||
self = super().__new__(cls, name, bases, namespace) | ||
|
||
# Avoid recursion: don't set up a pointer to | ||
# a pointer (to a pointer...) | ||
if bases == (c_void_p,): | ||
# When creating PtrBase itself, the name | ||
# is not yet available | ||
return self | ||
if issubclass(self, PtrBase): | ||
return self | ||
|
||
if bases == (object,): | ||
ptr_bases = (self, PtrBase) | ||
else: | ||
ptr_bases = (self, POINTER(bases[0])) | ||
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {}) | ||
ctypes._pointer_type_cache[self] = p | ||
return self | ||
|
||
class p_meta(PyCSimpleType, ct_meta): | ||
pass | ||
|
||
class PtrBase(c_void_p, metaclass=p_meta): | ||
pass | ||
|
||
class CtBase(object, metaclass=ct_meta): | ||
pass | ||
|
||
class Sub(CtBase): | ||
pass | ||
|
||
class Sub2(Sub): | ||
pass | ||
|
||
self.assertIsInstance(POINTER(Sub2), p_meta) | ||
self.assertTrue(issubclass(POINTER(Sub2), Sub2)) | ||
self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub))) | ||
self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase))) | ||
|
||
def test_creating_pointer_in_dunder_new_2(self): | ||
# A simpler variant of the above, used in `CoClass` of the `comtypes` | ||
# project. | ||
|
||
class ct_meta(type): | ||
def __new__(cls, name, bases, namespace): | ||
self = super().__new__(cls, name, bases, namespace) | ||
if isinstance(self, p_meta): | ||
return self | ||
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {}) | ||
ctypes._pointer_type_cache[self] = p | ||
return self | ||
|
||
class p_meta(PyCSimpleType, ct_meta): | ||
pass | ||
|
||
class Core(object): | ||
pass | ||
|
||
class CtBase(Core, metaclass=ct_meta): | ||
pass | ||
|
||
class Sub(CtBase): | ||
pass | ||
|
||
self.assertIsInstance(POINTER(Sub), p_meta) | ||
self.assertTrue(issubclass(POINTER(Sub), Sub)) |