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 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()
782 class ESC[4;38;5;81mCompressedZipImportTestCase(ESC[4;38;5;149mUncompressedZipImportTestCase):
783 compression = ZIP_DEFLATED
784
785
786 class ESC[4;38;5;81mBadFileZipImportTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
869 def tearDownModule():
870 os_helper.unlink(TESTMOD)
871
872
873 if __name__ == "__main__":
874 unittest.main()