Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eb8d627

Browse files
committed
Issue #6074: Apply an appropriate fix for importlib based imports
1 parent 90eb8ae commit eb8d627

4 files changed

Lines changed: 1998 additions & 1933 deletions

File tree

Lib/importlib/_bootstrap.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,9 @@ def _cache_bytecode(self, source_path, bytecode_path, data):
10481048
mode = _os.stat(source_path).st_mode
10491049
except OSError:
10501050
mode = 0o666
1051+
# We always ensure write access so we can update cached files
1052+
# later even when the source files are read-only on Windows (#6074)
1053+
mode |= 0o200
10511054
return self.set_data(bytecode_path, data, _mode=mode)
10521055

10531056
def set_data(self, path, data, *, _mode=0o666):

Lib/test/test_import.py

Lines changed: 107 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import textwrap
1515
import errno
1616
import shutil
17+
import contextlib
18+
import time
1719

1820
import test.support
1921
from test.support import (
@@ -33,6 +35,24 @@ def remove_files(name):
3335
rmtree('__pycache__')
3436

3537

38+
@contextlib.contextmanager
39+
def _ready_to_import(name=None, source=""):
40+
# sets up a temporary directory and removes it
41+
# creates the module file
42+
# temporarily clears the module from sys.modules (if any)
43+
name = name or "spam"
44+
with script_helper.temp_dir() as tempdir:
45+
path = script_helper.make_script(tempdir, name, source)
46+
old_module = sys.modules.pop(name, None)
47+
try:
48+
sys.path.insert(0, tempdir)
49+
yield name, path
50+
sys.path.remove(tempdir)
51+
finally:
52+
if old_module is not None:
53+
sys.modules[name] = old_module
54+
55+
3656
class ImportTests(unittest.TestCase):
3757

3858
def setUp(self):
@@ -101,54 +121,6 @@ def test_with_extension(ext):
101121
finally:
102122
del sys.path[0]
103123

104-
@unittest.skipUnless(os.name == 'posix',
105-
"test meaningful only on posix systems")
106-
def test_creation_mode(self):
107-
mask = 0o022
108-
with temp_umask(mask):
109-
sys.path.insert(0, os.curdir)
110-
try:
111-
fname = TESTFN + os.extsep + "py"
112-
create_empty_file(fname)
113-
fn = imp.cache_from_source(fname)
114-
unlink(fn)
115-
importlib.invalidate_caches()
116-
__import__(TESTFN)
117-
if not os.path.exists(fn):
118-
self.fail("__import__ did not result in creation of "
119-
"either a .pyc or .pyo file")
120-
s = os.stat(fn)
121-
# Check that the umask is respected, and the executable bits
122-
# aren't set.
123-
self.assertEqual(oct(stat.S_IMODE(s.st_mode)), oct(0o666 & ~mask))
124-
finally:
125-
del sys.path[0]
126-
remove_files(TESTFN)
127-
unload(TESTFN)
128-
129-
@unittest.skipUnless(os.name == 'posix',
130-
"test meaningful only on posix systems")
131-
def test_cached_mode_issue_2051(self):
132-
mode = 0o600
133-
source = TESTFN + ".py"
134-
with script_helper.temp_dir() as tempdir:
135-
path = script_helper.make_script(tempdir, TESTFN,
136-
"key='top secret'")
137-
os.chmod(path, mode)
138-
compiled = imp.cache_from_source(path)
139-
sys.path.insert(0, tempdir)
140-
try:
141-
__import__(TESTFN)
142-
finally:
143-
sys.path.remove(tempdir)
144-
145-
if not os.path.exists(compiled):
146-
self.fail("__import__ did not result in creation of "
147-
"either a .pyc or .pyo file")
148-
stat_info = os.stat(compiled)
149-
150-
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
151-
152124
def test_bug7732(self):
153125
source = TESTFN + '.py'
154126
os.mkdir(source)
@@ -345,6 +317,92 @@ def test_bogus_fromlist(self):
345317
self.fail("fromlist must allow bogus names")
346318

347319

320+
class FilePermissionTests(unittest.TestCase):
321+
# tests for file mode on cached .pyc/.pyo files
322+
323+
@unittest.skipUnless(os.name == 'posix',
324+
"test meaningful only on posix systems")
325+
def test_creation_mode(self):
326+
mask = 0o022
327+
with temp_umask(mask), _ready_to_import() as (name, path):
328+
cached_path = imp.cache_from_source(path)
329+
module = __import__(name)
330+
if not os.path.exists(cached_path):
331+
self.fail("__import__ did not result in creation of "
332+
"either a .pyc or .pyo file")
333+
stat_info = os.stat(cached_path)
334+
335+
# Check that the umask is respected, and the executable bits
336+
# aren't set.
337+
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)),
338+
oct(0o666 & ~mask))
339+
340+
@unittest.skipUnless(os.name == 'posix',
341+
"test meaningful only on posix systems")
342+
def test_cached_mode_issue_2051(self):
343+
# permissions of .pyc should match those of .py, regardless of mask
344+
mode = 0o600
345+
with temp_umask(0o022), _ready_to_import() as (name, path):
346+
cached_path = imp.cache_from_source(path)
347+
os.chmod(path, mode)
348+
__import__(name)
349+
if not os.path.exists(cached_path):
350+
self.fail("__import__ did not result in creation of "
351+
"either a .pyc or .pyo file")
352+
stat_info = os.stat(cached_path)
353+
354+
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
355+
356+
@unittest.skipUnless(os.name == 'posix',
357+
"test meaningful only on posix systems")
358+
def test_cached_readonly(self):
359+
mode = 0o400
360+
with temp_umask(0o022), _ready_to_import() as (name, path):
361+
cached_path = imp.cache_from_source(path)
362+
os.chmod(path, mode)
363+
__import__(name)
364+
if not os.path.exists(cached_path):
365+
self.fail("__import__ did not result in creation of "
366+
"either a .pyc or .pyo file")
367+
stat_info = os.stat(cached_path)
368+
369+
expected = mode | 0o200 # Account for fix for issue #6074
370+
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(expected))
371+
372+
def test_pyc_always_writable(self):
373+
# Initially read-only .pyc files on Windows used to cause problems
374+
# with later updates, see issue #6074 for details
375+
with _ready_to_import() as (name, path):
376+
# Write a Python file, make it read-only and import it
377+
with open(path, 'w') as f:
378+
f.write("x = 'original'\n")
379+
# Tweak the mtime of the source to ensure pyc gets updated later
380+
s = os.stat(path)
381+
os.utime(path, (s.st_atime, s.st_mtime-100000000))
382+
os.chmod(path, 0o400)
383+
m = __import__(name)
384+
self.assertEqual(m.x, 'original')
385+
# Change the file and then reimport it
386+
os.chmod(path, 0o600)
387+
with open(path, 'w') as f:
388+
f.write("x = 'rewritten'\n")
389+
unload(name)
390+
importlib.invalidate_caches()
391+
m = __import__(name)
392+
self.assertEqual(m.x, 'rewritten')
393+
# Now delete the source file and check the pyc was rewritten
394+
unlink(path)
395+
unload(name)
396+
importlib.invalidate_caches()
397+
if __debug__:
398+
bytecode_only = path + "c"
399+
else:
400+
bytecode_only = path + "o"
401+
os.rename(imp.cache_from_source(path), bytecode_only)
402+
m = __import__(name)
403+
self.assertEqual(m.x, 'rewritten')
404+
405+
348406
class PycRewritingTests(unittest.TestCase):
349407
# Test that the `co_filename` attribute on code objects always points
350408
# to the right file, even when various things happen (e.g. both the .py
@@ -945,7 +1003,7 @@ def load_module(*args):
9451003

9461004

9471005
def test_main(verbose=None):
948-
run_unittest(ImportTests, PycacheTests,
1006+
run_unittest(ImportTests, PycacheTests, FilePermissionTests,
9491007
PycRewritingTests, PathsTests, RelativeImportTests,
9501008
OverridingImportBuiltinTests,
9511009
ImportlibBootstrapTests,

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ What's New in Python 3.3.1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #6074: Ensure cached bytecode files can always be updated by the
16+
user that created them, even when the source file is read-only.
17+
1518
- Issue #14783: Improve int() docstring and switch docstrings for str(),
1619
range(), and slice() to use multi-line signatures.
1720

0 commit comments

Comments
 (0)