https://github.com/python/cpython/commit/bce9df97d5349e351666055bd797c99e55a1770a
commit: bce9df97d5349e351666055bd797c99e55a1770a
branch: 3.12
author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com>
committer: encukou <encu...@gmail.com>
date: 2024-10-29T14:15:09+01:00
summary:

[3.12] gh-125783: Add tests to prevent regressions with the combination of 
`ctypes` and metaclasses. (GH-125881) (GH-125988)

cherry picked from commit 13844094609cf8265a2eed023e33c7002f3f530d
by Jun Komoda

Also: Add test_ctypes/_support.py from 3.13+
This partially backports be89ee5649031e08f191bf596fa20a09c5698079
(https://github.com/python/cpython/pull/113727)
by AN Long


Co-authored-by: Jun Komoda <45822440+jun...@users.noreply.github.com>
Co-authored-by: AN Long <a...@users.noreply.github.com>
Co-authored-by: Erlend E. Aasland <erlend.aasl...@protonmail.com>

files:
A Lib/test/test_ctypes/_support.py
A Lib/test/test_ctypes/test_c_simple_type_meta.py

diff --git a/Lib/test/test_ctypes/_support.py b/Lib/test/test_ctypes/_support.py
new file mode 100644
index 00000000000000..e4c2b33825ae8f
--- /dev/null
+++ b/Lib/test/test_ctypes/_support.py
@@ -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
diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py 
b/Lib/test/test_ctypes/test_c_simple_type_meta.py
new file mode 100644
index 00000000000000..fa5144a3ca01bb
--- /dev/null
+++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py
@@ -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))

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to