aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changelog.d/1418.change.rst1
-rw-r--r--pkg_resources/__init__.py10
-rw-r--r--pkg_resources/tests/test_pkg_resources.py33
3 files changed, 42 insertions, 2 deletions
diff --git a/changelog.d/1418.change.rst b/changelog.d/1418.change.rst
new file mode 100644
index 00000000..d7656f57
--- /dev/null
+++ b/changelog.d/1418.change.rst
@@ -0,0 +1 @@
+Solved race in when creating egg cache directories.
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 67015408..4f42156d 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -47,6 +47,11 @@ except ImportError:
# Python 3.2 compatibility
import imp as _imp
+try:
+ FileExistsError
+except NameError:
+ FileExistsError = OSError
+
from pkg_resources.extern import six
from pkg_resources.extern.six.moves import urllib, map, filter
@@ -3030,7 +3035,10 @@ def _bypass_ensure_directory(path):
dirname, filename = split(path)
if dirname and filename and not isdir(dirname):
_bypass_ensure_directory(dirname)
- mkdir(dirname, 0o755)
+ try:
+ mkdir(dirname, 0o755)
+ except FileExistsError:
+ pass
def split_sections(s):
diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py
index 4e2cac94..62a39b8f 100644
--- a/pkg_resources/tests/test_pkg_resources.py
+++ b/pkg_resources/tests/test_pkg_resources.py
@@ -12,6 +12,11 @@ import stat
import distutils.dist
import distutils.command.install_egg_info
+try:
+ from unittest import mock
+except ImportError:
+ import mock
+
from pkg_resources.extern.six.moves import map
from pkg_resources.extern.six import text_type, string_types
@@ -138,8 +143,34 @@ class TestResourceManager:
message = "Unexpected type from get_cache_path: " + type_
assert isinstance(path, string_types), message
+ def test_get_cache_path_race(self, tmpdir):
+ # Patch to os.path.isdir to create a race condition
+ def patched_isdir(dirname, unpatched_isdir=pkg_resources.isdir):
+ patched_isdir.dirnames.append(dirname)
+
+ was_dir = unpatched_isdir(dirname)
+ if not was_dir:
+ os.makedirs(dirname)
+ return was_dir
+
+ patched_isdir.dirnames = []
+
+ # Get a cache path with a "race condition"
+ mgr = pkg_resources.ResourceManager()
+ mgr.set_extraction_path(str(tmpdir))
+
+ archive_name = os.sep.join(('foo', 'bar', 'baz'))
+ with mock.patch.object(pkg_resources, 'isdir', new=patched_isdir):
+ mgr.get_cache_path(archive_name)
+
+ # Because this test relies on the implementation details of this
+ # function, these assertions are a sentinel to ensure that the
+ # test suite will not fail silently if the implementation changes.
+ called_dirnames = patched_isdir.dirnames
+ assert len(called_dirnames) == 2
+ assert called_dirnames[0].split(os.sep)[-2:] == ['foo', 'bar']
+ assert called_dirnames[1].split(os.sep)[-1:] == ['foo']
-class TestIndependence:
"""
Tests to ensure that pkg_resources runs independently from setuptools.
"""