test_util.py 34.7 KB
Newer Older
1
from . import util
2 3 4 5
abc = util.import_importlib('importlib.abc')
init = util.import_importlib('importlib')
machinery = util.import_importlib('importlib.machinery')
importlib_util = util.import_importlib('importlib.util')
6

7
import importlib.util
8
import os
9
import pathlib
10
import string
11
import sys
12
from test import support
13 14
import types
import unittest
15
import unittest.mock
16
import warnings
17 18


19
class DecodeSourceBytesTests:
20 21 22 23 24

    source = "string ='ü'"

    def test_ut8_default(self):
        source_bytes = self.source.encode('utf-8')
25
        self.assertEqual(self.util.decode_source(source_bytes), self.source)
26 27 28 29 30

    def test_specified_encoding(self):
        source = '# coding=latin-1\n' + self.source
        source_bytes = source.encode('latin-1')
        assert source_bytes != source.encode('utf-8')
31
        self.assertEqual(self.util.decode_source(source_bytes), source)
32 33 34 35

    def test_universal_newlines(self):
        source = '\r\n'.join([self.source, self.source])
        source_bytes = source.encode('utf-8')
36
        self.assertEqual(self.util.decode_source(source_bytes),
37 38
                         '\n'.join([self.source, self.source]))

39 40 41

(Frozen_DecodeSourceBytesTests,
 Source_DecodeSourceBytesTests
42 43 44 45 46 47
 ) = util.test_both(DecodeSourceBytesTests, util=importlib_util)


class ModuleFromSpecTests:

    def test_no_create_module(self):
48 49 50
        class Loader:
            def exec_module(self, module):
                pass
51
        spec = self.machinery.ModuleSpec('test', Loader())
52
        with self.assertRaises(ImportError):
53
            module = self.util.module_from_spec(spec)
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

    def test_create_module_returns_None(self):
        class Loader(self.abc.Loader):
            def create_module(self, spec):
                return None
        spec = self.machinery.ModuleSpec('test', Loader())
        module = self.util.module_from_spec(spec)
        self.assertIsInstance(module, types.ModuleType)
        self.assertEqual(module.__name__, spec.name)

    def test_create_module(self):
        name = 'already set'
        class CustomModule(types.ModuleType):
            pass
        class Loader(self.abc.Loader):
            def create_module(self, spec):
                module = CustomModule(spec.name)
                module.__name__ = name
                return module
        spec = self.machinery.ModuleSpec('test', Loader())
        module = self.util.module_from_spec(spec)
        self.assertIsInstance(module, CustomModule)
        self.assertEqual(module.__name__, name)

    def test___name__(self):
        spec = self.machinery.ModuleSpec('test', object())
        module = self.util.module_from_spec(spec)
        self.assertEqual(module.__name__, spec.name)

    def test___spec__(self):
        spec = self.machinery.ModuleSpec('test', object())
        module = self.util.module_from_spec(spec)
        self.assertEqual(module.__spec__, spec)

    def test___loader__(self):
        loader = object()
        spec = self.machinery.ModuleSpec('test', loader)
        module = self.util.module_from_spec(spec)
        self.assertIs(module.__loader__, loader)

    def test___package__(self):
        spec = self.machinery.ModuleSpec('test.pkg', object())
        module = self.util.module_from_spec(spec)
        self.assertEqual(module.__package__, spec.parent)

    def test___path__(self):
        spec = self.machinery.ModuleSpec('test', object(), is_package=True)
        module = self.util.module_from_spec(spec)
        self.assertEqual(module.__path__, spec.submodule_search_locations)

    def test___file__(self):
        spec = self.machinery.ModuleSpec('test', object(), origin='some/path')
        spec.has_location = True
        module = self.util.module_from_spec(spec)
        self.assertEqual(module.__file__, spec.origin)

    def test___cached__(self):
        spec = self.machinery.ModuleSpec('test', object())
        spec.cached = 'some/path'
        spec.has_location = True
        module = self.util.module_from_spec(spec)
        self.assertEqual(module.__cached__, spec.cached)

