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