mirror of
https://github.com/python/cpython.git
synced 2025-08-03 08:34:29 +00:00
Issue #13645: pyc files now contain the size of the corresponding source
code, to avoid timestamp collisions (especially on filesystems with a low timestamp resolution) when checking for freshness of the bytecode.
This commit is contained in:
parent
1f918c1480
commit
5136ac0ca2
14 changed files with 167 additions and 49 deletions
|
@ -5,6 +5,7 @@ from .. import abc as testing_abc
|
|||
from .. import util
|
||||
from . import util as source_util
|
||||
|
||||
import collections
|
||||
import imp
|
||||
import inspect
|
||||
import io
|
||||
|
@ -40,8 +41,10 @@ class SourceLoaderMock(SourceOnlyLoaderMock):
|
|||
def __init__(self, path, magic=imp.get_magic()):
|
||||
super().__init__(path)
|
||||
self.bytecode_path = imp.cache_from_source(self.path)
|
||||
self.source_size = len(self.source)
|
||||
data = bytearray(magic)
|
||||
data.extend(marshal._w_long(self.source_mtime))
|
||||
data.extend(marshal._w_long(self.source_size))
|
||||
code_object = compile(self.source, self.path, 'exec',
|
||||
dont_inherit=True)
|
||||
data.extend(marshal.dumps(code_object))
|
||||
|
@ -56,9 +59,9 @@ class SourceLoaderMock(SourceOnlyLoaderMock):
|
|||
else:
|
||||
raise IOError
|
||||
|
||||
def path_mtime(self, path):
|
||||
def path_stats(self, path):
|
||||
assert path == self.path
|
||||
return self.source_mtime
|
||||
return {'mtime': self.source_mtime, 'size': self.source_size}
|
||||
|
||||
def set_data(self, path, data):
|
||||
self.written[path] = bytes(data)
|
||||
|
@ -657,6 +660,7 @@ class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
|
|||
self.assertIn(self.cached, self.loader.written)
|
||||
data = bytearray(imp.get_magic())
|
||||
data.extend(marshal._w_long(self.loader.source_mtime))
|
||||
data.extend(marshal._w_long(self.loader.source_size))
|
||||
data.extend(marshal.dumps(code_object))
|
||||
self.assertEqual(self.loader.written[self.cached], bytes(data))
|
||||
|
||||
|
@ -847,7 +851,7 @@ class AbstractMethodImplTests(unittest.TestCase):
|
|||
# Required abstractmethods.
|
||||
self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
|
||||
# Optional abstractmethods.
|
||||
self.raises_NotImplementedError(ins,'path_mtime', 'set_data')
|
||||
self.raises_NotImplementedError(ins,'path_stats', 'set_data')
|
||||
|
||||
def test_PyLoader(self):
|
||||
self.raises_NotImplementedError(self.PyLoader(), 'source_path',
|
||||
|
|
|
@ -70,11 +70,6 @@ class SimpleTest(unittest.TestCase):
|
|||
module_dict_id = id(module.__dict__)
|
||||
with open(mapping['_temp'], 'w') as file:
|
||||
file.write("testing_var = 42\n")
|
||||
# For filesystems where the mtime is only to a second granularity,
|
||||
# everything that has happened above can be too fast;
|
||||
# force an mtime on the source that is guaranteed to be different
|
||||
# than the original mtime.
|
||||
loader.path_mtime = self.fake_mtime(loader.path_mtime)
|
||||
module = loader.load_module('_temp')
|
||||
self.assertTrue('testing_var' in module.__dict__,
|
||||
"'testing_var' not in "
|
||||
|
@ -190,10 +185,17 @@ class BadBytecodeTest(unittest.TestCase):
|
|||
del_source=del_source)
|
||||
test('_temp', mapping, bc_path)
|
||||
|
||||
def _test_partial_size(self, test, *, del_source=False):
|
||||
with source_util.create_modules('_temp') as mapping:
|
||||
bc_path = self.manipulate_bytecode('_temp', mapping,
|
||||
lambda bc: bc[:11],
|
||||
del_source=del_source)
|
||||
test('_temp', mapping, bc_path)
|
||||
|
||||
def _test_no_marshal(self, *, del_source=False):
|
||||
with source_util.create_modules('_temp') as mapping:
|
||||
bc_path = self.manipulate_bytecode('_temp', mapping,
|
||||
lambda bc: bc[:8],
|
||||
lambda bc: bc[:12],
|
||||
del_source=del_source)
|
||||
file_path = mapping['_temp'] if not del_source else bc_path
|
||||
with self.assertRaises(EOFError):
|
||||
|
@ -202,7 +204,7 @@ class BadBytecodeTest(unittest.TestCase):
|
|||
def _test_non_code_marshal(self, *, del_source=False):
|
||||
with source_util.create_modules('_temp') as mapping:
|
||||
bytecode_path = self.manipulate_bytecode('_temp', mapping,
|
||||
lambda bc: bc[:8] + marshal.dumps(b'abcd'),
|
||||
lambda bc: bc[:12] + marshal.dumps(b'abcd'),
|
||||
del_source=del_source)
|
||||
file_path = mapping['_temp'] if not del_source else bytecode_path
|
||||
with self.assertRaises(ImportError):
|
||||
|
@ -211,7 +213,7 @@ class BadBytecodeTest(unittest.TestCase):
|
|||
def _test_bad_marshal(self, *, del_source=False):
|
||||
with source_util.create_modules('_temp') as mapping:
|
||||
bytecode_path = self.manipulate_bytecode('_temp', mapping,
|
||||
lambda bc: bc[:8] + b'<test>',
|
||||
lambda bc: bc[:12] + b'<test>',
|
||||
del_source=del_source)
|
||||
file_path = mapping['_temp'] if not del_source else bytecode_path
|
||||
with self.assertRaises(EOFError):
|
||||
|
@ -235,7 +237,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
|
|||
def test(name, mapping, bytecode_path):
|
||||
self.import_(mapping[name], name)
|
||||
with open(bytecode_path, 'rb') as file:
|
||||
self.assertGreater(len(file.read()), 8)
|
||||
self.assertGreater(len(file.read()), 12)
|
||||
|
||||
self._test_empty_file(test)
|
||||
|
||||
|
@ -243,7 +245,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
|
|||
def test(name, mapping, bytecode_path):
|
||||
self.import_(mapping[name], name)
|
||||
with open(bytecode_path, 'rb') as file:
|
||||
self.assertGreater(len(file.read()), 8)
|
||||
self.assertGreater(len(file.read()), 12)
|
||||
|
||||
self._test_partial_magic(test)
|
||||
|
||||
|
@ -254,7 +256,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
|
|||
def test(name, mapping, bytecode_path):
|
||||
self.import_(mapping[name], name)
|
||||
with open(bytecode_path, 'rb') as file:
|
||||
self.assertGreater(len(file.read()), 8)
|
||||
self.assertGreater(len(file.read()), 12)
|
||||
|
||||
self._test_magic_only(test)
|
||||
|
||||
|
@ -276,10 +278,21 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
|
|||
def test(name, mapping, bc_path):
|
||||
self.import_(mapping[name], name)
|
||||
with open(bc_path, 'rb') as file:
|
||||
self.assertGreater(len(file.read()), 8)
|
||||
self.assertGreater(len(file.read()), 12)
|
||||
|
||||
self._test_partial_timestamp(test)
|
||||
|
||||
@source_util.writes_bytecode_files
|
||||
def test_partial_size(self):
|
||||
# When the size is partial, regenerate the .pyc, else
|
||||
# raise EOFError.
|
||||
def test(name, mapping, bc_path):
|
||||
self.import_(mapping[name], name)
|
||||
with open(bc_path, 'rb') as file:
|
||||
self.assertGreater(len(file.read()), 12)
|
||||
|
||||
self._test_partial_size(test)
|
||||
|
||||
@source_util.writes_bytecode_files
|
||||
def test_no_marshal(self):
|
||||
# When there is only the magic number and timestamp, raise EOFError.
|
||||
|
@ -375,6 +388,13 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
|
|||
|
||||
self._test_partial_timestamp(test, del_source=True)
|
||||
|
||||
def test_partial_size(self):
|
||||
def test(name, mapping, bytecode_path):
|
||||
with self.assertRaises(EOFError):
|
||||
self.import_(bytecode_path, name)
|
||||
|
||||
self._test_partial_size(test, del_source=True)
|
||||
|
||||
def test_no_marshal(self):
|
||||
self._test_no_marshal(del_source=True)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue