Skip to content

Commit

Permalink
pythongh-103092: Test _ctypes type hierarchy and features (python#113727
Browse files Browse the repository at this point in the history
)

Test the following features for _ctypes types:
- disallow instantiation
- inheritance (MRO)
- immutability
- type name

The following _ctypes types are tested:
- Array
- CField
- COMError
- PyCArrayType
- PyCFuncPtrType
- PyCPointerType
- PyCSimpleType
- PyCStructType
- Structure
- Union
- UnionType
- _CFuncPtr
- _Pointer
- _SimpleCData

Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
  • Loading branch information
2 people authored and Glyphack committed Jan 27, 2024
1 parent 888b7c5 commit 541a914
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 5 deletions.
24 changes: 24 additions & 0 deletions Lib/test/test_ctypes/_support.py
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
14 changes: 14 additions & 0 deletions Lib/test/test_ctypes/test_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
c_char, c_wchar, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
c_long, c_ulonglong, c_float, c_double, c_longdouble)
from test.support import bigmemtest, _2G
from ._support import (_CData, PyCArrayType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


formats = "bBhHiIlLqQfd"
Expand All @@ -23,6 +25,18 @@ def ARRAY(*args):


class ArrayTestCase(unittest.TestCase):
def test_inheritance_hierarchy(self):
self.assertEqual(Array.mro(), [Array, _CData, object])

self.assertEqual(PyCArrayType.__name__, "PyCArrayType")
self.assertEqual(type(PyCArrayType), type)

def test_type_flags(self):
for cls in Array, PyCArrayType:
with self.subTest(cls=cls):
self.assertTrue(cls.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(cls.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)

def test_simple(self):
# create classes holding simple numeric types, and check
# various properties.
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_ctypes/test_funcptr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import unittest
from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr,
c_void_p, c_char_p, c_char, c_int, c_uint, c_long)
from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


try:
Expand All @@ -15,6 +17,18 @@


class CFuncPtrTestCase(unittest.TestCase):
def test_inheritance_hierarchy(self):
self.assertEqual(_CFuncPtr.mro(), [_CFuncPtr, _CData, object])

self.assertEqual(PyCFuncPtrType.__name__, "PyCFuncPtrType")
self.assertEqual(type(PyCFuncPtrType), type)

def test_type_flags(self):
for cls in _CFuncPtr, PyCFuncPtrType:
with self.subTest(cls=cls):
self.assertTrue(_CFuncPtr.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(_CFuncPtr.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)

def test_basic(self):
X = WINFUNCTYPE(c_int, c_int, c_int)

Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_ctypes/test_pointers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong,
c_float, c_double)
from ._support import (_CData, PyCPointerType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


ctype_types = [c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
Expand All @@ -19,6 +21,18 @@


class PointersTestCase(unittest.TestCase):
def test_inheritance_hierarchy(self):
self.assertEqual(_Pointer.mro(), [_Pointer, _CData, object])

self.assertEqual(PyCPointerType.__name__, "PyCPointerType")
self.assertEqual(type(PyCPointerType), type)

def test_type_flags(self):
for cls in _Pointer, PyCPointerType:
with self.subTest(cls=cls):
self.assertTrue(_Pointer.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(_Pointer.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)

def test_pointer_crash(self):

class A(POINTER(c_ulong)):
Expand Down
17 changes: 16 additions & 1 deletion Lib/test/test_ctypes/test_simplesubclasses.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import unittest
from ctypes import Structure, CFUNCTYPE, c_int
from ctypes import Structure, CFUNCTYPE, c_int, _SimpleCData
from ._support import (_CData, PyCSimpleType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


class MyInt(c_int):
Expand All @@ -10,6 +12,19 @@ def __eq__(self, other):


class Test(unittest.TestCase):
def test_inheritance_hierarchy(self):
self.assertEqual(_SimpleCData.mro(), [_SimpleCData, _CData, object])

self.assertEqual(PyCSimpleType.__name__, "PyCSimpleType")
self.assertEqual(type(PyCSimpleType), type)

self.assertEqual(c_int.mro(), [c_int, _SimpleCData, _CData, object])

def test_type_flags(self):
for cls in _SimpleCData, PyCSimpleType:
with self.subTest(cls=cls):
self.assertTrue(_SimpleCData.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(_SimpleCData.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)

def test_compare(self):
self.assertEqual(MyInt(3), MyInt(3))
Expand Down
13 changes: 9 additions & 4 deletions Lib/test/test_ctypes/test_struct_fields.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import unittest
from ctypes import Structure, Union, sizeof, c_char, c_int
from ._support import (CField, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


class StructFieldsTestCase(unittest.TestCase):
Expand All @@ -12,7 +14,6 @@ class StructFieldsTestCase(unittest.TestCase):
# 4. The type is subclassed
#
# When they are finalized, assigning _fields_ is no longer allowed.

def test_1_A(self):
class X(Structure):
pass
Expand Down Expand Up @@ -56,11 +57,15 @@ class X(Structure):
self.assertEqual(bytes(x), b'a\x00###')

def test_6(self):
class X(Structure):
_fields_ = [("x", c_int)]
CField = type(X.x)
self.assertRaises(TypeError, CField)

def test_cfield_type_flags(self):
self.assertTrue(CField.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)

def test_cfield_inheritance_hierarchy(self):
self.assertEqual(CField.mro(), [CField, object])

def test_gh99275(self):
class BrokenStructure(Structure):
def __init_subclass__(cls, **kwargs):
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_ctypes/test_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from struct import calcsize
from collections import namedtuple
from test import support
from ._support import (_CData, PyCStructType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


class SubclassesTest(unittest.TestCase):
Expand Down Expand Up @@ -70,6 +72,19 @@ class StructureTestCase(unittest.TestCase):
"d": c_double,
}

def test_inheritance_hierarchy(self):
self.assertEqual(Structure.mro(), [Structure, _CData, object])

self.assertEqual(PyCStructType.__name__, "PyCStructType")
self.assertEqual(type(PyCStructType), type)


def test_type_flags(self):
for cls in Structure, PyCStructType:
with self.subTest(cls=cls):
self.assertTrue(Structure.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(Structure.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)

def test_simple_structs(self):
for code, tp in self.formats.items():
class X(Structure):
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_ctypes/test_unions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import unittest
from ctypes import Union
from ._support import (_CData, UnionType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)


class ArrayTestCase(unittest.TestCase):
def test_inheritance_hierarchy(self):
self.assertEqual(Union.mro(), [Union, _CData, object])

self.assertEqual(UnionType.__name__, "UnionType")
self.assertEqual(type(UnionType), type)

def test_type_flags(self):
for cls in Union, UnionType:
with self.subTest(cls=Union):
self.assertTrue(Union.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(Union.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
6 changes: 6 additions & 0 deletions Lib/test/test_ctypes/test_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
_pointer_type_cache,
c_void_p, c_char, c_int, c_long)
from test import support
from ._support import Py_TPFLAGS_DISALLOW_INSTANTIATION, Py_TPFLAGS_IMMUTABLETYPE


@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test')
Expand Down Expand Up @@ -73,6 +74,11 @@ def test_COMError(self):
self.assertEqual(ex.text, "text")
self.assertEqual(ex.details, ("details",))

self.assertEqual(COMError.mro(),
[COMError, Exception, BaseException, object])
self.assertFalse(COMError.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
self.assertTrue(COMError.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)


@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test')
class TestWinError(unittest.TestCase):
Expand Down

0 comments on commit 541a914

Please sign in to comment.