|
| 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