1import importlib
2import importlib.util
3import os
4import os.path
5import py_compile
6import sys
7from test import support
8from test.support import script_helper
9import unittest
10import warnings
11with warnings.catch_warnings():
12    warnings.simplefilter('ignore', DeprecationWarning)
13    import imp
14import _imp
15
16
17def requires_load_dynamic(meth):
18    """Decorator to skip a test if not running under CPython or lacking
19    imp.load_dynamic()."""
20    meth = support.cpython_only(meth)
21    return unittest.skipIf(not hasattr(imp, 'load_dynamic'),
22                           'imp.load_dynamic() required')(meth)
23
24
25class LockTests(unittest.TestCase):
26
27    """Very basic test of import lock functions."""
28
29    def verify_lock_state(self, expected):
30        self.assertEqual(imp.lock_held(), expected,
31                             "expected imp.lock_held() to be %r" % expected)
32    def testLock(self):
33        LOOPS = 50
34
35        # The import lock may already be held, e.g. if the test suite is run
36        # via "import test.autotest".
37        lock_held_at_start = imp.lock_held()
38        self.verify_lock_state(lock_held_at_start)
39
40        for i in range(LOOPS):
41            imp.acquire_lock()
42            self.verify_lock_state(True)
43
44        for i in range(LOOPS):
45            imp.release_lock()
46
47        # The original state should be restored now.
48        self.verify_lock_state(lock_held_at_start)
49
50        if not lock_held_at_start:
51            try:
52                imp.release_lock()
53            except RuntimeError:
54                pass
55            else:
56                self.fail("release_lock() without lock should raise "
57                            "RuntimeError")
58
59class ImportTests(unittest.TestCase):
60    def setUp(self):
61        mod = importlib.import_module('test.encoded_modules')
62        self.test_strings = mod.test_strings
63        self.test_path = mod.__path__
64
65    def test_import_encoded_module(self):
66        for modname, encoding, teststr in self.test_strings:
67            mod = importlib.import_module('test.encoded_modules.'
68                                          'module_' + modname)
69            self.assertEqual(teststr, mod.test)
70
71    def test_find_module_encoding(self):
72        for mod, encoding, _ in self.test_strings:
73            with imp.find_module('module_' + mod, self.test_path)[0] as fd:
74                self.assertEqual(fd.encoding, encoding)
75
76        path = [os.path.dirname(__file__)]
77        with self.assertRaises(SyntaxError):
78            imp.find_module('badsyntax_pep3120', path)
79
80    def test_issue1267(self):
81        for mod, encoding, _ in self.test_strings:
82            fp, filename, info  = imp.find_module('module_' + mod,
83                                                  self.test_path)
84            with fp:
85                self.assertNotEqual(fp, None)
86                self.assertEqual(fp.encoding, encoding)
87                self.assertEqual(fp.tell(), 0)
88                self.assertEqual(fp.readline(), '# test %s encoding\n'
89                                 % encoding)
90
91        fp, filename, info = imp.find_module("tokenize")
92        with fp:
93            self.assertNotEqual(fp, None)
94            self.assertEqual(fp.encoding, "utf-8")
95            self.assertEqual(fp.tell(), 0)
96            self.assertEqual(fp.readline(),
97                             '"""Tokenization help for Python programs.\n')
98
99    def test_issue3594(self):
100        temp_mod_name = 'test_imp_helper'
101        sys.path.insert(0, '.')
102        try:
103            with open(temp_mod_name + '.py', 'w') as file:
104                file.write("# coding: cp1252\nu = 'test.test_imp'\n")
105            file, filename, info = imp.find_module(temp_mod_name)
106            file.close()
107            self.assertEqual(file.encoding, 'cp1252')
108        finally:
109            del sys.path[0]
110            support.unlink(temp_mod_name + '.py')
111            support.unlink(temp_mod_name + '.pyc')
112
113    def test_issue5604(self):
114        # Test cannot cover imp.load_compiled function.
115        # Martin von Loewis note what shared library cannot have non-ascii
116        # character because init_xxx function cannot be compiled
117        # and issue never happens for dynamic modules.
118        # But sources modified to follow generic way for processing paths.
119
120        # the return encoding could be uppercase or None
121        fs_encoding = sys.getfilesystemencoding()
122
123        # covers utf-8 and Windows ANSI code pages
124        # one non-space symbol from every page
125        # (http://en.wikipedia.org/wiki/Code_page)
126        known_locales = {
127            'utf-8' : b'\xc3\xa4',
128            'cp1250' : b'\x8C',
129            'cp1251' : b'\xc0',
130            'cp1252' : b'\xc0',
131            'cp1253' : b'\xc1',
132            'cp1254' : b'\xc0',
133            'cp1255' : b'\xe0',
134            'cp1256' : b'\xe0',
135            'cp1257' : b'\xc0',
136            'cp1258' : b'\xc0',
137            }
138
139        if sys.platform == 'darwin':
140            self.assertEqual(fs_encoding, 'utf-8')
141            # Mac OS X uses the Normal Form D decomposition
142            # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
143            special_char = b'a\xcc\x88'
144        else:
145            special_char = known_locales.get(fs_encoding)
146
147        if not special_char:
148            self.skipTest("can't run this test with %s as filesystem encoding"
149                          % fs_encoding)
150        decoded_char = special_char.decode(fs_encoding)
151        temp_mod_name = 'test_imp_helper_' + decoded_char
152        test_package_name = 'test_imp_helper_package_' + decoded_char
153        init_file_name = os.path.join(test_package_name, '__init__.py')
154        try:
155            # if the curdir is not in sys.path the test fails when run with
156            # ./python ./Lib/test/regrtest.py test_imp
157            sys.path.insert(0, os.curdir)
158            with open(temp_mod_name + '.py', 'w') as file:
159                file.write('a = 1\n')
160            file, filename, info = imp.find_module(temp_mod_name)
161            with file:
162                self.assertIsNotNone(file)
163                self.assertTrue(filename[:-3].endswith(temp_mod_name))
164                self.assertEqual(info[0], '.py')
165                self.assertEqual(info[1], 'r')
166                self.assertEqual(info[2], imp.PY_SOURCE)
167
168                mod = imp.load_module(temp_mod_name, file, filename, info)
169                self.assertEqual(mod.a, 1)
170
171            with warnings.catch_warnings():
172                warnings.simplefilter('ignore')
173                mod = imp.load_source(temp_mod_name, temp_mod_name + '.py')
174            self.assertEqual(mod.a, 1)
175
176            with warnings.catch_warnings():
177                warnings.simplefilter('ignore')
178                if not sys.dont_write_bytecode:
179                    mod = imp.load_compiled(
180                        temp_mod_name,
181                        imp.cache_from_source(temp_mod_name + '.py'))
182            self.assertEqual(mod.a, 1)
183
184            if not os.path.exists(test_package_name):
185                os.mkdir(test_package_name)
186            with open(init_file_name, 'w') as file:
187                file.write('b = 2\n')
188            with warnings.catch_warnings():
189                warnings.simplefilter('ignore')
190                package = imp.load_package(test_package_name, test_package_name)
191            self.assertEqual(package.b, 2)
192        finally:
193            del sys.path[0]
194            for ext in ('.py', '.pyc'):
195                support.unlink(temp_mod_name + ext)
196                support.unlink(init_file_name + ext)
197            support.rmtree(test_package_name)
198            support.rmtree('__pycache__')
199
200    def test_issue9319(self):
201        path = os.path.dirname(__file__)
202        self.assertRaises(SyntaxError,
203                          imp.find_module, "badsyntax_pep3120", [path])
204
205    def test_load_from_source(self):
206        # Verify that the imp module can correctly load and find .py files
207        # XXX (ncoghlan): It would be nice to use support.CleanImport
208        # here, but that breaks because the os module registers some
209        # handlers in copy_reg on import. Since CleanImport doesn't
210        # revert that registration, the module is left in a broken
211        # state after reversion. Reinitialising the module contents
212        # and just reverting os.environ to its previous state is an OK
213        # workaround
214        orig_path = os.path
215        orig_getenv = os.getenv
216        with support.EnvironmentVarGuard():
217            x = imp.find_module("os")
218            self.addCleanup(x[0].close)
219            new_os = imp.load_module("os", *x)
220            self.assertIs(os, new_os)
221            self.assertIs(orig_path, new_os.path)
222            self.assertIsNot(orig_getenv, new_os.getenv)
223
224    @requires_load_dynamic
225    def test_issue15828_load_extensions(self):
226        # Issue 15828 picked up that the adapter between the old imp API
227        # and importlib couldn't handle C extensions
228        example = "_heapq"
229        x = imp.find_module(example)
230        file_ = x[0]
231        if file_ is not None:
232            self.addCleanup(file_.close)
233        mod = imp.load_module(example, *x)
234        self.assertEqual(mod.__name__, example)
235
236    @requires_load_dynamic
237    def test_issue16421_multiple_modules_in_one_dll(self):
238        # Issue 16421: loading several modules from the same compiled file fails
239        m = '_testimportmultiple'
240        fileobj, pathname, description = imp.find_module(m)
241        fileobj.close()
242        mod0 = imp.load_dynamic(m, pathname)
243        mod1 = imp.load_dynamic('_testimportmultiple_foo', pathname)
244        mod2 = imp.load_dynamic('_testimportmultiple_bar', pathname)
245        self.assertEqual(mod0.__name__, m)
246        self.assertEqual(mod1.__name__, '_testimportmultiple_foo')
247        self.assertEqual(mod2.__name__, '_testimportmultiple_bar')
248        with self.assertRaises(ImportError):
249            imp.load_dynamic('nonexistent', pathname)
250
251    @requires_load_dynamic
252    def test_load_dynamic_ImportError_path(self):
253        # Issue #1559549 added `name` and `path` attributes to ImportError
254        # in order to provide better detail. Issue #10854 implemented those
255        # attributes on import failures of extensions on Windows.
256        path = 'bogus file path'
257        name = 'extension'
258        with self.assertRaises(ImportError) as err:
259            imp.load_dynamic(name, path)
260        self.assertIn(path, err.exception.path)
261        self.assertEqual(name, err.exception.name)
262
263    @requires_load_dynamic
264    def test_load_module_extension_file_is_None(self):
265        # When loading an extension module and the file is None, open one
266        # on the behalf of imp.load_dynamic().
267        # Issue #15902
268        name = '_testimportmultiple'
269        found = imp.find_module(name)
270        if found[0] is not None:
271            found[0].close()
272        if found[2][2] != imp.C_EXTENSION:
273            self.skipTest("found module doesn't appear to be a C extension")
274        imp.load_module(name, None, *found[1:])
275
276    @requires_load_dynamic
277    def test_issue24748_load_module_skips_sys_modules_check(self):
278        name = 'test.imp_dummy'
279        try:
280            del sys.modules[name]
281        except KeyError:
282            pass
283        try:
284            module = importlib.import_module(name)
285            spec = importlib.util.find_spec('_testmultiphase')
286            module = imp.load_dynamic(name, spec.origin)
287            self.assertEqual(module.__name__, name)
288            self.assertEqual(module.__spec__.name, name)
289            self.assertEqual(module.__spec__.origin, spec.origin)
290            self.assertRaises(AttributeError, getattr, module, 'dummy_name')
291            self.assertEqual(module.int_const, 1969)
292            self.assertIs(sys.modules[name], module)
293        finally:
294            try:
295                del sys.modules[name]
296            except KeyError:
297                pass
298
299    @unittest.skipIf(sys.dont_write_bytecode,
300        "test meaningful only when writing bytecode")
301    def test_bug7732(self):
302        with support.temp_cwd():
303            source = support.TESTFN + '.py'
304            os.mkdir(source)
305            self.assertRaisesRegex(ImportError, '^No module',
306                imp.find_module, support.TESTFN, ["."])
307
308    def test_multiple_calls_to_get_data(self):
309        # Issue #18755: make sure multiple calls to get_data() can succeed.
310        loader = imp._LoadSourceCompatibility('imp', imp.__file__,
311                                              open(imp.__file__))
312        loader.get_data(imp.__file__)  # File should be closed
313        loader.get_data(imp.__file__)  # Will need to create a newly opened file
314
315    def test_load_source(self):
316        # Create a temporary module since load_source(name) modifies
317        # sys.modules[name] attributes like __loader___
318        modname = f"tmp{__name__}"
319        mod = type(sys.modules[__name__])(modname)
320        with support.swap_item(sys.modules, modname, mod):
321            with self.assertRaisesRegex(ValueError, 'embedded null'):
322                imp.load_source(modname, __file__ + "\0")
323
324    @support.cpython_only
325    def test_issue31315(self):
326        # There shouldn't be an assertion failure in imp.create_dynamic(),
327        # when spec.name is not a string.
328        create_dynamic = support.get_attribute(imp, 'create_dynamic')
329        class BadSpec:
330            name = None
331            origin = 'foo'
332        with self.assertRaises(TypeError):
333            create_dynamic(BadSpec())
334
335    def test_issue_35321(self):
336        # Both _frozen_importlib and _frozen_importlib_external
337        # should have a spec origin of "frozen" and
338        # no need to clean up imports in this case.
339
340        import _frozen_importlib_external
341        self.assertEqual(_frozen_importlib_external.__spec__.origin, "frozen")
342
343        import _frozen_importlib
344        self.assertEqual(_frozen_importlib.__spec__.origin, "frozen")
345
346    def test_source_hash(self):
347        self.assertEqual(_imp.source_hash(42, b'hi'), b'\xc6\xe7Z\r\x03:}\xab')
348        self.assertEqual(_imp.source_hash(43, b'hi'), b'\x85\x9765\xf8\x9a\x8b9')
349
350    def test_pyc_invalidation_mode_from_cmdline(self):
351        cases = [
352            ([], "default"),
353            (["--check-hash-based-pycs", "default"], "default"),
354            (["--check-hash-based-pycs", "always"], "always"),
355            (["--check-hash-based-pycs", "never"], "never"),
356        ]
357        for interp_args, expected in cases:
358            args = interp_args + [
359                "-c",
360                "import _imp; print(_imp.check_hash_based_pycs)",
361            ]
362            res = script_helper.assert_python_ok(*args)
363            self.assertEqual(res.out.strip().decode('utf-8'), expected)
364
365    def test_find_and_load_checked_pyc(self):
366        # issue 34056
367        with support.temp_cwd():
368            with open('mymod.py', 'wb') as fp:
369                fp.write(b'x = 42\n')
370            py_compile.compile(
371                'mymod.py',
372                doraise=True,
373                invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
374            )
375            file, path, description = imp.find_module('mymod', path=['.'])
376            mod = imp.load_module('mymod', file, path, description)
377        self.assertEqual(mod.x, 42)
378
379
380class ReloadTests(unittest.TestCase):
381
382    """Very basic tests to make sure that imp.reload() operates just like
383    reload()."""
384
385    def test_source(self):
386        # XXX (ncoghlan): It would be nice to use test.support.CleanImport
387        # here, but that breaks because the os module registers some
388        # handlers in copy_reg on import. Since CleanImport doesn't
389        # revert that registration, the module is left in a broken
390        # state after reversion. Reinitialising the module contents
391        # and just reverting os.environ to its previous state is an OK
392        # workaround
393        with support.EnvironmentVarGuard():
394            import os
395            imp.reload(os)
396
397    def test_extension(self):
398        with support.CleanImport('time'):
399            import time
400            imp.reload(time)
401
402    def test_builtin(self):
403        with support.CleanImport('marshal'):
404            import marshal
405            imp.reload(marshal)
406
407    def test_with_deleted_parent(self):
408        # see #18681
409        from html import parser
410        html = sys.modules.pop('html')
411        def cleanup():
412            sys.modules['html'] = html
413        self.addCleanup(cleanup)
414        with self.assertRaisesRegex(ImportError, 'html'):
415            imp.reload(parser)
416
417
418class PEP3147Tests(unittest.TestCase):
419    """Tests of PEP 3147."""
420
421    tag = imp.get_tag()
422
423    @unittest.skipUnless(sys.implementation.cache_tag is not None,
424                         'requires sys.implementation.cache_tag not be None')
425    def test_cache_from_source(self):
426        # Given the path to a .py file, return the path to its PEP 3147
427        # defined .pyc file (i.e. under __pycache__).
428        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
429        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
430                              'qux.{}.pyc'.format(self.tag))
431        self.assertEqual(imp.cache_from_source(path, True), expect)
432
433    @unittest.skipUnless(sys.implementation.cache_tag is not None,
434                         'requires sys.implementation.cache_tag to not be '
435                         'None')
436    def test_source_from_cache(self):
437        # Given the path to a PEP 3147 defined .pyc file, return the path to
438        # its source.  This tests the good path.
439        path = os.path.join('foo', 'bar', 'baz', '__pycache__',
440                            'qux.{}.pyc'.format(self.tag))
441        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
442        self.assertEqual(imp.source_from_cache(path), expect)
443
444
445class NullImporterTests(unittest.TestCase):
446    @unittest.skipIf(support.TESTFN_UNENCODABLE is None,
447                     "Need an undecodeable filename")
448    def test_unencodeable(self):
449        name = support.TESTFN_UNENCODABLE
450        os.mkdir(name)
451        try:
452            self.assertRaises(ImportError, imp.NullImporter, name)
453        finally:
454            os.rmdir(name)
455
456
457if __name__ == "__main__":
458    unittest.main()
459