(Frozen_ModuleFromSpecTests,
 Source_ModuleFromSpecTests
) = util.test_both(ModuleFromSpecTests, abc=abc, machinery=machinery,
                   util=importlib_util)
121

122 123

class ModuleForLoaderTests:
124 125 126

    """Tests for importlib.util.module_for_loader."""

127 128
    @classmethod
    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 134 135

    def test_warning(self):
        # Should raise a PendingDeprecationWarning when used.
        with warnings.catch_warnings():
136 137
            warnings.simplefilter('error', DeprecationWarning)
            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 143 144 145 146
        return fxn(self, name)

    def raise_exception(self, name):
        def to_wrap(self, module):
            raise ImportError
147
        fxn = self.module_for_loader(to_wrap)
148 149 150 151 152 153 154 155 156
        try:
            fxn(self, name)
        except ImportError:
            pass

    def test_new_module(self):
        # Test that when no module exists in sys.modules a new module is
        # created.
        module_name = 'a.b.c'
157
        with util.uncache(module_name):
158
            module = self.return_module(module_name)
159 160
            self.assertIn(module_name, sys.modules)
        self.assertIsInstance(module, types.ModuleType)
161 162 163 164
        self.assertEqual(module.__name__, module_name)

    def test_reload(self):
        # Test that a module is reused if already in sys.modules.
165 166 167
        class FakeLoader:
            def is_package(self, name):
                return True
168
            @self.module_for_loader
169 170
            def load_module(self, module):
                return module
171
        name = 'a.b.c'
172
        module = types.ModuleType('a.b.c')
173 174
        module.__loader__ = 42
        module.__package__ = 42
175
        with util.uncache(name):
176
            sys.modules[name] = module
177 178
            loader = FakeLoader()
            returned_module = loader.load_module(name)
Brett Cannon's avatar
Brett Cannon committed
179
            self.assertIs(returned_module, sys.modules[name])
180 181
            self.assertEqual(module.__loader__, loader)
            self.assertEqual(module.__package__, name)
182 183 184 185 186

    def test_new_module_failure(self):
        # Test that a module is removed from sys.modules if added but an
        # exception is raised.
        name = 'a.b.c'
187
        with util.uncache(name):
188
            self.raise_exception(name)
189
            self.assertNotIn(name, sys.modules)
190 191 192 193

    def test_reload_failure(self):
        # Test that a failure on reload leaves the module in-place.
        name = 'a.b.c'
194
        module = types.ModuleType(name)
195
        with util.uncache(name):
196 197
            sys.modules[name] = module
            self.raise_exception(name)
Brett Cannon's avatar
Brett Cannon committed
198
            self.assertIs(module, sys.modules[name])
199

200 201
    def test_decorator_attrs(self):
        def fxn(self, module): pass
202
        wrapped = self.module_for_loader(fxn)
203 204
        self.assertEqual(wrapped.__name__, fxn.__name__)
        self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
205

206 207 208 209 210 211 212 213
    def test_false_module(self):
        # If for some odd reason a module is considered false, still return it
        # from sys.modules.
        class FalseModule(types.ModuleType):
            def __bool__(self): return False

        name = 'mod'
        module = FalseModule(name)
214
        with util.uncache(name):
215 216 217
            self.assertFalse(module)
            sys.modules[name] = module
            given = self.return_module(name)
218
            self.assertIs(given, module)
219

220 221 222 223 224 225 226 227
    def test_attributes_set(self):
        # __name__, __loader__, and __package__ should be set (when
        # is_package() is defined; undefined implicitly tested elsewhere).
        class FakeLoader:
            def __init__(self, is_package):
                self._pkg = is_package
            def is_package(self, name):
                return self._pkg
228
            @self.module_for_loader
229 230 231 232
            def load_module(self, module):
                return module

        name = 'pkg.mod'
233
        with util.uncache(name):
234 235 236 237 238 239 240
            loader = FakeLoader(False)
            module = loader.load_module(name)
            self.assertEqual(module.__name__, name)
            self.assertIs(module.__loader__, loader)
            self.assertEqual(module.__package__, 'pkg')

        name = 'pkg.sub'
