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