Repository: pypa/setuptools

Issue:
[Errno 17] File exists when extracting files to egg cache (or creating egg cache directory)
We are experiencing intermittent failures when running tests for one of our Python applications while installing dependencies during test execution in our CI system. The tests are being run with pytest, and we are using the pytest-xdist plugin to run tests in multiple processes in parallel to reduce the execution time.

Here is an example stack trace:
```
.eggs/py-1.5.2-py2.7.egg/py/_path/common.py:377: in visit
    for x in Visitor(fil, rec, ignore, bf, sort).gen(self):
.eggs/py-1.5.2-py2.7.egg/py/_path/common.py:429: in gen
    for p in self.gen(subdir):
.eggs/py-1.5.2-py2.7.egg/py/_path/common.py:419: in gen
    if p.check(dir=1) and (rec is None or rec(p))])
.eggs/pytest-3.2.5-py2.7.egg/_pytest/main.py:737: in _recurse
    ihook = self.gethookproxy(path)
.eggs/pytest-3.2.5-py2.7.egg/_pytest/main.py:641: in gethookproxy
    my_conftestmodules = pm._getconftestmodules(fspath)
.eggs/pytest-3.2.5-py2.7.egg/_pytest/config.py:356: in _getconftestmodules
    mod = self._importconftest(conftestpath)
.eggs/pytest-3.2.5-py2.7.egg/_pytest/config.py:381: in _importconftest
    raise ConftestImportFailure(conftestpath, sys.exc_info())
E   ConftestImportFailure: ExtractionError("Can't extract file(s) to egg cache\n\nThe following error occurred while trying to extract file(s)\nto the Python egg cache:\n\n  [Errno 17] File exists: '/home/travis/.cache/Python-Eggs'\n\nThe Python egg cache directory is currently set to:\n\n  /home/travis/.cache/Python-Eggs\n\nPerhaps your account does not have write access to this directory?\nYou can change the cache directory by setting the PYTHON_EGG_CACHE\nenvironment variable to point to an accessible directory.\n",)
E     File "/home/travis/build/finn/fiaas-deploy-daemon/.eggs/pytest-3.2.5-py2.7.egg/_pytest/assertion/rewrite.py", line 212, in load_module
E       py.builtin.exec_(co, mod.__dict__)
E     File "/home/travis/build/finn/fiaas-deploy-daemon/.eggs/py-1.5.2-py2.7.egg/py/_builtin.py", line 221, in exec_
E       exec2(obj, globals, locals)
E     File "<string>", line 7, in exec2
E     File "/home/travis/build/finn/fiaas-deploy-daemon/tests/fiaas_deploy_daemon/conftest.py", line 9, in <module>
E       from fiaas_deploy_daemon.specs.models import AppSpec, ResourceRequirementSpec, ResourcesSpec, PrometheusSpec, \
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/__init__.py", line 13, in <module>
E       from .crd import CustomResourceDefinitionBindings, DisabledCustomResourceDefinitionBindings
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/crd/__init__.py", line 7, in <module>
E       from .watcher import CrdWatcher
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/crd/watcher.py", line 12, in <module>
E       from ..deployer import DeployerEvent
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/deployer/__init__.py", line 8, in <module>
E       from .deploy import Deployer
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/deployer/deploy.py", line 7, in <module>
E       from .kubernetes.ready_check import ReadyCheck
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/deployer/kubernetes/__init__.py", line 7, in <module>
E       from .adapter import K8s
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/deployer/kubernetes/adapter.py", line 9, in <module>
E       from ...specs.models import ResourcesSpec, ResourceRequirementSpec
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/specs/__init__.py", line 10, in <module>
E       from .app_config_downloader import AppConfigDownloader
E     File "/home/travis/build/finn/fiaas-deploy-daemon/fiaas_deploy_daemon/specs/app_config_downloader.py", line 1, in <module>
E       import yaml
E     File "build/bdist.linux-x86_64/egg/yaml/__init__.py", line 14, in <module>
E     File "build/bdist.linux-x86_64/egg/yaml/cyaml.py", line 5, in <module>
E     File "build/bdist.linux-x86_64/egg/_yaml.py", line 7, in <module>
E     File "build/bdist.linux-x86_64/egg/_yaml.py", line 4, in __bootstrap__
E     File "/home/travis/.local/share/virtualenvs/fiaas-deploy-daemon/lib/python2.7/site-packages/pkg_resources/__init__.py", line 1226, in resource_filename
E       self, resource_name
E     File "/home/travis/.local/share/virtualenvs/fiaas-deploy-daemon/lib/python2.7/site-packages/pkg_resources/__init__.py", line 1726, in get_resource_filename
E       self._extract_resource(manager, self._eager_to_zip(name))
E     File "/home/travis/.local/share/virtualenvs/fiaas-deploy-daemon/lib/python2.7/site-packages/pkg_resources/__init__.py", line 1756, in _extract_resource
E       self.egg_name, self._parts(zip_path)
E     File "/home/travis/.local/share/virtualenvs/fiaas-deploy-daemon/lib/python2.7/site-packages/pkg_resources/__init__.py", line 1293, in get_cache_path
E       self.extraction_error()
E     File "/home/travis/.local/share/virtualenvs/fiaas-deploy-daemon/lib/python2.7/site-packages/pkg_resources/__init__.py", line 1273, in extraction_error
E       raise err
```

