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