241
        with util.uncache(name):
242 243 244 245 246 247
            loader = FakeLoader(True)
            module = loader.load_module(name)
            self.assertEqual(module.__name__, name)
            self.assertIs(module.__loader__, loader)
            self.assertEqual(module.__package__, name)

248 249 250

(Frozen_ModuleForLoaderTests,
 Source_ModuleForLoaderTests
251
 ) = util.test_both(ModuleForLoaderTests, util=importlib_util)
252

253 254

class SetPackageTests:
255

256
    """Tests for importlib.util.set_package."""
257 258 259

    def verify(self, module, expect):
        """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 264 265
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            wrapped()
266
        self.assertTrue(hasattr(module, '__package__'))
267 268 269 270 271
        self.assertEqual(expect, module.__package__)

    def test_top_level(self):
        # __package__ should be set to the empty string if a top-level module.
        # Implicitly tests when package is set to None.
272
        module = types.ModuleType('module')
273 274 275 276 277
        module.__package__ = None
        self.verify(module, '')

    def test_package(self):
        # Test setting __package__ for a package.
278
        module = types.ModuleType('pkg')
279 280 281 282 283 284
        module.__path__ = ['<path>']
        module.__package__ = None
        self.verify(module, 'pkg')

    def test_submodule(self):
        # Test __package__ for a module in a package.
285
        module = types.ModuleType('pkg.mod')
286 287 288 289 290
        module.__package__ = None
        self.verify(module, 'pkg')

    def test_setting_if_missing(self):
        # __package__ should be set if it is missing.
291
        module = types.ModuleType('mod')
292 293 294 295 296 297 298
        if hasattr(module, '__package__'):
            delattr(module, '__package__')
        self.verify(module, '')

    def test_leaving_alone(self):
        # If __package__ is set and not None then leave it alone.
        for value in (True, False):
299
            module = types.ModuleType('mod')
300 301 302
            module.__package__ = value
            self.verify(module, value)

303 304
    def test_decorator_attrs(self):
        def fxn(module): pass
305 306 307
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            wrapped = self.util.set_package(fxn)
308 309
        self.assertEqual(wrapped.__name__, fxn.__name__)
        self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
310

311 312 313

(Frozen_SetPackageTests,
 Source_SetPackageTests
314
 ) = util.test_both(SetPackageTests, util=importlib_util)
315

316

317
class SetLoaderTests:
318 319 320

    """Tests importlib.util.set_loader()."""

321 322 323 324 325 326 327 328 329
    @property
    def DummyLoader(self):
        # Set DummyLoader on the class lazily.
        class DummyLoader:
            @self.util.set_loader
            def load_module(self, module):
                return self.module
        self.__class__.DummyLoader = DummyLoader
        return DummyLoader
330 331 332

    def test_no_attribute(self):
        loader = self.DummyLoader()
333
        loader.module = types.ModuleType('blah')
334 335 336 337
        try:
            del loader.module.__loader__
        except AttributeError:
            pass
338 339 340
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            self.assertEqual(loader, loader.load_module('blah').__loader__)
341 342 343

    def test_attribute_is_None(self):
        loader = self.DummyLoader()
344
        loader.module = types.ModuleType('blah')
345
        loader.module.__loader__ = None
346 347 348
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            self.assertEqual(loader, loader.load_module('blah').__loader__)
349 350 351

    def test_not_reset(self):
        loader = self.DummyLoader()
352
        loader.module = types.ModuleType('blah')
353
        loader.module.__loader__ = 42
354 355 356
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            self.assertEqual(42, loader.load_module('blah').__loader__)
357 358


359 360
(Frozen_SetLoaderTests,
 Source_SetLoaderTests
361
 ) = util.test_both(SetLoaderTests, util=importlib_util)
362 363 364


class ResolveNameTests:
365 366 367 368 369

    """Tests importlib.util.resolve_name()."""

    def test_absolute(self):
        # 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 377 378

    def test_no_package(self):
        # .bacon in ''
        with self.assertRaises(ValueError):
379
            self.util.resolve_name('.bacon', '')
380 381 382 383

    def test_in_package(self):
        # .bacon in spam
        self.assertEqual('spam.eggs.bacon',
384
                         self.util.resolve_name('.bacon', 'spam.eggs'))
385 386 387 388

    def test_other_package(self):
        # ..bacon in spam.bacon
        self.assertEqual('spam.bacon',
389
                         self.util.resolve_name('..bacon', 'spam.eggs'))
390 391 392 393

    def test_escape(self):
        # ..bacon in spam
        with self.assertRaises(ValueError):
394
            self.util.resolve_name('..bacon', 'spam')
395

396 397 398

(Frozen_ResolveNameTests,
 Source_ResolveNameTests
399
 ) = util.test_both(ResolveNameTests, util=importlib_util)
400

401

402 403 404 405 406 407 408 409
class FindSpecTests:

    class FakeMetaFinder:
        @staticmethod
        def find_spec(name, path=None, target=None): return name, path, target

    def test_sys_modules(self):
        name = 'some_mod'
410
        with util.uncache(name):
411 412 413 414 415 416 417 418 419 420 421
            module = types.ModuleType(name)
            loader = 'a loader!'
            spec = self.machinery.ModuleSpec(name, loader)
            module.__loader__ = loader
            module.__spec__ = spec
            sys.modules[name] = module
            found = self.util.find_spec(name)
            self.assertEqual(found, spec)

    def test_sys_modules_without___loader__(self):
        name = 'some_mod'
422
        with util.uncache(name):
423 424 425 426 427 428 429 430 431 432 433
            module = types.ModuleType(name)
            del module.__loader__
            loader = 'a loader!'
            spec = self.machinery.ModuleSpec(name, loader)
            module.__spec__ = spec
            sys.modules[name] = module
            found = self.util.find_spec(name)
            self.assertEqual(found, spec)

    def test_sys_modules_spec_is_None(self):
        name = 'some_mod'
434
        with util.uncache(name):
435 436 437 438 439 440 441 442
            module = types.ModuleType(name)
            module.__spec__ = None
            sys.modules[name] = module
            with self.assertRaises(ValueError):
                self.util.find_spec(name)

    def test_sys_modules_loader_is_None(self):
        name = 'some_mod'
443
        with util.uncache(name):
444 445 446 447 448 449 450 451 452
            module = types.ModuleType(name)
            spec = self.machinery.ModuleSpec(name, None)
            module.__spec__ = spec
            sys.modules[name] = module
            found = self.util.find_spec(name)
            self.assertEqual(found, spec)

    def test_sys_modules_spec_is_not_set(self):
        name = 'some_mod'
453
        with util.uncache(name):
454 455 456 457 458 459 460 461 462 463 464
            module = types.ModuleType(name)
            try:
                del module.__spec__
            except AttributeError:
                pass
            sys.modules[name] = module
            with self.assertRaises(ValueError):
                self.util.find_spec(name)

    def test_success(self):
        name = 'some_mod'
465 466
        with util.uncache(name):
            with util.import_state(meta_path=[self.FakeMetaFinder]):
467 468 469 470 471 472 473 474 475 476
                self.assertEqual((name, None, None),
                                 self.util.find_spec(name))

    def test_nothing(self):
        # None is returned upon failure to find a loader.
        self.assertIsNone(self.util.find_spec('nevergoingtofindthismodule'))

    def test_find_submodule(self):
        name = 'spam'
        subname = 'ham'
477 478
        with util.temp_module(name, pkg=True) as pkg_dir:
            fullname, _ = util.submodule(name, subname, pkg_dir)
479 480 481 482 483 484 485 486 487 488 489
            spec = self.util.find_spec(fullname)
            self.assertIsNot(spec, None)
            self.assertIn(name, sorted(sys.modules))
            self.assertNotIn(fullname, sorted(sys.modules))
            # Ensure successive calls behave the same.
            spec_again = self.util.find_spec(fullname)
            self.assertEqual(spec_again, spec)

    def test_find_submodule_parent_already_imported(self):
        name = 'spam'
        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 494 495 496 497 498 499 500 501 502 503
            spec = self.util.find_spec(fullname)
            self.assertIsNot(spec, None)
            self.assertIn(name, sorted(sys.modules))
            self.assertNotIn(fullname, sorted(sys.modules))
            # Ensure successive calls behave the same.
            spec_again = self.util.find_spec(fullname)
            self.assertEqual(spec_again, spec)

    def test_find_relative_module(self):
        name = 'spam'
        subname = 'ham'
504 505
        with util.temp_module(name, pkg=True) as pkg_dir:
            fullname, _ = util.submodule(name, subname, pkg_dir)
506 507 508 509 510 511 512 513 514 515 516 517
            relname = '.' + subname
            spec = self.util.find_spec(relname, name)
            self.assertIsNot(spec, None)
            self.assertIn(name, sorted(sys.modules))
            self.assertNotIn(fullname, sorted(sys.modules))
            # Ensure successive calls behave the same.
            spec_again = self.util.find_spec(fullname)
            self.assertEqual(spec_again, spec)

    def test_find_relative_module_missing_package(self):
        name = 'spam'
        subname = 'ham'
518 519
        with util.temp_module(name, pkg=True) as pkg_dir:
            fullname, _ = util.submodule(name, subname, pkg_dir)
520 521 522 523 524 525
            relname = '.' + subname
            with self.assertRaises(ValueError):
                self.util.find_spec(relname)
            self.assertNotIn(name, sorted(sys.modules))
            self.assertNotIn(fullname, sorted(sys.modules))

526 527 528 529 530 531
    def test_find_submodule_in_module(self):
        # ModuleNotFoundError raised when a module is specified as
        # a parent instead of a package.
        with self.assertRaises(ModuleNotFoundError):
            self.util.find_spec('module.name')

532

533 534
(Frozen_FindSpecTests,
 Source_FindSpecTests
535
 ) = util.test_both(FindSpecTests, init=init, util=importlib_util,
536
                         machinery=machinery)
537 538


539
class MagicNumberTests:
540 541 542

    def test_length(self):
        # Should be 4 bytes.
543
        self.assertEqual(len(self.util.MAGIC_NUMBER), 4)
544 545 546

    def test_incorporates_rn(self):
        # The magic number uses \r\n to come out wrong when splitting on lines.
547 548
        self.assertTrue(self.util.MAGIC_NUMBER.endswith(b'\r\n'))

549 550 551

(Frozen_MagicNumberTests,
 Source_MagicNumberTests
552
 ) = util.test_both(MagicNumberTests, util=importlib_util)
553 554


555
class 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 562
    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag not be None')
563 564 565 566 567 568
    def test_cache_from_source(self):
        # Given the path to a .py file, return the path to its PEP 3147
        # defined .pyc file (i.e. under __pycache__).
        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
                              'qux.{}.pyc'.format(self.tag))
569 570
        self.assertEqual(self.util.cache_from_source(path, optimization=''),
                         expect)
571 572 573 574 575

    def test_cache_from_source_no_cache_tag(self):
        # No cache tag means NotImplementedError.
        with support.swap_attr(sys.implementation, 'cache_tag', None):
            with self.assertRaises(NotImplementedError):
576
                self.util.cache_from_source('whatever.py')
577 578 579 580 581 582

    def test_cache_from_source_no_dot(self):
        # Directory with a dot, filename without dot.
        path = os.path.join('foo.bar', 'file')
        expect = os.path.join('foo.bar', '__pycache__',
                              'file{}.pyc'.format(self.tag))
583 584
        self.assertEqual(self.util.cache_from_source(path, optimization=''),
                         expect)
585

586 587 588
    def test_cache_from_source_debug_override(self):
        # Given the path to a .py file, return the path to its PEP 3147/PEP 488
        # defined .pyc file (i.e. under __pycache__).
589
        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
590 591 592 593 594 595 596 597 598 599 600 601
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            self.assertEqual(self.util.cache_from_source(path, False),
                             self.util.cache_from_source(path, optimization=1))
            self.assertEqual(self.util.cache_from_source(path, True),
                             self.util.cache_from_source(path, optimization=''))
        with warnings.catch_warnings():
            warnings.simplefilter('error')
            with self.assertRaises(DeprecationWarning):
                self.util.cache_from_source(path, False)
            with self.assertRaises(DeprecationWarning):
                self.util.cache_from_source(path, True)
602 603 604 605

    def test_cache_from_source_cwd(self):
        path = 'foo.py'
        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
606 607
        self.assertEqual(self.util.cache_from_source(path, optimization=''),
                         expect)
608 609 610 611 612 613 614 615 616

    def test_cache_from_source_override(self):
        # When debug_override is not None, it can be any true-ish or false-ish
        # value.
        path = os.path.join('foo', 'bar', 'baz.py')
        # However if the bool-ishness can't be determined, the exception
        # propagates.
        class Bearish:
            def __bool__(self): raise RuntimeError
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            self.assertEqual(self.util.cache_from_source(path, []),
                             self.util.cache_from_source(path, optimization=1))
            self.assertEqual(self.util.cache_from_source(path, [17]),
                             self.util.cache_from_source(path, optimization=''))
            with self.assertRaises(RuntimeError):
                self.util.cache_from_source('/foo/bar/baz.py', Bearish())


    def test_cache_from_source_optimization_empty_string(self):
        # Setting 'optimization' to '' leads to no optimization tag (PEP 488).
        path = 'foo.py'
        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
        self.assertEqual(self.util.cache_from_source(path, optimization=''),
                         expect)

    def test_cache_from_source_optimization_None(self):
        # Setting 'optimization' to None uses the interpreter's optimization.
        # (PEP 488)
        path = 'foo.py'
        optimization_level = sys.flags.optimize
        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
        if optimization_level == 0:
            expect = almost_expect + '.pyc'
        elif optimization_level <= 2:
            expect = almost_expect + '.opt-{}.pyc'.format(optimization_level)
        else:
            msg = '{!r} is a non-standard optimization level'.format(optimization_level)
            self.skipTest(msg)
        self.assertEqual(self.util.cache_from_source(path, optimization=None),
                         expect)

    def test_cache_from_source_optimization_set(self):
        # The 'optimization' parameter accepts anything that has a string repr
        # that passes str.alnum().
        path = 'foo.py'
        valid_characters = string.ascii_letters + string.digits
        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
        got = self.util.cache_from_source(path, optimization=valid_characters)
        # Test all valid characters are accepted.
        self.assertEqual(got,
                         almost_expect + '.opt-{}.pyc'.format(valid_characters))
        # str() should be called on argument.
        self.assertEqual(self.util.cache_from_source(path, optimization=42),
                         almost_expect + '.opt-42.pyc')
        # Invalid characters raise ValueError.
        with self.assertRaises(ValueError):
            self.util.cache_from_source(path, optimization='path/is/bad')

    def test_cache_from_source_debug_override_optimization_both_set(self):
        # Can only set one of the optimization-related parameters.
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            with self.assertRaises(TypeError):
                self.util.cache_from_source('foo.py', False, optimization='')
673 674 675 676 677 678

    @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
                     'test meaningful only where os.altsep is defined')
    def test_sep_altsep_and_sep_cache_from_source(self):
        # Windows path and PEP 3147 where sep is right of altsep.
        self.assertEqual(
679
            self.util.cache_from_source('\\foo\\bar\\baz/qux.py', optimization=''),
680 681
            '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))

682 683
    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag not be None')
684
    def test_cache_from_source_path_like_arg(self):
685 686 687 688 689 690
        path = pathlib.PurePath('foo', 'bar', 'baz', 'qux.py')
        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
                              'qux.{}.pyc'.format(self.tag))
        self.assertEqual(self.util.cache_from_source(path, optimization=''),
                         expect)

691 692
    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag to not be None')
693 694 695 696 697 698
    def test_source_from_cache(self):
        # Given the path to a PEP 3147 defined .pyc file, return the path to
        # its source.  This tests the good path.
        path = os.path.join('foo', 'bar', 'baz', '__pycache__',
                            'qux.{}.pyc'.format(self.tag))
        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
699
        self.assertEqual(self.util.source_from_cache(path), expect)
700 701 702 703 704 705

    def test_source_from_cache_no_cache_tag(self):
        # If sys.implementation.cache_tag is None, raise NotImplementedError.
        path = os.path.join('blah', '__pycache__', 'whatever.pyc')
        with support.swap_attr(sys.implementation, 'cache_tag', None):
            with self.assertRaises(NotImplementedError):
706
                self.util.source_from_cache(path)
707 708 709 710 711

    def test_source_from_cache_bad_path(self):
        # When the path to a pyc file is not in PEP 3147 format, a ValueError
        # is raised.
        self.assertRaises(
712
            ValueError, self.util.source_from_cache, '/foo/bar/bazqux.pyc')
713 714 715 716

    def test_source_from_cache_no_slash(self):
        # No slashes at all in path -> ValueError
        self.assertRaises(
717
            ValueError, self.util.source_from_cache, 'foo.cpython-32.pyc')
718 719 720 721

    def test_source_from_cache_too_few_dots(self):
        # Too few dots in final path component -> ValueError
        self.assertRaises(
722
            ValueError, self.util.source_from_cache, '__pycache__/foo.pyc')
723 724

    def test_source_from_cache_too_many_dots(self):
725 726 727 728 729 730
        with self.assertRaises(ValueError):
            self.util.source_from_cache(
                    '__pycache__/foo.cpython-32.opt-1.foo.pyc')

    def test_source_from_cache_not_opt(self):
        # Non-`opt-` path component -> ValueError
731
        self.assertRaises(
732
            ValueError, self.util.source_from_cache,
733 734 735 736 737
            '__pycache__/foo.cpython-32.foo.pyc')

    def test_source_from_cache_no__pycache__(self):
        # Another problem with the path -> ValueError
        self.assertRaises(
738
            ValueError, self.util.source_from_cache,
739 740
            '/foo/bar/foo.cpython-32.foo.pyc')

741 742 743 744 745 746 747 748 749 750 751
    def test_source_from_cache_optimized_bytecode(self):
        # Optimized bytecode is not an issue.
        path = os.path.join('__pycache__', 'foo.{}.opt-1.pyc'.format(self.tag))
        self.assertEqual(self.util.source_from_cache(path), 'foo.py')

    def test_source_from_cache_missing_optimization(self):
        # An empty optimization level is a no-no.
        path = os.path.join('__pycache__', 'foo.{}.opt-.pyc'.format(self.tag))
        with self.assertRaises(ValueError):
            self.util.source_from_cache(path)

752 753
    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag to not be None')
754 755 756 757 758 759
    def test_source_from_cache_path_like_arg(self):
        path = pathlib.PurePath('foo', 'bar', 'baz', '__pycache__',
                                'qux.{}.pyc'.format(self.tag))
        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
        self.assertEqual(self.util.source_from_cache(path), expect)

760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag to not be None')
    def test_cache_from_source_respects_pycache_prefix(self):
        # If pycache_prefix is set, cache_from_source will return a bytecode
        # path inside that directory (in a subdirectory mirroring the .py file's
        # path) rather than in a __pycache__ dir next to the py file.
        pycache_prefixes = [
            os.path.join(os.path.sep, 'tmp', 'bytecode'),
            os.path.join(os.path.sep, 'tmp', '\u2603'),  # non-ASCII in path!
            os.path.join(os.path.sep, 'tmp', 'trailing-slash') + os.path.sep,
        ]
        drive = ''
        if os.name == 'nt':
            drive = 'C:'
            pycache_prefixes = [
                f'{drive}{prefix}' for prefix in pycache_prefixes]
            pycache_prefixes += [r'\\?\C:\foo', r'\\localhost\c$\bar']
        for pycache_prefix in pycache_prefixes:
            with self.subTest(path=pycache_prefix):
                path = drive + os.path.join(
                    os.path.sep, 'foo', 'bar', 'baz', 'qux.py')
                expect = os.path.join(
                    pycache_prefix, 'foo', 'bar', 'baz',
                    'qux.{}.pyc'.format(self.tag))
                with util.temporary_pycache_prefix(pycache_prefix):
                    self.assertEqual(
                        self.util.cache_from_source(path, optimization=''),
                        expect)

    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag to not be None')
    def test_cache_from_source_respects_pycache_prefix_relative(self):
        # If the .py path we are given is relative, we will resolve to an
        # absolute path before prefixing with pycache_prefix, to avoid any
        # possible ambiguity.
        pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
        root = os.path.splitdrive(os.getcwd())[0] + os.path.sep
        expect = os.path.join(
            pycache_prefix,
            os.path.relpath(os.getcwd(), root),
            'foo', 'bar', 'baz', f'qux.{self.tag}.pyc')
        with util.temporary_pycache_prefix(pycache_prefix):
            self.assertEqual(
                self.util.cache_from_source(path, optimization=''),
                expect)

    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag to not be None')
    def test_source_from_cache_inside_pycache_prefix(self):
        # If pycache_prefix is set and the cache path we get is inside it,
        # we return an absolute path to the py file based on the remainder of
        # the path within pycache_prefix.
        pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
        path = os.path.join(pycache_prefix, 'foo', 'bar', 'baz',
                            f'qux.{self.tag}.pyc')
        expect = os.path.join(os.path.sep, 'foo', 'bar', 'baz', 'qux.py')
        with util.temporary_pycache_prefix(pycache_prefix):
            self.assertEqual(self.util.source_from_cache(path), expect)

    @unittest.skipIf(sys.implementation.cache_tag is None,
                     'requires sys.implementation.cache_tag to not be None')
    def test_source_from_cache_outside_pycache_prefix(self):
        # If pycache_prefix is set but the cache path we get is not inside
        # it, just ignore it and handle the cache path according to the default
        # behavior.
        pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
        path = os.path.join('foo', 'bar', 'baz', '__pycache__',
                            f'qux.{self.tag}.pyc')
        expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
        with util.temporary_pycache_prefix(pycache_prefix):
            self.assertEqual(self.util.source_from_cache(path), expect)

833 834 835

(Frozen_PEP3147Tests,
 Source_PEP3147Tests
836
 ) = util.test_both(PEP3147Tests, util=importlib_util)
837

838

839 840 841 842 843
class MagicNumberTests(unittest.TestCase):
    """
    Test release compatibility issues relating to importlib
    """
    @unittest.skipUnless(
844
        sys.version_info.releaselevel in ('candidate', 'final'),
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
        'only applies to candidate or final python release levels'
    )
    def test_magic_number(self):
        """
        Each python minor release should generally have a MAGIC_NUMBER
        that does not change once the release reaches candidate status.

        Once a release reaches candidate status, the value of the constant
        EXPECTED_MAGIC_NUMBER in this test should be changed.
        This test will then check that the actual MAGIC_NUMBER matches
        the expected value for the release.

        In exceptional cases, it may be required to change the MAGIC_NUMBER
        for a maintenance release. In this case the change should be
        discussed in python-dev. If a change is required, community
        stakeholders such as OS package maintainers must be notified
        in advance. Such exceptional releases will then require an
        adjustment to this test case.
        """
864
        EXPECTED_MAGIC_NUMBER = 3410
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
        actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little')

        msg = (
            "To avoid breaking backwards compatibility with cached bytecode "
            "files that can't be automatically regenerated by the current "
            "user, candidate and final releases require the current  "
            "importlib.util.MAGIC_NUMBER to match the expected "
            "magic number in this test. Set the expected "
            "magic number in this test to the current MAGIC_NUMBER to "
            "continue with the release.\n\n"
            "Changing the MAGIC_NUMBER for a maintenance release "
            "requires discussion in python-dev and notification of "
            "community stakeholders."
        )
        self.assertEqual(EXPECTED_MAGIC_NUMBER, actual, msg)


882
if __name__ == '__main__':
883
    unittest.main()