from test.support import import_helper, subTests import contextlib import unittest import types import sys _testlimitedcapi = import_helper.import_module('_testlimitedcapi') _testcapi = import_helper.import_module('_testcapi') class FakeSpec: name = 'module' # See Modules/_testlimitedcapi/slots.c for the definitions. # This module is full of "magic constants" which simply need to match # between the C and Python part of the tests. class TypeSlotsTests(unittest.TestCase): def test_basic_type_slots(self): cls = _testlimitedcapi.type_from_slots("basic") self.assertEqual(cls.__name__, "MyType") # Py_TPFLAGS_IMMUTABLETYPE is *not* set cls.attr = 123 # Py_TPFLAGS_BASETYPE is *not* set with self.assertRaises(TypeError): class Sub(cls): pass def test_mod_slot_in_type(self): with self.assertRaisesRegex(SystemError, "invalid.* 100 .*Py_mod_name"): _testlimitedcapi.type_from_slots("foreign_slot") def test_size_slots(self): cls = _testlimitedcapi.type_from_slots("basicsize") self.assertGreaterEqual(cls.__basicsize__, 256) cls = _testlimitedcapi.type_from_slots("extra_basicsize") self.assertGreaterEqual(cls.__basicsize__, object.__basicsize__ + 256) cls = _testlimitedcapi.type_from_slots("itemsize") self.assertGreaterEqual(cls.__itemsize__, 16) def test_flag_slots(self): cls = _testlimitedcapi.type_from_slots("flags") with self.assertRaises(TypeError): # Py_TPFLAGS_IMMUTABLETYPE is set cls.attr = 123 class Sub(cls): # Py_TPFLAGS_BASETYPE is set pass def test_func_slots(self): cls = _testlimitedcapi.type_from_slots("matmul_123") self.assertEqual(cls() @ None, 123) def test_optional_end(self): with self.assertRaisesRegex(SystemError, "invalid flags.*Py_slot_end"): cls = _testlimitedcapi.type_from_slots("optional_end") def test_invalid(self): with self.assertRaisesRegex(SystemError, "Py_slot_invalid"): cls = _testlimitedcapi.type_from_slots("invalid") with self.assertRaisesRegex(SystemError, f"slot ID {0xfbad}"): cls = _testlimitedcapi.type_from_slots("invalid_fbad") cls = _testlimitedcapi.type_from_slots("optional_invalid") self.assertGreaterEqual(cls.__basicsize__, object.__basicsize__ + 256) cls = _testlimitedcapi.type_from_slots("optional_invalid_fbad") self.assertGreaterEqual(cls.__basicsize__, object.__basicsize__ + 256) @subTests("case_name", ["old_slot_numbers", "new_slot_numbers"]) def test_compat_slot_numbers(self, case_name): cls = _testlimitedcapi.type_from_slots(case_name) obj = cls() # Py_bf_getbuffer (1), Py_bf_releasebuffer (2) self.assertEqual(obj.buf_counter, 0) mem = memoryview(obj) self.assertEqual(bytes(mem), b"buf\0") self.assertEqual(obj.buf_counter, 1) mem.release() self.assertEqual(obj.buf_counter, 0) # Py_mp_ass_subscript (3) with self.assertRaises(KeyError): obj["key"] = "value" # Py_mp_length (4) self.assertEqual(len(obj), 456) def test_nonstatic_tp_members(self): with self.assertRaisesRegex(SystemError, "Py_tp_members.*STATIC"): _testlimitedcapi.type_from_slots("nonstatic_tp_members") def test_intptr_flags(self): cls = _testlimitedcapi.type_from_slots("intptr_flags_macro") with self.assertRaises(TypeError): # Py_TPFLAGS_IMMUTABLETYPE is set cls.attr = 123 cls = _testlimitedcapi.type_from_slots("intptr_flags_struct") with self.assertRaises(TypeError): # Py_TPFLAGS_IMMUTABLETYPE is set cls.attr = 123 cls = _testlimitedcapi.type_from_slots("intptr_static") cls.attribute = 123 def test_nested(self): cls = _testlimitedcapi.type_from_slots("nested") self.assertEqual(cls() + 1, 123) self.assertEqual(cls() - 1, 234) cls = _testlimitedcapi.type_from_slots("nested_max") self.assertEqual(cls() + 1, 123) self.assertEqual(cls() - 1, 234) self.assertEqual(cls() * 1, 345) self.assertEqual(cls() / 1, 456) self.assertEqual(cls() % 1, 567) with self.assertRaisesRegex(SystemError, "too many levels"): _testlimitedcapi.type_from_slots("nested_over_limit") cls = _testlimitedcapi.type_from_slots("nested_old") self.assertEqual(cls() + 1, 123) self.assertEqual(cls() - 1, 234) cls = _testlimitedcapi.type_from_slots("nested_old_max") self.assertEqual(cls() + 1, 123) self.assertEqual(cls() - 1, 234) self.assertEqual(cls() * 1, 345) self.assertEqual(cls() / 1, 456) self.assertEqual(cls() % 1, 567) with self.assertRaisesRegex(SystemError, "too many levels"): _testlimitedcapi.type_from_slots("nested_old_over_limit") cls = _testlimitedcapi.type_from_slots("nested_pingpong") self.assertEqual(cls() + 1, 123) self.assertEqual(cls() - 1, 234) self.assertEqual(cls() * 1, 345) self.assertEqual(cls() / 1, 456) self.assertEqual(cls() % 1, 567) # Slot names aren't exposed to Python yet; see Include/slots_generated.h # for the definitions. @subTests("slot_number", [ *range(1, 83), # Original slots *range(88, 92), # New compat slot values *range(95, 99), # Slots for PyType_Spec fields *range(107, 109), # Slots for PyType_FromMetaclass args ]) def test_null_slot_handling(self, slot_number): if slot_number == 56: # Py_tp_doc return elif slot_number == 72 or slot_number >= 95: # Py_tp_members; all new slots ctx = self.assertRaisesRegex( SystemError, "NULL not allowed|must be positive") ctx_old = ctx else: ctx = self.assertWarnsRegex(DeprecationWarning, "NULL") ctx_old = contextlib.nullcontext() with ctx: _testlimitedcapi.type_from_null_slot(slot_number) if slot_number < 95: with ctx_old: _testlimitedcapi.type_from_null_spec_slot(slot_number) def test_repeat_warning(self): with self.assertWarnsRegex(DeprecationWarning, "multiple"): cls = _testlimitedcapi.type_from_slots("repeat_add") self.assertEqual(cls() + 1, 456) def test_repeat_error(self): with self.assertRaisesRegex(SystemError, "multiple"): cls = _testlimitedcapi.type_from_slots("repeat_module") class ModuleSlotsTests(unittest.TestCase): def test_basic_module_slots(self): mod = _testlimitedcapi.module_from_slots("basic", FakeSpec()) self.assertIsInstance(mod, types.ModuleType) def test_type_slot_in_module(self): with self.assertRaisesRegex(SystemError, "invalid.* 95 .*Py_tp_name"): _testlimitedcapi.module_from_slots("foreign_slot", FakeSpec()) def test_size_slots(self): mod = _testlimitedcapi.module_from_slots("state_size", FakeSpec()) self.assertEqual(mod.state_size, 42) def test_flag_slots(self): mod = _testlimitedcapi.module_from_slots("multi_interp", FakeSpec()) def test_exec_slot(self): mod = _testlimitedcapi.module_from_slots("exec", FakeSpec()) self.assertEqual(mod.exec_done, "yes") def test_optional_end(self): with self.assertRaisesRegex(SystemError, "invalid flags.*Py_slot_end"): _testlimitedcapi.module_from_slots("optional_end", FakeSpec()) def test_invalid(self): with self.assertRaisesRegex(SystemError, "Py_slot_invalid"): _testlimitedcapi.module_from_slots("invalid", FakeSpec()) with self.assertRaisesRegex(SystemError, f"slot ID {0xfbad}"): _testlimitedcapi.module_from_slots("invalid_fbad", FakeSpec()) mod = _testlimitedcapi.module_from_slots("optional_invalid", FakeSpec()) self.assertEqual(mod.exec_done, "yes") mod = _testlimitedcapi.module_from_slots("optional_invalid_fbad", FakeSpec()) self.assertEqual(mod.exec_done, "yes") @subTests("case_name", ["old_slot_numbers", "new_slot_numbers"]) def test_compat_slot_numbers(self, case_name): mod = _testlimitedcapi.module_from_slots(case_name, FakeSpec()) self.assertEqual(mod.exec_done, "yes") @subTests("case_name", ["old_slot_number_create", "new_slot_number_create"]) def test_compat_slot_number_create(self, case_name): spec = FakeSpec() mod = _testlimitedcapi.module_from_slots(case_name, spec) self.assertIs(mod, spec) @subTests("slot_number", [4, 87]) def test_compat_slot_number_gil(self, slot_number): spec = FakeSpec() gil_enabled = sys._is_gil_enabled() mod = _testlimitedcapi.module_from_gil_slot(slot_number, spec) self.assertEqual(gil_enabled, sys._is_gil_enabled()) def test_nonstatic_mod_methods(self): with self.assertRaisesRegex(SystemError, "Py_mod_methods.*STATIC"): _testlimitedcapi.module_from_slots("nonstatic_mod_methods", FakeSpec()) def test_intptr_methods(self): mod = _testlimitedcapi.module_from_slots("intptr_methods", FakeSpec()) self.assertEqual(mod.type_from_slots.__name__, "type_from_slots") def test_nested(self): mod = _testlimitedcapi.module_from_slots("nested", FakeSpec()) self.assertEqual(mod.exec_done, "yes") self.assertEqual(mod.__doc__, "doc") mod = _testlimitedcapi.module_from_slots("nested_max", FakeSpec()) self.assertEqual(mod.exec_done, "yes") self.assertEqual(mod.state_size, 53) self.assertEqual(mod.__doc__, "doc") with self.assertRaisesRegex(SystemError, "too many levels"): _testlimitedcapi.module_from_slots("nested_over_limit", FakeSpec()) mod = _testlimitedcapi.module_from_slots("nested_old", FakeSpec()) self.assertEqual(mod.exec_done, "yes") self.assertEqual(mod.__doc__, "doc") mod = _testlimitedcapi.module_from_slots("nested_old_max", FakeSpec()) self.assertEqual(mod.exec_done, "yes") self.assertEqual(mod.state_size, 53) self.assertEqual(mod.__doc__, "doc") with self.assertRaisesRegex(SystemError, "too many levels"): _testlimitedcapi.module_from_slots("nested_old_over_limit", FakeSpec()) mod = _testlimitedcapi.module_from_slots("nested_pingpong", FakeSpec()) self.assertEqual(mod.exec_done, "yes") self.assertEqual(mod.state_size, 53) self.assertEqual(mod.__doc__, "doc") def test_nested_nonstatic_from_def(self): with self.assertRaisesRegex(SystemError, "must be static"): _testcapi.module_from_def_nonstatic_nested(FakeSpec()) # Slot names aren't exposed to Python yet; see Include/slots_generated.h # for the definitions. @subTests("slot_number", [ *range(1, 5), # Old compat slot values *range(84, 88), # New compat slot values *range(100, 107), # Slots for PyModuleDef fields *range(109, 111), # Slots new in 3.15 ]) def test_null_slot_handling(self, slot_number): if slot_number in {3, 86, 4, 87, 102}: # Py_mod_mult.interp., Py_mod_gil, Py_mod_state_size return elif slot_number in {2, 85} or slot_number > 85: # Py_mod_exec, new slots ctx = self.assertRaisesRegex(SystemError, "NULL not allowed") ctx_old = ctx else: ctx = self.assertWarnsRegex(DeprecationWarning, "NULL") ctx_old = contextlib.nullcontext() with ctx: _testlimitedcapi.module_from_null_slot(slot_number, FakeSpec()) with ctx_old: _testcapi.module_from_null_def_slot(slot_number, FakeSpec()) def test_repeat_error(self): with self.assertRaisesRegex(SystemError, "multiple"): _testlimitedcapi.module_from_slots("repeat_create", FakeSpec()) with self.assertRaisesRegex(SystemError, "multiple"): _testlimitedcapi.module_from_slots("repeat_exec", FakeSpec()) with self.assertRaisesRegex(SystemError, "multiple"): _testlimitedcapi.module_from_slots("repeat_gil", FakeSpec())