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(ImportError): 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(ImportError): 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(ImportError): 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 # Each python minor release should generally have a MAGIC_NUMBER 849 # that does not change once the release reaches candidate status. 850 851 # Once a release reaches candidate status, the value of the constant 852 # EXPECTED_MAGIC_NUMBER in this test should be changed. 853 # This test will then check that the actual MAGIC_NUMBER matches 854 # the expected value for the release. 855 856 # In exceptional cases, it may be required to change the MAGIC_NUMBER 857 # for a maintenance release. In this case the change should be 858 # discussed in python-dev. If a change is required, community 859 # stakeholders such as OS package maintainers must be notified 860 # in advance. Such exceptional releases will then require an 861 # adjustment to this test case. 862 EXPECTED_MAGIC_NUMBER = 3439 863 actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little') 864 865 msg = ( 866 "To avoid breaking backwards compatibility with cached bytecode " 867 "files that can't be automatically regenerated by the current " 868 "user, candidate and final releases require the current " 869 "importlib.util.MAGIC_NUMBER to match the expected " 870 "magic number in this test. Set the expected " 871 "magic number in this test to the current MAGIC_NUMBER to " 872 "continue with the release.\n\n" 873 "Changing the MAGIC_NUMBER for a maintenance release " 874 "requires discussion in python-dev and notification of " 875 "community stakeholders." 876 ) 877 self.assertEqual(EXPECTED_MAGIC_NUMBER, actual, msg) 878 879 880if __name__ == '__main__': 881 unittest.main() 882