While I'm not that familiar with the inner workings of pkg_resources, I took a look at the code referenced at the end of the stack trace, and my theory is that since the tests run in parallel, multiple threads may end up calling [_bypass_ensure_directory](https://github.com/pypa/setuptools/blob/master/pkg_resources/__init__.py#L3075-L3082) at the same time, and that the error stems from a file system race condition between `isdir` and `mkdir` when creating the directory, similar to in https://github.com/pypa/setuptools/pull/1083. Perhaps using the same solution could work here?


Discussion:
This should only happen if some dependencies are installed as zipped eggs. Can you confirm that's the case?
It looks to me like that is the case; the part of the application code that seems to trigger this error is consistently `import yaml`, which is provided by PyYAML. If I install the dependencies of the application on my local machine in a clean virtualenv I end up with a PyYAML .egg in site-packages, and the yaml module seems to originate from this .egg;
```
>>> import yaml
>>> yaml.__file__
'/Users/oyvoverg/.pyenv/versions/fdd-test2/lib/python2.7/site-packages/PyYAML-3.12-py2.7-macosx-10.12-x86_64.egg/yaml/__init__.pyc'
```
```
$ file /Users/oyvoverg/.pyenv/versions/fdd-test2/lib/python2.7/site-packages/PyYAML-3.12-py2.7-macosx-10.12-x86_64.egg
/Users/oyvoverg/.pyenv/versions/fdd-test2/lib/python2.7/site-packages/PyYAML-3.12-py2.7-macosx-10.12-x86_64.egg: Zip archive data, at least v2.0 to extract
```

For completeness; the relevant part of the CI build log seems to indicate that this happens there as well:

```
[2018-01-19 13:09:29] Searching for PyYAML==3.12
[2018-01-19 13:09:29] Reading https://pypi.python.org/simple/PyYAML/
[2018-01-19 13:09:29] Downloading https://pypi.python.org/packages/6b/f0/a0250248ea260d55748fff586d89a32afbb22656f4498b08d2636a48d4ec/PyYAML-3.12.zip#md5=aa5e762e79b19bb80dbcf04f82178832
[2018-01-19 13:09:29] Best match: PyYAML 3.12
[2018-01-19 13:09:29] Processing PyYAML-3.12.zip
[2018-01-19 13:09:29] Writing /tmp/easy_install-ARA5nv/PyYAML-3.12/setup.cfg
[2018-01-19 13:09:29] Running PyYAML-3.12/setup.py -q bdist_egg --dist-dir /tmp/easy_install-ARA5nv/PyYAML-3.12/egg-dist-tmp-zLU2L7
[2018-01-19 13:09:29] In file included from ext/_yaml.c:271:0:
[2018-01-19 13:09:29] ext/_yaml.h:6:0: warning: "PyUnicode_FromString" redefined
[2018-01-19 13:09:29]  #define PyUnicode_FromString(s) PyUnicode_DecodeUTF8((s), strlen(s), "strict")
[2018-01-19 13:09:29]  ^

.... lots of output from compiling native extensions omitted

[2018-01-19 13:09:34] zip_safe flag not set; analyzing archive contents...
[2018-01-19 13:09:34] Moving PyYAML-3.12-py2.7-linux-x86_64.egg to /home/travis/.local/share/virtualenvs/fiaas-deploy-daemon/lib/python2.7/site-packages
[2018-01-19 13:09:34] Adding PyYAML 3.12 to easy-install.pth file
```
That's weird, with Python 2, PyYAML is deemed zip-safe, but not with Python 3...

Results of running `python{2|3} -m {virtualenv|venv} && venv/bin/python easy_install.py {option} PyYAML`:

Python version     | easy_install option | result
----------------------- | ------------------------- | --------
Python 2.7 |  | zipped egg
Python 2.7 | --zip-ok | zipped egg
Python 2.7 | --always-unzip | unzipped egg
Python 3.6 |  | unzipped egg
Python 3.6 | --zip-ok | zipped egg
Python 3.6 | --always-unzip | unzipped egg

Apparently the use of `__file__` in `_yaml.py` is not detected when using Python 2:
```python
def __bootstrap__():
    global __bootstrap__, __loader__, __file__
    import sys, pkg_resources, imp
    __file__ = pkg_resources.resource_filename(__name__, '_yaml.so')
    __loader__ = None; del __bootstrap__, __loader__
    imp.load_dynamic(__name__,__file__)
__bootstrap__()
```

Which actually might be correct because stubs are supposed to be ignored!?:
* because with Python 2, `scan_module('build/bdist.linux-x86_64/egg', 'build/bdist.linux-x86_64/egg', '_yaml.pyc', ['build/bdist.linux-x86_64/egg/_yaml.py'])` will consider `_yaml.pyc` a stub and ignore it (not scan it)
* while with Python 3, `scan_module('build/bdist.linux-x86_64/egg', 'build/bdist.linux-x86_64/egg/__pycache__', '_yaml.cpython-36.pyc', ['build/bdist.linux-x86_64/egg/_yaml.py'])` does not consider `_yaml.cpython-36.pyc` a stub (as it should)

I wonder if it would not make sense to deprecate support for installing eggs as zipped anyway: make `--always-unzip` the default. @jaraco ?

As a workaround, you could use `--always-unzip`.
Thanks for the workaround, I've applied it to the application's build system and it looks good this far at least.
I feel like changing the default here only pushes the can down the road. I'm fine with making `--always-unzip` the default, but I don't think that properly addresses the two issues identified in this ticket.:

- the race condition of isdir/mkdir for zip-safe packages.
- improper detection of pyyaml as zip safe when it is not (detection of _yaml.py as a stub).


Isn't ignoring _yaml.py as a stub the correct (explicit) behavior?
I think the normal behavior of the stubs is meant for extension modules, when mod.pyo is the module name, then setuptools creates a mod.py as a stub. But in the case of `_yaml.py`, it's not a setuptools-created stub but instead a proper Python module supplied by the project, and it's only a coincidence that it happens to look like a setuptools-created stub. In any case, `_yaml` isn't an extension module but it's being detected as one, and that's the failure. And the reason it doesn't happen on Python 3 is because the .pyc is created with a different name than the module (including the `.cpython-36` before the extension).
So there are multiple issues at play then. With the following sample project:
* `setup.py`:
```python
from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize("helloworld.pyx")
)
```
* `helloworld.pyx`:
```python
print "Hello World"
```

And building an egg with `python setup.py bdist_egg`:

