# The C functions used by this module are in: # Modules/_testcapi/module.c import unittest import types from test.support import import_helper, subTests # Skip this test if the _testcapi module isn't available. _testcapi = import_helper.import_module('_testcapi') class FakeSpec: name = 'testmod' DEF_SLOTS = ( 'Py_mod_name', 'Py_mod_doc', 'Py_mod_state_size', 'Py_mod_methods', 'Py_mod_state_traverse', 'Py_mod_state_clear', 'Py_mod_state_free', 'Py_mod_token', ) def def_and_token(mod): return ( _testcapi.pymodule_get_def(mod), _testcapi.pymodule_get_token(mod), ) class TestModFromSlotsAndSpec(unittest.TestCase): def test_empty(self): mod = _testcapi.module_from_slots_empty(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') size = _testcapi.pymodule_get_state_size(mod) self.assertEqual(size, 0) def test_null_slots(self): with self.assertRaises(SystemError): _testcapi.module_from_slots_null(FakeSpec()) def test_none_spec(self): # The spec currently must contain a name with self.assertRaises(AttributeError): _testcapi.module_from_slots_empty(None) with self.assertRaises(AttributeError): _testcapi.module_from_slots_name(None) def test_name(self): # Py_mod_name (and PyModuleDef.m_name) are currently ignored when # spec is given. # We still test that it's accepted. mod = _testcapi.module_from_slots_name(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, None) def test_doc(self): mod = _testcapi.module_from_slots_doc(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, 'the docstring') def test_size(self): mod = _testcapi.module_from_slots_size(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, None) size = _testcapi.pymodule_get_state_size(mod) self.assertEqual(size, 123) def test_methods(self): mod = _testcapi.module_from_slots_methods(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, None) self.assertEqual(mod.a_method(456), (mod, 456)) def test_gc(self): mod = _testcapi.module_from_slots_gc(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, None) # Check that the requested hook functions (which module_from_slots_gc # stores as attributes) match what's in the module (as retrieved by # _testinternalcapi.module_get_gc_hooks) _testinternalcapi = import_helper.import_module('_testinternalcapi') traverse, clear, free = _testinternalcapi.module_get_gc_hooks(mod) self.assertEqual(traverse, mod.traverse) self.assertEqual(clear, mod.clear) self.assertEqual(free, mod.free) def test_token(self): mod = _testcapi.module_from_slots_token(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, _testcapi.module_test_token)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, None) def test_exec(self): mod = _testcapi.module_from_slots_exec(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) self.assertEqual(def_and_token(mod), (0, 0)) self.assertEqual(mod.__name__, 'testmod') self.assertEqual(mod.__doc__, None) self.assertEqual(mod.a_number, 456) def test_create(self): spec = FakeSpec() spec._gimme_this = "not a module object" mod = _testcapi.module_from_slots_create(spec) self.assertIsInstance(mod, str) self.assertEqual(mod, "not a module object") with self.assertRaises(TypeError): _testcapi.pymodule_get_def(mod), with self.assertRaises(TypeError): _testcapi.pymodule_get_token(mod) def test_def_slot(self): """Slots that replace PyModuleDef fields can't be used with PyModuleDef """ for name in DEF_SLOTS: with self.subTest(name): spec = FakeSpec() spec._test_slot_id = getattr(_testcapi, name) with self.assertRaises(SystemError) as cm: _testcapi.module_from_def_slot(spec) self.assertIn(name, str(cm.exception)) self.assertIn("PyModuleDef", str(cm.exception)) def test_repeated_def_slot(self): """Slots that replace PyModuleDef fields can't be repeated""" for name in (*DEF_SLOTS, 'Py_mod_exec'): with self.subTest(name): spec = FakeSpec() spec._test_slot_id = getattr(_testcapi, name) with self.assertRaises(SystemError) as cm: _testcapi.module_from_slots_repeat_slot(spec) self.assertIn(name, str(cm.exception)) self.assertIn("more than one", str(cm.exception)) def test_null_def_slot(self): """Slots that replace PyModuleDef fields can't be NULL""" for name in (*DEF_SLOTS, 'Py_mod_exec'): with self.subTest(name): spec = FakeSpec() spec._test_slot_id = getattr(_testcapi, name) with self.assertRaises(SystemError) as cm: _testcapi.module_from_slots_null_slot(spec) self.assertIn(name, str(cm.exception)) self.assertIn("NULL", str(cm.exception)) def test_def_multiple_exec(self): """PyModule_Exec runs all exec slots of PyModuleDef-defined module""" mod = _testcapi.module_from_def_multiple_exec(FakeSpec()) self.assertFalse(hasattr(mod, 'a_number')) _testcapi.pymodule_exec(mod) self.assertEqual(mod.a_number, 456) self.assertEqual(mod.another_number, 789) _testcapi.pymodule_exec(mod) self.assertEqual(mod.a_number, 456) self.assertEqual(mod.another_number, -789) def_ptr, token = def_and_token(mod) self.assertEqual(def_ptr, token) def test_def_token(self): """In PyModuleDef-defined modules, the def is the token""" mod = _testcapi.module_from_def_multiple_exec(FakeSpec()) def_ptr, token = def_and_token(mod) self.assertEqual(def_ptr, token) self.assertGreater(def_ptr, 0) @subTests('name, expected_size', [ (__name__, 0), # Python module ('_testsinglephase', -1), # single-phase init ('sys', -1), ]) def test_get_state_size(self, name, expected_size): mod = import_helper.import_module(name) size = _testcapi.pymodule_get_state_size(mod) self.assertEqual(size, expected_size)