Commit d4f5ad6c authored by Nick Coghlan's avatar Nick Coghlan

Merge fix from #6074 from 3.3

parents 0772398a eb8d627b
...@@ -1048,6 +1048,9 @@ class SourceFileLoader(FileLoader, SourceLoader): ...@@ -1048,6 +1048,9 @@ class SourceFileLoader(FileLoader, SourceLoader):
mode = _os.stat(source_path).st_mode mode = _os.stat(source_path).st_mode
except OSError: except OSError:
mode = 0o666 mode = 0o666
# We always ensure write access so we can update cached files
# later even when the source files are read-only on Windows (#6074)
mode |= 0o200
return self.set_data(bytecode_path, data, _mode=mode) return self.set_data(bytecode_path, data, _mode=mode)
def set_data(self, path, data, *, _mode=0o666): def set_data(self, path, data, *, _mode=0o666):
......
...@@ -14,6 +14,8 @@ import unittest ...@@ -14,6 +14,8 @@ import unittest
import textwrap import textwrap
import errno import errno
import shutil import shutil
import contextlib
import time
import test.support import test.support
from test.support import ( from test.support import (
...@@ -33,6 +35,24 @@ def remove_files(name): ...@@ -33,6 +35,24 @@ def remove_files(name):
rmtree('__pycache__') rmtree('__pycache__')
@contextlib.contextmanager
def _ready_to_import(name=None, source=""):
# sets up a temporary directory and removes it
# creates the module file
# temporarily clears the module from sys.modules (if any)
name = name or "spam"
with script_helper.temp_dir() as tempdir:
path = script_helper.make_script(tempdir, name, source)
old_module = sys.modules.pop(name, None)
try:
sys.path.insert(0, tempdir)
yield name, path
sys.path.remove(tempdir)
finally:
if old_module is not None:
sys.modules[name] = old_module
class ImportTests(unittest.TestCase): class ImportTests(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -101,54 +121,6 @@ class ImportTests(unittest.TestCase): ...@@ -101,54 +121,6 @@ class ImportTests(unittest.TestCase):
finally: finally:
del sys.path[0] del sys.path[0]
@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
def test_creation_mode(self):
mask = 0o022
with temp_umask(mask):
sys.path.insert(0, os.curdir)
try:
fname = TESTFN + os.extsep + "py"
create_empty_file(fname)
fn = imp.cache_from_source(fname)
unlink(fn)
importlib.invalidate_caches()
__import__(TESTFN)
if not os.path.exists(fn):
self.fail("__import__ did not result in creation of "
"either a .pyc or .pyo file")
s = os.stat(fn)
# Check that the umask is respected, and the executable bits
# aren't set.
self.assertEqual(oct(stat.S_IMODE(s.st_mode)), oct(0o666 & ~mask))
finally:
del sys.path[0]
remove_files(TESTFN)
unload(TESTFN)
@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
def test_cached_mode_issue_2051(self):
mode = 0o600
source = TESTFN + ".py"
with script_helper.temp_dir() as tempdir:
path = script_helper.make_script(tempdir, TESTFN,
"key='top secret'")
os.chmod(path, mode)
compiled = imp.cache_from_source(path)
sys.path.insert(0, tempdir)
try:
__import__(TESTFN)
finally:
sys.path.remove(tempdir)
if not os.path.exists(compiled):
self.fail("__import__ did not result in creation of "
"either a .pyc or .pyo file")
stat_info = os.stat(compiled)
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
def test_bug7732(self): def test_bug7732(self):
source = TESTFN + '.py' source = TESTFN + '.py'
os.mkdir(source) os.mkdir(source)
...@@ -345,6 +317,92 @@ class ImportTests(unittest.TestCase): ...@@ -345,6 +317,92 @@ class ImportTests(unittest.TestCase):
self.fail("fromlist must allow bogus names") self.fail("fromlist must allow bogus names")
class FilePermissionTests(unittest.TestCase):
# tests for file mode on cached .pyc/.pyo files
@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
def test_creation_mode(self):
mask = 0o022
with temp_umask(mask), _ready_to_import() as (name, path):
cached_path = imp.cache_from_source(path)
module = __import__(name)
if not os.path.exists(cached_path):
self.fail("__import__ did not result in creation of "
"either a .pyc or .pyo file")
stat_info = os.stat(cached_path)
# Check that the umask is respected, and the executable bits
# aren't set.
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)),
oct(0o666 & ~mask))
@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
def test_cached_mode_issue_2051(self):
# permissions of .pyc should match those of .py, regardless of mask
mode = 0o600
with temp_umask(0o022), _ready_to_import() as (name, path):
cached_path = imp.cache_from_source(path)
os.chmod(path, mode)
__import__(name)
if not os.path.exists(cached_path):
self.fail("__import__ did not result in creation of "
"either a .pyc or .pyo file")
stat_info = os.stat(cached_path)
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
def test_cached_readonly(self):
mode = 0o400
with temp_umask(0o022), _ready_to_import() as (name, path):
cached_path = imp.cache_from_source(path)
os.chmod(path, mode)
__import__(name)
if not os.path.exists(cached_path):
self.fail("__import__ did not result in creation of "
"either a .pyc or .pyo file")
stat_info = os.stat(cached_path)
expected = mode | 0o200 # Account for fix for issue #6074
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(expected))
def test_pyc_always_writable(self):
# Initially read-only .pyc files on Windows used to cause problems
# with later updates, see issue #6074 for details
with _ready_to_import() as (name, path):
# Write a Python file, make it read-only and import it
with open(path, 'w') as f:
f.write("x = 'original'\n")
# Tweak the mtime of the source to ensure pyc gets updated later
s = os.stat(path)
os.utime(path, (s.st_atime, s.st_mtime-100000000))
os.chmod(path, 0o400)
m = __import__(name)
self.assertEqual(m.x, 'original')
# Change the file and then reimport it
os.chmod(path, 0o600)
with open(path, 'w') as f:
f.write("x = 'rewritten'\n")
unload(name)
importlib.invalidate_caches()
m = __import__(name)
self.assertEqual(m.x, 'rewritten')
# Now delete the source file and check the pyc was rewritten
unlink(path)
unload(name)
importlib.invalidate_caches()
if __debug__:
bytecode_only = path + "c"
else:
bytecode_only = path + "o"
os.rename(imp.cache_from_source(path), bytecode_only)
m = __import__(name)
self.assertEqual(m.x, 'rewritten')
class PycRewritingTests(unittest.TestCase): class PycRewritingTests(unittest.TestCase):
# Test that the `co_filename` attribute on code objects always points # Test that the `co_filename` attribute on code objects always points
# to the right file, even when various things happen (e.g. both the .py # to the right file, even when various things happen (e.g. both the .py
...@@ -945,7 +1003,7 @@ class ImportTracebackTests(unittest.TestCase): ...@@ -945,7 +1003,7 @@ class ImportTracebackTests(unittest.TestCase):
def test_main(verbose=None): def test_main(verbose=None):
run_unittest(ImportTests, PycacheTests, run_unittest(ImportTests, PycacheTests, FilePermissionTests,
PycRewritingTests, PathsTests, RelativeImportTests, PycRewritingTests, PathsTests, RelativeImportTests,
OverridingImportBuiltinTests, OverridingImportBuiltinTests,
ImportlibBootstrapTests, ImportlibBootstrapTests,
......
...@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1? ...@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #6074: Ensure cached bytecode files can always be updated by the
user that created them, even when the source file is read-only.
- Issue #16166: Add PY_LITTLE_ENDIAN and PY_BIG_ENDIAN macros and unified - Issue #16166: Add PY_LITTLE_ENDIAN and PY_BIG_ENDIAN macros and unified
endianess detection and handling. endianess detection and handling.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment