1from test.test_importlib import util
2
3abc = util.import_importlib('importlib.abc')
4init = util.import_importlib('importlib')
5machinery = util.import_importlib('importlib.machinery')
6importlib_util = util.import_importlib('importlib.util')
7
8import importlib.util
9import os
10import pathlib
11import string
12import sys
13from test import support
14import types
15import unittest
16import unittest.mock
17import warnings
18
19
20class DecodeSourceBytesTests:
21
22    source = "string ='ü'"
23
24    def test_ut8_default(self):
25        source_bytes = self.source.encode('utf-8')
26        self.assertEqual(self.util.decode_source(source_bytes), self.source)
27
28    def test_specified_encoding(self):
29        source = '# coding=latin-1\n' + self.source
30        source_bytes = source.encode('latin-1')
31        assert source_bytes != source.encode('utf-8')
32        self.assertEqual(self.util.decode_source(source_bytes), source)
33
34    def test_universal_newlines(self):
35        source = '\r\n'.join([self.source, self.source])
36        source_bytes = source.encode('utf-8')
37        self.assertEqual(self.util.decode_source(source_bytes),
38                         '\n'.join([self.source, self.source]))
39
40
41(Frozen_DecodeSourceBytesTests,
42 Source_DecodeSourceBytesTests
43 ) = util.test_both(DecodeSourceBytesTests, util=importlib_util)
44
45
46class ModuleFromSpecTests:
47
48    def test_no_create_module(self):
49        class Loader:
50            def exec_module(self, module):
51                pass
52        spec = self.machinery.ModuleSpec('test', Loader())
53        with self.assertRaises(ImportError):
54            module = self.util.module_from_spec(spec)
55
56    def test_create_module_returns_None(self):
57        class Loader(self.abc.Loader):
58            def create_module(self, spec):
59                return None
60        spec = self.machinery.ModuleSpec('test', Loader())
61        module = self.util.module_from_spec(spec)
62        self.assertIsInstance(module, types.ModuleType)
63        self.assertEqual(module.__name__, spec.name)
64
65    def test_create_module(self):
66        name = 'already set'
67        class CustomModule(types.ModuleType):
68            pass
69        class Loader(self.abc.Loader):
70            def create_module(self, spec):
71                module = CustomModule(spec.name)
72                module.__name__ = name
73                return module
74        spec = self.machinery.ModuleSpec('test', Loader())
75        module = self.util.module_from_spec(spec)
76        self.assertIsInstance(module, CustomModule)
77        self.assertEqual(module.__name__, name)
78
79    def test___name__(self):
80        spec = self.machinery.ModuleSpec('test', object())
81        module = self.util.module_from_spec(spec)
82        self.assertEqual(module.__name__, spec.name)
83
84    def test___spec__(self):
85        spec = self.machinery.ModuleSpec('test', object())
86        module = self.util.module_from_spec(spec)
87        self.assertEqual(module.__spec__, spec)
88
89    def test___loader__(self):
90        loader = object()
91        spec = self.machinery.ModuleSpec('test', loader)
92        module = self.util.module_from_spec(spec)
93        self.assertIs(module.__loader__, loader)
94
95    def test___package__(self):
96        spec = self.machinery.ModuleSpec('test.pkg', object())
97        module = self.util.module_from_spec(spec)
98        self.assertEqual(module.__package__, spec.parent)
99
100    def test___path__(self):
101        spec = self.machinery.ModuleSpec('test', object(), is_package=True)
102        module = self.util.module_from_spec(spec)
103        self.assertEqual(module.__path__, spec.submodule_search_locations)
104
105    def test___file__(self):
106        spec = self.machinery.ModuleSpec('test', object(), origin='some/path')
107        spec.has_location = True
108        module = self.util.module_from_spec(spec)
109        self.assertEqual(module.__file__, spec.origin)
110
111    def test___cached__(self):
112        spec = self.machinery.ModuleSpec('test', object())
113        spec.cached = 'some/path'
114        spec.has_location = True
115        module = self.util.module_from_spec(spec)
116        self.assertEqual(module.__cached__, spec.cached)
117
118(Frozen_ModuleFromSpecTests,
119 Source_ModuleFromSpecTests
120) = util.test_both(ModuleFromSpecTests, abc=abc, machinery=machinery,
121                   util=importlib_util)
122
123
124class ModuleForLoaderTests:
125
126    """Tests for importlib.util.module_for_loader."""
127
128    @classmethod
129    def module_for_loader(cls, func):
130        with warnings.catch_warnings():
131            warnings.simplefilter('ignore', DeprecationWarning)
132            return cls.util.module_for_loader(func)
133
134    def test_warning(self):
135        # Should raise a PendingDeprecationWarning when used.
136        with warnings.catch_warnings():
137            warnings.simplefilter('error', DeprecationWarning)
138            with self.assertRaises(DeprecationWarning):
139                func = self.util.module_for_loader(lambda x: x)
140
141    def return_module(self, name):
142        fxn = self.module_for_loader(lambda self, module: module)
143        return fxn(self, name)
144
145    def raise_exception(self, name):
146        def to_wrap(self, module):
147            raise ImportError
148        fxn = self.module_for_loader(to_wrap)
149        try:
150            fxn(self, name)
151        except ImportError:
152            pass
153
154    def test_new_module(self):
155        # Test that when no module exists in sys.modules a new module is
156        # created.
157        module_name = 'a.b.c'
158        with util.uncache(module_name):
159            module = self.return_module(module_name)
160            self.assertIn(module_name, sys.modules)
161        self.assertIsInstance(module, types.ModuleType)
162        self.assertEqual(module.__name__, module_name)
163
164    def test_reload(self):
165        # Test that a module is reused if already in sys.modules.
166        class FakeLoader:
167            def is_package(self, name):
168                return True
169            @self.module_for_loader
170            def load_module(self, module):
171                return module
172        name = 'a.b.c'
173        module = types.ModuleType('a.b.c')
174        module.__loader__ = 42
175        module.__package__ = 42
176        with util.uncache(name):
177            sys.modules[name] = module
178            loader = FakeLoader()
179            returned_module = loader.load_module(name)
180            self.assertIs(returned_module, sys.modules[name])
181            self.assertEqual(module.__loader__, loader)
182            self.assertEqual(module.__package__, name)
183
184    def test_new_module_failure(self):
185        # Test that a module is removed from sys.modules if added but an
186        # exception is raised.
187        name = 'a.b.c'
188        with util.uncache(name):
189            self.raise_exception(name)
190            self.assertNotIn(name, sys.modules)
191
192    def test_reload_failure(self):
193        # Test that a failure on reload leaves the module in-place.
194        name = 'a.b.c'
195        module = types.ModuleType(name)
196        with util.uncache(name):
197            sys.modules[name] = module
198            self.raise_exception(name)
199            self.assertIs(module, sys.modules[name])
200
201    def test_decorator_attrs(self):
202        def fxn(self, module): pass
203        wrapped = self.module_for_loader(fxn)
204        self.assertEqual(wrapped.__name__, fxn.__name__)
205        self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
206
207    def test_false_module(self):
208        # If for some odd reason a module is considered false, still return it
209        # from sys.modules.
210        class FalseModule(types.ModuleType):
211            def __bool__(self): return False
212
213        name = 'mod'
214        module = FalseModule(name)
215        with util.uncache(name):
216            self.assertFalse(module)
217            sys.modules[name] = module
218            given = self.return_module(name)
219            self.assertIs(given, module)
220
221    def test_attributes_set(self):
222        # __name__, __loader__, and __package__ should be set (when
223        # is_package() is defined; undefined implicitly tested elsewhere).
224        class FakeLoader:
225            def __init__(self, is_package):
226                self._pkg = is_package
227            def is_package(self, name):
228                return self._pkg
229            @self.module_for_loader
230            def load_module(self, module):
231                return module
232
233        name = 'pkg.mod'
234        with util.uncache(name):
235            loader = FakeLoader(False)
236            module = loader.load_module(name)
237            self.assertEqual(module.__name__, name)
238            self.assertIs(module.__loader__, loader)
239            self.assertEqual(module.__package__, 'pkg')
240
241        name = 'pkg.sub'
242        with util.uncache(name):
243            loader = FakeLoader(True)
244            module = loader.load_module(name)
245            self.assertEqual(module.__name__, name)
246            self.assertIs(module.__loader__, loader)
247            self.assertEqual(module.__package__, name)
248
249
250(Frozen_ModuleForLoaderTests,
251 Source_ModuleForLoaderTests
252 ) = util.test_both(ModuleForLoaderTests, util=importlib_util)
253
254
255class SetPackageTests:
256
257    """Tests for importlib.util.set_package."""
258
259    def verify(self, module, expect):
260        """Verify the module has the expected value for __package__ after
261        passing through set_package."""
262        fxn = lambda: module
263        wrapped = self.util.set_package(fxn)
264        with warnings.catch_warnings():
265            warnings.simplefilter('ignore', DeprecationWarning)
266            wrapped()
267        self.assertTrue(hasattr(module, '__package__'))
268        self.assertEqual(expect, module.__package__)
269
270    def test_top_level(self):
271        # __package__ should be set to the empty string if a top-level module.
272        # Implicitly tests when package is set to None.
273        module = types.ModuleType('module')
274        module.__package__ = None
275        self.verify(module, '')
276
277    def test_package(self):
278        # Test setting __package__ for a package.
279        module = types.ModuleType('pkg')
280        module.__path__ = ['<path>']
281        module.__package__ = None
282        self.verify(module, 'pkg')
283
284    def test_submodule(self):
285        # Test __package__ for a module in a package.
286        module = types.ModuleType('pkg.mod')
287        module.__package__ = None
288        self.verify(module, 'pkg')
289
290    def test_setting_if_missing(self):
291        # __package__ should be set if it is missing.
292        module = types.ModuleType('mod')
293        if hasattr(module, '__package__'):
294            delattr(module, '__package__')
295        self.verify(module, '')
296
297    def test_leaving_alone(self):
298        # If __package__ is set and not None then leave it alone.
299        for value in (True, False):
300            module = types.ModuleType('mod')
301            module.__package__ = value
302            self.verify(module, value)
303
304    def test_decorator_attrs(self):
305        def fxn(module): pass
306        with warnings.catch_warnings():
307            warnings.simplefilter('ignore', DeprecationWarning)
308            wrapped = self.util.set_package(fxn)
309        self.assertEqual(wrapped.__name__, fxn.__name__)
310        self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
311
312
313(Frozen_SetPackageTests,
314 Source_SetPackageTests
315 ) = util.test_both(SetPackageTests, util=importlib_util)
316
317
318class SetLoaderTests:
319
320    """Tests importlib.util.set_loader()."""
321
322    @property
323    def DummyLoader(self):
324        # Set DummyLoader on the class lazily.
325        class DummyLoader:
326            @self.util.set_loader
327            def load_module(self, module):
328                return self.module
329        self.__class__.DummyLoader = DummyLoader
330        return DummyLoader
331
332    def test_no_attribute(self):
333        loader = self.DummyLoader()
334        loader.module = types.ModuleType('blah')
335        try:
336            del loader.module.__loader__
337        except AttributeError:
338            pass
339        with warnings.catch_warnings():
340            warnings.simplefilter('ignore', DeprecationWarning)
341            self.assertEqual(loader, loader.load_module('blah').__loader__)
342
343    def test_attribute_is_None(self):
344        loader = self.DummyLoader()
345        loader.module = types.ModuleType('blah')
346        loader.module.__loader__ = None
347        with warnings.catch_warnings():
348            warnings.simplefilter('ignore', DeprecationWarning)
349            self.assertEqual(loader, loader.load_module('blah').__loader__)
350
351    def test_not_reset(self):
352        loader = self.DummyLoader()
353        loader.module = types.ModuleType('blah')
354        loader.module.__loader__ = 42
355        with warnings.catch_warnings():
356            warnings.simplefilter('ignore', DeprecationWarning)
357            self.assertEqual(42, loader.load_module('blah').__loader__)
358
359
360(Frozen_SetLoaderTests,
361 Source_SetLoaderTests
362 ) = util.test_both(SetLoaderTests, util=importlib_util)
363
364
365class ResolveNameTests:
366
367    """Tests importlib.util.resolve_name()."""
368
369    def test_absolute(self):
370        # bacon
371        self.assertEqual('bacon', self.util.resolve_name('bacon', None))
372
373    def test_absolute_within_package(self):
374        # bacon in spam
375        self.assertEqual('bacon', self.util.resolve_name('bacon', 'spam'))
376
377    def test_no_package(self):
378        # .bacon in ''
379        with self.assertRaises(ImportError):
380            self.util.resolve_name('.bacon', '')
381
382    def test_in_package(self):
383        # .bacon in spam
384        self.assertEqual('spam.eggs.bacon',
385                         self.util.resolve_name('.bacon', 'spam.eggs'))
386
387    def test_other_package(self):
388        # ..bacon in spam.bacon
389        self.assertEqual('spam.bacon',
390                         self.util.resolve_name('..bacon', 'spam.eggs'))
391
392    def test_escape(self):
393        # ..bacon in spam
394        with self.assertRaises(ImportError):
395            self.util.resolve_name('..bacon', 'spam')
396
397
398(Frozen_ResolveNameTests,
399 Source_ResolveNameTests
400 ) = util.test_both(ResolveNameTests, util=importlib_util)
401
402
403class FindSpecTests:
404
405    class FakeMetaFinder:
406        @staticmethod
407        def find_spec(name, path=None, target=None): return name, path, target
408
409    def test_sys_modules(self):
410        name = 'some_mod'
411        with util.uncache(name):
412            module = types.ModuleType(name)
413            loader = 'a loader!'
414            spec = self.machinery.ModuleSpec(name, loader)
415            module.__loader__ = loader
416            module.__spec__ = spec
417            sys.modules[name] = module
418            found = self.util.find_spec(name)
419            self.assertEqual(found, spec)
420
421    def test_sys_modules_without___loader__(self):
422        name = 'some_mod'
423        with util.uncache(name):
424            module = types.ModuleType(name)
425            del module.__loader__
426            loader = 'a loader!'
427            spec = self.machinery.ModuleSpec(name, loader)
428            module.__spec__ = spec
429            sys.modules[name] = module
430            found = self.util.find_spec(name)
431            self.assertEqual(found, spec)
432
433    def test_sys_modules_spec_is_None(self):
434        name = 'some_mod'
435        with util.uncache(name):
436            module = types.ModuleType(name)
437            module.__spec__ = None
438            sys.modules[name] = module
439            with self.assertRaises(ValueError):
440                self.util.find_spec(name)
441
442    def test_sys_modules_loader_is_None(self):
443        name = 'some_mod'
444        with util.uncache(name):
445            module = types.ModuleType(name)
446            spec = self.machinery.ModuleSpec(name, None)
447            module.__spec__ = spec
448            sys.modules[name] = module
449            found = self.util.find_spec(name)
450            self.assertEqual(found, spec)
451
452    def test_sys_modules_spec_is_not_set(self):
453        name = 'some_mod'
454        with util.uncache(name):
455            module = types.ModuleType(name)
456            try:
457                del module.__spec__
458            except AttributeError:
459                pass
460            sys.modules[name] = module
461            with self.assertRaises(ValueError):
462                self.util.find_spec(name)
463
464    def test_success(self):
465        name = 'some_mod'
466        with util.uncache(name):
467            with util.import_state(meta_path=[self.FakeMetaFinder]):
468                self.assertEqual((name, None, None),
469                                 self.util.find_spec(name))
470
471    def test_nothing(self):
472        # None is returned upon failure to find a loader.
473        self.assertIsNone(self.util.find_spec('nevergoingtofindthismodule'))
474
475    def test_find_submodule(self):
476        name = 'spam'
477        subname = 'ham'
478        with util.temp_module(name, pkg=True) as pkg_dir:
479            fullname, _ = util.submodule(name, subname, pkg_dir)
480            spec = self.util.find_spec(fullname)
481            self.assertIsNot(spec, None)
482            self.assertIn(name, sorted(sys.modules))
483            self.assertNotIn(fullname, sorted(sys.modules))
484            # Ensure successive calls behave the same.
485            spec_again = self.util.find_spec(fullname)
486            self.assertEqual(spec_again, spec)
487
488    def test_find_submodule_parent_already_imported(self):
489        name = 'spam'
490        subname = 'ham'
491        with util.temp_module(name, pkg=True) as pkg_dir:
492            self.init.import_module(name)
493            fullname, _ = util.submodule(name, subname, pkg_dir)
494            spec = self.util.find_spec(fullname)
495            self.assertIsNot(spec, None)
496            self.assertIn(name, sorted(sys.modules))
497            self.assertNotIn(fullname, sorted(sys.modules))
498            # Ensure successive calls behave the same.
499            spec_again = self.util.find_spec(fullname)
500            self.assertEqual(spec_again, spec)
501
502    def test_find_relative_module(self):
503        name = 'spam'
504        subname = 'ham'
505        with util.temp_module(name, pkg=True) as pkg_dir:
506            fullname, _ = util.submodule(name, subname, pkg_dir)
507            relname = '.' + subname
508            spec = self.util.find_spec(relname, name)
509            self.assertIsNot(spec, None)
510            self.assertIn(name, sorted(sys.modules))
511            self.assertNotIn(fullname, sorted(sys.modules))
512            # Ensure successive calls behave the same.
513            spec_again = self.util.find_spec(fullname)
514            self.assertEqual(spec_again, spec)
515
516    def test_find_relative_module_missing_package(self):
517        name = 'spam'
518        subname = 'ham'
519        with util.temp_module(name, pkg=True) as pkg_dir:
520            fullname, _ = util.submodule(name, subname, pkg_dir)
521            relname = '.' + subname
522            with self.assertRaises(ImportError):
523                self.util.find_spec(relname)
524            self.assertNotIn(name, sorted(sys.modules))
525            self.assertNotIn(fullname, sorted(sys.modules))
526
527    def test_find_submodule_in_module(self):
528        # ModuleNotFoundError raised when a module is specified as
529        # a parent instead of a package.
530        with self.assertRaises(ModuleNotFoundError):
531            self.util.find_spec('module.name')
532
533
534(Frozen_FindSpecTests,
535 Source_FindSpecTests
536 ) = util.test_both(FindSpecTests, init=init, util=importlib_util,
537                         machinery=machinery)
538
539
540class MagicNumberTests:
541
542    def test_length(self):
543        # Should be 4 bytes.
544        self.assertEqual(len(self.util.MAGIC_NUMBER), 4)
545
546    def test_incorporates_rn(self):
547        # The magic number uses \r\n to come out wrong when splitting on lines.
548        self.assertTrue(self.util.MAGIC_NUMBER.endswith(b'\r\n'))
549
550
551(Frozen_MagicNumberTests,
552 Source_MagicNumberTests
553 ) = util.test_both(MagicNumberTests, util=importlib_util)
554
555
556class PEP3147Tests:
557
558    """Tests of PEP 3147-related functions: cache_from_source and source_from_cache."""
559
560    tag = sys.implementation.cache_tag
561
562    @unittest.skipIf(sys.implementation.cache_tag is None,
563                     'requires sys.implementation.cache_tag not be None')
564    def test_cache_from_source(self):
565        # Given the path to a .py file, return the path to its PEP 3147
566        # defined .pyc file (i.e. under __pycache__).
567        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
568        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
569                              'qux.{}.pyc'.format(self.tag))
570        self.assertEqual(self.util.cache_from_source(path, optimization=''),
571                         expect)
572
573    def test_cache_from_source_no_cache_tag(self):
574        # No cache tag means NotImplementedError.
575        with support.swap_attr(sys.implementation, 'cache_tag', None):
576            with self.assertRaises(NotImplementedError):
577                self.util.cache_from_source('whatever.py')
578
579    def test_cache_from_source_no_dot(self):
580        # Directory with a dot, filename without dot.
581        path = os.path.join('foo.bar', 'file')
582        expect = os.path.join('foo.bar', '__pycache__',
583                              'file{}.pyc'.format(self.tag))
584        self.assertEqual(self.util.cache_from_source(path, optimization=''),
585                         expect)
586
587    def test_cache_from_source_debug_override(self):
588        # Given the path to a .py file, return the path to its PEP 3147/PEP 488
589        # defined .pyc file (i.e. under __pycache__).
590        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
591        with warnings.catch_warnings():
592            warnings.simplefilter('ignore')
593            self.assertEqual(self.util.cache_from_source(path, False),
594                             self.util.cache_from_source(path, optimization=1))
595            self.assertEqual(self.util.cache_from_source(path, True),
596                             self.util.cache_from_source(path, optimization=''))
597        with warnings.catch_warnings():
598            warnings.simplefilter('error')
599            with self.assertRaises(DeprecationWarning):
600                self.util.cache_from_source(path, False)
601            with self.assertRaises(DeprecationWarning):
602                self.util.cache_from_source(path, True)
603
604    def test_cache_from_source_cwd(self):
605        path = 'foo.py'
606        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
607        self.assertEqual(self.util.cache_from_source(path, optimization=''),
608                         expect)
609
610    def test_cache_from_source_override(self):
611        # When debug_override is not None, it can be any true-ish or false-ish
612        # value.
613        path = os.path.join('foo', 'bar', 'baz.py')
614        # However if the bool-ishness can't be determined, the exception
615        # propagates.
616        class Bearish:
617            def __bool__(self): raise RuntimeError
618        with warnings.catch_warnings():
619            warnings.simplefilter('ignore')
620            self.assertEqual(self.util.cache_from_source(path, []),
621                             self.util.cache_from_source(path, optimization=1))
622            self.assertEqual(self.util.cache_from_source(path, [17]),
623                             self.util.cache_from_source(path, optimization=''))
624            with self.assertRaises(RuntimeError):
625                self.util.cache_from_source('/foo/bar/baz.py', Bearish())
626
627
628    def test_cache_from_source_optimization_empty_string(self):
629        # Setting 'optimization' to '' leads to no optimization tag (PEP 488).
630        path = 'foo.py'
631        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
632        self.assertEqual(self.util.cache_from_source(path, optimization=''),
633                         expect)
634
635    def test_cache_from_source_optimization_None(self):
636        # Setting 'optimization' to None uses the interpreter's optimization.
637        # (PEP 488)
638        path = 'foo.py'
639        optimization_level = sys.flags.optimize
640        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
641        if optimization_level == 0:
642            expect = almost_expect + '.pyc'
643        elif optimization_level <= 2:
644            expect = almost_expect + '.opt-{}.pyc'.format(optimization_level)
645        else:
646            msg = '{!r} is a non-standard optimization level'.format(optimization_level)
647            self.skipTest(msg)
648        self.assertEqual(self.util.cache_from_source(path, optimization=None),
649                         expect)
650
651    def test_cache_from_source_optimization_set(self):
652        # The 'optimization' parameter accepts anything that has a string repr
653        # that passes str.alnum().
654        path = 'foo.py'
655        valid_characters = string.ascii_letters + string.digits
656        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
657        got = self.util.cache_from_source(path, optimization=valid_characters)
658        # Test all valid characters are accepted.
659        self.assertEqual(got,
660                         almost_expect + '.opt-{}.pyc'.format(valid_characters))
661        # str() should be called on argument.
662        self.assertEqual(self.util.cache_from_source(path, optimization=42),
663                         almost_expect + '.opt-42.pyc')
664        # Invalid characters raise ValueError.
665        with self.assertRaises(ValueError):
666            self.util.cache_from_source(path, optimization='path/is/bad')
667
668    def test_cache_from_source_debug_override_optimization_both_set(self):
669        # Can only set one of the optimization-related parameters.
670        with warnings.catch_warnings():
671            warnings.simplefilter('ignore')
672            with self.assertRaises(TypeError):
673                self.util.cache_from_source('foo.py', False, optimization='')
674
675    @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
676                     'test meaningful only where os.altsep is defined')
677    def test_sep_altsep_and_sep_cache_from_source(self):
678        # Windows path and PEP 3147 where sep is right of altsep.
679        self.assertEqual(
680            self.util.cache_from_source('\\foo\\bar\\baz/qux.py', optimization=''),
681            '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
682
683    @unittest.skipIf(sys.implementation.cache_tag is None,
684                     'requires sys.implementation.cache_tag not be None')
685    def test_cache_from_source_path_like_arg(self):
686        path = pathlib.PurePath('foo', 'bar', 'baz', 'qux.py')
687        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
688                              'qux.{}.pyc'.format(self.tag))
689        self.assertEqual(self.util.cache_from_source(path, optimization=''),
690                         expect)
691
692    @unittest.skipIf(sys.implementation.cache_tag is None,
693                     'requires sys.implementation.cache_tag to not be None')
694    def test_source_from_cache(self):
695        # Given the path to a PEP 3147 defined .pyc file, return the path to
696        # its source.  This tests the good path.
697        path = os.path.join('foo', 'bar', 'baz', '__pycache__',
698                            'qux.{}.pyc'.format(self.tag))
699        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
700        self.assertEqual(self.util.source_from_cache(path), expect)
701
702    def test_source_from_cache_no_cache_tag(self):
703        # If sys.implementation.cache_tag is None, raise NotImplementedError.
704        path = os.path.join('blah', '__pycache__', 'whatever.pyc')
705        with support.swap_attr(sys.implementation, 'cache_tag', None):
706            with self.assertRaises(NotImplementedError):
707                self.util.source_from_cache(path)
708
709    def test_source_from_cache_bad_path(self):
710        # When the path to a pyc file is not in PEP 3147 format, a ValueError
711        # is raised.
712        self.assertRaises(
713            ValueError, self.util.source_from_cache, '/foo/bar/bazqux.pyc')
714
715    def test_source_from_cache_no_slash(self):
716        # No slashes at all in path -> ValueError
717        self.assertRaises(
718            ValueError, self.util.source_from_cache, 'foo.cpython-32.pyc')
719
720    def test_source_from_cache_too_few_dots(self):
721        # Too few dots in final path component -> ValueError
722        self.assertRaises(
723            ValueError, self.util.source_from_cache, '__pycache__/foo.pyc')
724
725    def test_source_from_cache_too_many_dots(self):
726        with self.assertRaises(ValueError):
727            self.util.source_from_cache(
728                    '__pycache__/foo.cpython-32.opt-1.foo.pyc')
729
730    def test_source_from_cache_not_opt(self):
731        # Non-`opt-` path component -> ValueError
732        self.assertRaises(
733            ValueError, self.util.source_from_cache,
734            '__pycache__/foo.cpython-32.foo.pyc')
735
736    def test_source_from_cache_no__pycache__(self):
737        # Another problem with the path -> ValueError
738        self.assertRaises(
739            ValueError, self.util.source_from_cache,
740            '/foo/bar/foo.cpython-32.foo.pyc')
741
742    def test_source_from_cache_optimized_bytecode(self):
743        # Optimized bytecode is not an issue.
744        path = os.path.join('__pycache__', 'foo.{}.opt-1.pyc'.format(self.tag))
745        self.assertEqual(self.util.source_from_cache(path), 'foo.py')
746
747    def test_source_from_cache_missing_optimization(self):
748        # An empty optimization level is a no-no.
749        path = os.path.join('__pycache__', 'foo.{}.opt-.pyc'.format(self.tag))
750        with self.assertRaises(ValueError):
751            self.util.source_from_cache(path)
752
753    @unittest.skipIf(sys.implementation.cache_tag is None,
754                     'requires sys.implementation.cache_tag to not be None')
755    def test_source_from_cache_path_like_arg(self):
756        path = pathlib.PurePath('foo', 'bar', 'baz', '__pycache__',
757                                'qux.{}.pyc'.format(self.tag))
758        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
759        self.assertEqual(self.util.source_from_cache(path), expect)
760
761    @unittest.skipIf(sys.implementation.cache_tag is None,
762                     'requires sys.implementation.cache_tag to not be None')
763    def test_cache_from_source_respects_pycache_prefix(self):
764        # If pycache_prefix is set, cache_from_source will return a bytecode
765        # path inside that directory (in a subdirectory mirroring the .py file's
766        # path) rather than in a __pycache__ dir next to the py file.
767        pycache_prefixes = [
768            os.path.join(os.path.sep, 'tmp', 'bytecode'),
769            os.path.join(os.path.sep, 'tmp', '\u2603'),  # non-ASCII in path!
770            os.path.join(os.path.sep, 'tmp', 'trailing-slash') + os.path.sep,
771        ]
772        drive = ''
773        if os.name == 'nt':
774            drive = 'C:'
775            pycache_prefixes = [
776                f'{drive}{prefix}' for prefix in pycache_prefixes]
777            pycache_prefixes += [r'\\?\C:\foo', r'\\localhost\c$\bar']
778        for pycache_prefix in pycache_prefixes:
779            with self.subTest(path=pycache_prefix):
780                path = drive + os.path.join(
781                    os.path.sep, 'foo', 'bar', 'baz', 'qux.py')
782                expect = os.path.join(
783                    pycache_prefix, 'foo', 'bar', 'baz',
784                    'qux.{}.pyc'.format(self.tag))
785                with util.temporary_pycache_prefix(pycache_prefix):
786                    self.assertEqual(
787                        self.util.cache_from_source(path, optimization=''),
788                        expect)
789
790    @unittest.skipIf(sys.implementation.cache_tag is None,
791                     'requires sys.implementation.cache_tag to not be None')
792    def test_cache_from_source_respects_pycache_prefix_relative(self):
793        # If the .py path we are given is relative, we will resolve to an
794        # absolute path before prefixing with pycache_prefix, to avoid any
795        # possible ambiguity.
796        pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
797        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
798        root = os.path.splitdrive(os.getcwd())[0] + os.path.sep
799        expect = os.path.join(
800            pycache_prefix,
801            os.path.relpath(os.getcwd(), root),
802            'foo', 'bar', 'baz', f'qux.{self.tag}.pyc')
803        with util.temporary_pycache_prefix(pycache_prefix):
804            self.assertEqual(
805                self.util.cache_from_source(path, optimization=''),
806                expect)
807
808    @unittest.skipIf(sys.implementation.cache_tag is None,
809                     'requires sys.implementation.cache_tag to not be None')
810    def test_source_from_cache_inside_pycache_prefix(self):
811        # If pycache_prefix is set and the cache path we get is inside it,
812        # we return an absolute path to the py file based on the remainder of
813        # the path within pycache_prefix.
814        pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
815        path = os.path.join(pycache_prefix, 'foo', 'bar', 'baz',
816                            f'qux.{self.tag}.pyc')
817        expect = os.path.join(os.path.sep, 'foo', 'bar', 'baz', 'qux.py')
818        with util.temporary_pycache_prefix(pycache_prefix):
819            self.assertEqual(self.util.source_from_cache(path), expect)
820
821    @unittest.skipIf(sys.implementation.cache_tag is None,
822                     'requires sys.implementation.cache_tag to not be None')
823    def test_source_from_cache_outside_pycache_prefix(self):
824        # If pycache_prefix is set but the cache path we get is not inside
825        # it, just ignore it and handle the cache path according to the default
826        # behavior.
827        pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
828        path = os.path.join('foo', 'bar', 'baz', '__pycache__',
829                            f'qux.{self.tag}.pyc')
830        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
831        with util.temporary_pycache_prefix(pycache_prefix):
832            self.assertEqual(self.util.source_from_cache(path), expect)
833
834
835(Frozen_PEP3147Tests,
836 Source_PEP3147Tests
837 ) = util.test_both(PEP3147Tests, util=importlib_util)
838
839
840class MagicNumberTests(unittest.TestCase):
841    """
842    Test release compatibility issues relating to importlib
843    """
844    @unittest.skipUnless(
845        sys.version_info.releaselevel in ('candidate', 'final'),
846        'only applies to candidate or final python release levels'
847    )
848    def test_magic_number(self):
849        # Each python minor release should generally have a MAGIC_NUMBER
850        # that does not change once the release reaches candidate status.
851
852        # Once a release reaches candidate status, the value of the constant
853        # EXPECTED_MAGIC_NUMBER in this test should be changed.
854        # This test will then check that the actual MAGIC_NUMBER matches
855        # the expected value for the release.
856
857        # In exceptional cases, it may be required to change the MAGIC_NUMBER
858        # for a maintenance release. In this case the change should be
859        # discussed in python-dev. If a change is required, community
860        # stakeholders such as OS package maintainers must be notified
861        # in advance. Such exceptional releases will then require an
862        # adjustment to this test case.
863        EXPECTED_MAGIC_NUMBER = 3495
864        actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little')
865
866        msg = (
867            "To avoid breaking backwards compatibility with cached bytecode "
868            "files that can't be automatically regenerated by the current "
869            "user, candidate and final releases require the current  "
870            "importlib.util.MAGIC_NUMBER to match the expected "
871            "magic number in this test. Set the expected "
872            "magic number in this test to the current MAGIC_NUMBER to "
873            "continue with the release.\n\n"
874            "Changing the MAGIC_NUMBER for a maintenance release "
875            "requires discussion in python-dev and notification of "
876            "community stakeholders."
877        )
878        self.assertEqual(EXPECTED_MAGIC_NUMBER, actual, msg)
879
880
881if __name__ == '__main__':
882    unittest.main()
883