Commits

Benjamin Peterson committed b57145c

only try to create the __pycache__ dir (not a tree to it) fixes #60

Also, improve error handling surrounding __pycache__ creation.

Comments (0)

Files changed (3)

 Changes between 2.1.0 and 2.1.1.DEV
 ----------------------------------------------
 
+- fix error conditions involving the creation of __pycache__
 - fix assertion rewriting on inserts involving strings containing '%'
 - fix assertion rewriting on calls with a ** arg
 - don't cache rewritten modules if bytecode generation is disabled

_pytest/assertion/rewrite.py

 
 import ast
 import collections
+import errno
 import itertools
 import imp
 import marshal
         cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
         if write:
             try:
-                py.path.local(cache_dir).ensure(dir=True)
-            except py.error.EACCES:
-                state.trace("read only directory: %r" % (fn_pypath.dirname,))
-                write = False
-            except py.error.EEXIST:
-                state.trace("failure to create directory: %r" % (
-                    fn_pypath.dirname,))
-                raise
-                #write = False
+                os.mkdir(cache_dir)
+            except OSError:
+                e = sys.exc_info()[1].errno
+                if e == errno.EEXIST:
+                    # Either the __pycache__ directory already exists (the
+                    # common case) or it's blocked by a non-dir node. In the
+                    # latter case, we'll ignore it in _write_pyc.
+                    pass
+                elif e == errno.ENOTDIR:
+                    # One of the path components was not a directory, likely
+                    # because we're in a zip file.
+                    write = False
+                elif e == errno.EACCES:
+                    state.trace("read only directory: %r" % (fn_pypath.dirname,))
+                    write = False
+                else:
+                    raise
         cache_name = fn_pypath.basename[:-3] + "." + PYTEST_TAG + ".pyc"
         pyc = os.path.join(cache_dir, cache_name)
         # Notice that even if we're in a read-only directory, I'm going to check
     # little reason deviate, and I hope sometime to be able to use
     # imp.load_compiled to load them. (See the comment in load_module above.)
     mtime = int(source_path.mtime())
-    fp = open(pyc, "wb")
+    try:
+        fp = open(pyc, "wb")
+    except IOError:
+        if sys.exc_info()[1].errno == errno.ENOTDIR:
+            # This happens when we get a EEXIST in find_module creating the
+            # __pycache__ directory and __pycache__ is by some non-dir node.
+            return False
+        raise
     try:
         fp.write(imp.get_magic())
         fp.write(struct.pack("<l", mtime))
         marshal.dump(co, fp)
     finally:
         fp.close()
+    return True
 
 def _rewrite_test(state, fn):
     """Try to read and rewrite *fn* and return the code object."""
         # When not on windows, assume rename is atomic. Dump the code object
         # into a file specific to this process and atomically replace it.
         proc_pyc = pyc + "." + str(os.getpid())
-        _write_pyc(co, fn, proc_pyc)
-        os.rename(proc_pyc, pyc)
+        if _write_pyc(co, fn, proc_pyc):
+            os.rename(proc_pyc, pyc)
     return co
 
 def _read_pyc(source, pyc):

testing/test_assertrewrite.py

 import sys
+import zipfile
 import py
 import pytest
 
 
 class TestRewriteOnImport:
 
+    def test_pycache_is_a_file(self, testdir):
+        testdir.tmpdir.join("__pycache__").write("Hello")
+        testdir.makepyfile("""
+def test_rewritten():
+    assert "@py_builtins" in globals()""")
+        assert testdir.runpytest().ret == 0
+
+    def test_zipfile(self, testdir):
+        z = testdir.tmpdir.join("myzip.zip")
+        z_fn = str(z)
+        f = zipfile.ZipFile(z_fn, "w")
+        try:
+            f.writestr("test_gum/__init__.py", "")
+            f.writestr("test_gum/test_lizard.py", "")
+        finally:
+            f.close()
+        z.chmod(256)
+        testdir.makepyfile("""
+import sys
+sys.path.append(%r)
+import test_gum.test_lizard""" % (z_fn,))
+        assert testdir.runpytest().ret == 0
+
     def test_readonly(self, testdir):
         sub = testdir.mkdir("testing")
         sub.join("test_readonly.py").write(