1414import textwrap
1515import errno
1616import shutil
17+ import contextlib
18+ import time
1719
1820import test .support
1921from 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+
3656class 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+
348406class 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
9471005def test_main (verbose = None ):
948- run_unittest (ImportTests , PycacheTests ,
1006+ run_unittest (ImportTests , PycacheTests , FilePermissionTests ,
9491007 PycRewritingTests , PathsTests , RelativeImportTests ,
9501008 OverridingImportBuiltinTests ,
9511009 ImportlibBootstrapTests ,
0 commit comments