1from test.test_importlib import abc, util
2
3importlib = util.import_importlib('importlib')
4importlib_abc = util.import_importlib('importlib.abc')
5machinery = util.import_importlib('importlib.machinery')
6importlib_util = util.import_importlib('importlib.util')
7
8import errno
9import marshal
10import os
11import py_compile
12import shutil
13import stat
14import sys
15import types
16import unittest
17import warnings
18
19from test.support.import_helper import make_legacy_pyc, unload
20
21from test.test_py_compile import without_source_date_epoch
22from test.test_py_compile import SourceDateEpochTestMeta
23
24
25class SimpleTest(abc.LoaderTests):
26
27    """Should have no issue importing a source module [basic]. And if there is
28    a syntax error, it should raise a SyntaxError [syntax error].
29
30    """
31
32    def setUp(self):
33        self.name = 'spam'
34        self.filepath = os.path.join('ham', self.name + '.py')
35        self.loader = self.machinery.SourceFileLoader(self.name, self.filepath)
36
37    def test_load_module_API(self):
38        class Tester(self.abc.FileLoader):
39            def get_source(self, _): return 'attr = 42'
40            def is_package(self, _): return False
41
42        loader = Tester('blah', 'blah.py')
43        self.addCleanup(unload, 'blah')
44        with warnings.catch_warnings():
45            warnings.simplefilter('ignore', DeprecationWarning)
46            module = loader.load_module()  # Should not raise an exception.
47
48    def test_get_filename_API(self):
49        # If fullname is not set then assume self.path is desired.
50        class Tester(self.abc.FileLoader):
51            def get_code(self, _): pass
52            def get_source(self, _): pass
53            def is_package(self, _): pass
54            def module_repr(self, _): pass
55
56        path = 'some_path'
57        name = 'some_name'
58        loader = Tester(name, path)
59        self.assertEqual(path, loader.get_filename(name))
60        self.assertEqual(path, loader.get_filename())
61        self.assertEqual(path, loader.get_filename(None))
62        with self.assertRaises(ImportError):
63            loader.get_filename(name + 'XXX')
64
65    def test_equality(self):
66        other = self.machinery.SourceFileLoader(self.name, self.filepath)
67        self.assertEqual(self.loader, other)
68
69    def test_inequality(self):
70        other = self.machinery.SourceFileLoader('_' + self.name, self.filepath)
71        self.assertNotEqual(self.loader, other)
72
73    # [basic]
74    def test_module(self):
75        with util.create_modules('_temp') as mapping:
76            loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
77            with warnings.catch_warnings():
78                warnings.simplefilter('ignore', DeprecationWarning)
79                module = loader.load_module('_temp')
80            self.assertIn('_temp', sys.modules)
81            check = {'__name__': '_temp', '__file__': mapping['_temp'],
82                     '__package__': ''}
83            for attr, value in check.items():
84                self.assertEqual(getattr(module, attr), value)
85
86    def test_package(self):
87        with util.create_modules('_pkg.__init__') as mapping:
88            loader = self.machinery.SourceFileLoader('_pkg',
89                                                 mapping['_pkg.__init__'])
90            with warnings.catch_warnings():
91                warnings.simplefilter('ignore', DeprecationWarning)
92                module = loader.load_module('_pkg')
93            self.assertIn('_pkg', sys.modules)
94            check = {'__name__': '_pkg', '__file__': mapping['_pkg.__init__'],
95                     '__path__': [os.path.dirname(mapping['_pkg.__init__'])],
96                     '__package__': '_pkg'}
97            for attr, value in check.items():
98                self.assertEqual(getattr(module, attr), value)
99
100
101    def test_lacking_parent(self):
102        with util.create_modules('_pkg.__init__', '_pkg.mod')as mapping:
103            loader = self.machinery.SourceFileLoader('_pkg.mod',
104                                                    mapping['_pkg.mod'])
105            with warnings.catch_warnings():
106                warnings.simplefilter('ignore', DeprecationWarning)
107                module = loader.load_module('_pkg.mod')
108            self.assertIn('_pkg.mod', sys.modules)
109            check = {'__name__': '_pkg.mod', '__file__': mapping['_pkg.mod'],
110                     '__package__': '_pkg'}
111            for attr, value in check.items():
112                self.assertEqual(getattr(module, attr), value)
113
114    def fake_mtime(self, fxn):
115        """Fake mtime to always be higher than expected."""
116        return lambda name: fxn(name) + 1
117
118    def test_module_reuse(self):
119        with util.create_modules('_temp') as mapping:
120            loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
121            with warnings.catch_warnings():
122                warnings.simplefilter('ignore', DeprecationWarning)
123                module = loader.load_module('_temp')
124            module_id = id(module)
125            module_dict_id = id(module.__dict__)
126            with open(mapping['_temp'], 'w', encoding='utf-8') as file:
127                file.write("testing_var = 42\n")
128            with warnings.catch_warnings():
129                warnings.simplefilter('ignore', DeprecationWarning)
130                module = loader.load_module('_temp')
131            self.assertIn('testing_var', module.__dict__,
132                         "'testing_var' not in "
133                            "{0}".format(list(module.__dict__.keys())))
134            self.assertEqual(module, sys.modules['_temp'])
135            self.assertEqual(id(module), module_id)
136            self.assertEqual(id(module.__dict__), module_dict_id)
137
138    def test_state_after_failure(self):
139        # A failed reload should leave the original module intact.
140        attributes = ('__file__', '__path__', '__package__')
141        value = '<test>'
142        name = '_temp'
143        with util.create_modules(name) as mapping:
144            orig_module = types.ModuleType(name)
145            for attr in attributes:
146                setattr(orig_module, attr, value)
147            with open(mapping[name], 'w', encoding='utf-8') as file:
148                file.write('+++ bad syntax +++')
149            loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
150            with self.assertRaises(SyntaxError):
151                loader.exec_module(orig_module)
152            for attr in attributes:
153                self.assertEqual(getattr(orig_module, attr), value)
154            with self.assertRaises(SyntaxError):
155                with warnings.catch_warnings():
156                    warnings.simplefilter('ignore', DeprecationWarning)
157                    loader.load_module(name)
158            for attr in attributes:
159                self.assertEqual(getattr(orig_module, attr), value)
160
161    # [syntax error]
162    def test_bad_syntax(self):
163        with util.create_modules('_temp') as mapping:
164            with open(mapping['_temp'], 'w', encoding='utf-8') as file:
165                file.write('=')
166            loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
167            with self.assertRaises(SyntaxError):
168                with warnings.catch_warnings():
169                    warnings.simplefilter('ignore', DeprecationWarning)
170                    loader.load_module('_temp')
171            self.assertNotIn('_temp', sys.modules)
172
173    def test_file_from_empty_string_dir(self):
174        # Loading a module found from an empty string entry on sys.path should
175        # not only work, but keep all attributes relative.
176        file_path = '_temp.py'
177        with open(file_path, 'w', encoding='utf-8') as file:
178            file.write("# test file for importlib")
179        try:
180            with util.uncache('_temp'):
181                loader = self.machinery.SourceFileLoader('_temp', file_path)
182                with warnings.catch_warnings():
183                    warnings.simplefilter('ignore', DeprecationWarning)
184                    mod = loader.load_module('_temp')
185                self.assertEqual(file_path, mod.__file__)
186                self.assertEqual(self.util.cache_from_source(file_path),
187                                 mod.__cached__)
188        finally:
189            os.unlink(file_path)
190            pycache = os.path.dirname(self.util.cache_from_source(file_path))
191            if os.path.exists(pycache):
192                shutil.rmtree(pycache)
193
194    @util.writes_bytecode_files
195    def test_timestamp_overflow(self):
196        # When a modification timestamp is larger than 2**32, it should be
197        # truncated rather than raise an OverflowError.
198        with util.create_modules('_temp') as mapping:
199            source = mapping['_temp']
200            compiled = self.util.cache_from_source(source)
201            with open(source, 'w', encoding='utf-8') as f:
202                f.write("x = 5")
203            try:
204                os.utime(source, (2 ** 33 - 5, 2 ** 33 - 5))
205            except OverflowError:
206                self.skipTest("cannot set modification time to large integer")
207            except OSError as e:
208                if e.errno != getattr(errno, 'EOVERFLOW', None):
209                    raise
210                self.skipTest("cannot set modification time to large integer ({})".format(e))
211            loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
212            # PEP 451
213            module = types.ModuleType('_temp')
214            module.__spec__ = self.util.spec_from_loader('_temp', loader)
215            loader.exec_module(module)
216            self.assertEqual(module.x, 5)
217            self.assertTrue(os.path.exists(compiled))
218            os.unlink(compiled)
219            # PEP 302
220            with warnings.catch_warnings():
221                warnings.simplefilter('ignore', DeprecationWarning)
222                mod = loader.load_module('_temp')
223            # Sanity checks.
224            self.assertEqual(mod.__cached__, compiled)
225            self.assertEqual(mod.x, 5)
226            # The pyc file was created.
227            self.assertTrue(os.path.exists(compiled))
228
229    def test_unloadable(self):
230        loader = self.machinery.SourceFileLoader('good name', {})
231        module = types.ModuleType('bad name')
232        module.__spec__ = self.machinery.ModuleSpec('bad name', loader)
233        with self.assertRaises(ImportError):
234            loader.exec_module(module)
235        with self.assertRaises(ImportError):
236            with warnings.catch_warnings():
237                warnings.simplefilter('ignore', DeprecationWarning)
238                loader.load_module('bad name')
239
240    @util.writes_bytecode_files
241    def test_checked_hash_based_pyc(self):
242        with util.create_modules('_temp') as mapping:
243            source = mapping['_temp']
244            pyc = self.util.cache_from_source(source)
245            with open(source, 'wb') as fp:
246                fp.write(b'state = "old"')
247            os.utime(source, (50, 50))
248            py_compile.compile(
249                source,
250                invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
251            )
252            loader = self.machinery.SourceFileLoader('_temp', source)
253            mod = types.ModuleType('_temp')
254            mod.__spec__ = self.util.spec_from_loader('_temp', loader)
255            loader.exec_module(mod)
256            self.assertEqual(mod.state, 'old')
257            # Write a new source with the same mtime and size as before.
258            with open(source, 'wb') as fp:
259                fp.write(b'state = "new"')
260            os.utime(source, (50, 50))
261            loader.exec_module(mod)
262            self.assertEqual(mod.state, 'new')
263            with open(pyc, 'rb') as fp:
264                data = fp.read()
265            self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b11)
266            self.assertEqual(
267                self.util.source_hash(b'state = "new"'),
268                data[8:16],
269            )
270
271    @util.writes_bytecode_files
272    def test_overridden_checked_hash_based_pyc(self):
273        with util.create_modules('_temp') as mapping, \
274             unittest.mock.patch('_imp.check_hash_based_pycs', 'never'):
275            source = mapping['_temp']
276            pyc = self.util.cache_from_source(source)
277            with open(source, 'wb') as fp:
278                fp.write(b'state = "old"')
279            os.utime(source, (50, 50))
280            py_compile.compile(
281                source,
282                invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
283            )
284            loader = self.machinery.SourceFileLoader('_temp', source)
285            mod = types.ModuleType('_temp')
286            mod.__spec__ = self.util.spec_from_loader('_temp', loader)
287            loader.exec_module(mod)
288            self.assertEqual(mod.state, 'old')
289            # Write a new source with the same mtime and size as before.
290            with open(source, 'wb') as fp:
291                fp.write(b'state = "new"')
292            os.utime(source, (50, 50))
293            loader.exec_module(mod)
294            self.assertEqual(mod.state, 'old')
295
296    @util.writes_bytecode_files
297    def test_unchecked_hash_based_pyc(self):
298        with util.create_modules('_temp') as mapping:
299            source = mapping['_temp']
300            pyc = self.util.cache_from_source(source)
301            with open(source, 'wb') as fp:
302                fp.write(b'state = "old"')
303            os.utime(source, (50, 50))
304            py_compile.compile(
305                source,
306                invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH,
307            )
308            loader = self.machinery.SourceFileLoader('_temp', source)
309            mod = types.ModuleType('_temp')
310            mod.__spec__ = self.util.spec_from_loader('_temp', loader)
311            loader.exec_module(mod)
312            self.assertEqual(mod.state, 'old')
313            # Update the source file, which should be ignored.
314            with open(source, 'wb') as fp:
315                fp.write(b'state = "new"')
316            loader.exec_module(mod)
317            self.assertEqual(mod.state, 'old')
318            with open(pyc, 'rb') as fp:
319                data = fp.read()
320            self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b1)
321            self.assertEqual(
322                self.util.source_hash(b'state = "old"'),
323                data[8:16],
324            )
325
326    @util.writes_bytecode_files
327    def test_overridden_unchecked_hash_based_pyc(self):
328        with util.create_modules('_temp') as mapping, \
329             unittest.mock.patch('_imp.check_hash_based_pycs', 'always'):
330            source = mapping['_temp']
331            pyc = self.util.cache_from_source(source)
332            with open(source, 'wb') as fp:
333                fp.write(b'state = "old"')
334            os.utime(source, (50, 50))
335            py_compile.compile(
336                source,
337                invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH,
338            )
339            loader = self.machinery.SourceFileLoader('_temp', source)
340            mod = types.ModuleType('_temp')
341            mod.__spec__ = self.util.spec_from_loader('_temp', loader)
342            loader.exec_module(mod)
343            self.assertEqual(mod.state, 'old')
344            # Update the source file, which should be ignored.
345            with open(source, 'wb') as fp:
346                fp.write(b'state = "new"')
347            loader.exec_module(mod)
348            self.assertEqual(mod.state, 'new')
349            with open(pyc, 'rb') as fp:
350                data = fp.read()
351            self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b1)
352            self.assertEqual(
353                self.util.source_hash(b'state = "new"'),
354                data[8:16],
355            )
356
357
358(Frozen_SimpleTest,
359 Source_SimpleTest
360 ) = util.test_both(SimpleTest, importlib=importlib, machinery=machinery,
361                    abc=importlib_abc, util=importlib_util)
362
363
364class SourceDateEpochTestMeta(SourceDateEpochTestMeta,
365                              type(Source_SimpleTest)):
366    pass
367
368
369class SourceDateEpoch_SimpleTest(Source_SimpleTest,
370                                 metaclass=SourceDateEpochTestMeta,
371                                 source_date_epoch=True):
372    pass
373
374
375class BadBytecodeTest:
376
377    def import_(self, file, module_name):
378        raise NotImplementedError
379
380    def manipulate_bytecode(self,
381                            name, mapping, manipulator, *,
382                            del_source=False,
383                            invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP):
384        """Manipulate the bytecode of a module by passing it into a callable
385        that returns what to use as the new bytecode."""
386        try:
387            del sys.modules['_temp']
388        except KeyError:
389            pass
390        py_compile.compile(mapping[name], invalidation_mode=invalidation_mode)
391        if not del_source:
392            bytecode_path = self.util.cache_from_source(mapping[name])
393        else:
394            os.unlink(mapping[name])
395            bytecode_path = make_legacy_pyc(mapping[name])
396        if manipulator:
397            with open(bytecode_path, 'rb') as file:
398                bc = file.read()
399                new_bc = manipulator(bc)
400            with open(bytecode_path, 'wb') as file:
401                if new_bc is not None:
402                    file.write(new_bc)
403        return bytecode_path
404
405    def _test_empty_file(self, test, *, del_source=False):
406        with util.create_modules('_temp') as mapping:
407            bc_path = self.manipulate_bytecode('_temp', mapping,
408                                                lambda bc: b'',
409                                                del_source=del_source)
410            test('_temp', mapping, bc_path)
411
412    @util.writes_bytecode_files
413    def _test_partial_magic(self, test, *, del_source=False):
414        # When their are less than 4 bytes to a .pyc, regenerate it if
415        # possible, else raise ImportError.
416        with util.create_modules('_temp') as mapping:
417            bc_path = self.manipulate_bytecode('_temp', mapping,
418                                                lambda bc: bc[:3],
419                                                del_source=del_source)
420            test('_temp', mapping, bc_path)
421
422    def _test_magic_only(self, test, *, del_source=False):
423        with util.create_modules('_temp') as mapping:
424            bc_path = self.manipulate_bytecode('_temp', mapping,
425                                                lambda bc: bc[:4],
426                                                del_source=del_source)
427            test('_temp', mapping, bc_path)
428
429    def _test_partial_flags(self, test, *, del_source=False):
430        with util.create_modules('_temp') as mapping:
431            bc_path = self.manipulate_bytecode('_temp', mapping,
432                                               lambda bc: bc[:7],
433                                               del_source=del_source)
434            test('_temp', mapping, bc_path)
435
436    def _test_partial_hash(self, test, *, del_source=False):
437        with util.create_modules('_temp') as mapping:
438            bc_path = self.manipulate_bytecode(
439                '_temp',
440                mapping,
441                lambda bc: bc[:13],
442                del_source=del_source,
443                invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
444            )
445            test('_temp', mapping, bc_path)
446        with util.create_modules('_temp') as mapping:
447            bc_path = self.manipulate_bytecode(
448                '_temp',
449                mapping,
450                lambda bc: bc[:13],
451                del_source=del_source,
452                invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH,
453            )
454            test('_temp', mapping, bc_path)
455
456    def _test_partial_timestamp(self, test, *, del_source=False):
457        with util.create_modules('_temp') as mapping:
458            bc_path = self.manipulate_bytecode('_temp', mapping,
459                                                lambda bc: bc[:11],
460                                                del_source=del_source)
461            test('_temp', mapping, bc_path)
462
463    def _test_partial_size(self, test, *, del_source=False):
464        with util.create_modules('_temp') as mapping:
465            bc_path = self.manipulate_bytecode('_temp', mapping,
466                                                lambda bc: bc[:15],
467                                                del_source=del_source)
468            test('_temp', mapping, bc_path)
469
470    def _test_no_marshal(self, *, del_source=False):
471        with util.create_modules('_temp') as mapping:
472            bc_path = self.manipulate_bytecode('_temp', mapping,
473                                                lambda bc: bc[:16],
474                                                del_source=del_source)
475            file_path = mapping['_temp'] if not del_source else bc_path
476            with self.assertRaises(EOFError):
477                self.import_(file_path, '_temp')
478
479    def _test_non_code_marshal(self, *, del_source=False):
480        with util.create_modules('_temp') as mapping:
481            bytecode_path = self.manipulate_bytecode('_temp', mapping,
482                                    lambda bc: bc[:16] + marshal.dumps(b'abcd'),
483                                    del_source=del_source)
484            file_path = mapping['_temp'] if not del_source else bytecode_path
485            with self.assertRaises(ImportError) as cm:
486                self.import_(file_path, '_temp')
487            self.assertEqual(cm.exception.name, '_temp')
488            self.assertEqual(cm.exception.path, bytecode_path)
489
490    def _test_bad_marshal(self, *, del_source=False):
491        with util.create_modules('_temp') as mapping:
492            bytecode_path = self.manipulate_bytecode('_temp', mapping,
493                                                lambda bc: bc[:16] + b'<test>',
494                                                del_source=del_source)
495            file_path = mapping['_temp'] if not del_source else bytecode_path
496            with self.assertRaises(EOFError):
497                self.import_(file_path, '_temp')
498
499    def _test_bad_magic(self, test, *, del_source=False):
500        with util.create_modules('_temp') as mapping:
501            bc_path = self.manipulate_bytecode('_temp', mapping,
502                                    lambda bc: b'\x00\x00\x00\x00' + bc[4:])
503            test('_temp', mapping, bc_path)
504
505
506class BadBytecodeTestPEP451(BadBytecodeTest):
507
508    def import_(self, file, module_name):
509        loader = self.loader(module_name, file)
510        module = types.ModuleType(module_name)
511        module.__spec__ = self.util.spec_from_loader(module_name, loader)
512        loader.exec_module(module)
513
514
515class BadBytecodeTestPEP302(BadBytecodeTest):
516
517    def import_(self, file, module_name):
518        loader = self.loader(module_name, file)
519        with warnings.catch_warnings():
520            warnings.simplefilter('ignore', DeprecationWarning)
521            module = loader.load_module(module_name)
522        self.assertIn(module_name, sys.modules)
523
524
525class SourceLoaderBadBytecodeTest:
526
527    @classmethod
528    def setUpClass(cls):
529        cls.loader = cls.machinery.SourceFileLoader
530
531    @util.writes_bytecode_files
532    def test_empty_file(self):
533        # When a .pyc is empty, regenerate it if possible, else raise
534        # ImportError.
535        def test(name, mapping, bytecode_path):
536            self.import_(mapping[name], name)
537            with open(bytecode_path, 'rb') as file:
538                self.assertGreater(len(file.read()), 16)
539
540        self._test_empty_file(test)
541
542    def test_partial_magic(self):
543        def test(name, mapping, bytecode_path):
544            self.import_(mapping[name], name)
545            with open(bytecode_path, 'rb') as file:
546                self.assertGreater(len(file.read()), 16)
547
548        self._test_partial_magic(test)
549
550    @util.writes_bytecode_files
551    def test_magic_only(self):
552        # When there is only the magic number, regenerate the .pyc if possible,
553        # else raise EOFError.
554        def test(name, mapping, bytecode_path):
555            self.import_(mapping[name], name)
556            with open(bytecode_path, 'rb') as file:
557                self.assertGreater(len(file.read()), 16)
558
559        self._test_magic_only(test)
560
561    @util.writes_bytecode_files
562    def test_bad_magic(self):
563        # When the magic number is different, the bytecode should be
564        # regenerated.
565        def test(name, mapping, bytecode_path):
566            self.import_(mapping[name], name)
567            with open(bytecode_path, 'rb') as bytecode_file:
568                self.assertEqual(bytecode_file.read(4),
569                                 self.util.MAGIC_NUMBER)
570
571        self._test_bad_magic(test)
572
573    @util.writes_bytecode_files
574    def test_partial_timestamp(self):
575        # When the timestamp is partial, regenerate the .pyc, else
576        # raise EOFError.
577        def test(name, mapping, bc_path):
578            self.import_(mapping[name], name)
579            with open(bc_path, 'rb') as file:
580                self.assertGreater(len(file.read()), 16)
581
582        self._test_partial_timestamp(test)
583
584    @util.writes_bytecode_files
585    def test_partial_flags(self):
586        # When the flags is partial, regenerate the .pyc, else raise EOFError.
587        def test(name, mapping, bc_path):
588            self.import_(mapping[name], name)
589            with open(bc_path, 'rb') as file:
590                self.assertGreater(len(file.read()), 16)
591
592        self._test_partial_flags(test)
593
594    @util.writes_bytecode_files
595    def test_partial_hash(self):
596        # When the hash is partial, regenerate the .pyc, else raise EOFError.
597        def test(name, mapping, bc_path):
598            self.import_(mapping[name], name)
599            with open(bc_path, 'rb') as file:
600                self.assertGreater(len(file.read()), 16)
601
602        self._test_partial_hash(test)
603
604    @util.writes_bytecode_files
605    def test_partial_size(self):
606        # When the size is partial, regenerate the .pyc, else
607        # raise EOFError.
608        def test(name, mapping, bc_path):
609            self.import_(mapping[name], name)
610            with open(bc_path, 'rb') as file:
611                self.assertGreater(len(file.read()), 16)
612
613        self._test_partial_size(test)
614
615    @util.writes_bytecode_files
616    def test_no_marshal(self):
617        # When there is only the magic number and timestamp, raise EOFError.
618        self._test_no_marshal()
619
620    @util.writes_bytecode_files
621    def test_non_code_marshal(self):
622        self._test_non_code_marshal()
623        # XXX ImportError when sourceless
624
625    # [bad marshal]
626    @util.writes_bytecode_files
627    def test_bad_marshal(self):
628        # Bad marshal data should raise a ValueError.
629        self._test_bad_marshal()
630
631    # [bad timestamp]
632    @util.writes_bytecode_files
633    @without_source_date_epoch
634    def test_old_timestamp(self):
635        # When the timestamp is older than the source, bytecode should be
636        # regenerated.
637        zeros = b'\x00\x00\x00\x00'
638        with util.create_modules('_temp') as mapping:
639            py_compile.compile(mapping['_temp'])
640            bytecode_path = self.util.cache_from_source(mapping['_temp'])
641            with open(bytecode_path, 'r+b') as bytecode_file:
642                bytecode_file.seek(8)
643                bytecode_file.write(zeros)
644            self.import_(mapping['_temp'], '_temp')
645            source_mtime = os.path.getmtime(mapping['_temp'])
646            source_timestamp = self.importlib._pack_uint32(source_mtime)
647            with open(bytecode_path, 'rb') as bytecode_file:
648                bytecode_file.seek(8)
649                self.assertEqual(bytecode_file.read(4), source_timestamp)
650
651    # [bytecode read-only]
652    @util.writes_bytecode_files
653    def test_read_only_bytecode(self):
654        # When bytecode is read-only but should be rewritten, fail silently.
655        with util.create_modules('_temp') as mapping:
656            # Create bytecode that will need to be re-created.
657            py_compile.compile(mapping['_temp'])
658            bytecode_path = self.util.cache_from_source(mapping['_temp'])
659            with open(bytecode_path, 'r+b') as bytecode_file:
660                bytecode_file.seek(0)
661                bytecode_file.write(b'\x00\x00\x00\x00')
662            # Make the bytecode read-only.
663            os.chmod(bytecode_path,
664                        stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
665            try:
666                # Should not raise OSError!
667                self.import_(mapping['_temp'], '_temp')
668            finally:
669                # Make writable for eventual clean-up.
670                os.chmod(bytecode_path, stat.S_IWUSR)
671
672
673class SourceLoaderBadBytecodeTestPEP451(
674        SourceLoaderBadBytecodeTest, BadBytecodeTestPEP451):
675    pass
676
677
678(Frozen_SourceBadBytecodePEP451,
679 Source_SourceBadBytecodePEP451
680 ) = util.test_both(SourceLoaderBadBytecodeTestPEP451, importlib=importlib,
681                    machinery=machinery, abc=importlib_abc,
682                    util=importlib_util)
683
684
685class SourceLoaderBadBytecodeTestPEP302(
686        SourceLoaderBadBytecodeTest, BadBytecodeTestPEP302):
687    pass
688
689
690(Frozen_SourceBadBytecodePEP302,
691 Source_SourceBadBytecodePEP302
692 ) = util.test_both(SourceLoaderBadBytecodeTestPEP302, importlib=importlib,
693                    machinery=machinery, abc=importlib_abc,
694                    util=importlib_util)
695
696
697class SourcelessLoaderBadBytecodeTest:
698
699    @classmethod
700    def setUpClass(cls):
701        cls.loader = cls.machinery.SourcelessFileLoader
702
703    def test_empty_file(self):
704        def test(name, mapping, bytecode_path):
705            with self.assertRaises(ImportError) as cm:
706                self.import_(bytecode_path, name)
707            self.assertEqual(cm.exception.name, name)
708            self.assertEqual(cm.exception.path, bytecode_path)
709
710        self._test_empty_file(test, del_source=True)
711
712    def test_partial_magic(self):
713        def test(name, mapping, bytecode_path):
714            with self.assertRaises(ImportError) as cm:
715                self.import_(bytecode_path, name)
716            self.assertEqual(cm.exception.name, name)
717            self.assertEqual(cm.exception.path, bytecode_path)
718        self._test_partial_magic(test, del_source=True)
719
720    def test_magic_only(self):
721        def test(name, mapping, bytecode_path):
722            with self.assertRaises(EOFError):
723                self.import_(bytecode_path, name)
724
725        self._test_magic_only(test, del_source=True)
726
727    def test_bad_magic(self):
728        def test(name, mapping, bytecode_path):
729            with self.assertRaises(ImportError) as cm:
730                self.import_(bytecode_path, name)
731            self.assertEqual(cm.exception.name, name)
732            self.assertEqual(cm.exception.path, bytecode_path)
733
734        self._test_bad_magic(test, del_source=True)
735
736    def test_partial_timestamp(self):
737        def test(name, mapping, bytecode_path):
738            with self.assertRaises(EOFError):
739                self.import_(bytecode_path, name)
740
741        self._test_partial_timestamp(test, del_source=True)
742
743    def test_partial_flags(self):
744        def test(name, mapping, bytecode_path):
745            with self.assertRaises(EOFError):
746                self.import_(bytecode_path, name)
747
748        self._test_partial_flags(test, del_source=True)
749
750    def test_partial_hash(self):
751        def test(name, mapping, bytecode_path):
752            with self.assertRaises(EOFError):
753                self.import_(bytecode_path, name)
754
755        self._test_partial_hash(test, del_source=True)
756
757    def test_partial_size(self):
758        def test(name, mapping, bytecode_path):
759            with self.assertRaises(EOFError):
760                self.import_(bytecode_path, name)
761
762        self._test_partial_size(test, del_source=True)
763
764    def test_no_marshal(self):
765        self._test_no_marshal(del_source=True)
766
767    def test_non_code_marshal(self):
768        self._test_non_code_marshal(del_source=True)
769
770
771class SourcelessLoaderBadBytecodeTestPEP451(SourcelessLoaderBadBytecodeTest,
772        BadBytecodeTestPEP451):
773    pass
774
775
776(Frozen_SourcelessBadBytecodePEP451,
777 Source_SourcelessBadBytecodePEP451
778 ) = util.test_both(SourcelessLoaderBadBytecodeTestPEP451, importlib=importlib,
779                    machinery=machinery, abc=importlib_abc,
780                    util=importlib_util)
781
782
783class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest,
784        BadBytecodeTestPEP302):
785    pass
786
787
788(Frozen_SourcelessBadBytecodePEP302,
789 Source_SourcelessBadBytecodePEP302
790 ) = util.test_both(SourcelessLoaderBadBytecodeTestPEP302, importlib=importlib,
791                    machinery=machinery, abc=importlib_abc,
792                    util=importlib_util)
793
794
795if __name__ == '__main__':
796    unittest.main()
797