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