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