| import importlib | |
| import os | |
| import sys | |
| import textwrap | |
| import unittest | |
| from test.support import import_helper, os_helper | |
| _testlimitedcapi = import_helper.import_module('_testlimitedcapi') | |
| class CapsuleImportTests(unittest.TestCase): | |
| """Tests for PyCapsule_Import().""" | |
| @classmethod | |
| def setUpClass(cls): | |
| tmp = cls.tmp = cls.enterClassContext(os_helper.temp_dir()) | |
| cls.enterClassContext(import_helper.DirsOnSysPath(tmp)) | |
| cls.write_file(os.path.join(tmp, 'capsule_mod.py'), ''' | |
| import _testlimitedcapi | |
| capsule = _testlimitedcapi.capsule_new('capsule_mod.capsule') | |
| капсула = _testlimitedcapi.capsule_new('capsule_mod.капсула') | |
| mismatched = _testlimitedcapi.capsule_new('other.name') | |
| nonutf8 = _testlimitedcapi.capsule_new(b'capsule_mod.nonutf8\\xff') | |
| nullname = _testlimitedcapi.capsule_new(None) | |
| not_capsule = 42 | |
| class ns: | |
| nested = _testlimitedcapi.capsule_new('capsule_mod.ns.nested') | |
| def __getattr__(name): | |
| if name == 'bad_attr': | |
| raise FloatingPointError('bad attribute') | |
| raise AttributeError(name) | |
| ''') | |
| pkg = os.path.join(tmp, 'capsule_pkg') | |
| os.mkdir(pkg) | |
| cls.write_file(os.path.join(pkg, '__init__.py'), '') | |
| cls.write_file(os.path.join(pkg, 'sub.py'), ''' | |
| import _testlimitedcapi | |
| capsule = _testlimitedcapi.capsule_new('capsule_pkg.sub.capsule') | |
| ''') | |
| autopkg = os.path.join(tmp, 'capsule_autopkg') | |
| os.mkdir(autopkg) | |
| cls.write_file(os.path.join(autopkg, '__init__.py'), 'from . import sub\n') | |
| cls.write_file(os.path.join(autopkg, 'sub.py'), ''' | |
| import _testlimitedcapi | |
| capsule = _testlimitedcapi.capsule_new('capsule_autopkg.sub.capsule') | |
| ''') | |
| cls.write_file(os.path.join(tmp, 'capsule_broken.py'), '1/0\n') | |
| importlib.invalidate_caches() | |
| def setUp(self): | |
| for name in ('capsule_mod', 'capsule_pkg.sub', 'capsule_pkg', | |
| 'capsule_autopkg.sub', 'capsule_autopkg', | |
| 'capsule_broken'): | |
| self.addCleanup(import_helper.unload, name) | |
| @staticmethod | |
| def write_file(path, source): | |
| with open(path, 'w', encoding='utf-8') as f: | |
| f.write(textwrap.dedent(source)) | |
| def check_import(self, name, no_block=0): | |
| # _testlimitedcapi.PyCapsule_Import() returns the name stored as the | |
| # pointer by _testlimitedcapi.capsule_new(). | |
| self.assertEqual(_testlimitedcapi.PyCapsule_Import(name, no_block), name) | |
| def test_import(self): | |
| # The module is imported if not already imported. | |
| self.assertNotIn('capsule_mod', sys.modules) | |
| self.check_import('capsule_mod.capsule') | |
| # Attributes after the first component are plain attribute lookups. | |
| self.check_import('capsule_mod.ns.nested') | |
| # Non-ASCII capsule and attribute name. | |
| self.check_import('capsule_mod.капсула') | |
| # The no_block argument is ignored. | |
| self.check_import('capsule_mod.capsule', 1) | |
| @unittest.skipUnless(os_helper.TESTFN_NONASCII, | |
| 'requires non-ASCII file name support') | |
| def test_non_ascii_module_name(self): | |
| name = os_helper.TESTFN_NONASCII | |
| self.write_file(os.path.join(self.tmp, name + '.py'), f''' | |
| import _testlimitedcapi | |
| capsule = _testlimitedcapi.capsule_new('{name}.capsule') | |
| ''') | |
| importlib.invalidate_caches() | |
| self.addCleanup(import_helper.unload, name) | |
| self.check_import(f'{name}.capsule') | |
| def test_submodule(self): | |
| # A submodule not imported by its package is imported if needed. | |
| self.assertNotIn('capsule_pkg.sub', sys.modules) | |
| self.check_import('capsule_pkg.sub.capsule') | |
| self.assertIn('capsule_pkg.sub', sys.modules) | |
| # It is also found if already imported. | |
| self.check_import('capsule_pkg.sub.capsule') | |
| # A submodule imported by its package is found. | |
| self.check_import('capsule_autopkg.sub.capsule') | |
| def test_invalid_name(self): | |
| pycapsule_import = _testlimitedcapi.PyCapsule_Import | |
| # Non-existing module. | |
| self.assertRaisesRegex(ModuleNotFoundError, | |
| "No module named 'capsule_nonexistent'", | |
| pycapsule_import, 'capsule_nonexistent.capsule') | |
| # Non-UTF-8 module name. | |
| self.assertRaises(UnicodeDecodeError, | |
| pycapsule_import, b'\xff\xfe.capsule') | |
| # Empty module name. | |
| self.assertRaisesRegex(ValueError, 'Empty module name', | |
| pycapsule_import, '.capsule_mod.capsule') | |
| # Empty name. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, '') | |
| # Only a dot. | |
| self.assertRaisesRegex(ValueError, 'Empty module name', | |
| pycapsule_import, '.') | |
| # Non-existing attribute. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.nonexistent') | |
| # Empty attribute name. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.') | |
| # Consecutive dots. | |
| self.assertRaisesRegex(ModuleNotFoundError, | |
| "No module named 'capsule_mod.'", | |
| pycapsule_import, 'capsule_mod..capsule') | |
| # Attribute of an object which is not a module. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.not_capsule.capsule') | |
| # No attribute name. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod') | |
| # CRASHES pycapsule_import(NULL) | |
| def test_invalid_capsule(self): | |
| pycapsule_import = _testlimitedcapi.PyCapsule_Import | |
| # The attribute is not a capsule. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.not_capsule') | |
| # The capsule name does not match the requested name. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.mismatched') | |
| # The capsule name contains a byte not decodable from UTF-8. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.nonutf8') | |
| # Even the exactly matching name fails: the attribute lookup | |
| # requires a name decodable from UTF-8. | |
| self.assertRaises(UnicodeDecodeError, | |
| pycapsule_import, b'capsule_mod.nonutf8\xff') | |
| # The capsule name is NULL. | |
| self.assertRaisesRegex(AttributeError, 'is not valid', | |
| pycapsule_import, 'capsule_mod.nullname') | |
| def test_error_from_import(self): | |
| # The exception raised during importing the module is propagated. | |
| self.assertRaises(ZeroDivisionError, | |
| _testlimitedcapi.PyCapsule_Import, 'capsule_broken.capsule') | |
| def test_error_from_attribute_lookup(self): | |
| self.assertRaises(FloatingPointError, | |
| _testlimitedcapi.PyCapsule_Import, 'capsule_mod.bad_attr') | |
| self.assertRaises(FloatingPointError, | |
| _testlimitedcapi.PyCapsule_Import, 'capsule_mod.bad_attr.capsule') | |
| if __name__ == "__main__": | |
| unittest.main() |