1import sys 2import os 3import marshal 4import importlib 5import importlib.util 6import struct 7import time 8import unittest 9import unittest.mock 10import warnings 11 12from test import support 13from test.support import import_helper 14from test.support import os_helper 15 16from zipfile import ZipFile, ZipInfo, ZIP_STORED, ZIP_DEFLATED 17 18import zipimport 19import linecache 20import doctest 21import inspect 22import io 23from traceback import extract_tb, extract_stack, print_tb 24try: 25 import zlib 26except ImportError: 27 zlib = None 28 29test_src = """\ 30def get_name(): 31 return __name__ 32def get_file(): 33 return __file__ 34""" 35test_co = compile(test_src, "<???>", "exec") 36raise_src = 'def do_raise(): raise TypeError\n' 37 38def make_pyc(co, mtime, size): 39 data = marshal.dumps(co) 40 pyc = (importlib.util.MAGIC_NUMBER + 41 struct.pack("<iLL", 0, 42 int(mtime) & 0xFFFF_FFFF, size & 0xFFFF_FFFF) + data) 43 return pyc 44 45def module_path_to_dotted_name(path): 46 return path.replace(os.sep, '.') 47 48NOW = time.time() 49test_pyc = make_pyc(test_co, NOW, len(test_src)) 50 51 52TESTMOD = "ziptestmodule" 53TESTPACK = "ziptestpackage" 54TESTPACK2 = "ziptestpackage2" 55TEMP_DIR = os.path.abspath("junk95142") 56TEMP_ZIP = os.path.abspath("junk95142.zip") 57 58pyc_file = importlib.util.cache_from_source(TESTMOD + '.py') 59pyc_ext = '.pyc' 60 61 62class ImportHooksBaseTestCase(unittest.TestCase): 63 64 def setUp(self): 65 self.path = sys.path[:] 66 self.meta_path = sys.meta_path[:] 67 self.path_hooks = sys.path_hooks[:] 68 sys.path_importer_cache.clear() 69 self.modules_before = import_helper.modules_setup() 70 71 def tearDown(self): 72 sys.path[:] = self.path 73 sys.meta_path[:] = self.meta_path 74 sys.path_hooks[:] = self.path_hooks 75 sys.path_importer_cache.clear() 76 import_helper.modules_cleanup(*self.modules_before) 77 78 79class UncompressedZipImportTestCase(ImportHooksBaseTestCase): 80 81 compression = ZIP_STORED 82 83 def setUp(self): 84 # We're reusing the zip archive path, so we must clear the 85 # cached directory info and linecache. 86 linecache.clearcache() 87 zipimport._zip_directory_cache.clear() 88 ImportHooksBaseTestCase.setUp(self) 89 90 def makeTree(self, files, dirName=TEMP_DIR): 91 # Create a filesystem based set of modules/packages 92 # defined by files under the directory dirName. 93 self.addCleanup(os_helper.rmtree, dirName) 94 95 for name, (mtime, data) in files.items(): 96 path = os.path.join(dirName, name) 97 if path[-1] == os.sep: 98 if not os.path.isdir(path): 99 os.makedirs(path) 100 else: 101 dname = os.path.dirname(path) 102 if not os.path.isdir(dname): 103 os.makedirs(dname) 104 with open(path, 'wb') as fp: 105 fp.write(data) 106 107 def makeZip(self, files, zipName=TEMP_ZIP, **kw): 108 # Create a zip archive based set of modules/packages 109 # defined by files in the zip file zipName. If the 110 # key 'stuff' exists in kw it is prepended to the archive. 111 self.addCleanup(os_helper.unlink, zipName) 112 113 with ZipFile(zipName, "w") as z: 114 for name, (mtime, data) in files.items(): 115 zinfo = ZipInfo(name, time.localtime(mtime)) 116 zinfo.compress_type = self.compression 117 z.writestr(zinfo, data) 118 comment = kw.get("comment", None) 119 if comment is not None: 120 z.comment = comment 121 122 stuff = kw.get("stuff", None) 123 if stuff is not None: 124 # Prepend 'stuff' to the start of the zipfile 125 with open(zipName, "rb") as f: 126 data = f.read() 127 with open(zipName, "wb") as f: 128 f.write(stuff) 129 f.write(data) 130 131 def doTest(self, expected_ext, files, *modules, **kw): 132 self.makeZip(files, **kw) 133 134 sys.path.insert(0, TEMP_ZIP) 135 136 mod = importlib.import_module(".".join(modules)) 137 138 call = kw.get('call') 139 if call is not None: 140 call(mod) 141 142 if expected_ext: 143 file = mod.get_file() 144 self.assertEqual(file, os.path.join(TEMP_ZIP, 145 *modules) + expected_ext) 146 147 def testAFakeZlib(self): 148 # 149 # This could cause a stack overflow before: importing zlib.py 150 # from a compressed archive would cause zlib to be imported 151 # which would find zlib.py in the archive, which would... etc. 152 # 153 # This test *must* be executed first: it must be the first one 154 # to trigger zipimport to import zlib (zipimport caches the 155 # zlib.decompress function object, after which the problem being 156 # tested here wouldn't be a problem anymore... 157 # (Hence the 'A' in the test method name: to make it the first 158 # item in a list sorted by name, like 159 # unittest.TestLoader.getTestCaseNames() does.) 160 # 161 # This test fails on platforms on which the zlib module is 162 # statically linked, but the problem it tests for can't 163 # occur in that case (builtin modules are always found first), 164 # so we'll simply skip it then. Bug #765456. 165 # 166 if "zlib" in sys.builtin_module_names: 167 self.skipTest('zlib is a builtin module') 168 if "zlib" in sys.modules: 169 del sys.modules["zlib"] 170 files = {"zlib.py": (NOW, test_src)} 171 try: 172 self.doTest(".py", files, "zlib") 173 except ImportError: 174 if self.compression != ZIP_DEFLATED: 175 self.fail("expected test to not raise ImportError") 176 else: 177 if self.compression != ZIP_STORED: 178 self.fail("expected test to raise ImportError") 179 180 def testPy(self): 181 files = {TESTMOD + ".py": (NOW, test_src)} 182 self.doTest(".py", files, TESTMOD) 183 184 def testPyc(self): 185 files = {TESTMOD + pyc_ext: (NOW, test_pyc)} 186 self.doTest(pyc_ext, files, TESTMOD) 187 188 def testBoth(self): 189 files = {TESTMOD + ".py": (NOW, test_src), 190 TESTMOD + pyc_ext: (NOW, test_pyc)} 191 self.doTest(pyc_ext, files, TESTMOD) 192 193 def testUncheckedHashBasedPyc(self): 194 source = b"state = 'old'" 195 source_hash = importlib.util.source_hash(source) 196 bytecode = importlib._bootstrap_external._code_to_hash_pyc( 197 compile(source, "???", "exec"), 198 source_hash, 199 False, # unchecked 200 ) 201 files = {TESTMOD + ".py": (NOW, "state = 'new'"), 202 TESTMOD + ".pyc": (NOW - 20, bytecode)} 203 def check(mod): 204 self.assertEqual(mod.state, 'old') 205 self.doTest(None, files, TESTMOD, call=check) 206 207 @unittest.mock.patch('_imp.check_hash_based_pycs', 'always') 208 def test_checked_hash_based_change_pyc(self): 209 source = b"state = 'old'" 210 source_hash = importlib.util.source_hash(source) 211 bytecode = importlib._bootstrap_external._code_to_hash_pyc( 212 compile(source, "???", "exec"), 213 source_hash, 214 False, 215 ) 216 files = {TESTMOD + ".py": (NOW, "state = 'new'"), 217 TESTMOD + ".pyc": (NOW - 20, bytecode)} 218 def check(mod): 219 self.assertEqual(mod.state, 'new') 220 self.doTest(None, files, TESTMOD, call=check) 221 222 def testEmptyPy(self): 223 files = {TESTMOD + ".py": (NOW, "")} 224 self.doTest(None, files, TESTMOD) 225 226 def testBadMagic(self): 227 # make pyc magic word invalid, forcing loading from .py 228 badmagic_pyc = bytearray(test_pyc) 229 badmagic_pyc[0] ^= 0x04 # flip an arbitrary bit 230 files = {TESTMOD + ".py": (NOW, test_src), 231 TESTMOD + pyc_ext: (NOW, badmagic_pyc)} 232 self.doTest(".py", files, TESTMOD) 233 234 def testBadMagic2(self): 235 # make pyc magic word invalid, causing an ImportError 236 badmagic_pyc = bytearray(test_pyc) 237 badmagic_pyc[0] ^= 0x04 # flip an arbitrary bit 238 files = {TESTMOD + pyc_ext: (NOW, badmagic_pyc)} 239 try: 240 self.doTest(".py", files, TESTMOD) 241 self.fail("This should not be reached") 242 except zipimport.ZipImportError as exc: 243 self.assertIsInstance(exc.__cause__, ImportError) 244 self.assertIn("magic number", exc.__cause__.msg) 245 246 def testBadMTime(self): 247 badtime_pyc = bytearray(test_pyc) 248 # flip the second bit -- not the first as that one isn't stored in the 249 # .py's mtime in the zip archive. 250 badtime_pyc[11] ^= 0x02 251 files = {TESTMOD + ".py": (NOW, test_src), 252 TESTMOD + pyc_ext: (NOW, badtime_pyc)} 253 self.doTest(".py", files, TESTMOD) 254 255 def test2038MTime(self): 256 # Make sure we can handle mtimes larger than what a 32-bit signed number 257 # can hold. 258 twenty_thirty_eight_pyc = make_pyc(test_co, 2**32 - 1, len(test_src)) 259 files = {TESTMOD + ".py": (NOW, test_src), 260 TESTMOD + pyc_ext: (NOW, twenty_thirty_eight_pyc)} 261 self.doTest(".py", files, TESTMOD) 262 263 def testPackage(self): 264 packdir = TESTPACK + os.sep 265 files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), 266 packdir + TESTMOD + pyc_ext: (NOW, test_pyc)} 267 self.doTest(pyc_ext, files, TESTPACK, TESTMOD) 268 269 def testSubPackage(self): 270 # Test that subpackages function when loaded from zip 271 # archives. 272 packdir = TESTPACK + os.sep 273 packdir2 = packdir + TESTPACK2 + os.sep 274 files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), 275 packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), 276 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 277 self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD) 278 279 def testSubNamespacePackage(self): 280 # Test that implicit namespace subpackages function 281 # when loaded from zip archives. 282 packdir = TESTPACK + os.sep 283 packdir2 = packdir + TESTPACK2 + os.sep 284 # The first two files are just directory entries (so have no data). 285 files = {packdir: (NOW, ""), 286 packdir2: (NOW, ""), 287 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 288 self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD) 289 290 def testMixedNamespacePackage(self): 291 # Test implicit namespace packages spread between a 292 # real filesystem and a zip archive. 293 packdir = TESTPACK + os.sep 294 packdir2 = packdir + TESTPACK2 + os.sep 295 packdir3 = packdir2 + TESTPACK + '3' + os.sep 296 files1 = {packdir: (NOW, ""), 297 packdir + TESTMOD + pyc_ext: (NOW, test_pyc), 298 packdir2: (NOW, ""), 299 packdir3: (NOW, ""), 300 packdir3 + TESTMOD + pyc_ext: (NOW, test_pyc), 301 packdir2 + TESTMOD + '3' + pyc_ext: (NOW, test_pyc), 302 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 303 files2 = {packdir: (NOW, ""), 304 packdir + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), 305 packdir2: (NOW, ""), 306 packdir2 + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), 307 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 308 309 zip1 = os.path.abspath("path1.zip") 310 self.makeZip(files1, zip1) 311 312 zip2 = TEMP_DIR 313 self.makeTree(files2, zip2) 314 315 # zip2 should override zip1. 316 sys.path.insert(0, zip1) 317 sys.path.insert(0, zip2) 318 319 mod = importlib.import_module(TESTPACK) 320 321 # if TESTPACK is functioning as a namespace pkg then 322 # there should be two entries in the __path__. 323 # First should be path2 and second path1. 324 self.assertEqual(2, len(mod.__path__)) 325 p1, p2 = mod.__path__ 326 self.assertEqual(os.path.basename(TEMP_DIR), p1.split(os.sep)[-2]) 327 self.assertEqual("path1.zip", p2.split(os.sep)[-2]) 328 329 # packdir3 should import as a namespace package. 330 # Its __path__ is an iterable of 1 element from zip1. 331 mod = importlib.import_module(packdir3.replace(os.sep, '.')[:-1]) 332 self.assertEqual(1, len(mod.__path__)) 333 mpath = list(mod.__path__)[0].split('path1.zip' + os.sep)[1] 334 self.assertEqual(packdir3[:-1], mpath) 335 336 # TESTPACK/TESTMOD only exists in path1. 337 mod = importlib.import_module('.'.join((TESTPACK, TESTMOD))) 338 self.assertEqual("path1.zip", mod.__file__.split(os.sep)[-3]) 339 340 # And TESTPACK/(TESTMOD + '2') only exists in path2. 341 mod = importlib.import_module('.'.join((TESTPACK, TESTMOD + '2'))) 342 self.assertEqual(os.path.basename(TEMP_DIR), 343 mod.__file__.split(os.sep)[-3]) 344 345 # One level deeper... 346 subpkg = '.'.join((TESTPACK, TESTPACK2)) 347 mod = importlib.import_module(subpkg) 348 self.assertEqual(2, len(mod.__path__)) 349 p1, p2 = mod.__path__ 350 self.assertEqual(os.path.basename(TEMP_DIR), p1.split(os.sep)[-3]) 351 self.assertEqual("path1.zip", p2.split(os.sep)[-3]) 352 353 # subpkg.TESTMOD exists in both zips should load from zip2. 354 mod = importlib.import_module('.'.join((subpkg, TESTMOD))) 355 self.assertEqual(os.path.basename(TEMP_DIR), 356 mod.__file__.split(os.sep)[-4]) 357 358 # subpkg.TESTMOD + '2' only exists in zip2. 359 mod = importlib.import_module('.'.join((subpkg, TESTMOD + '2'))) 360 self.assertEqual(os.path.basename(TEMP_DIR), 361 mod.__file__.split(os.sep)[-4]) 362 363 # Finally subpkg.TESTMOD + '3' only exists in zip1. 364 mod = importlib.import_module('.'.join((subpkg, TESTMOD + '3'))) 365 self.assertEqual('path1.zip', mod.__file__.split(os.sep)[-4]) 366 367 def testNamespacePackage(self): 368 # Test implicit namespace packages spread between multiple zip 369 # archives. 370 packdir = TESTPACK + os.sep 371 packdir2 = packdir + TESTPACK2 + os.sep 372 packdir3 = packdir2 + TESTPACK + '3' + os.sep 373 files1 = {packdir: (NOW, ""), 374 packdir + TESTMOD + pyc_ext: (NOW, test_pyc), 375 packdir2: (NOW, ""), 376 packdir3: (NOW, ""), 377 packdir3 + TESTMOD + pyc_ext: (NOW, test_pyc), 378 packdir2 + TESTMOD + '3' + pyc_ext: (NOW, test_pyc), 379 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 380 zip1 = os.path.abspath("path1.zip") 381 self.makeZip(files1, zip1) 382 383 files2 = {packdir: (NOW, ""), 384 packdir + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), 385 packdir2: (NOW, ""), 386 packdir2 + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), 387 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 388 zip2 = os.path.abspath("path2.zip") 389 self.makeZip(files2, zip2) 390 391 # zip2 should override zip1. 392 sys.path.insert(0, zip1) 393 sys.path.insert(0, zip2) 394 395 mod = importlib.import_module(TESTPACK) 396 397 # if TESTPACK is functioning as a namespace pkg then 398 # there should be two entries in the __path__. 399 # First should be path2 and second path1. 400 self.assertEqual(2, len(mod.__path__)) 401 p1, p2 = mod.__path__ 402 self.assertEqual("path2.zip", p1.split(os.sep)[-2]) 403 self.assertEqual("path1.zip", p2.split(os.sep)[-2]) 404 405 # packdir3 should import as a namespace package. 406 # Tts __path__ is an iterable of 1 element from zip1. 407 mod = importlib.import_module(packdir3.replace(os.sep, '.')[:-1]) 408 self.assertEqual(1, len(mod.__path__)) 409 mpath = list(mod.__path__)[0].split('path1.zip' + os.sep)[1] 410 self.assertEqual(packdir3[:-1], mpath) 411 412 # TESTPACK/TESTMOD only exists in path1. 413 mod = importlib.import_module('.'.join((TESTPACK, TESTMOD))) 414 self.assertEqual("path1.zip", mod.__file__.split(os.sep)[-3]) 415 416 # And TESTPACK/(TESTMOD + '2') only exists in path2. 417 mod = importlib.import_module('.'.join((TESTPACK, TESTMOD + '2'))) 418 self.assertEqual("path2.zip", mod.__file__.split(os.sep)[-3]) 419 420 # One level deeper... 421 subpkg = '.'.join((TESTPACK, TESTPACK2)) 422 mod = importlib.import_module(subpkg) 423 self.assertEqual(2, len(mod.__path__)) 424 p1, p2 = mod.__path__ 425 self.assertEqual("path2.zip", p1.split(os.sep)[-3]) 426 self.assertEqual("path1.zip", p2.split(os.sep)[-3]) 427 428 # subpkg.TESTMOD exists in both zips should load from zip2. 429 mod = importlib.import_module('.'.join((subpkg, TESTMOD))) 430 self.assertEqual('path2.zip', mod.__file__.split(os.sep)[-4]) 431 432 # subpkg.TESTMOD + '2' only exists in zip2. 433 mod = importlib.import_module('.'.join((subpkg, TESTMOD + '2'))) 434 self.assertEqual('path2.zip', mod.__file__.split(os.sep)[-4]) 435 436 # Finally subpkg.TESTMOD + '3' only exists in zip1. 437 mod = importlib.import_module('.'.join((subpkg, TESTMOD + '3'))) 438 self.assertEqual('path1.zip', mod.__file__.split(os.sep)[-4]) 439 440 def testZipImporterMethods(self): 441 packdir = TESTPACK + os.sep 442 packdir2 = packdir + TESTPACK2 + os.sep 443 files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), 444 packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), 445 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), 446 "spam" + pyc_ext: (NOW, test_pyc)} 447 448 self.addCleanup(os_helper.unlink, TEMP_ZIP) 449 with ZipFile(TEMP_ZIP, "w") as z: 450 for name, (mtime, data) in files.items(): 451 zinfo = ZipInfo(name, time.localtime(mtime)) 452 zinfo.compress_type = self.compression 453 zinfo.comment = b"spam" 454 z.writestr(zinfo, data) 455 456 zi = zipimport.zipimporter(TEMP_ZIP) 457 self.assertEqual(zi.archive, TEMP_ZIP) 458 self.assertTrue(zi.is_package(TESTPACK)) 459 460 # PEP 302 461 with warnings.catch_warnings(): 462 warnings.simplefilter("ignore", DeprecationWarning) 463 find_mod = zi.find_module('spam') 464 self.assertIsNotNone(find_mod) 465 self.assertIsInstance(find_mod, zipimport.zipimporter) 466 self.assertFalse(find_mod.is_package('spam')) 467 load_mod = find_mod.load_module('spam') 468 self.assertEqual(find_mod.get_filename('spam'), load_mod.__file__) 469 470 mod = zi.load_module(TESTPACK) 471 self.assertEqual(zi.get_filename(TESTPACK), mod.__file__) 472 473 # PEP 451 474 spec = zi.find_spec('spam') 475 self.assertIsNotNone(spec) 476 self.assertIsInstance(spec.loader, zipimport.zipimporter) 477 self.assertFalse(spec.loader.is_package('spam')) 478 exec_mod = importlib.util.module_from_spec(spec) 479 spec.loader.exec_module(exec_mod) 480 self.assertEqual(spec.loader.get_filename('spam'), exec_mod.__file__) 481 482 spec = zi.find_spec(TESTPACK) 483 mod = importlib.util.module_from_spec(spec) 484 spec.loader.exec_module(mod) 485 self.assertEqual(zi.get_filename(TESTPACK), mod.__file__) 486 487 existing_pack_path = importlib.import_module(TESTPACK).__path__[0] 488 expected_path_path = os.path.join(TEMP_ZIP, TESTPACK) 489 self.assertEqual(existing_pack_path, expected_path_path) 490 491 self.assertFalse(zi.is_package(packdir + '__init__')) 492 self.assertTrue(zi.is_package(packdir + TESTPACK2)) 493 self.assertFalse(zi.is_package(packdir2 + TESTMOD)) 494 495 mod_path = packdir2 + TESTMOD 496 mod_name = module_path_to_dotted_name(mod_path) 497 mod = importlib.import_module(mod_name) 498 self.assertTrue(mod_name in sys.modules) 499 self.assertIsNone(zi.get_source(TESTPACK)) 500 self.assertIsNone(zi.get_source(mod_path)) 501 self.assertEqual(zi.get_filename(mod_path), mod.__file__) 502 # To pass in the module name instead of the path, we must use the 503 # right importer 504 loader = mod.__spec__.loader 505 self.assertIsNone(loader.get_source(mod_name)) 506 self.assertEqual(loader.get_filename(mod_name), mod.__file__) 507 508 # test prefix and archivepath members 509 zi2 = zipimport.zipimporter(TEMP_ZIP + os.sep + TESTPACK) 510 self.assertEqual(zi2.archive, TEMP_ZIP) 511 self.assertEqual(zi2.prefix, TESTPACK + os.sep) 512 513 def testInvalidateCaches(self): 514 packdir = TESTPACK + os.sep 515 packdir2 = packdir + TESTPACK2 + os.sep 516 files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), 517 packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), 518 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), 519 "spam" + pyc_ext: (NOW, test_pyc)} 520 self.addCleanup(os_helper.unlink, TEMP_ZIP) 521 with ZipFile(TEMP_ZIP, "w") as z: 522 for name, (mtime, data) in files.items(): 523 zinfo = ZipInfo(name, time.localtime(mtime)) 524 zinfo.compress_type = self.compression 525 zinfo.comment = b"spam" 526 z.writestr(zinfo, data) 527 528 zi = zipimport.zipimporter(TEMP_ZIP) 529 self.assertEqual(zi._files.keys(), files.keys()) 530 # Check that the file information remains accurate after reloading 531 zi.invalidate_caches() 532 self.assertEqual(zi._files.keys(), files.keys()) 533 # Add a new file to the ZIP archive 534 newfile = {"spam2" + pyc_ext: (NOW, test_pyc)} 535 files.update(newfile) 536 with ZipFile(TEMP_ZIP, "a") as z: 537 for name, (mtime, data) in newfile.items(): 538 zinfo = ZipInfo(name, time.localtime(mtime)) 539 zinfo.compress_type = self.compression 540 zinfo.comment = b"spam" 541 z.writestr(zinfo, data) 542 # Check that we can detect the new file after invalidating the cache 543 zi.invalidate_caches() 544 self.assertEqual(zi._files.keys(), files.keys()) 545 spec = zi.find_spec('spam2') 546 self.assertIsNotNone(spec) 547 self.assertIsInstance(spec.loader, zipimport.zipimporter) 548 # Check that the cached data is removed if the file is deleted 549 os.remove(TEMP_ZIP) 550 zi.invalidate_caches() 551 self.assertFalse(zi._files) 552 self.assertIsNone(zipimport._zip_directory_cache.get(zi.archive)) 553 self.assertIsNone(zi.find_spec("name_does_not_matter")) 554 555 def testZipImporterMethodsInSubDirectory(self): 556 packdir = TESTPACK + os.sep 557 packdir2 = packdir + TESTPACK2 + os.sep 558 files = {packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), 559 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} 560 561 self.addCleanup(os_helper.unlink, TEMP_ZIP) 562 with ZipFile(TEMP_ZIP, "w") as z: 563 for name, (mtime, data) in files.items(): 564 zinfo = ZipInfo(name, time.localtime(mtime)) 565 zinfo.compress_type = self.compression 566 zinfo.comment = b"eggs" 567 z.writestr(zinfo, data) 568 569 zi = zipimport.zipimporter(TEMP_ZIP + os.sep + packdir) 570 self.assertEqual(zi.archive, TEMP_ZIP) 571 self.assertEqual(zi.prefix, packdir) 572 self.assertTrue(zi.is_package(TESTPACK2)) 573 # PEP 302 574 with warnings.catch_warnings(): 575 warnings.simplefilter("ignore", DeprecationWarning) 576 mod = zi.load_module(TESTPACK2) 577 self.assertEqual(zi.get_filename(TESTPACK2), mod.__file__) 578 # PEP 451 579 spec = zi.find_spec(TESTPACK2) 580 mod = importlib.util.module_from_spec(spec) 581 spec.loader.exec_module(mod) 582 self.assertEqual(spec.loader.get_filename(TESTPACK2), mod.__file__) 583 584 self.assertFalse(zi.is_package(TESTPACK2 + os.sep + '__init__')) 585 self.assertFalse(zi.is_package(TESTPACK2 + os.sep + TESTMOD)) 586 587 pkg_path = TEMP_ZIP + os.sep + packdir + TESTPACK2 588 zi2 = zipimport.zipimporter(pkg_path) 589 # PEP 302 590 with warnings.catch_warnings(): 591 warnings.simplefilter("ignore", DeprecationWarning) 592 find_mod_dotted = zi2.find_module(TESTMOD) 593 self.assertIsNotNone(find_mod_dotted) 594 self.assertIsInstance(find_mod_dotted, zipimport.zipimporter) 595 self.assertFalse(zi2.is_package(TESTMOD)) 596 load_mod = find_mod_dotted.load_module(TESTMOD) 597 self.assertEqual( 598 find_mod_dotted.get_filename(TESTMOD), load_mod.__file__) 599 600 # PEP 451 601 spec = zi2.find_spec(TESTMOD) 602 self.assertIsNotNone(spec) 603 self.assertIsInstance(spec.loader, zipimport.zipimporter) 604 self.assertFalse(spec.loader.is_package(TESTMOD)) 605 load_mod = importlib.util.module_from_spec(spec) 606 spec.loader.exec_module(load_mod) 607 self.assertEqual( 608 spec.loader.get_filename(TESTMOD), load_mod.__file__) 609 610 mod_path = TESTPACK2 + os.sep + TESTMOD 611 mod_name = module_path_to_dotted_name(mod_path) 612 mod = importlib.import_module(mod_name) 613 self.assertTrue(mod_name in sys.modules) 614 self.assertIsNone(zi.get_source(TESTPACK2)) 615 self.assertIsNone(zi.get_source(mod_path)) 616 self.assertEqual(zi.get_filename(mod_path), mod.__file__) 617 # To pass in the module name instead of the path, we must use the 618 # right importer. 619 loader = mod.__loader__ 620 self.assertIsNone(loader.get_source(mod_name)) 621 self.assertEqual(loader.get_filename(mod_name), mod.__file__) 622 623 def testGetData(self): 624 self.addCleanup(os_helper.unlink, TEMP_ZIP) 625 with ZipFile(TEMP_ZIP, "w") as z: 626 z.compression = self.compression 627 name = "testdata.dat" 628 data = bytes(x for x in range(256)) 629 z.writestr(name, data) 630 631 zi = zipimport.zipimporter(TEMP_ZIP) 632 self.assertEqual(data, zi.get_data(name)) 633 self.assertIn('zipimporter object', repr(zi)) 634 635 def testImporterAttr(self): 636 src = """if 1: # indent hack 637 def get_file(): 638 return __file__ 639 if __loader__.get_data("some.data") != b"some data": 640 raise AssertionError("bad data")\n""" 641 pyc = make_pyc(compile(src, "<???>", "exec"), NOW, len(src)) 642 files = {TESTMOD + pyc_ext: (NOW, pyc), 643 "some.data": (NOW, "some data")} 644 self.doTest(pyc_ext, files, TESTMOD) 645 646 def testDefaultOptimizationLevel(self): 647 # zipimport should use the default optimization level (#28131) 648 src = """if 1: # indent hack 649 def test(val): 650 assert(val) 651 return val\n""" 652 files = {TESTMOD + '.py': (NOW, src)} 653 self.makeZip(files) 654 sys.path.insert(0, TEMP_ZIP) 655 mod = importlib.import_module(TESTMOD) 656 self.assertEqual(mod.test(1), 1) 657 if __debug__: 658 self.assertRaises(AssertionError, mod.test, False) 659 else: 660 self.assertEqual(mod.test(0), 0) 661 662 def testImport_WithStuff(self): 663 # try importing from a zipfile which contains additional 664 # stuff at the beginning of the file 665 files = {TESTMOD + ".py": (NOW, test_src)} 666 self.doTest(".py", files, TESTMOD, 667 stuff=b"Some Stuff"*31) 668 669 def assertModuleSource(self, module): 670 self.assertEqual(inspect.getsource(module), test_src) 671 672 def testGetSource(self): 673 files = {TESTMOD + ".py": (NOW, test_src)} 674 self.doTest(".py", files, TESTMOD, call=self.assertModuleSource) 675 676 def testGetCompiledSource(self): 677 pyc = make_pyc(compile(test_src, "<???>", "exec"), NOW, len(test_src)) 678 files = {TESTMOD + ".py": (NOW, test_src), 679 TESTMOD + pyc_ext: (NOW, pyc)} 680 self.doTest(pyc_ext, files, TESTMOD, call=self.assertModuleSource) 681 682 def runDoctest(self, callback): 683 files = {TESTMOD + ".py": (NOW, test_src), 684 "xyz.txt": (NOW, ">>> log.append(True)\n")} 685 self.doTest(".py", files, TESTMOD, call=callback) 686 687 def doDoctestFile(self, module): 688 log = [] 689 old_master, doctest.master = doctest.master, None 690 try: 691 doctest.testfile( 692 'xyz.txt', package=module, module_relative=True, 693 globs=locals() 694 ) 695 finally: 696 doctest.master = old_master 697 self.assertEqual(log,[True]) 698 699 def testDoctestFile(self): 700 self.runDoctest(self.doDoctestFile) 701 702 def doDoctestSuite(self, module): 703 log = [] 704 doctest.DocFileTest( 705 'xyz.txt', package=module, module_relative=True, 706 globs=locals() 707 ).run() 708 self.assertEqual(log,[True]) 709 710 def testDoctestSuite(self): 711 self.runDoctest(self.doDoctestSuite) 712 713 def doTraceback(self, module): 714 try: 715 module.do_raise() 716 except Exception as e: 717 tb = e.__traceback__.tb_next 718 719 f,lno,n,line = extract_tb(tb, 1)[0] 720 self.assertEqual(line, raise_src.strip()) 721 722 f,lno,n,line = extract_stack(tb.tb_frame, 1)[0] 723 self.assertEqual(line, raise_src.strip()) 724 725 s = io.StringIO() 726 print_tb(tb, 1, s) 727 self.assertTrue(s.getvalue().endswith( 728 ' def do_raise(): raise TypeError\n' 729 '' if support.has_no_debug_ranges() else 730 ' ^^^^^^^^^^^^^^^\n' 731 )) 732 else: 733 raise AssertionError("This ought to be impossible") 734 735 def testTraceback(self): 736 files = {TESTMOD + ".py": (NOW, raise_src)} 737 self.doTest(None, files, TESTMOD, call=self.doTraceback) 738 739 @unittest.skipIf(os_helper.TESTFN_UNENCODABLE is None, 740 "need an unencodable filename") 741 def testUnencodable(self): 742 filename = os_helper.TESTFN_UNENCODABLE + ".zip" 743 self.addCleanup(os_helper.unlink, filename) 744 with ZipFile(filename, "w") as z: 745 zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) 746 zinfo.compress_type = self.compression 747 z.writestr(zinfo, test_src) 748 spec = zipimport.zipimporter(filename).find_spec(TESTMOD) 749 mod = importlib.util.module_from_spec(spec) 750 spec.loader.exec_module(mod) 751 752 def testBytesPath(self): 753 filename = os_helper.TESTFN + ".zip" 754 self.addCleanup(os_helper.unlink, filename) 755 with ZipFile(filename, "w") as z: 756 zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) 757 zinfo.compress_type = self.compression 758 z.writestr(zinfo, test_src) 759 760 zipimport.zipimporter(filename) 761 with self.assertRaises(TypeError): 762 zipimport.zipimporter(os.fsencode(filename)) 763 with self.assertRaises(TypeError): 764 zipimport.zipimporter(bytearray(os.fsencode(filename))) 765 with self.assertRaises(TypeError): 766 zipimport.zipimporter(memoryview(os.fsencode(filename))) 767 768 def testComment(self): 769 files = {TESTMOD + ".py": (NOW, test_src)} 770 self.doTest(".py", files, TESTMOD, comment=b"comment") 771 772 def testBeginningCruftAndComment(self): 773 files = {TESTMOD + ".py": (NOW, test_src)} 774 self.doTest(".py", files, TESTMOD, stuff=b"cruft" * 64, comment=b"hi") 775 776 def testLargestPossibleComment(self): 777 files = {TESTMOD + ".py": (NOW, test_src)} 778 self.doTest(".py", files, TESTMOD, comment=b"c" * ((1 << 16) - 1)) 779 780 781@support.requires_zlib() 782class CompressedZipImportTestCase(UncompressedZipImportTestCase): 783 compression = ZIP_DEFLATED 784 785 786class BadFileZipImportTestCase(unittest.TestCase): 787 def assertZipFailure(self, filename): 788 self.assertRaises(zipimport.ZipImportError, 789 zipimport.zipimporter, filename) 790 791 def testNoFile(self): 792 self.assertZipFailure('AdfjdkFJKDFJjdklfjs') 793 794 def testEmptyFilename(self): 795 self.assertZipFailure('') 796 797 def testBadArgs(self): 798 self.assertRaises(TypeError, zipimport.zipimporter, None) 799 self.assertRaises(TypeError, zipimport.zipimporter, TESTMOD, kwd=None) 800 self.assertRaises(TypeError, zipimport.zipimporter, 801 list(os.fsencode(TESTMOD))) 802 803 def testFilenameTooLong(self): 804 self.assertZipFailure('A' * 33000) 805 806 def testEmptyFile(self): 807 os_helper.unlink(TESTMOD) 808 os_helper.create_empty_file(TESTMOD) 809 self.assertZipFailure(TESTMOD) 810 811 @unittest.skipIf(support.is_wasi, "mode 000 not supported.") 812 def testFileUnreadable(self): 813 os_helper.unlink(TESTMOD) 814 fd = os.open(TESTMOD, os.O_CREAT, 000) 815 try: 816 os.close(fd) 817 818 with self.assertRaises(zipimport.ZipImportError) as cm: 819 zipimport.zipimporter(TESTMOD) 820 finally: 821 # If we leave "the read-only bit" set on Windows, nothing can 822 # delete TESTMOD, and later tests suffer bogus failures. 823 os.chmod(TESTMOD, 0o666) 824 os_helper.unlink(TESTMOD) 825 826 def testNotZipFile(self): 827 os_helper.unlink(TESTMOD) 828 fp = open(TESTMOD, 'w+') 829 fp.write('a' * 22) 830 fp.close() 831 self.assertZipFailure(TESTMOD) 832 833 # XXX: disabled until this works on Big-endian machines 834 def _testBogusZipFile(self): 835 os_helper.unlink(TESTMOD) 836 fp = open(TESTMOD, 'w+') 837 fp.write(struct.pack('=I', 0x06054B50)) 838 fp.write('a' * 18) 839 fp.close() 840 z = zipimport.zipimporter(TESTMOD) 841 842 try: 843 with warnings.catch_warnings(): 844 warnings.simplefilter("ignore", DeprecationWarning) 845 self.assertRaises(TypeError, z.load_module, None) 846 self.assertRaises(TypeError, z.find_module, None) 847 self.assertRaises(TypeError, z.find_spec, None) 848 self.assertRaises(TypeError, z.exec_module, None) 849 self.assertRaises(TypeError, z.is_package, None) 850 self.assertRaises(TypeError, z.get_code, None) 851 self.assertRaises(TypeError, z.get_data, None) 852 self.assertRaises(TypeError, z.get_source, None) 853 854 error = zipimport.ZipImportError 855 self.assertIsNone(z.find_module('abc')) 856 self.assertIsNone(z.find_spec('abc')) 857 858 with warnings.catch_warnings(): 859 warnings.simplefilter("ignore", DeprecationWarning) 860 self.assertRaises(error, z.load_module, 'abc') 861 self.assertRaises(error, z.get_code, 'abc') 862 self.assertRaises(OSError, z.get_data, 'abc') 863 self.assertRaises(error, z.get_source, 'abc') 864 self.assertRaises(error, z.is_package, 'abc') 865 finally: 866 zipimport._zip_directory_cache.clear() 867 868 869def tearDownModule(): 870 os_helper.unlink(TESTMOD) 871 872 873if __name__ == "__main__": 874 unittest.main() 875