| import importlib.util | |
| import os.path | |
| import sys | |
| import types | |
| import unittest | |
| from test.support import os_helper | |
| from test.support import import_helper | |
| from test.support.warnings_helper import check_warnings | |
| _testcapi = import_helper.import_module('_testcapi') | |
| _testlimitedcapi = import_helper.import_module('_testlimitedcapi') | |
| NULL = None | |
| class ImportTests(unittest.TestCase): | |
| def test_getmagicnumber(self): | |
| # Test PyImport_GetMagicNumber() | |
| magic = _testlimitedcapi.PyImport_GetMagicNumber() | |
| self.assertEqual(magic, | |
| int.from_bytes(importlib.util.MAGIC_NUMBER, 'little')) | |
| def test_getmagictag(self): | |
| # Test PyImport_GetMagicTag() | |
| tag = _testlimitedcapi.PyImport_GetMagicTag() | |
| self.assertEqual(tag, sys.implementation.cache_tag) | |
| def test_getmoduledict(self): | |
| # Test PyImport_GetModuleDict() | |
| modules = _testlimitedcapi.PyImport_GetModuleDict() | |
| self.assertIs(modules, sys.modules) | |
| def check_import_loaded_module(self, import_module): | |
| for name in ('os', 'sys', 'test', 'unittest'): | |
| with self.subTest(name=name): | |
| self.assertIn(name, sys.modules) | |
| old_module = sys.modules[name] | |
| module = import_module(name) | |
| self.assertIsInstance(module, types.ModuleType) | |
| self.assertIs(module, old_module) | |
| def check_import_fresh_module(self, import_module): | |
| old_modules = dict(sys.modules) | |
| try: | |
| for name in ('colorsys', 'math'): | |
| with self.subTest(name=name): | |
| sys.modules.pop(name, None) | |
| module = import_module(name) | |
| self.assertIsInstance(module, types.ModuleType) | |
| self.assertIs(module, sys.modules[name]) | |
| self.assertEqual(module.__name__, name) | |
| finally: | |
| sys.modules.clear() | |
| sys.modules.update(old_modules) | |
| def test_getmodule(self): | |
| # Test PyImport_GetModule() | |
| getmodule = _testlimitedcapi.PyImport_GetModule | |
| self.check_import_loaded_module(getmodule) | |
| nonexistent = 'nonexistent' | |
| self.assertNotIn(nonexistent, sys.modules) | |
| self.assertIs(getmodule(nonexistent), KeyError) | |
| self.assertIs(getmodule(''), KeyError) | |
| self.assertIs(getmodule(object()), KeyError) | |
| self.assertRaises(TypeError, getmodule, []) # unhashable | |
| # CRASHES getmodule(NULL) | |
| def check_addmodule(self, add_module, accept_nonstr=False): | |
| # create a new module | |
| names = ['nonexistent'] | |
| if accept_nonstr: | |
| names.append(b'\xff') # non-UTF-8 | |
| # PyImport_AddModuleObject() accepts non-string names | |
| names.append(tuple(['hashable non-string'])) | |
| for name in names: | |
| with self.subTest(name=name): | |
| self.assertNotIn(name, sys.modules) | |
| try: | |
| module = add_module(name) | |
| self.assertIsInstance(module, types.ModuleType) | |
| self.assertEqual(module.__name__, name) | |
| self.assertIs(module, sys.modules[name]) | |
| finally: | |
| sys.modules.pop(name, None) | |
| # get an existing module | |
| self.check_import_loaded_module(add_module) | |
| def test_addmoduleobject(self): | |
| # Test PyImport_AddModuleObject() | |
| addmoduleobject = _testlimitedcapi.PyImport_AddModuleObject | |
| self.check_addmodule(addmoduleobject, accept_nonstr=True) | |
| self.assertRaises(TypeError, addmoduleobject, []) # unhashable | |
| # CRASHES addmoduleobject(NULL) | |
| def test_addmodule(self): | |
| # Test PyImport_AddModule() | |
| addmodule = _testlimitedcapi.PyImport_AddModule | |
| self.check_addmodule(addmodule) | |
| self.assertRaises(UnicodeDecodeError, addmodule, b'\xff') | |
| # CRASHES addmodule(NULL) | |
| def test_addmoduleref(self): | |
| # Test PyImport_AddModuleRef() | |
| addmoduleref = _testlimitedcapi.PyImport_AddModuleRef | |
| self.check_addmodule(addmoduleref) | |
| self.assertRaises(UnicodeDecodeError, addmoduleref, b'\xff') | |
| # CRASHES addmoduleref(NULL) | |
| def check_import_func(self, import_module): | |
| self.check_import_loaded_module(import_module) | |
| self.check_import_fresh_module(import_module) | |
| self.assertRaises(ModuleNotFoundError, import_module, 'nonexistent') | |
| self.assertRaises(ValueError, import_module, '') | |
| def test_import(self): | |
| # Test PyImport_Import() | |
| import_ = _testlimitedcapi.PyImport_Import | |
| self.check_import_func(import_) | |
| self.assertRaises(TypeError, import_, b'os') | |
| self.assertRaises(SystemError, import_, NULL) | |
| def test_importmodule(self): | |
| # Test PyImport_ImportModule() | |
| importmodule = _testlimitedcapi.PyImport_ImportModule | |
| self.check_import_func(importmodule) | |
| self.assertRaises(UnicodeDecodeError, importmodule, b'\xff') | |
| # CRASHES importmodule(NULL) | |
| def test_importmodulenoblock(self): | |
| # Test deprecated (stable ABI only) PyImport_ImportModuleNoBlock() | |
| importmodulenoblock = _testlimitedcapi.PyImport_ImportModuleNoBlock | |
| with check_warnings(('', DeprecationWarning)): | |
| self.check_import_func(importmodulenoblock) | |
| self.assertRaises(UnicodeDecodeError, importmodulenoblock, b'\xff') | |
| # CRASHES importmodulenoblock(NULL) | |
| def check_frozen_import(self, import_frozen_module): | |
| # Importing a frozen module executes its code, so start by unloading | |
| # the module to execute the code in a new (temporary) module. | |
| old_zipimport = sys.modules.pop('zipimport') | |
| try: | |
| self.assertEqual(import_frozen_module('zipimport'), 1) | |
| # import zipimport again | |
| self.assertEqual(import_frozen_module('zipimport'), 1) | |
| finally: | |
| sys.modules['zipimport'] = old_zipimport | |
| # not a frozen module | |
| self.assertEqual(import_frozen_module('sys'), 0) | |
| self.assertEqual(import_frozen_module('nonexistent'), 0) | |
| self.assertEqual(import_frozen_module(''), 0) | |
| def test_importfrozenmodule(self): | |
| # Test PyImport_ImportFrozenModule() | |
| importfrozenmodule = _testlimitedcapi.PyImport_ImportFrozenModule | |
| self.check_frozen_import(importfrozenmodule) | |
| self.assertRaises(UnicodeDecodeError, importfrozenmodule, b'\xff') | |
| # CRASHES importfrozenmodule(NULL) | |
| def test_importfrozenmoduleobject(self): | |
| # Test PyImport_ImportFrozenModuleObject() | |
| importfrozenmoduleobject = _testlimitedcapi.PyImport_ImportFrozenModuleObject | |
| self.check_frozen_import(importfrozenmoduleobject) | |
| self.assertEqual(importfrozenmoduleobject(b'zipimport'), 0) | |
| self.assertEqual(importfrozenmoduleobject(NULL), 0) | |
| def test_importmoduleex(self): | |
| # Test PyImport_ImportModuleEx() | |
| importmoduleex = _testlimitedcapi.PyImport_ImportModuleEx | |
| self.check_import_func(lambda name: importmoduleex(name, NULL, NULL, NULL)) | |
| self.assertRaises(ModuleNotFoundError, importmoduleex, 'nonexistent', NULL, NULL, NULL) | |
| self.assertRaises(ValueError, importmoduleex, '', NULL, NULL, NULL) | |
| self.assertRaises(UnicodeDecodeError, importmoduleex, b'\xff', NULL, NULL, NULL) | |
| # CRASHES importmoduleex(NULL, NULL, NULL, NULL) | |
| def check_importmodulelevel(self, importmodulelevel): | |
| self.check_import_func(lambda name: importmodulelevel(name, NULL, NULL, NULL, 0)) | |
| self.assertRaises(ModuleNotFoundError, importmodulelevel, 'nonexistent', NULL, NULL, NULL, 0) | |
| self.assertRaises(ValueError, importmodulelevel, '', NULL, NULL, NULL, 0) | |
| if __package__: | |
| self.assertIs(importmodulelevel('test_import', globals(), NULL, NULL, 1), | |
| sys.modules['test.test_capi.test_import']) | |
| self.assertIs(importmodulelevel('test_capi', globals(), NULL, NULL, 2), | |
| sys.modules['test.test_capi']) | |
| self.assertRaises(ValueError, importmodulelevel, 'os', NULL, NULL, NULL, -1) | |
| with self.assertWarns(ImportWarning): | |
| self.assertRaises(KeyError, importmodulelevel, 'test_import', {}, NULL, NULL, 1) | |
| self.assertRaises(TypeError, importmodulelevel, 'test_import', [], NULL, NULL, 1) | |
| def test_importmodulelevel(self): | |
| # Test PyImport_ImportModuleLevel() | |
| importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevel | |
| self.check_importmodulelevel(importmodulelevel) | |
| self.assertRaises(UnicodeDecodeError, importmodulelevel, b'\xff', NULL, NULL, NULL, 0) | |
| # CRASHES importmodulelevel(NULL, NULL, NULL, NULL, 0) | |
| def test_importmodulelevelobject(self): | |
| # Test PyImport_ImportModuleLevelObject() | |
| importmodulelevel = _testlimitedcapi.PyImport_ImportModuleLevelObject | |
| self.check_importmodulelevel(importmodulelevel) | |
| self.assertRaises(TypeError, importmodulelevel, b'os', NULL, NULL, NULL, 0) | |
| self.assertRaises(ValueError, importmodulelevel, NULL, NULL, NULL, NULL, 0) | |
| def check_executecodemodule(self, execute_code, *args): | |
| name = 'test_import_executecode' | |
| try: | |
| # Create a temporary module where the code will be executed | |
| self.assertNotIn(name, sys.modules) | |
| module = _testlimitedcapi.PyImport_AddModuleRef(name) | |
| self.assertNotHasAttr(module, 'attr') | |
| # Execute the code | |
| code = compile('attr = 1', '<test>', 'exec') | |
| module2 = execute_code(name, code, *args) | |
| self.assertIs(module2, module) | |
| # Check the function side effects | |
| self.assertEqual(module.attr, 1) | |
| finally: | |
| sys.modules.pop(name, None) | |
| return module.__spec__.origin | |
| def test_executecodemodule(self): | |
| # Test PyImport_ExecCodeModule() | |
| execcodemodule = _testlimitedcapi.PyImport_ExecCodeModule | |
| self.check_executecodemodule(execcodemodule) | |
| code = compile('attr = 1', '<test>', 'exec') | |
| self.assertRaises(UnicodeDecodeError, execcodemodule, b'\xff', code) | |
| # CRASHES execcodemodule(NULL, code) | |
| # CRASHES execcodemodule(name, NULL) | |
| def test_executecodemoduleex(self): | |
| # Test PyImport_ExecCodeModuleEx() | |
| execcodemoduleex = _testlimitedcapi.PyImport_ExecCodeModuleEx | |
| # Test NULL path (it should not crash) | |
| self.check_executecodemodule(execcodemoduleex, NULL) | |
| # Test non-NULL path | |
| pathname = b'pathname' | |
| origin = self.check_executecodemodule(execcodemoduleex, pathname) | |
| self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | |
| pathname = os_helper.TESTFN_UNDECODABLE | |
| if pathname: | |
| origin = self.check_executecodemodule(execcodemoduleex, pathname) | |
| self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | |
| code = compile('attr = 1', '<test>', 'exec') | |
| self.assertRaises(UnicodeDecodeError, execcodemoduleex, b'\xff', code, NULL) | |
| # CRASHES execcodemoduleex(NULL, code, NULL) | |
| # CRASHES execcodemoduleex(name, NULL, NULL) | |
| def check_executecode_pathnames(self, execute_code_func, object=False): | |
| # Test non-NULL pathname and NULL cpathname | |
| # Test NULL paths (it should not crash) | |
| self.check_executecodemodule(execute_code_func, NULL, NULL) | |
| pathname = 'pathname' | |
| origin = self.check_executecodemodule(execute_code_func, pathname, NULL) | |
| self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | |
| origin = self.check_executecodemodule(execute_code_func, NULL, pathname) | |
| if not object: | |
| self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | |
| pathname = os_helper.TESTFN_UNDECODABLE | |
| if pathname: | |
| if object: | |
| pathname = os.fsdecode(pathname) | |
| origin = self.check_executecodemodule(execute_code_func, pathname, NULL) | |
| self.assertEqual(origin, os.path.abspath(os.fsdecode(pathname))) | |
| self.check_executecodemodule(execute_code_func, NULL, pathname) | |
| # Test NULL pathname and non-NULL cpathname | |
| try: | |
| pyc_filename = importlib.util.cache_from_source(__file__) | |
| except NotImplementedError: | |
| return | |
| py_filename = importlib.util.source_from_cache(pyc_filename) | |
| origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename) | |
| if not object: | |
| self.assertEqual(origin, py_filename) | |
| def test_executecodemodulewithpathnames(self): | |
| # Test PyImport_ExecCodeModuleWithPathnames() | |
| execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleWithPathnames | |
| self.check_executecode_pathnames(execute_code_func) | |
| code = compile('attr = 1', '<test>', 'exec') | |
| self.assertRaises(UnicodeDecodeError, execute_code_func, b'\xff', code, NULL, NULL) | |
| # CRASHES execute_code_func(NULL, code, NULL, NULL) | |
| # CRASHES execute_code_func(name, NULL, NULL, NULL) | |
| def test_executecodemoduleobject(self): | |
| # Test PyImport_ExecCodeModuleObject() | |
| execute_code_func = _testlimitedcapi.PyImport_ExecCodeModuleObject | |
| self.check_executecode_pathnames(execute_code_func, object=True) | |
| code = compile('attr = 1', '<test>', 'exec') | |
| self.assertRaises(TypeError, execute_code_func, [], code, NULL, NULL) | |
| nonstring = tuple(['hashable non-string']) | |
| self.assertRaises(AttributeError, execute_code_func, nonstring, code, NULL, NULL) | |
| sys.modules.pop(nonstring, None) | |
| # CRASHES execute_code_func(NULL, code, NULL, NULL) | |
| # CRASHES execute_code_func(name, NULL, NULL, NULL) | |
| def check_importmoduleattr(self, importmoduleattr): | |
| self.assertIs(importmoduleattr('sys', 'argv'), sys.argv) | |
| self.assertIs(importmoduleattr('types', 'ModuleType'), types.ModuleType) | |
| # module name containing a dot | |
| attr = importmoduleattr('email.message', 'Message') | |
| from email.message import Message | |
| self.assertIs(attr, Message) | |
| with self.assertRaises(ImportError): | |
| # nonexistent module | |
| importmoduleattr('nonexistentmodule', 'attr') | |
| with self.assertRaises(AttributeError): | |
| # nonexistent attribute | |
| importmoduleattr('sys', 'nonexistentattr') | |
| with self.assertRaises(AttributeError): | |
| # attribute name containing a dot | |
| importmoduleattr('sys', 'implementation.name') | |
| def test_importmoduleattr(self): | |
| # Test PyImport_ImportModuleAttr() | |
| importmoduleattr = _testcapi.PyImport_ImportModuleAttr | |
| self.check_importmoduleattr(importmoduleattr) | |
| # Invalid module name type | |
| for mod_name in (object(), 123, b'bytes'): | |
| with self.subTest(mod_name=mod_name): | |
| with self.assertRaises(TypeError): | |
| importmoduleattr(mod_name, "attr") | |
| # Invalid attribute name type | |
| for attr_name in (object(), 123, b'bytes'): | |
| with self.subTest(attr_name=attr_name): | |
| with self.assertRaises(TypeError): | |
| importmoduleattr("sys", attr_name) | |
| with self.assertRaises(SystemError): | |
| importmoduleattr(NULL, "argv") | |
| # CRASHES importmoduleattr("sys", NULL) | |
| def test_importmoduleattrstring(self): | |
| # Test PyImport_ImportModuleAttrString() | |
| importmoduleattr = _testcapi.PyImport_ImportModuleAttrString | |
| self.check_importmoduleattr(importmoduleattr) | |
| with self.assertRaises(UnicodeDecodeError): | |
| importmoduleattr(b"sys\xff", "argv") | |
| with self.assertRaises(UnicodeDecodeError): | |
| importmoduleattr("sys", b"argv\xff") | |
| # CRASHES importmoduleattr(NULL, "argv") | |
| # CRASHES importmoduleattr("sys", NULL) | |
| # TODO: test PyImport_GetImporter() | |
| # TODO: test PyImport_ReloadModule() | |
| # TODO: test PyImport_ExtendInittab() | |
| # PyImport_AppendInittab() is tested by test_embed | |
| if __name__ == "__main__": | |
| unittest.main() |