* Python 2.7 marks the egg as `zip-safe` (`build/bdist.linux-x86_64/egg/helloworld.pyc` is correctly detected as a stub): correct behavior
* but Python 3.6 marks the egg as `not-zip-safe` because `build/bdist.linux-x86_64/egg/__pycache__/helloworld.cpython-36.pyc` is not detected as a stub: bug, no?
I do believe that the introduction of `__pycache__/{module}.{pyver}.pyc` ([PEP 3147](https://www.python.org/dev/peps/pep-3147/)) has violated these expectations. So yes, that does sound like the bug.
I'm about to submit a PR for this. 

For google-ability and thread sanity, here's the error that I get for this from our CI system. 

Funny enough, we've actually had this fixed for a year, but we never upstreamed the patch (sorry!) - We're making that wrong right today.

Sure, the zip safe thing is a good catch, but the real catch is that we've got a very edgecase where high volume CICD systems will hit this, and developers wont, which is why the community in general is not out with pitchforks. Since our codebase for the particular product we're hitting this on is so large, we have to use massive build machines, so we hit this race condition like clockwork.

```
07:08:44 Traceback (most recent call last):
07:08:44   File "bin/assembler.py", line 14, in <module>
07:08:44 Traceback (most recent call last):
07:08:44   File "bin/assembler.py", line 14, in <module>
07:08:44         from jinja2 import Template, FileSystemLoader, Environment
07:08:44 from jinja2 import Template, FileSystemLoader, Environment
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/__init__.py", line 33, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/__init__.py", line 33, in <module>
07:08:44   File "bin/assembler.py", line 14, in <module>
07:08:44     from jinja2 import Template, FileSystemLoader, Environment
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/__init__.py", line 33, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/environment.py", line 13, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/environment.py", line 13, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/environment.py", line 13, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/nodes.py", line 19, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/nodes.py", line 19, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/nodes.py", line 19, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/utils.py", line 531, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/utils.py", line 531, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/Jinja2-2.8-py2.7.egg/jinja2/utils.py", line 531, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/__init__.py", line 292, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/__init__.py", line 292, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/__init__.py", line 292, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/_speedups.py", line 7, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/_speedups.py", line 7, in <module>
07:08:44   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/_speedups.py", line 7, in <module>
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/_speedups.py", line 4, in __bootstrap__
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/_speedups.py", line 4, in __bootstrap__
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/MarkupSafe-0.23-py2.7-linux-x86_64.egg/markupsafe/_speedups.py", line 4, in __bootstrap__
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1133, in resource_filename
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1133, in resource_filename
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1133, in resource_filename
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1639, in get_resource_filename
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1639, in get_resource_filename
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1639, in get_resource_filename
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1669, in _extract_resource
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1669, in _extract_resource
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1669, in _extract_resource
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1200, in get_cache_path
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1200, in get_cache_path
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1200, in get_cache_path
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1180, in extraction_error
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1180, in extraction_error
07:08:45   File "/home/jenkins/workspace/job-hidden/master/.build_python/lib/python2.7/site-packages/setuptools-39.2.0-py2.7.egg/pkg_resources/__init__.py", line 1180, in extraction_error
07:08:45 pkg_resources.ExtractionErrorpkg_resources.: ExtractionError: pkg_resources.ExtractionError: Can't extract file(s) to egg cache
07:08:45 
07:08:45 The following error occurred while trying to extract file(s)
07:08:45 to the Python egg cache:
07:08:45 
07:08:45   [Errno 17] File exists: '/home/jenkins/.cache/Python-Eggs'
07:08:45 
07:08:45 The Python egg cache directory is currently set to:
07:08:45 
07:08:45   /home/jenkins/.cache/Python-Eggs
07:08:45 
07:08:45 Perhaps your account does not have write access to this directory?
07:08:45 You can change the cache directory by setting the PYTHON_EGG_CACHE
07:08:45 environment variable to point to an accessible directory.
07:08:45 Can't extract file(s) to egg cache
```

Resolving patch:
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -47,6 +47,11 @@
     # 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):


Test patch:
diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py
--- a/pkg_resources/tests/test_pkg_resources.py
+++ b/pkg_resources/tests/test_pkg_resources.py
@@ -12,6 +12,11 @@
 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 @@ def test_get_cache_path(self):
         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.
     """