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