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(ValueError):
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(ValueError):
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(ValueError):
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        """
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        """
864        EXPECTED_MAGIC_NUMBER = 3413
865        actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little')
866
867        msg = (
868            "To avoid breaking backwards compatibility with cached bytecode "
869            "files that can't be automatically regenerated by the current "
870            "user, candidate and final releases require the current  "
871            "importlib.util.MAGIC_NUMBER to match the expected "
872            "magic number in this test. Set the expected "
873            "magic number in this test to the current MAGIC_NUMBER to "
874            "continue with the release.\n\n"
875            "Changing the MAGIC_NUMBER for a maintenance release "
876            "requires discussion in python-dev and notification of "
877            "community stakeholders."
878        )
879        self.assertEqual(EXPECTED_MAGIC_NUMBER, actual, msg)
880
881
882if __name__ == '__main__':
883    unittest.main()
884