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