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`.
This commit is contained in:
Petr Viktorin 2024-10-10 16:27:52 +02:00 committed by GitHub
parent c914212474
commit 01fc3b34cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 368 additions and 357 deletions

View file

@ -0,0 +1,353 @@
"""Common tests for ctypes.Structure and ctypes.Union"""
import unittest
from ctypes import (Structure, Union, POINTER, sizeof, alignment,
c_char, 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, PyCStructType, UnionType,
Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
from struct import calcsize
class StructUnionTestBase:
formats = {"c": c_char,
"b": c_byte,
"B": c_ubyte,
"h": c_short,
"H": c_ushort,
"i": c_int,
"I": c_uint,
"l": c_long,
"L": c_ulong,
"q": c_longlong,
"Q": c_ulonglong,
"f": c_float,
"d": c_double,
}
def test_subclass(self):
class X(self.cls):
_fields_ = [("a", c_int)]
class Y(X):
_fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.check_sizeof(Y,
struct_size=sizeof(c_int)*2,
union_size=sizeof(c_int))
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
def test_subclass_delayed(self):
class X(self.cls):
pass
self.assertEqual(sizeof(X), 0)
X._fields_ = [("a", c_int)]
class Y(X):
pass
self.assertEqual(sizeof(Y), sizeof(X))
Y._fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.check_sizeof(Y,
struct_size=sizeof(c_int)*2,
union_size=sizeof(c_int))
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
def test_inheritance_hierarchy(self):
self.assertEqual(self.cls.mro(), [self.cls, _CData, object])
self.assertEqual(type(self.metacls), type)
def test_type_flags(self):
for cls in self.cls, self.metacls:
with self.subTest(cls=cls):
self.assertTrue(cls.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(cls.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
def test_metaclass_details(self):
# Abstract classes (whose metaclass __init__ was not called) can't be
# instantiated directly
NewClass = self.metacls.__new__(self.metacls, 'NewClass',
(self.cls,), {})
for cls in self.cls, NewClass:
with self.subTest(cls=cls):
with self.assertRaisesRegex(TypeError, "abstract class"):
obj = cls()
# Cannot call the metaclass __init__ more than once
class T(self.cls):
_fields_ = [("x", c_char),
("y", c_char)]
with self.assertRaisesRegex(SystemError, "already initialized"):
self.metacls.__init__(T, 'ptr', (), {})
def test_alignment(self):
class X(self.cls):
_fields_ = [("x", c_char * 3)]
self.assertEqual(alignment(X), calcsize("s"))
self.assertEqual(sizeof(X), calcsize("3s"))
class Y(self.cls):
_fields_ = [("x", c_char * 3),
("y", c_int)]
self.assertEqual(alignment(Y), alignment(c_int))
self.check_sizeof(Y,
struct_size=calcsize("3s i"),
union_size=max(calcsize("3s"), calcsize("i")))
class SI(self.cls):
_fields_ = [("a", X),
("b", Y)]
self.assertEqual(alignment(SI), max(alignment(Y), alignment(X)))
self.check_sizeof(SI,
struct_size=calcsize("3s0i 3si 0i"),
union_size=max(calcsize("3s"), calcsize("i")))
class IS(self.cls):
_fields_ = [("b", Y),
("a", X)]
self.assertEqual(alignment(SI), max(alignment(X), alignment(Y)))
self.check_sizeof(IS,
struct_size=calcsize("3si 3s 0i"),
union_size=max(calcsize("3s"), calcsize("i")))
class XX(self.cls):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), alignment(X))
self.check_sizeof(XX,
struct_size=calcsize("3s 3s 0s"),
union_size=calcsize("3s"))
def test_empty(self):
# I had problems with these
#
# Although these are pathological cases: Empty Structures!
class X(self.cls):
_fields_ = []
# Is this really the correct alignment, or should it be 0?
self.assertTrue(alignment(X) == 1)
self.assertTrue(sizeof(X) == 0)
class XX(self.cls):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), 1)
self.assertEqual(sizeof(XX), 0)
def test_fields(self):
# test the offset and size attributes of Structure/Union fields.
class X(self.cls):
_fields_ = [("x", c_int),
("y", c_char)]
self.assertEqual(X.x.offset, 0)
self.assertEqual(X.x.size, sizeof(c_int))
if self.cls == Structure:
self.assertEqual(X.y.offset, sizeof(c_int))
else:
self.assertEqual(X.y.offset, 0)
self.assertEqual(X.y.size, sizeof(c_char))
# readonly
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
# XXX Should we check nested data types also?
# offset is always relative to the class...
def test_invalid_field_types(self):
class POINT(self.cls):
pass
self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)])
def test_invalid_name(self):
# field name must be string
def declare_with_name(name):
class S(self.cls):
_fields_ = [(name, c_int)]
self.assertRaises(TypeError, declare_with_name, b"x")
def test_intarray_fields(self):
class SomeInts(self.cls):
_fields_ = [("a", c_int * 4)]
# can use tuple to initialize array (but not list!)
self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1])
self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0])
self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2])
self.assertEqual(SomeInts((1, 2)).a[6:4:-1], [])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4])
# too long
# XXX Should raise ValueError?, not RuntimeError
self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5))
def test_huge_field_name(self):
# issue12881: segfault with large structure field names
def create_class(length):
class S(self.cls):
_fields_ = [('x' * length, c_int)]
for length in [10 ** i for i in range(0, 8)]:
try:
create_class(length)
except MemoryError:
# MemoryErrors are OK, we just don't want to segfault
pass
def test_abstract_class(self):
class X(self.cls):
_abstract_ = "something"
with self.assertRaisesRegex(TypeError, r"^abstract class$"):
X()
def test_methods(self):
self.assertIn("in_dll", dir(type(self.cls)))
self.assertIn("from_address", dir(type(self.cls)))
self.assertIn("in_dll", dir(type(self.cls)))
class StructureTestCase(unittest.TestCase, StructUnionTestBase):
cls = Structure
metacls = PyCStructType
def test_metaclass_name(self):
self.assertEqual(self.metacls.__name__, "PyCStructType")
def check_sizeof(self, cls, *, struct_size, union_size):
self.assertEqual(sizeof(cls), struct_size)
def test_simple_structs(self):
for code, tp in self.formats.items():
class X(Structure):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("c%c0%c" % (code, code)), code))
class UnionTestCase(unittest.TestCase, StructUnionTestBase):
cls = Union
metacls = UnionType
def test_metaclass_name(self):
self.assertEqual(self.metacls.__name__, "UnionType")
def check_sizeof(self, cls, *, struct_size, union_size):
self.assertEqual(sizeof(cls), union_size)
def test_simple_unions(self):
for code, tp in self.formats.items():
class X(Union):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("%c" % (code)), code))
class PointerMemberTestBase:
def test(self):
# a Structure/Union with a POINTER field
class S(self.cls):
_fields_ = [("array", POINTER(c_int))]
s = S()
# We can assign arrays of the correct type
s.array = (c_int * 3)(1, 2, 3)
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
s.array[0] = 42
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [42, 2, 3])
s.array[0] = 1
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
class PointerMemberTestCase_Struct(unittest.TestCase, PointerMemberTestBase):
cls = Structure
def test_none_to_pointer_fields(self):
class S(self.cls):
_fields_ = [("x", c_int),
("p", POINTER(c_int))]
s = S()
s.x = 12345678
s.p = None
self.assertEqual(s.x, 12345678)
class PointerMemberTestCase_Union(unittest.TestCase, PointerMemberTestBase):
cls = Union
def test_none_to_pointer_fields(self):
class S(self.cls):
_fields_ = [("x", c_int),
("p", POINTER(c_int))]
s = S()
s.x = 12345678
s.p = None
self.assertFalse(s.p) # NULL pointers are falsy
class TestRecursiveBase:
def test_contains_itself(self):
class Recursive(self.cls):
pass
try:
Recursive._fields_ = [("next", Recursive)]
except AttributeError as details:
self.assertIn("Structure or union cannot contain itself",
str(details))
else:
self.fail("Structure or union cannot contain itself")
def test_vice_versa(self):
class First(self.cls):
pass
class Second(self.cls):
pass
First._fields_ = [("second", Second)]
try:
Second._fields_ = [("first", First)]
except AttributeError as details:
self.assertIn("_fields_ is final", str(details))
else:
self.fail("AttributeError not raised")
class TestRecursiveStructure(unittest.TestCase, TestRecursiveBase):
cls = Structure
class TestRecursiveUnion(unittest.TestCase, TestRecursiveBase):
cls = Union

View file

@ -1,209 +1,24 @@
"""Tests for ctypes.Structure
Features common with Union should go in test_structunion.py instead.
"""
from platform import architecture as _architecture
import struct
import sys
import unittest
from ctypes import (CDLL, Structure, Union, POINTER, sizeof, byref, alignment,
from ctypes import (CDLL, Structure, Union, POINTER, sizeof, byref,
c_void_p, c_char, c_wchar, c_byte, c_ubyte,
c_uint8, c_uint16, c_uint32,
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
c_uint8, c_uint16, c_uint32, c_int, c_uint,
c_long, c_ulong, c_longlong, c_float, c_double)
from ctypes.util import find_library
from struct import calcsize
from collections import namedtuple
from test import support
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
from ._support import (_CData, PyCStructType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
class SubclassesTest(unittest.TestCase):
def test_subclass(self):
class X(Structure):
_fields_ = [("a", c_int)]
class Y(X):
_fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.assertEqual(sizeof(Y), sizeof(c_int)*2)
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
def test_subclass_delayed(self):
class X(Structure):
pass
self.assertEqual(sizeof(X), 0)
X._fields_ = [("a", c_int)]
class Y(X):
pass
self.assertEqual(sizeof(Y), sizeof(X))
Y._fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.assertEqual(sizeof(Y), sizeof(c_int)*2)
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
class StructureTestCase(unittest.TestCase):
formats = {"c": c_char,
"b": c_byte,
"B": c_ubyte,
"h": c_short,
"H": c_ushort,
"i": c_int,
"I": c_uint,
"l": c_long,
"L": c_ulong,
"q": c_longlong,
"Q": c_ulonglong,
"f": c_float,
"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_metaclass_details(self):
# Abstract classes (whose metaclass __init__ was not called) can't be
# instantiated directly
NewStructure = PyCStructType.__new__(PyCStructType, 'NewStructure',
(Structure,), {})
for cls in Structure, NewStructure:
with self.subTest(cls=cls):
with self.assertRaisesRegex(TypeError, "abstract class"):
obj = cls()
# Cannot call the metaclass __init__ more than once
class T(Structure):
_fields_ = [("x", c_char),
("y", c_char)]
with self.assertRaisesRegex(SystemError, "already initialized"):
PyCStructType.__init__(T, 'ptr', (), {})
def test_simple_structs(self):
for code, tp in self.formats.items():
class X(Structure):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("c%c0%c" % (code, code)), code))
def test_unions(self):
for code, tp in self.formats.items():
class X(Union):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("%c" % (code)), code))
def test_struct_alignment(self):
class X(Structure):
_fields_ = [("x", c_char * 3)]
self.assertEqual(alignment(X), calcsize("s"))
self.assertEqual(sizeof(X), calcsize("3s"))
class Y(Structure):
_fields_ = [("x", c_char * 3),
("y", c_int)]
self.assertEqual(alignment(Y), alignment(c_int))
self.assertEqual(sizeof(Y), calcsize("3si"))
class SI(Structure):
_fields_ = [("a", X),
("b", Y)]
self.assertEqual(alignment(SI), max(alignment(Y), alignment(X)))
self.assertEqual(sizeof(SI), calcsize("3s0i 3si 0i"))
class IS(Structure):
_fields_ = [("b", Y),
("a", X)]
self.assertEqual(alignment(SI), max(alignment(X), alignment(Y)))
self.assertEqual(sizeof(IS), calcsize("3si 3s 0i"))
class XX(Structure):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), alignment(X))
self.assertEqual(sizeof(XX), calcsize("3s 3s 0s"))
def test_empty(self):
# I had problems with these
#
# Although these are pathological cases: Empty Structures!
class X(Structure):
_fields_ = []
class Y(Union):
_fields_ = []
# Is this really the correct alignment, or should it be 0?
self.assertTrue(alignment(X) == alignment(Y) == 1)
self.assertTrue(sizeof(X) == sizeof(Y) == 0)
class XX(Structure):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), 1)
self.assertEqual(sizeof(XX), 0)
def test_fields(self):
# test the offset and size attributes of Structure/Union fields.
class X(Structure):
_fields_ = [("x", c_int),
("y", c_char)]
self.assertEqual(X.x.offset, 0)
self.assertEqual(X.x.size, sizeof(c_int))
self.assertEqual(X.y.offset, sizeof(c_int))
self.assertEqual(X.y.size, sizeof(c_char))
# readonly
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
class X(Union):
_fields_ = [("x", c_int),
("y", c_char)]
self.assertEqual(X.x.offset, 0)
self.assertEqual(X.x.size, sizeof(c_int))
self.assertEqual(X.y.offset, 0)
self.assertEqual(X.y.size, sizeof(c_char))
# readonly
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
# XXX Should we check nested data types also?
# offset is always relative to the class...
def test_packed(self):
class X(Structure):
_fields_ = [("a", c_byte),
@ -290,36 +105,6 @@ class StructureTestCase(unittest.TestCase):
pt = POINT(y=2, x=1)
self.assertEqual((pt.x, pt.y), (1, 2))
def test_invalid_field_types(self):
class POINT(Structure):
pass
self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)])
def test_invalid_name(self):
# field name must be string
def declare_with_name(name):
class S(Structure):
_fields_ = [(name, c_int)]
self.assertRaises(TypeError, declare_with_name, b"x")
def test_intarray_fields(self):
class SomeInts(Structure):
_fields_ = [("a", c_int * 4)]
# can use tuple to initialize array (but not list!)
self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1])
self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0])
self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2])
self.assertEqual(SomeInts((1, 2)).a[6:4:-1], [])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4])
# too long
# XXX Should raise ValueError?, not RuntimeError
self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5))
def test_nested_initializers(self):
# test initializing nested structures
class Phone(Structure):
@ -374,37 +159,12 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(msg,
"(Phone) TypeError: too many initializers")
def test_huge_field_name(self):
# issue12881: segfault with large structure field names
def create_class(length):
class S(Structure):
_fields_ = [('x' * length, c_int)]
for length in [10 ** i for i in range(0, 8)]:
try:
create_class(length)
except MemoryError:
# MemoryErrors are OK, we just don't want to segfault
pass
def get_except(self, func, *args):
try:
func(*args)
except Exception as detail:
return detail.__class__, str(detail)
def test_abstract_class(self):
class X(Structure):
_abstract_ = "something"
# try 'X()'
cls, msg = self.get_except(eval, "X()", locals())
self.assertEqual((cls, msg), (TypeError, "abstract class"))
def test_methods(self):
self.assertIn("in_dll", dir(type(Structure)))
self.assertIn("from_address", dir(type(Structure)))
self.assertIn("in_dll", dir(type(Structure)))
def test_positional_args(self):
# see also http://bugs.python.org/issue5042
class W(Structure):
@ -507,6 +267,8 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(s.second, got.second)
def _test_issue18060(self, Vector):
# Regression tests for gh-62260
# The call to atan2() should succeed if the
# class fields were correctly cloned in the
# subclasses. Otherwise, it will segfault.
@ -698,6 +460,7 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(result.data[i], float(i+1))
def test_38368(self):
# Regression test for gh-82549
class U(Union):
_fields_ = [
('f1', c_uint8 * 16),
@ -719,9 +482,9 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(f2, [0x4567, 0x0123, 0xcdef, 0x89ab,
0x3210, 0x7654, 0xba98, 0xfedc])
@unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576')
@unittest.skipIf(True, 'Test disabled for now - see gh-60779/gh-60780')
def test_union_by_value(self):
# See bpo-16575
# See gh-60779
# These should mirror the structures in Modules/_ctypes/_ctypes_test.c
@ -800,9 +563,9 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(test5.nested.an_int, 0)
self.assertEqual(test5.another_int, 0)
@unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576')
@unittest.skipIf(True, 'Test disabled for now - see gh-60779/gh-60780')
def test_bitfield_by_value(self):
# See bpo-16576
# See gh-60780
# These should mirror the structures in Modules/_ctypes/_ctypes_test.c
@ -882,75 +645,5 @@ class StructureTestCase(unittest.TestCase):
'a union by value, which is unsupported.')
class PointerMemberTestCase(unittest.TestCase):
def test(self):
# a Structure with a POINTER field
class S(Structure):
_fields_ = [("array", POINTER(c_int))]
s = S()
# We can assign arrays of the correct type
s.array = (c_int * 3)(1, 2, 3)
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
# The following are bugs, but are included here because the unittests
# also describe the current behaviour.
#
# This fails with SystemError: bad arg to internal function
# or with IndexError (with a patch I have)
s.array[0] = 42
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [42, 2, 3])
s.array[0] = 1
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
def test_none_to_pointer_fields(self):
class S(Structure):
_fields_ = [("x", c_int),
("p", POINTER(c_int))]
s = S()
s.x = 12345678
s.p = None
self.assertEqual(s.x, 12345678)
class TestRecursiveStructure(unittest.TestCase):
def test_contains_itself(self):
class Recursive(Structure):
pass
try:
Recursive._fields_ = [("next", Recursive)]
except AttributeError as details:
self.assertIn("Structure or union cannot contain itself",
str(details))
else:
self.fail("Structure or union cannot contain itself")
def test_vice_versa(self):
class First(Structure):
pass
class Second(Structure):
pass
First._fields_ = [("second", Second)]
try:
Second._fields_ = [("first", First)]
except AttributeError as details:
self.assertIn("_fields_ is final", str(details))
else:
self.fail("AttributeError not raised")
if __name__ == '__main__':
unittest.main()

View file

@ -1,35 +0,0 @@
import unittest
from ctypes import Union, c_char
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)
def test_metaclass_details(self):
# Abstract classes (whose metaclass __init__ was not called) can't be
# instantiated directly
NewUnion = UnionType.__new__(UnionType, 'NewUnion',
(Union,), {})
for cls in Union, NewUnion:
with self.subTest(cls=cls):
with self.assertRaisesRegex(TypeError, "abstract class"):
obj = cls()
# Cannot call the metaclass __init__ more than once
class T(Union):
_fields_ = [("x", c_char),
("y", c_char)]
with self.assertRaisesRegex(SystemError, "already initialized"):
UnionType.__init__(T, 'ptr', (), {})