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