1 import sys
2 import os
3 import marshal
4 import importlib
5 import importlib.util
6 import struct
7 import time
8 import unittest
9 import unittest.mock
10 import warnings
11
12 from test import support
13 from test.support import import_helper
14 from test.support import os_helper
15
16 from zipfile import ZipFile, ZipInfo, ZIP_STORED, ZIP_DEFLATED
17
18 import zipimport
19 import linecache
20 import doctest
21 import inspect
22 import io
23 from traceback import extract_tb, extract_stack, print_tb
24 try:
25 import zlib
26 except ImportError:
27 zlib = None
28
29 test_src = """\
30 def get_name():
31 return __name__
32 def get_file():
33 return __file__
34 """
35 test_co = compile(test_src, "<???>", "exec")
36 raise_src = 'def do_raise(): raise TypeError\n'
37
38 def 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
45 def module_path_to_dotted_name(path):
46 return path.replace(os.sep, '.')
47
48 NOW = time.time()
49 test_pyc = make_pyc(test_co, NOW, len(test_src))
50
51
52 TESTMOD = "ziptestmodule"
53 TESTPACK = "ziptestpackage"
54 TESTPACK2 = "ziptestpackage2"
55 TEMP_DIR = os.path.abspath("junk95142")
56 TEMP_ZIP = os.path.abspath("junk95142.zip")
57
58 pyc_file = importlib.util.cache_from_source(TESTMOD + '.py')
59 pyc_ext = '.pyc'
60
61
62 class ESC[4;38;5;81mImportHooksBaseTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
79 class ESC[4;38;5;81mUncompressedZipImportTestCase(ESC[4;38;5;149mImportHooksBaseTestCase):
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
464 mod = zi.load_module(TESTPACK)
465 self.assertEqual(zi.get_filename(TESTPACK), mod.__file__)
466
467 # PEP 451
468 spec = zi.find_spec('spam')
469 self.assertIsNotNone(spec)
470 self.assertIsInstance(spec.loader, zipimport.zipimporter)
471 self.assertFalse(spec.loader.is_package('spam'))
472 exec_mod = importlib.util.module_from_spec(spec)
473 spec.loader.exec_module(exec_mod)
474 self.assertEqual(spec.loader.get_filename('spam'), exec_mod.__file__)
475
476 spec = zi.find_spec(TESTPACK)
477 mod = importlib.util.module_from_spec(spec)
478 spec.loader.exec_module(mod)
479 self.assertEqual(zi.get_filename(TESTPACK), mod.__file__)
480
481 existing_pack_path = importlib.import_module(TESTPACK).__path__[0]
482 expected_path_path = os.path.join(TEMP_ZIP, TESTPACK)
483 self.assertEqual(existing_pack_path, expected_path_path)
484
485 self.assertFalse(zi.is_package(packdir + '__init__'))
486 self.assertTrue(zi.is_package(packdir + TESTPACK2))
487 self.assertFalse(zi.is_package(packdir2 + TESTMOD))
488
489 mod_path = packdir2 + TESTMOD
490 mod_name = module_path_to_dotted_name(mod_path)
491 mod = importlib.import_module(mod_name)
492 self.assertTrue(mod_name in sys.modules)
493 self.assertIsNone(zi.get_source(TESTPACK))
494 self.assertIsNone(zi.get_source(mod_path))
495 self.assertEqual(zi.get_filename(mod_path), mod.__file__)
496 # To pass in the module name instead of the path, we must use the
497 # right importer
498 loader = mod.__spec__.loader
499 self.assertIsNone(loader.get_source(mod_name))
500 self.assertEqual(loader.get_filename(mod_name), mod.__file__)
501
502 # test prefix and archivepath members
503 zi2 = zipimport.zipimporter(TEMP_ZIP + os.sep + TESTPACK)
504 self.assertEqual(zi2.archive, TEMP_ZIP)
505 self.assertEqual(zi2.prefix, TESTPACK + os.sep)
506
507 def testInvalidateCaches(self):
508 packdir = TESTPACK + os.sep
509 packdir2 = packdir + TESTPACK2 + os.sep
510 files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc),
511 packdir2 + "__init__" + pyc_ext: (NOW, test_pyc),
512 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc),
513 "spam" + pyc_ext: (NOW, test_pyc)}
514 self.addCleanup(os_helper.unlink, TEMP_ZIP)
515 with ZipFile(TEMP_ZIP, "w") as z:
516 for name, (mtime, data) in files.items():
517 zinfo = ZipInfo(name, time.localtime(mtime))
518 zinfo.compress_type = self.compression
519 zinfo.comment = b"spam"
520 z.writestr(zinfo, data)
521
522 zi = zipimport.zipimporter(TEMP_ZIP)
523 self.assertEqual(zi._files.keys(), files.keys())
524 # Check that the file information remains accurate after reloading
525 zi.invalidate_caches()
526 self.assertEqual(zi._files.keys(), files.keys())
527 # Add a new file to the ZIP archive
528 newfile = {"spam2" + pyc_ext: (NOW, test_pyc)}
529 files.update(newfile)
530 with ZipFile(TEMP_ZIP, "a") as z:
531 for name, (mtime, data) in newfile.items():
532 zinfo = ZipInfo(name, time.localtime(mtime))
533 zinfo.compress_type = self.compression
534 zinfo.comment = b"spam"
535 z.writestr(zinfo, data)
536 # Check that we can detect the new file after invalidating the cache
537 zi.invalidate_caches()
538 self.assertEqual(zi._files.keys(), files.keys())
539 spec = zi.find_spec('spam2')
540 self.assertIsNotNone(spec)
541 self.assertIsInstance(spec.loader, zipimport.zipimporter)
542 # Check that the cached data is removed if the file is deleted
543 os.remove(TEMP_ZIP)
544 zi.invalidate_caches()
545 self.assertFalse(zi._files)
546 self.assertIsNone(zipimport._zip_directory_cache.get(zi.archive))
547 self.assertIsNone(zi.find_spec("name_does_not_matter"))
548
549 def testZipImporterMethodsInSubDirectory(self):
550 packdir = TESTPACK + os.sep
551 packdir2 = packdir + TESTPACK2 + os.sep
552 files = {packdir2 + "__init__" + pyc_ext: (NOW, test_pyc),
553 packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}
554
555 self.addCleanup(os_helper.unlink, TEMP_ZIP)
556 with ZipFile(TEMP_ZIP, "w") as z:
557 for name, (mtime, data) in files.items():
558 zinfo = ZipInfo(name, time.localtime(mtime))
559 zinfo.compress_type = self.compression
560 zinfo.comment = b"eggs"
561 z.writestr(zinfo, data)
562
563 zi = zipimport.zipimporter(TEMP_ZIP + os.sep + packdir)
564 self.assertEqual(zi.archive, TEMP_ZIP)
565 self.assertEqual(zi.prefix, packdir)
566 self.assertTrue(zi.is_package(TESTPACK2))
567 # PEP 302
568 with warnings.catch_warnings():
569 warnings.simplefilter("ignore", DeprecationWarning)
570 mod = zi.load_module(TESTPACK2)
571 self.assertEqual(zi.get_filename(TESTPACK2), mod.__file__)
572 # PEP 451
573 spec = zi.find_spec(TESTPACK2)
574 mod = importlib.util.module_from_spec(spec)
575 spec.loader.exec_module(mod)
576 self.assertEqual(spec.loader.get_filename(TESTPACK2), mod.__file__)
577
578 self.assertFalse(zi.is_package(TESTPACK2 + os.sep + '__init__'))
579 self.assertFalse(zi.is_package(TESTPACK2 + os.sep + TESTMOD))
580
581 pkg_path = TEMP_ZIP + os.sep + packdir + TESTPACK2
582 zi2 = zipimport.zipimporter(pkg_path)
583
584 # PEP 451
585 spec = zi2.find_spec(TESTMOD)
586 self.assertIsNotNone(spec)
587 self.assertIsInstance(spec.loader, zipimport.zipimporter)
588 self.assertFalse(spec.loader.is_package(TESTMOD))
589 load_mod = importlib.util.module_from_spec(spec)
590 spec.loader.exec_module(load_mod)
591 self.assertEqual(
592 spec.loader.get_filename(TESTMOD), load_mod.__file__)
593
594 mod_path = TESTPACK2 + os.sep + TESTMOD
595 mod_name = module_path_to_dotted_name(mod_path)
596 mod = importlib.import_module(mod_name)
597 self.assertTrue(mod_name in sys.modules)
598 self.assertIsNone(zi.get_source(TESTPACK2))
599 self.assertIsNone(zi.get_source(mod_path))
600 self.assertEqual(zi.get_filename(mod_path), mod.__file__)
601 # To pass in the module name instead of the path, we must use the
602 # right importer.
603 loader = mod.__loader__
604 self.assertIsNone(loader.get_source(mod_name))
605 self.assertEqual(loader.get_filename(mod_name), mod.__file__)
606
607 def testGetData(self):
608 self.addCleanup(os_helper.unlink, TEMP_ZIP)
609 with ZipFile(TEMP_ZIP, "w") as z:
610 z.compression = self.compression
611 name = "testdata.dat"
612 data = bytes(x for x in range(256))
613 z.writestr(name, data)
614
615 zi = zipimport.zipimporter(TEMP_ZIP)
616 self.assertEqual(data, zi.get_data(name))
617 self.assertIn('zipimporter object', repr(zi))
618
619 def testImporterAttr(self):
620 src = """if 1: # indent hack
621 def get_file():
622 return __file__
623 if __loader__.get_data("some.data") != b"some data":
624 raise AssertionError("bad data")\n"""
625 pyc = make_pyc(compile(src, "<???>", "exec"), NOW, len(src))
626 files = {TESTMOD + pyc_ext: (NOW, pyc),
627 "some.data": (NOW, "some data")}
628 self.doTest(pyc_ext, files, TESTMOD)
629
630 def testDefaultOptimizationLevel(self):
631 # zipimport should use the default optimization level (#28131)
632 src = """if 1: # indent hack
633 def test(val):
634 assert(val)
635 return val\n"""
636 files = {TESTMOD + '.py': (NOW, src)}
637 self.makeZip(files)
638 sys.path.insert(0, TEMP_ZIP)
639 mod = importlib.import_module(TESTMOD)
640 self.assertEqual(mod.test(1), 1)
641 if __debug__:
642 self.assertRaises(AssertionError, mod.test, False)
643 else:
644 self.assertEqual(mod.test(0), 0)
645
646 def testImport_WithStuff(self):
647 # try importing from a zipfile which contains additional
648 # stuff at the beginning of the file
649 files = {TESTMOD + ".py": (NOW, test_src)}
650 self.doTest(".py", files, TESTMOD,
651 stuff=b"Some Stuff"*31)
652
653 def assertModuleSource(self, module):
654 self.assertEqual(inspect.getsource(module), test_src)
655
656 def testGetSource(self):
657 files = {TESTMOD + ".py": (NOW, test_src)}
658 self.doTest(".py", files, TESTMOD, call=self.assertModuleSource)
659
660 def testGetCompiledSource(self):
661 pyc = make_pyc(compile(test_src, "<???>", "exec"), NOW, len(test_src))
662 files = {TESTMOD + ".py": (NOW, test_src),
663 TESTMOD + pyc_ext: (NOW, pyc)}
664 self.doTest(pyc_ext, files, TESTMOD, call=self.assertModuleSource)
665
666 def runDoctest(self, callback):
667 files = {TESTMOD + ".py": (NOW, test_src),
668 "xyz.txt": (NOW, ">>> log.append(True)\n")}
669 self.doTest(".py", files, TESTMOD, call=callback)
670
671 def doDoctestFile(self, module):
672 log = []
673 old_master, doctest.master = doctest.master, None
674 try:
675 doctest.testfile(
676 'xyz.txt', package=module, module_relative=True,
677 globs=locals()
678 )
679 finally:
680 doctest.master = old_master
681 self.assertEqual(log,[True])
682
683 def testDoctestFile(self):
684 self.runDoctest(self.doDoctestFile)
685
686 def doDoctestSuite(self, module):
687 log = []
688 doctest.DocFileTest(
689 'xyz.txt', package=module, module_relative=True,
690 globs=locals()
691 ).run()
692 self.assertEqual(log,[True])
693
694 def testDoctestSuite(self):
695 self.runDoctest(self.doDoctestSuite)
696
697 def doTraceback(self, module):
698 try:
699 module.do_raise()
700 except Exception as e:
701 tb = e.__traceback__.tb_next
702
703 f,lno,n,line = extract_tb(tb, 1)[0]
704 self.assertEqual(line, raise_src.strip())
705
706 f,lno,n,line = extract_stack(tb.tb_frame, 1)[0]
707 self.assertEqual(line, raise_src.strip())
708
709 s = io.StringIO()
710 print_tb(tb, 1, s)
711 self.assertTrue(s.getvalue().endswith(
712 ' def do_raise(): raise TypeError\n'
713 '' if support.has_no_debug_ranges() else
714 ' ^^^^^^^^^^^^^^^\n'
715 ))
716 else:
717 raise AssertionError("This ought to be impossible")
718
719 def testTraceback(self):
720 files = {TESTMOD + ".py": (NOW, raise_src)}
721 self.doTest(None, files, TESTMOD, call=self.doTraceback)
722
723 @unittest.skipIf(os_helper.TESTFN_UNENCODABLE is None,
724 "need an unencodable filename")
725 def testUnencodable(self):
726 filename = os_helper.TESTFN_UNENCODABLE + ".zip"
727 self.addCleanup(os_helper.unlink, filename)
728 with ZipFile(filename, "w") as z:
729 zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW))
730 zinfo.compress_type = self.compression
731 z.writestr(zinfo, test_src)
732 spec = zipimport.zipimporter(filename).find_spec(TESTMOD)
733 mod = importlib.util.module_from_spec(spec)
734 spec.loader.exec_module(mod)
735
736 def testBytesPath(self):
737 filename = os_helper.TESTFN + ".zip"
738 self.addCleanup(os_helper.unlink, filename)
739 with ZipFile(filename, "w") as z:
740 zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW))
741 zinfo.compress_type = self.compression
742 z.writestr(zinfo, test_src)
743
744 zipimport.zipimporter(filename)
745 with self.assertRaises(TypeError):
746 zipimport.zipimporter(os.fsencode(filename))
747 with self.assertRaises(TypeError):
748 zipimport.zipimporter(bytearray(os.fsencode(filename)))
749 with self.assertRaises(TypeError):
750 zipimport.zipimporter(memoryview(os.fsencode(filename)))
751
752 def testComment(self):
753 files = {TESTMOD + ".py": (NOW, test_src)}
754 self.doTest(".py", files, TESTMOD, comment=b"comment")
755
756 def testBeginningCruftAndComment(self):
757 files = {TESTMOD + ".py": (NOW, test_src)}
758 self.doTest(".py", files, TESTMOD, stuff=b"cruft" * 64, comment=b"hi")
759
760 def testLargestPossibleComment(self):
761 files = {TESTMOD + ".py": (NOW, test_src)}
762 self.doTest(".py", files, TESTMOD, comment=b"c" * ((1 << 16) - 1))
763
764
765 @support.requires_zlib()
766 class ESC[4;38;5;81mCompressedZipImportTestCase(ESC[4;38;5;149mUncompressedZipImportTestCase):
767 compression = ZIP_DEFLATED
768
769
770 class ESC[4;38;5;81mBadFileZipImportTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
771 def assertZipFailure(self, filename):
772 self.assertRaises(zipimport.ZipImportError,
773 zipimport.zipimporter, filename)
774
775 def testNoFile(self):
776 self.assertZipFailure('AdfjdkFJKDFJjdklfjs')
777
778 def testEmptyFilename(self):
779 self.assertZipFailure('')
780
781 def testBadArgs(self):
782 self.assertRaises(TypeError, zipimport.zipimporter, None)
783 self.assertRaises(TypeError, zipimport.zipimporter, TESTMOD, kwd=None)
784 self.assertRaises(TypeError, zipimport.zipimporter,
785 list(os.fsencode(TESTMOD)))
786
787 def testFilenameTooLong(self):
788 self.assertZipFailure('A' * 33000)
789
790 def testEmptyFile(self):
791 os_helper.unlink(TESTMOD)
792 os_helper.create_empty_file(TESTMOD)
793 self.assertZipFailure(TESTMOD)
794
795 @unittest.skipIf(support.is_wasi, "mode 000 not supported.")
796 def testFileUnreadable(self):
797 os_helper.unlink(TESTMOD)
798 fd = os.open(TESTMOD, os.O_CREAT, 000)
799 try:
800 os.close(fd)
801
802 with self.assertRaises(zipimport.ZipImportError) as cm:
803 zipimport.zipimporter(TESTMOD)
804 finally:
805 # If we leave "the read-only bit" set on Windows, nothing can
806 # delete TESTMOD, and later tests suffer bogus failures.
807 os.chmod(TESTMOD, 0o666)
808 os_helper.unlink(TESTMOD)
809
810 def testNotZipFile(self):
811 os_helper.unlink(TESTMOD)
812 fp = open(TESTMOD, 'w+')
813 fp.write('a' * 22)
814 fp.close()
815 self.assertZipFailure(TESTMOD)
816
817 # XXX: disabled until this works on Big-endian machines
818 def _testBogusZipFile(self):
819 os_helper.unlink(TESTMOD)
820 fp = open(TESTMOD, 'w+')
821 fp.write(struct.pack('=I', 0x06054B50))
822 fp.write('a' * 18)
823 fp.close()
824 z = zipimport.zipimporter(TESTMOD)
825
826 try:
827 with warnings.catch_warnings():
828 warnings.simplefilter("ignore", DeprecationWarning)
829 self.assertRaises(TypeError, z.load_module, None)
830 self.assertRaises(TypeError, z.find_module, None)
831 self.assertRaises(TypeError, z.find_spec, None)
832 self.assertRaises(TypeError, z.exec_module, None)
833 self.assertRaises(TypeError, z.is_package, None)
834 self.assertRaises(TypeError, z.get_code, None)
835 self.assertRaises(TypeError, z.get_data, None)
836 self.assertRaises(TypeError, z.get_source, None)
837
838 error = zipimport.ZipImportError
839 self.assertIsNone(z.find_spec('abc'))
840
841 with warnings.catch_warnings():
842 warnings.simplefilter("ignore", DeprecationWarning)
843 self.assertRaises(error, z.load_module, 'abc')
844 self.assertRaises(error, z.get_code, 'abc')
845 self.assertRaises(OSError, z.get_data, 'abc')
846 self.assertRaises(error, z.get_source, 'abc')
847 self.assertRaises(error, z.is_package, 'abc')
848 finally:
849 zipimport._zip_directory_cache.clear()
850
851
852 def tearDownModule():
853 os_helper.unlink(TESTMOD)
854
855
856 if __name__ == "__main__":
857 unittest.main()