Skip to content

Commit 01fc3b3

Browse files
authored
gh-124570: ctypes: Run some Structure tests on Union as well (GH-124976)
- Move some Structure tests to test_structunion; use a common base test class + two subclasses to run them on Union too - Remove test_union for now as it's redundant Note: `test_simple_structs` & `test_simple_unions` are in the common file because they share `formats`.
1 parent c914212 commit 01fc3b3

File tree

3 files changed

+368
-357
lines changed

3 files changed

+368
-357
lines changed
+353
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
"""Common tests for ctypes.Structure and ctypes.Union"""
2+
3+
import unittest
4+
from ctypes import (Structure, Union, POINTER, sizeof, alignment,
5+
c_char, c_byte, c_ubyte,
6+
c_short, c_ushort, c_int, c_uint,
7+
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
8+
from ._support import (_CData, PyCStructType, UnionType,
9+
Py_TPFLAGS_DISALLOW_INSTANTIATION,
10+
Py_TPFLAGS_IMMUTABLETYPE)
11+
from struct import calcsize
12+
13+
14+
class StructUnionTestBase:
15+
formats = {"c": c_char,
16+
"b": c_byte,
17+
"B": c_ubyte,
18+
"h": c_short,
19+
"H": c_ushort,
20+
"i": c_int,
21+
"I": c_uint,
22+
"l": c_long,
23+
"L": c_ulong,
24+
"q": c_longlong,
25+
"Q": c_ulonglong,
26+
"f": c_float,
27+
"d": c_double,
28+
}
29+
30+
def test_subclass(self):
31+
class X(self.cls):
32+
_fields_ = [("a", c_int)]
33+
34+
class Y(X):
35+
_fields_ = [("b", c_int)]
36+
37+
class Z(X):
38+
pass
39+
40+
self.assertEqual(sizeof(X), sizeof(c_int))
41+
self.check_sizeof(Y,
42+
struct_size=sizeof(c_int)*2,
43+
union_size=sizeof(c_int))
44+
self.assertEqual(sizeof(Z), sizeof(c_int))
45+
self.assertEqual(X._fields_, [("a", c_int)])
46+
self.assertEqual(Y._fields_, [("b", c_int)])
47+
self.assertEqual(Z._fields_, [("a", c_int)])
48+
49+
def test_subclass_delayed(self):
50+
class X(self.cls):
51+
pass
52+
self.assertEqual(sizeof(X), 0)
53+
X._fields_ = [("a", c_int)]
54+
55+
class Y(X):
56+
pass
57+
self.assertEqual(sizeof(Y), sizeof(X))
58+
Y._fields_ = [("b", c_int)]
59+
60+
class Z(X):
61+
pass
62+
63+
self.assertEqual(sizeof(X), sizeof(c_int))
64+
self.check_sizeof(Y,
65+
struct_size=sizeof(c_int)*2,
66+
union_size=sizeof(c_int))
67+
self.assertEqual(sizeof(Z), sizeof(c_int))
68+
self.assertEqual(X._fields_, [("a", c_int)])
69+
self.assertEqual(Y._fields_, [("b", c_int)])
70+
self.assertEqual(Z._fields_, [("a", c_int)])
71+
72+
def test_inheritance_hierarchy(self):
73+
self.assertEqual(self.cls.mro(), [self.cls, _CData, object])
74+
self.assertEqual(type(self.metacls), type)
75+
76+
def test_type_flags(self):
77+
for cls in self.cls, self.metacls:
78+
with self.subTest(cls=cls):
79+
self.assertTrue(cls.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
80+
self.assertFalse(cls.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
81+
82+
def test_metaclass_details(self):
83+
# Abstract classes (whose metaclass __init__ was not called) can't be
84+
# instantiated directly
85+
NewClass = self.metacls.__new__(self.metacls, 'NewClass',
86+
(self.cls,), {})
87+
for cls in self.cls, NewClass:
88+
with self.subTest(cls=cls):
89+
with self.assertRaisesRegex(TypeError, "abstract class"):
90+
obj = cls()
91+
92+
# Cannot call the metaclass __init__ more than once
93+
class T(self.cls):
94+
_fields_ = [("x", c_char),
95+
("y", c_char)]
96+
with self.assertRaisesRegex(SystemError, "already initialized"):
97+
self.metacls.__init__(T, 'ptr', (), {})
98+
99+
def test_alignment(self):
100+
class X(self.cls):
101+
_fields_ = [("x", c_char * 3)]
102+
self.assertEqual(alignment(X), calcsize("s"))
103+
self.assertEqual(sizeof(X), calcsize("3s"))
104+
105+
class Y(self.cls):
106+
_fields_ = [("x", c_char * 3),
107+
("y", c_int)]
108+
self.assertEqual(alignment(Y), alignment(c_int))
109+
self.check_sizeof(Y,
110+
struct_size=calcsize("3s i"),
111+
union_size=max(calcsize("3s"), calcsize("i")))
112+
113+
class SI(self.cls):
114+
_fields_ = [("a", X),
115+
("b", Y)]
116+
self.assertEqual(alignment(SI), max(alignment(Y), alignment(X)))
117+
self.check_sizeof(SI,
118+
struct_size=calcsize("3s0i 3si 0i"),
119+
union_size=max(calcsize("3s"), calcsize("i")))
120+
121+
class IS(self.cls):
122+
_fields_ = [("b", Y),
123+
("a", X)]
124+
125+
self.assertEqual(alignment(SI), max(alignment(X), alignment(Y)))
126+
self.check_sizeof(IS,
127+
struct_size=calcsize("3si 3s 0i"),
128+
union_size=max(calcsize("3s"), calcsize("i")))
129+
130+
class XX(self.cls):
131+
_fields_ = [("a", X),
132+
("b", X)]
133+
self.assertEqual(alignment(XX), alignment(X))
134+
self.check_sizeof(XX,
135+
struct_size=calcsize("3s 3s 0s"),
136+
union_size=calcsize("3s"))
137+
138+
def test_empty(self):
139+
# I had problems with these
140+
#
141+
# Although these are pathological cases: Empty Structures!
142+
class X(self.cls):
143+
_fields_ = []
144+
145+
# Is this really the correct alignment, or should it be 0?
146+
self.assertTrue(alignment(X) == 1)
147+
self.assertTrue(sizeof(X) == 0)
148+
149+
class XX(self.cls):
150+
_fields_ = [("a", X),
151+
("b", X)]
152+
153+
self.assertEqual(alignment(XX), 1)
154+
self.assertEqual(sizeof(XX), 0)
155+
156+
def test_fields(self):
157+
# test the offset and size attributes of Structure/Union fields.
158+
class X(self.cls):
159+
_fields_ = [("x", c_int),
160+
("y", c_char)]
161+
162+
self.assertEqual(X.x.offset, 0)
163+
self.assertEqual(X.x.size, sizeof(c_int))
164+
165+
if self.cls == Structure:
166+
self.assertEqual(X.y.offset, sizeof(c_int))
167+
else:
168+
self.assertEqual(X.y.offset, 0)
169+
self.assertEqual(X.y.size, sizeof(c_char))
170+
171+
# readonly
172+
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
173+
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
174+
175+
# XXX Should we check nested data types also?
176+
# offset is always relative to the class...
177+
178+
def test_invalid_field_types(self):
179+
class POINT(self.cls):
180+
pass
181+
self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)])
182+
183+
def test_invalid_name(self):
184+
# field name must be string
185+
def declare_with_name(name):
186+
class S(self.cls):
187+
_fields_ = [(name, c_int)]
188+
189+
self.assertRaises(TypeError, declare_with_name, b"x")
190+
191+
def test_intarray_fields(self):
192+
class SomeInts(self.cls):
193+
_fields_ = [("a", c_int * 4)]
194+
195+
# can use tuple to initialize array (but not list!)
196+
self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0])
197+
self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0])
198+
self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1])
199+
self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0])
200+
self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2])
201+
self.assertEqual(SomeInts((1, 2)).a[6:4:-1], [])
202+
self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4])
203+
self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4])
204+
# too long
205+
# XXX Should raise ValueError?, not RuntimeError
206+
self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5))
207+
208+
def test_huge_field_name(self):
209+
# issue12881: segfault with large structure field names
210+
def create_class(length):
211+
class S(self.cls):
212+
_fields_ = [('x' * length, c_int)]
213+
214+
for length in [10 ** i for i in range(0, 8)]:
215+
try:
216+
create_class(length)
217+
except MemoryError:
218+
# MemoryErrors are OK, we just don't want to segfault
219+
pass
220+
221+
def test_abstract_class(self):
222+
class X(self.cls):
223+
_abstract_ = "something"
224+
with self.assertRaisesRegex(TypeError, r"^abstract class$"):
225+
X()
226+
227+
def test_methods(self):
228+
self.assertIn("in_dll", dir(type(self.cls)))
229+
self.assertIn("from_address", dir(type(self.cls)))
230+
self.assertIn("in_dll", dir(type(self.cls)))
231+
232+
233+
class StructureTestCase(unittest.TestCase, StructUnionTestBase):
234+
cls = Structure
235+
metacls = PyCStructType
236+
237+
def test_metaclass_name(self):
238+
self.assertEqual(self.metacls.__name__, "PyCStructType")
239+
240+
def check_sizeof(self, cls, *, struct_size, union_size):
241+
self.assertEqual(sizeof(cls), struct_size)
242+
243+
def test_simple_structs(self):
244+
for code, tp in self.formats.items():
245+
class X(Structure):
246+
_fields_ = [("x", c_char),
247+
("y", tp)]
248+
self.assertEqual((sizeof(X), code),
249+
(calcsize("c%c0%c" % (code, code)), code))
250+
251+
252+
class UnionTestCase(unittest.TestCase, StructUnionTestBase):
253+
cls = Union
254+
metacls = UnionType
255+
256+
def test_metaclass_name(self):
257+
self.assertEqual(self.metacls.__name__, "UnionType")
258+
259+
def check_sizeof(self, cls, *, struct_size, union_size):
260+
self.assertEqual(sizeof(cls), union_size)
261+
262+
def test_simple_unions(self):
263+
for code, tp in self.formats.items():
264+
class X(Union):
265+
_fields_ = [("x", c_char),
266+
("y", tp)]
267+
self.assertEqual((sizeof(X), code),
268+
(calcsize("%c" % (code)), code))
269+
270+
271+
class PointerMemberTestBase:
272+
def test(self):
273+
# a Structure/Union with a POINTER field
274+
class S(self.cls):
275+
_fields_ = [("array", POINTER(c_int))]
276+
277+
s = S()
278+
# We can assign arrays of the correct type
279+
s.array = (c_int * 3)(1, 2, 3)
280+
items = [s.array[i] for i in range(3)]
281+
self.assertEqual(items, [1, 2, 3])
282+
283+
s.array[0] = 42
284+
285+
items = [s.array[i] for i in range(3)]
286+
self.assertEqual(items, [42, 2, 3])
287+
288+
s.array[0] = 1
289+
290+
items = [s.array[i] for i in range(3)]
291+
self.assertEqual(items, [1, 2, 3])
292+
293+
class PointerMemberTestCase_Struct(unittest.TestCase, PointerMemberTestBase):
294+
cls = Structure
295+
296+
def test_none_to_pointer_fields(self):
297+
class S(self.cls):
298+
_fields_ = [("x", c_int),
299+
("p", POINTER(c_int))]
300+
301+
s = S()
302+
s.x = 12345678
303+
s.p = None
304+
self.assertEqual(s.x, 12345678)
305+
306+
class PointerMemberTestCase_Union(unittest.TestCase, PointerMemberTestBase):
307+
cls = Union
308+
309+
def test_none_to_pointer_fields(self):
310+
class S(self.cls):
311+
_fields_ = [("x", c_int),
312+
("p", POINTER(c_int))]
313+
314+
s = S()
315+
s.x = 12345678
316+
s.p = None
317+
self.assertFalse(s.p) # NULL pointers are falsy
318+
319+
320+
class TestRecursiveBase:
321+
def test_contains_itself(self):
322+
class Recursive(self.cls):
323+
pass
324+
325+
try:
326+
Recursive._fields_ = [("next", Recursive)]
327+
except AttributeError as details:
328+
self.assertIn("Structure or union cannot contain itself",
329+
str(details))
330+
else:
331+
self.fail("Structure or union cannot contain itself")
332+
333+
334+
def test_vice_versa(self):
335+
class First(self.cls):
336+
pass
337+
class Second(self.cls):
338+
pass
339+
340+
First._fields_ = [("second", Second)]
341+
342+
try:
343+
Second._fields_ = [("first", First)]
344+
except AttributeError as details:
345+
self.assertIn("_fields_ is final", str(details))
346+
else:
347+
self.fail("AttributeError not raised")
348+
349+
class TestRecursiveStructure(unittest.TestCase, TestRecursiveBase):
350+
cls = Structure
351+
352+
class TestRecursiveUnion(unittest.TestCase, TestRecursiveBase):
353+
cls = Union

0 commit comments

Comments
 (0)