• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from test.support import is_apple_mobile
2from test.test_importlib import abc, util
3
4machinery = util.import_importlib('importlib.machinery')
5
6import os.path
7import sys
8import types
9import unittest
10import warnings
11import importlib.util
12import importlib
13from test import support
14from test.support import MISSING_C_DOCSTRINGS, script_helper
15
16
17class LoaderTests:
18
19    """Test ExtensionFileLoader."""
20
21    def setUp(self):
22        if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
23            raise unittest.SkipTest("Requires dynamic loading support.")
24        if util.EXTENSIONS.name in sys.builtin_module_names:
25            raise unittest.SkipTest(
26                f"{util.EXTENSIONS.name} is a builtin module"
27            )
28
29        # Apple extensions must be distributed as frameworks. This requires
30        # a specialist loader.
31        if is_apple_mobile:
32            self.LoaderClass = self.machinery.AppleFrameworkLoader
33        else:
34            self.LoaderClass = self.machinery.ExtensionFileLoader
35
36        self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
37
38    def load_module(self, fullname):
39        with warnings.catch_warnings():
40            warnings.simplefilter("ignore", DeprecationWarning)
41            return self.loader.load_module(fullname)
42
43    def test_equality(self):
44        other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
45        self.assertEqual(self.loader, other)
46
47    def test_inequality(self):
48        other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path)
49        self.assertNotEqual(self.loader, other)
50
51    def test_load_module_API(self):
52        # Test the default argument for load_module().
53        with warnings.catch_warnings():
54            warnings.simplefilter("ignore", DeprecationWarning)
55            self.loader.load_module()
56            self.loader.load_module(None)
57            with self.assertRaises(ImportError):
58                self.load_module('XXX')
59
60    def test_module(self):
61        with util.uncache(util.EXTENSIONS.name):
62            module = self.load_module(util.EXTENSIONS.name)
63            for attr, value in [('__name__', util.EXTENSIONS.name),
64                                ('__file__', util.EXTENSIONS.file_path),
65                                ('__package__', '')]:
66                self.assertEqual(getattr(module, attr), value)
67            self.assertIn(util.EXTENSIONS.name, sys.modules)
68            self.assertIsInstance(module.__loader__, self.LoaderClass)
69
70    # No extension module as __init__ available for testing.
71    test_package = None
72
73    # No extension module in a package available for testing.
74    test_lacking_parent = None
75
76    # No easy way to trigger a failure after a successful import.
77    test_state_after_failure = None
78
79    def test_unloadable(self):
80        name = 'asdfjkl;'
81        with self.assertRaises(ImportError) as cm:
82            self.load_module(name)
83        self.assertEqual(cm.exception.name, name)
84
85    def test_module_reuse(self):
86        with util.uncache(util.EXTENSIONS.name):
87            module1 = self.load_module(util.EXTENSIONS.name)
88            module2 = self.load_module(util.EXTENSIONS.name)
89            self.assertIs(module1, module2)
90
91    def test_is_package(self):
92        self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
93        for suffix in self.machinery.EXTENSION_SUFFIXES:
94            path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
95            loader = self.LoaderClass('pkg', path)
96            self.assertTrue(loader.is_package('pkg'))
97
98
99(Frozen_LoaderTests,
100 Source_LoaderTests
101 ) = util.test_both(LoaderTests, machinery=machinery)
102
103
104class SinglePhaseExtensionModuleTests(abc.LoaderTests):
105    # Test loading extension modules without multi-phase initialization.
106
107    def setUp(self):
108        if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
109            raise unittest.SkipTest("Requires dynamic loading support.")
110
111        # Apple extensions must be distributed as frameworks. This requires
112        # a specialist loader.
113        if is_apple_mobile:
114            self.LoaderClass = self.machinery.AppleFrameworkLoader
115        else:
116            self.LoaderClass = self.machinery.ExtensionFileLoader
117
118        self.name = '_testsinglephase'
119        if self.name in sys.builtin_module_names:
120            raise unittest.SkipTest(
121                f"{self.name} is a builtin module"
122            )
123        finder = self.machinery.FileFinder(None)
124        self.spec = importlib.util.find_spec(self.name)
125        assert self.spec
126
127        self.loader = self.LoaderClass(self.name, self.spec.origin)
128
129    def load_module(self):
130        with warnings.catch_warnings():
131            warnings.simplefilter("ignore", DeprecationWarning)
132            return self.loader.load_module(self.name)
133
134    def load_module_by_name(self, fullname):
135        # Load a module from the test extension by name.
136        origin = self.spec.origin
137        loader = self.LoaderClass(fullname, origin)
138        spec = importlib.util.spec_from_loader(fullname, loader)
139        module = importlib.util.module_from_spec(spec)
140        loader.exec_module(module)
141        return module
142
143    def test_module(self):
144        # Test loading an extension module.
145        with util.uncache(self.name):
146            module = self.load_module()
147            for attr, value in [('__name__', self.name),
148                                ('__file__', self.spec.origin),
149                                ('__package__', '')]:
150                self.assertEqual(getattr(module, attr), value)
151            with self.assertRaises(AttributeError):
152                module.__path__
153            self.assertIs(module, sys.modules[self.name])
154            self.assertIsInstance(module.__loader__, self.LoaderClass)
155
156    # No extension module as __init__ available for testing.
157    test_package = None
158
159    # No extension module in a package available for testing.
160    test_lacking_parent = None
161
162    # No easy way to trigger a failure after a successful import.
163    test_state_after_failure = None
164
165    def test_unloadable(self):
166        name = 'asdfjkl;'
167        with self.assertRaises(ImportError) as cm:
168            self.load_module_by_name(name)
169        self.assertEqual(cm.exception.name, name)
170
171    def test_unloadable_nonascii(self):
172        # Test behavior with nonexistent module with non-ASCII name.
173        name = 'fo\xf3'
174        with self.assertRaises(ImportError) as cm:
175            self.load_module_by_name(name)
176        self.assertEqual(cm.exception.name, name)
177
178    # It may make sense to add the equivalent to
179    # the following MultiPhaseExtensionModuleTests tests:
180    #
181    #  * test_nonmodule
182    #  * test_nonmodule_with_methods
183    #  * test_bad_modules
184    #  * test_nonascii
185
186
187(Frozen_SinglePhaseExtensionModuleTests,
188 Source_SinglePhaseExtensionModuleTests
189 ) = util.test_both(SinglePhaseExtensionModuleTests, machinery=machinery)
190
191
192class MultiPhaseExtensionModuleTests(abc.LoaderTests):
193    # Test loading extension modules with multi-phase initialization (PEP 489).
194
195    def setUp(self):
196        if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
197            raise unittest.SkipTest("Requires dynamic loading support.")
198
199        # Apple extensions must be distributed as frameworks. This requires
200        # a specialist loader.
201        if is_apple_mobile:
202            self.LoaderClass = self.machinery.AppleFrameworkLoader
203        else:
204            self.LoaderClass = self.machinery.ExtensionFileLoader
205
206        self.name = '_testmultiphase'
207        if self.name in sys.builtin_module_names:
208            raise unittest.SkipTest(
209                f"{self.name} is a builtin module"
210            )
211        finder = self.machinery.FileFinder(None)
212        self.spec = importlib.util.find_spec(self.name)
213        assert self.spec
214        self.loader = self.LoaderClass(self.name, self.spec.origin)
215
216    def load_module(self):
217        # Load the module from the test extension.
218        with warnings.catch_warnings():
219            warnings.simplefilter("ignore", DeprecationWarning)
220            return self.loader.load_module(self.name)
221
222    def load_module_by_name(self, fullname):
223        # Load a module from the test extension by name.
224        origin = self.spec.origin
225        loader = self.LoaderClass(fullname, origin)
226        spec = importlib.util.spec_from_loader(fullname, loader)
227        module = importlib.util.module_from_spec(spec)
228        loader.exec_module(module)
229        return module
230
231    # No extension module as __init__ available for testing.
232    test_package = None
233
234    # No extension module in a package available for testing.
235    test_lacking_parent = None
236
237    # Handling failure on reload is the up to the module.
238    test_state_after_failure = None
239
240    def test_module(self):
241        # Test loading an extension module.
242        with util.uncache(self.name):
243            module = self.load_module()
244            for attr, value in [('__name__', self.name),
245                                ('__file__', self.spec.origin),
246                                ('__package__', '')]:
247                self.assertEqual(getattr(module, attr), value)
248            with self.assertRaises(AttributeError):
249                module.__path__
250            self.assertIs(module, sys.modules[self.name])
251            self.assertIsInstance(module.__loader__, self.LoaderClass)
252
253    def test_functionality(self):
254        # Test basic functionality of stuff defined in an extension module.
255        with util.uncache(self.name):
256            module = self.load_module()
257            self.assertIsInstance(module, types.ModuleType)
258            ex = module.Example()
259            self.assertEqual(ex.demo('abcd'), 'abcd')
260            self.assertEqual(ex.demo(), None)
261            with self.assertRaises(AttributeError):
262                ex.abc
263            ex.abc = 0
264            self.assertEqual(ex.abc, 0)
265            self.assertEqual(module.foo(9, 9), 18)
266            self.assertIsInstance(module.Str(), str)
267            self.assertEqual(module.Str(1) + '23', '123')
268            with self.assertRaises(module.error):
269                raise module.error()
270            self.assertEqual(module.int_const, 1969)
271            self.assertEqual(module.str_const, 'something different')
272
273    def test_reload(self):
274        # Test that reload didn't re-set the module's attributes.
275        with util.uncache(self.name):
276            module = self.load_module()
277            ex_class = module.Example
278            importlib.reload(module)
279            self.assertIs(ex_class, module.Example)
280
281    def test_try_registration(self):
282        # Assert that the PyState_{Find,Add,Remove}Module C API doesn't work.
283        with util.uncache(self.name):
284            module = self.load_module()
285            with self.subTest('PyState_FindModule'):
286                self.assertEqual(module.call_state_registration_func(0), None)
287            with self.subTest('PyState_AddModule'):
288                with self.assertRaises(SystemError):
289                    module.call_state_registration_func(1)
290            with self.subTest('PyState_RemoveModule'):
291                with self.assertRaises(SystemError):
292                    module.call_state_registration_func(2)
293
294    def test_load_submodule(self):
295        # Test loading a simulated submodule.
296        module = self.load_module_by_name('pkg.' + self.name)
297        self.assertIsInstance(module, types.ModuleType)
298        self.assertEqual(module.__name__, 'pkg.' + self.name)
299        self.assertEqual(module.str_const, 'something different')
300
301    def test_load_short_name(self):
302        # Test loading module with a one-character name.
303        module = self.load_module_by_name('x')
304        self.assertIsInstance(module, types.ModuleType)
305        self.assertEqual(module.__name__, 'x')
306        self.assertEqual(module.str_const, 'something different')
307        self.assertNotIn('x', sys.modules)
308
309    def test_load_twice(self):
310        # Test that 2 loads result in 2 module objects.
311        module1 = self.load_module_by_name(self.name)
312        module2 = self.load_module_by_name(self.name)
313        self.assertIsNot(module1, module2)
314
315    def test_unloadable(self):
316        # Test nonexistent module.
317        name = 'asdfjkl;'
318        with self.assertRaises(ImportError) as cm:
319            self.load_module_by_name(name)
320        self.assertEqual(cm.exception.name, name)
321
322    def test_unloadable_nonascii(self):
323        # Test behavior with nonexistent module with non-ASCII name.
324        name = 'fo\xf3'
325        with self.assertRaises(ImportError) as cm:
326            self.load_module_by_name(name)
327        self.assertEqual(cm.exception.name, name)
328
329    def test_bad_modules(self):
330        # Test SystemError is raised for misbehaving extensions.
331        for name_base in [
332                'bad_slot_large',
333                'bad_slot_negative',
334                'create_int_with_state',
335                'negative_size',
336                'export_null',
337                'export_uninitialized',
338                'export_raise',
339                'export_unreported_exception',
340                'create_null',
341                'create_raise',
342                'create_unreported_exception',
343                'nonmodule_with_exec_slots',
344                'exec_err',
345                'exec_raise',
346                'exec_unreported_exception',
347                'multiple_create_slots',
348                'multiple_multiple_interpreters_slots',
349                ]:
350            with self.subTest(name_base):
351                name = self.name + '_' + name_base
352                with self.assertRaises(SystemError) as cm:
353                    self.load_module_by_name(name)
354
355                # If there is an unreported exception, it should be chained
356                # with the `SystemError`.
357                if "unreported_exception" in name_base:
358                    self.assertIsNotNone(cm.exception.__cause__)
359
360    def test_nonascii(self):
361        # Test that modules with non-ASCII names can be loaded.
362        # punycode behaves slightly differently in some-ASCII and no-ASCII
363        # cases, so test both.
364        cases = [
365            (self.name + '_zkou\u0161ka_na\u010dten\xed', 'Czech'),
366            ('\uff3f\u30a4\u30f3\u30dd\u30fc\u30c8\u30c6\u30b9\u30c8',
367             'Japanese'),
368            ]
369        for name, lang in cases:
370            with self.subTest(name):
371                module = self.load_module_by_name(name)
372                self.assertEqual(module.__name__, name)
373                if not MISSING_C_DOCSTRINGS:
374                    self.assertEqual(module.__doc__, "Module named in %s" % lang)
375
376
377(Frozen_MultiPhaseExtensionModuleTests,
378 Source_MultiPhaseExtensionModuleTests
379 ) = util.test_both(MultiPhaseExtensionModuleTests, machinery=machinery)
380
381
382class NonModuleExtensionTests(unittest.TestCase):
383    def test_nonmodule_cases(self):
384        # The test cases in this file cause the GIL to be enabled permanently
385        # in free-threaded builds, so they are run in a subprocess to isolate
386        # this effect.
387        script = support.findfile("test_importlib/extension/_test_nonmodule_cases.py")
388        script_helper.run_test_script(script)
389
390
391if __name__ == '__main__':
392    unittest.main()
393