about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py')
-rw-r--r--.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py1476
1 files changed, 1476 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py b/.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py
new file mode 100644
index 00000000..b58b0b66
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/setuptools/tests/test_easy_install.py
@@ -0,0 +1,1476 @@
+"""Easy install Tests"""
+
+import contextlib
+import io
+import itertools
+import logging
+import os
+import pathlib
+import re
+import site
+import subprocess
+import sys
+import tarfile
+import tempfile
+import time
+import warnings
+import zipfile
+from pathlib import Path
+from typing import NamedTuple
+from unittest import mock
+
+import pytest
+from jaraco import path
+
+import pkg_resources
+import setuptools.command.easy_install as ei
+from pkg_resources import Distribution as PRDistribution, normalize_path, working_set
+from setuptools import sandbox
+from setuptools._normalization import safer_name
+from setuptools.command.easy_install import PthDistributions
+from setuptools.dist import Distribution
+from setuptools.sandbox import run_setup
+from setuptools.tests import fail_on_ascii
+from setuptools.tests.server import MockServer, path_to_url
+
+from . import contexts
+from .textwrap import DALS
+
+import distutils.errors
+
+
+@pytest.fixture(autouse=True)
+def pip_disable_index(monkeypatch):
+    """
+    Important: Disable the default index for pip to avoid
+    querying packages in the index and potentially resolving
+    and installing packages there.
+    """
+    monkeypatch.setenv('PIP_NO_INDEX', 'true')
+
+
+class FakeDist:
+    def get_entry_map(self, group):
+        if group != 'console_scripts':
+            return {}
+        return {'name': 'ep'}
+
+    def as_requirement(self):
+        return 'spec'
+
+
+SETUP_PY = DALS(
+    """
+    from setuptools import setup
+
+    setup()
+    """
+)
+
+
+class TestEasyInstallTest:
+    def test_get_script_args(self):
+        header = ei.CommandSpec.best().from_environment().as_header()
+        dist = FakeDist()
+        args = next(ei.ScriptWriter.get_args(dist))
+        _name, script = itertools.islice(args, 2)
+        assert script.startswith(header)
+        assert "'spec'" in script
+        assert "'console_scripts'" in script
+        assert "'name'" in script
+        assert re.search('^# EASY-INSTALL-ENTRY-SCRIPT', script, flags=re.MULTILINE)
+
+    def test_no_find_links(self):
+        # new option '--no-find-links', that blocks find-links added at
+        # the project level
+        dist = Distribution()
+        cmd = ei.easy_install(dist)
+        cmd.check_pth_processing = lambda: True
+        cmd.no_find_links = True
+        cmd.find_links = ['link1', 'link2']
+        cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
+        cmd.args = ['ok']
+        cmd.ensure_finalized()
+        assert cmd.package_index.scanned_urls == {}
+
+        # let's try without it (default behavior)
+        cmd = ei.easy_install(dist)
+        cmd.check_pth_processing = lambda: True
+        cmd.find_links = ['link1', 'link2']
+        cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
+        cmd.args = ['ok']
+        cmd.ensure_finalized()
+        keys = sorted(cmd.package_index.scanned_urls.keys())
+        assert keys == ['link1', 'link2']
+
+    def test_write_exception(self):
+        """
+        Test that `cant_write_to_target` is rendered as a DistutilsError.
+        """
+        dist = Distribution()
+        cmd = ei.easy_install(dist)
+        cmd.install_dir = os.getcwd()
+        with pytest.raises(distutils.errors.DistutilsError):
+            cmd.cant_write_to_target()
+
+    def test_all_site_dirs(self, monkeypatch):
+        """
+        get_site_dirs should always return site dirs reported by
+        site.getsitepackages.
+        """
+        path = normalize_path('/setuptools/test/site-packages')
+
+        def mock_gsp():
+            return [path]
+
+        monkeypatch.setattr(site, 'getsitepackages', mock_gsp, raising=False)
+        assert path in ei.get_site_dirs()
+
+    def test_all_site_dirs_works_without_getsitepackages(self, monkeypatch):
+        monkeypatch.delattr(site, 'getsitepackages', raising=False)
+        assert ei.get_site_dirs()
+
+    @pytest.fixture
+    def sdist_unicode(self, tmpdir):
+        files = [
+            (
+                'setup.py',
+                DALS(
+                    """
+                    import setuptools
+                    setuptools.setup(
+                        name="setuptools-test-unicode",
+                        version="1.0",
+                        packages=["mypkg"],
+                        include_package_data=True,
+                    )
+                    """
+                ),
+            ),
+            (
+                'mypkg/__init__.py',
+                "",
+            ),
+            (
+                'mypkg/☃.txt',
+                "",
+            ),
+        ]
+        sdist_name = 'setuptools-test-unicode-1.0.zip'
+        sdist = tmpdir / sdist_name
+        # can't use make_sdist, because the issue only occurs
+        #  with zip sdists.
+        sdist_zip = zipfile.ZipFile(str(sdist), 'w')
+        for filename, content in files:
+            sdist_zip.writestr(filename, content)
+        sdist_zip.close()
+        return str(sdist)
+
+    @fail_on_ascii
+    def test_unicode_filename_in_sdist(self, sdist_unicode, tmpdir, monkeypatch):
+        """
+        The install command should execute correctly even if
+        the package has unicode filenames.
+        """
+        dist = Distribution({'script_args': ['easy_install']})
+        target = (tmpdir / 'target').ensure_dir()
+        cmd = ei.easy_install(
+            dist,
+            install_dir=str(target),
+            args=['x'],
+        )
+        monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target))
+        cmd.ensure_finalized()
+        cmd.easy_install(sdist_unicode)
+
+    @pytest.fixture
+    def sdist_unicode_in_script(self, tmpdir):
+        files = [
+            (
+                "setup.py",
+                DALS(
+                    """
+                    import setuptools
+                    setuptools.setup(
+                        name="setuptools-test-unicode",
+                        version="1.0",
+                        packages=["mypkg"],
+                        include_package_data=True,
+                        scripts=['mypkg/unicode_in_script'],
+                    )
+                    """
+                ),
+            ),
+            ("mypkg/__init__.py", ""),
+            (
+                "mypkg/unicode_in_script",
+                DALS(
+                    """
+                    #!/bin/sh
+                    # á
+
+                    non_python_fn() {
+                    }
+                """
+                ),
+            ),
+        ]
+        sdist_name = "setuptools-test-unicode-script-1.0.zip"
+        sdist = tmpdir / sdist_name
+        # can't use make_sdist, because the issue only occurs
+        #  with zip sdists.
+        sdist_zip = zipfile.ZipFile(str(sdist), "w")
+        for filename, content in files:
+            sdist_zip.writestr(filename, content.encode('utf-8'))
+        sdist_zip.close()
+        return str(sdist)
+
+    @fail_on_ascii
+    def test_unicode_content_in_sdist(
+        self, sdist_unicode_in_script, tmpdir, monkeypatch
+    ):
+        """
+        The install command should execute correctly even if
+        the package has unicode in scripts.
+        """
+        dist = Distribution({"script_args": ["easy_install"]})
+        target = (tmpdir / "target").ensure_dir()
+        cmd = ei.easy_install(dist, install_dir=str(target), args=["x"])
+        monkeypatch.setitem(os.environ, "PYTHONPATH", str(target))
+        cmd.ensure_finalized()
+        cmd.easy_install(sdist_unicode_in_script)
+
+    @pytest.fixture
+    def sdist_script(self, tmpdir):
+        files = [
+            (
+                'setup.py',
+                DALS(
+                    """
+                    import setuptools
+                    setuptools.setup(
+                        name="setuptools-test-script",
+                        version="1.0",
+                        scripts=["mypkg_script"],
+                    )
+                    """
+                ),
+            ),
+            (
+                'mypkg_script',
+                DALS(
+                    """
+                     #/usr/bin/python
+                     print('mypkg_script')
+                     """
+                ),
+            ),
+        ]
+        sdist_name = 'setuptools-test-script-1.0.zip'
+        sdist = str(tmpdir / sdist_name)
+        make_sdist(sdist, files)
+        return sdist
+
+    @pytest.mark.skipif(
+        not sys.platform.startswith('linux'), reason="Test can only be run on Linux"
+    )
+    def test_script_install(self, sdist_script, tmpdir, monkeypatch):
+        """
+        Check scripts are installed.
+        """
+        dist = Distribution({'script_args': ['easy_install']})
+        target = (tmpdir / 'target').ensure_dir()
+        cmd = ei.easy_install(
+            dist,
+            install_dir=str(target),
+            args=['x'],
+        )
+        monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target))
+        cmd.ensure_finalized()
+        cmd.easy_install(sdist_script)
+        assert (target / 'mypkg_script').exists()
+
+
+@pytest.mark.filterwarnings('ignore:Unbuilt egg')
+class TestPTHFileWriter:
+    def test_add_from_cwd_site_sets_dirty(self):
+        """a pth file manager should set dirty
+        if a distribution is in site but also the cwd
+        """
+        pth = PthDistributions('does-not_exist', [os.getcwd()])
+        assert not pth.dirty
+        pth.add(PRDistribution(os.getcwd()))
+        assert pth.dirty
+
+    def test_add_from_site_is_ignored(self):
+        location = '/test/location/does-not-have-to-exist'
+        # PthDistributions expects all locations to be normalized
+        location = pkg_resources.normalize_path(location)
+        pth = PthDistributions(
+            'does-not_exist',
+            [
+                location,
+            ],
+        )
+        assert not pth.dirty
+        pth.add(PRDistribution(location))
+        assert not pth.dirty
+
+    def test_many_pth_distributions_merge_together(self, tmpdir):
+        """
+        If the pth file is modified under the hood, then PthDistribution
+        will refresh its content before saving, merging contents when
+        necessary.
+        """
+        # putting the pth file in a dedicated sub-folder,
+        pth_subdir = tmpdir.join("pth_subdir")
+        pth_subdir.mkdir()
+        pth_path = str(pth_subdir.join("file1.pth"))
+        pth1 = PthDistributions(pth_path)
+        pth2 = PthDistributions(pth_path)
+        assert pth1.paths == pth2.paths == [], (
+            "unless there would be some default added at some point"
+        )
+        # and so putting the src_subdir in folder distinct than the pth one,
+        # so to keep it absolute by PthDistributions
+        new_src_path = tmpdir.join("src_subdir")
+        new_src_path.mkdir()  # must exist to be accounted
+        new_src_path_str = str(new_src_path)
+        pth1.paths.append(new_src_path_str)
+        pth1.save()
+        assert pth1.paths, (
+            "the new_src_path added must still be present/valid in pth1 after save"
+        )
+        # now,
+        assert new_src_path_str not in pth2.paths, (
+            "right before we save the entry should still not be present"
+        )
+        pth2.save()
+        assert new_src_path_str in pth2.paths, (
+            "the new_src_path entry should have been added by pth2 with its save() call"
+        )
+        assert pth2.paths[-1] == new_src_path, (
+            "and it should match exactly on the last entry actually "
+            "given we append to it in save()"
+        )
+        # finally,
+        assert PthDistributions(pth_path).paths == pth2.paths, (
+            "and we should have the exact same list at the end "
+            "with a fresh PthDistributions instance"
+        )
+
+
+@pytest.fixture
+def setup_context(tmpdir):
+    with (tmpdir / 'setup.py').open('w', encoding="utf-8") as f:
+        f.write(SETUP_PY)
+    with tmpdir.as_cwd():
+        yield tmpdir
+
+
+@pytest.mark.usefixtures("user_override")
+@pytest.mark.usefixtures("setup_context")
+class TestUserInstallTest:
+    # prevent check that site-packages is writable. easy_install
+    # shouldn't be writing to system site-packages during finalize
+    # options, but while it does, bypass the behavior.
+    prev_sp_write = mock.patch(
+        'setuptools.command.easy_install.easy_install.check_site_dir',
+        mock.Mock(),
+    )
+
+    # simulate setuptools installed in user site packages
+    @mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE)
+    @mock.patch('site.ENABLE_USER_SITE', True)
+    @prev_sp_write
+    def test_user_install_not_implied_user_site_enabled(self):
+        self.assert_not_user_site()
+
+    @mock.patch('site.ENABLE_USER_SITE', False)
+    @prev_sp_write
+    def test_user_install_not_implied_user_site_disabled(self):
+        self.assert_not_user_site()
+
+    @staticmethod
+    def assert_not_user_site():
+        # create a finalized easy_install command
+        dist = Distribution()
+        dist.script_name = 'setup.py'
+        cmd = ei.easy_install(dist)
+        cmd.args = ['py']
+        cmd.ensure_finalized()
+        assert not cmd.user, 'user should not be implied'
+
+    def test_multiproc_atexit(self):
+        pytest.importorskip('multiprocessing')
+
+        log = logging.getLogger('test_easy_install')
+        logging.basicConfig(level=logging.INFO, stream=sys.stderr)
+        log.info('this should not break')
+
+    @pytest.fixture
+    def foo_package(self, tmpdir):
+        egg_file = tmpdir / 'foo-1.0.egg-info'
+        with egg_file.open('w') as f:
+            f.write('Name: foo\n')
+        return str(tmpdir)
+
+    @pytest.fixture
+    def install_target(self, tmpdir):
+        target = str(tmpdir)
+        with mock.patch('sys.path', sys.path + [target]):
+            python_path = os.path.pathsep.join(sys.path)
+            with mock.patch.dict(os.environ, PYTHONPATH=python_path):
+                yield target
+
+    def test_local_index(self, foo_package, install_target):
+        """
+        The local index must be used when easy_install locates installed
+        packages.
+        """
+        dist = Distribution()
+        dist.script_name = 'setup.py'
+        cmd = ei.easy_install(dist)
+        cmd.install_dir = install_target
+        cmd.args = ['foo']
+        cmd.ensure_finalized()
+        cmd.local_index.scan([foo_package])
+        res = cmd.easy_install('foo')
+        actual = os.path.normcase(os.path.realpath(res.location))
+        expected = os.path.normcase(os.path.realpath(foo_package))
+        assert actual == expected
+
+    @contextlib.contextmanager
+    def user_install_setup_context(self, *args, **kwargs):
+        """
+        Wrap sandbox.setup_context to patch easy_install in that context to
+        appear as user-installed.
+        """
+        with self.orig_context(*args, **kwargs):
+            import setuptools.command.easy_install as ei
+
+            ei.__file__ = site.USER_SITE
+            yield
+
+    def patched_setup_context(self):
+        self.orig_context = sandbox.setup_context
+
+        return mock.patch(
+            'setuptools.sandbox.setup_context',
+            self.user_install_setup_context,
+        )
+
+
+@pytest.fixture
+def distutils_package():
+    distutils_setup_py = SETUP_PY.replace(
+        'from setuptools import setup',
+        'from distutils.core import setup',
+    )
+    with contexts.tempdir(cd=os.chdir):
+        with open('setup.py', 'w', encoding="utf-8") as f:
+            f.write(distutils_setup_py)
+        yield
+
+
+@pytest.mark.usefixtures("distutils_package")
+class TestDistutilsPackage:
+    def test_bdist_egg_available_on_distutils_pkg(self):
+        run_setup('setup.py', ['bdist_egg'])
+
+
+@pytest.fixture
+def mock_index():
+    # set up a server which will simulate an alternate package index.
+    p_index = MockServer()
+    if p_index.server_port == 0:
+        # Some platforms (Jython) don't find a port to which to bind,
+        # so skip test for them.
+        pytest.skip("could not find a valid port")
+    p_index.start()
+    return p_index
+
+
+class TestInstallRequires:
+    def test_setup_install_includes_dependencies(self, tmp_path, mock_index):
+        """
+        When ``python setup.py install`` is called directly, it will use easy_install
+        to fetch dependencies.
+        """
+        # TODO: Remove these tests once `setup.py install` is completely removed
+        project_root = tmp_path / "project"
+        project_root.mkdir(exist_ok=True)
+        install_root = tmp_path / "install"
+        install_root.mkdir(exist_ok=True)
+
+        self.create_project(project_root)
+        cmd = [
+            sys.executable,
+            '-c',
+            '__import__("setuptools").setup()',
+            'install',
+            '--install-base',
+            str(install_root),
+            '--install-lib',
+            str(install_root),
+            '--install-headers',
+            str(install_root),
+            '--install-scripts',
+            str(install_root),
+            '--install-data',
+            str(install_root),
+            '--install-purelib',
+            str(install_root),
+            '--install-platlib',
+            str(install_root),
+        ]
+        env = {**os.environ, "__EASYINSTALL_INDEX": mock_index.url}
+        cp = subprocess.run(
+            cmd,
+            cwd=str(project_root),
+            env=env,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT,
+            text=True,
+            encoding="utf-8",
+        )
+        assert cp.returncode != 0
+        try:
+            assert '/does-not-exist/' in {r.path for r in mock_index.requests}
+            assert next(
+                line
+                for line in cp.stdout.splitlines()
+                if "not find suitable distribution for" in line
+                and "does-not-exist" in line
+            )
+        except Exception:
+            if "failed to get random numbers" in cp.stdout:
+                pytest.xfail(f"{sys.platform} failure - {cp.stdout}")
+            raise
+
+    def create_project(self, root):
+        config = """
+        [metadata]
+        name = project
+        version = 42
+
+        [options]
+        install_requires = does-not-exist
+        py_modules = mod
+        """
+        (root / 'setup.cfg').write_text(DALS(config), encoding="utf-8")
+        (root / 'mod.py').touch()
+
+
+class TestSetupRequires:
+    def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch):
+        """
+        When easy_install installs a source distribution which specifies
+        setup_requires, it should honor the fetch parameters (such as
+        index-url, and find-links).
+        """
+        monkeypatch.setenv('PIP_RETRIES', '0')
+        monkeypatch.setenv('PIP_TIMEOUT', '0')
+        monkeypatch.setenv('PIP_NO_INDEX', 'false')
+        with contexts.quiet():
+            # create an sdist that has a build-time dependency.
+            with TestSetupRequires.create_sdist() as dist_file:
+                with contexts.tempdir() as temp_install_dir:
+                    with contexts.environment(PYTHONPATH=temp_install_dir):
+                        cmd = [
+                            sys.executable,
+                            '-c',
+                            '__import__("setuptools").setup()',
+                            'easy_install',
+                            '--index-url',
+                            mock_index.url,
+                            '--exclude-scripts',
+                            '--install-dir',
+                            temp_install_dir,
+                            dist_file,
+                        ]
+                        subprocess.Popen(cmd).wait()
+        # there should have been one requests to the server
+        assert [r.path for r in mock_index.requests] == ['/does-not-exist/']
+
+    @staticmethod
+    @contextlib.contextmanager
+    def create_sdist():
+        """
+        Return an sdist with a setup_requires dependency (of something that
+        doesn't exist)
+        """
+        with contexts.tempdir() as dir:
+            dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz')
+            make_sdist(
+                dist_path,
+                [
+                    (
+                        'setup.py',
+                        DALS(
+                            """
+                    import setuptools
+                    setuptools.setup(
+                        name="setuptools-test-fetcher",
+                        version="1.0",
+                        setup_requires = ['does-not-exist'],
+                    )
+                """
+                        ),
+                    ),
+                    ('setup.cfg', ''),
+                ],
+            )
+            yield dist_path
+
+    use_setup_cfg = (
+        (),
+        ('dependency_links',),
+        ('setup_requires',),
+        ('dependency_links', 'setup_requires'),
+    )
+
+    @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
+    def test_setup_requires_overrides_version_conflict(self, use_setup_cfg):
+        """
+        Regression test for distribution issue 323:
+        https://bitbucket.org/tarek/distribute/issues/323
+
+        Ensures that a distribution's setup_requires requirements can still be
+        installed and used locally even if a conflicting version of that
+        requirement is already on the path.
+        """
+
+        fake_dist = PRDistribution(
+            'does-not-matter', project_name='foobar', version='0.0'
+        )
+        working_set.add(fake_dist)
+
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                test_pkg = create_setup_requires_package(
+                    temp_dir, use_setup_cfg=use_setup_cfg
+                )
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                with contexts.quiet() as (stdout, _stderr):
+                    # Don't even need to install the package, just
+                    # running the setup.py at all is sufficient
+                    run_setup(test_setup_py, ['--name'])
+
+                lines = stdout.readlines()
+                assert len(lines) > 0
+                assert lines[-1].strip() == 'test_pkg'
+
+    @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
+    def test_setup_requires_override_nspkg(self, use_setup_cfg):
+        """
+        Like ``test_setup_requires_overrides_version_conflict`` but where the
+        ``setup_requires`` package is part of a namespace package that has
+        *already* been imported.
+        """
+
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                foobar_1_archive = os.path.join(temp_dir, 'foo_bar-0.1.tar.gz')
+                make_nspkg_sdist(foobar_1_archive, 'foo.bar', '0.1')
+                # Now actually go ahead an extract to the temp dir and add the
+                # extracted path to sys.path so foo.bar v0.1 is importable
+                foobar_1_dir = os.path.join(temp_dir, 'foo_bar-0.1')
+                os.mkdir(foobar_1_dir)
+                with tarfile.open(foobar_1_archive) as tf:
+                    tf.extraction_filter = lambda member, path: member
+                    tf.extractall(foobar_1_dir)
+                sys.path.insert(1, foobar_1_dir)
+
+                dist = PRDistribution(
+                    foobar_1_dir, project_name='foo.bar', version='0.1'
+                )
+                working_set.add(dist)
+
+                template = DALS(
+                    """\
+                    import foo  # Even with foo imported first the
+                                # setup_requires package should override
+                    import setuptools
+                    setuptools.setup(**%r)
+
+                    if not (hasattr(foo, '__path__') and
+                            len(foo.__path__) == 2):
+                        print('FAIL')
+
+                    if 'foo_bar-0.2' not in foo.__path__[0]:
+                        print('FAIL')
+                """
+                )
+
+                test_pkg = create_setup_requires_package(
+                    temp_dir,
+                    'foo.bar',
+                    '0.2',
+                    make_nspkg_sdist,
+                    template,
+                    use_setup_cfg=use_setup_cfg,
+                )
+
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+
+                with contexts.quiet() as (stdout, _stderr):
+                    try:
+                        # Don't even need to install the package, just
+                        # running the setup.py at all is sufficient
+                        run_setup(test_setup_py, ['--name'])
+                    except pkg_resources.VersionConflict:  # pragma: nocover
+                        pytest.fail(
+                            'Installing setup.py requirements caused a VersionConflict'
+                        )
+
+                assert 'FAIL' not in stdout.getvalue()
+                lines = stdout.readlines()
+                assert len(lines) > 0
+                assert lines[-1].strip() == 'test_pkg'
+
+    @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
+    def test_setup_requires_with_attr_version(self, use_setup_cfg):
+        def make_dependency_sdist(dist_path, distname, version):
+            files = [
+                (
+                    'setup.py',
+                    DALS(
+                        f"""
+                    import setuptools
+                    setuptools.setup(
+                        name={distname!r},
+                        version={version!r},
+                        py_modules=[{distname!r}],
+                    )
+                    """
+                    ),
+                ),
+                (
+                    distname + '.py',
+                    DALS(
+                        """
+                    version = 42
+                    """
+                    ),
+                ),
+            ]
+            make_sdist(dist_path, files)
+
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                test_pkg = create_setup_requires_package(
+                    temp_dir,
+                    setup_attrs=dict(version='attr: foobar.version'),
+                    make_package=make_dependency_sdist,
+                    use_setup_cfg=use_setup_cfg + ('version',),
+                )
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                with contexts.quiet() as (stdout, _stderr):
+                    run_setup(test_setup_py, ['--version'])
+                lines = stdout.readlines()
+                assert len(lines) > 0
+                assert lines[-1].strip() == '42'
+
+    def test_setup_requires_honors_pip_env(self, mock_index, monkeypatch):
+        monkeypatch.setenv('PIP_RETRIES', '0')
+        monkeypatch.setenv('PIP_TIMEOUT', '0')
+        monkeypatch.setenv('PIP_NO_INDEX', 'false')
+        monkeypatch.setenv('PIP_INDEX_URL', mock_index.url)
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                test_pkg = create_setup_requires_package(
+                    temp_dir,
+                    'python-xlib',
+                    '0.19',
+                    setup_attrs=dict(dependency_links=[]),
+                )
+                test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+                with open(test_setup_cfg, 'w', encoding="utf-8") as fp:
+                    fp.write(
+                        DALS(
+                            """
+                        [easy_install]
+                        index_url = https://pypi.org/legacy/
+                        """
+                        )
+                    )
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                with pytest.raises(distutils.errors.DistutilsError):
+                    run_setup(test_setup_py, ['--version'])
+        assert len(mock_index.requests) == 1
+        assert mock_index.requests[0].path == '/python-xlib/'
+
+    def test_setup_requires_with_pep508_url(self, mock_index, monkeypatch):
+        monkeypatch.setenv('PIP_RETRIES', '0')
+        monkeypatch.setenv('PIP_TIMEOUT', '0')
+        monkeypatch.setenv('PIP_INDEX_URL', mock_index.url)
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                dep_sdist = os.path.join(temp_dir, 'dep.tar.gz')
+                make_trivial_sdist(dep_sdist, 'dependency', '42')
+                dep_url = path_to_url(dep_sdist, authority='localhost')
+                test_pkg = create_setup_requires_package(
+                    temp_dir,
+                    # Ignored (overridden by setup_attrs)
+                    'python-xlib',
+                    '0.19',
+                    setup_attrs=dict(setup_requires=f'dependency @ {dep_url}'),
+                )
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                run_setup(test_setup_py, ['--version'])
+        assert len(mock_index.requests) == 0
+
+    def test_setup_requires_with_allow_hosts(self, mock_index):
+        """The `allow-hosts` option in not supported anymore."""
+        files = {
+            'test_pkg': {
+                'setup.py': DALS(
+                    """
+                    from setuptools import setup
+                    setup(setup_requires='python-xlib')
+                    """
+                ),
+                'setup.cfg': DALS(
+                    """
+                    [easy_install]
+                    allow_hosts = *
+                    """
+                ),
+            }
+        }
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                path.build(files, prefix=temp_dir)
+                setup_py = str(pathlib.Path(temp_dir, 'test_pkg', 'setup.py'))
+                with pytest.raises(distutils.errors.DistutilsError):
+                    run_setup(setup_py, ['--version'])
+        assert len(mock_index.requests) == 0
+
+    def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir):
+        """Check `python_requires` is honored."""
+        monkeypatch.setenv('PIP_RETRIES', '0')
+        monkeypatch.setenv('PIP_TIMEOUT', '0')
+        monkeypatch.setenv('PIP_NO_INDEX', '1')
+        monkeypatch.setenv('PIP_VERBOSE', '1')
+        dep_1_0_sdist = 'dep-1.0.tar.gz'
+        dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist))
+        dep_1_0_python_requires = '>=2.7'
+        make_python_requires_sdist(
+            str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires
+        )
+        dep_2_0_sdist = 'dep-2.0.tar.gz'
+        dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist))
+        dep_2_0_python_requires = (
+            f'!={sys.version_info.major}.{sys.version_info.minor}.*'
+        )
+        make_python_requires_sdist(
+            str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires
+        )
+        index = tmpdir / 'index.html'
+        index.write_text(
+            DALS(
+                """
+            <!DOCTYPE html>
+            <html><head><title>Links for dep</title></head>
+            <body>
+                <h1>Links for dep</h1>
+                <a href="{dep_1_0_url}"\
+data-requires-python="{dep_1_0_python_requires}">{dep_1_0_sdist}</a><br/>
+                <a href="{dep_2_0_url}"\
+data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/>
+            </body>
+            </html>
+            """
+            ).format(
+                dep_1_0_url=dep_1_0_url,
+                dep_1_0_sdist=dep_1_0_sdist,
+                dep_1_0_python_requires=dep_1_0_python_requires,
+                dep_2_0_url=dep_2_0_url,
+                dep_2_0_sdist=dep_2_0_sdist,
+                dep_2_0_python_requires=dep_2_0_python_requires,
+            ),
+            'utf-8',
+        )
+        index_url = path_to_url(str(index))
+        with contexts.save_pkg_resources_state():
+            test_pkg = create_setup_requires_package(
+                str(tmpdir),
+                'python-xlib',
+                '0.19',  # Ignored (overridden by setup_attrs).
+                setup_attrs=dict(setup_requires='dep', dependency_links=[index_url]),
+            )
+            test_setup_py = os.path.join(test_pkg, 'setup.py')
+            run_setup(test_setup_py, ['--version'])
+        eggs = list(
+            map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))
+        )
+        assert eggs == ['dep 1.0']
+
+    @pytest.mark.parametrize('with_dependency_links_in_setup_py', (False, True))
+    def test_setup_requires_with_find_links_in_setup_cfg(
+        self, monkeypatch, with_dependency_links_in_setup_py
+    ):
+        monkeypatch.setenv('PIP_RETRIES', '0')
+        monkeypatch.setenv('PIP_TIMEOUT', '0')
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                make_trivial_sdist(
+                    os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 'python-xlib', '42'
+                )
+                test_pkg = os.path.join(temp_dir, 'test_pkg')
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+                os.mkdir(test_pkg)
+                with open(test_setup_py, 'w', encoding="utf-8") as fp:
+                    if with_dependency_links_in_setup_py:
+                        dependency_links = [os.path.join(temp_dir, 'links')]
+                    else:
+                        dependency_links = []
+                    fp.write(
+                        DALS(
+                            """
+                        from setuptools import installer, setup
+                        setup(setup_requires='python-xlib==42',
+                        dependency_links={dependency_links!r})
+                        """
+                        ).format(dependency_links=dependency_links)
+                    )
+                with open(test_setup_cfg, 'w', encoding="utf-8") as fp:
+                    fp.write(
+                        DALS(
+                            """
+                        [easy_install]
+                        index_url = {index_url}
+                        find_links = {find_links}
+                        """
+                        ).format(
+                            index_url=os.path.join(temp_dir, 'index'),
+                            find_links=temp_dir,
+                        )
+                    )
+                run_setup(test_setup_py, ['--version'])
+
+    def test_setup_requires_with_transitive_extra_dependency(self, monkeypatch):
+        """
+        Use case: installing a package with a build dependency on
+        an already installed `dep[extra]`, which in turn depends
+        on `extra_dep` (whose is not already installed).
+        """
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                # Create source distribution for `extra_dep`.
+                make_trivial_sdist(
+                    os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 'extra_dep', '1.0'
+                )
+                # Create source tree for `dep`.
+                dep_pkg = os.path.join(temp_dir, 'dep')
+                os.mkdir(dep_pkg)
+                path.build(
+                    {
+                        'setup.py': DALS(
+                            """
+                          import setuptools
+                          setuptools.setup(
+                              name='dep', version='2.0',
+                              extras_require={'extra': ['extra_dep']},
+                          )
+                         """
+                        ),
+                        'setup.cfg': '',
+                    },
+                    prefix=dep_pkg,
+                )
+                # "Install" dep.
+                run_setup(os.path.join(dep_pkg, 'setup.py'), ['dist_info'])
+                working_set.add_entry(dep_pkg)
+                # Create source tree for test package.
+                test_pkg = os.path.join(temp_dir, 'test_pkg')
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                os.mkdir(test_pkg)
+                with open(test_setup_py, 'w', encoding="utf-8") as fp:
+                    fp.write(
+                        DALS(
+                            """
+                        from setuptools import installer, setup
+                        setup(setup_requires='dep[extra]')
+                        """
+                        )
+                    )
+                # Check...
+                monkeypatch.setenv('PIP_FIND_LINKS', str(temp_dir))
+                monkeypatch.setenv('PIP_NO_INDEX', '1')
+                monkeypatch.setenv('PIP_RETRIES', '0')
+                monkeypatch.setenv('PIP_TIMEOUT', '0')
+                run_setup(test_setup_py, ['--version'])
+
+    def test_setup_requires_with_distutils_command_dep(self, monkeypatch):
+        """
+        Use case: ensure build requirements' extras
+        are properly installed and activated.
+        """
+        with contexts.save_pkg_resources_state():
+            with contexts.tempdir() as temp_dir:
+                # Create source distribution for `extra_dep`.
+                make_sdist(
+                    os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'),
+                    [
+                        (
+                            'setup.py',
+                            DALS(
+                                """
+                          import setuptools
+                          setuptools.setup(
+                              name='extra_dep',
+                              version='1.0',
+                              py_modules=['extra_dep'],
+                          )
+                          """
+                            ),
+                        ),
+                        ('setup.cfg', ''),
+                        ('extra_dep.py', ''),
+                    ],
+                )
+                # Create source tree for `epdep`.
+                dep_pkg = os.path.join(temp_dir, 'epdep')
+                os.mkdir(dep_pkg)
+                path.build(
+                    {
+                        'setup.py': DALS(
+                            """
+                          import setuptools
+                          setuptools.setup(
+                              name='dep', version='2.0',
+                              py_modules=['epcmd'],
+                              extras_require={'extra': ['extra_dep']},
+                              entry_points='''
+                                           [distutils.commands]
+                                           epcmd = epcmd:epcmd [extra]
+                                           ''',
+                          )
+                         """
+                        ),
+                        'setup.cfg': '',
+                        'epcmd.py': DALS(
+                            """
+                                     from distutils.command.build_py import build_py
+
+                                     import extra_dep
+
+                                     class epcmd(build_py):
+                                         pass
+                                     """
+                        ),
+                    },
+                    prefix=dep_pkg,
+                )
+                # "Install" dep.
+                run_setup(os.path.join(dep_pkg, 'setup.py'), ['dist_info'])
+                working_set.add_entry(dep_pkg)
+                # Create source tree for test package.
+                test_pkg = os.path.join(temp_dir, 'test_pkg')
+                test_setup_py = os.path.join(test_pkg, 'setup.py')
+                os.mkdir(test_pkg)
+                with open(test_setup_py, 'w', encoding="utf-8") as fp:
+                    fp.write(
+                        DALS(
+                            """
+                        from setuptools import installer, setup
+                        setup(setup_requires='dep[extra]')
+                        """
+                        )
+                    )
+                # Check...
+                monkeypatch.setenv('PIP_FIND_LINKS', str(temp_dir))
+                monkeypatch.setenv('PIP_NO_INDEX', '1')
+                monkeypatch.setenv('PIP_RETRIES', '0')
+                monkeypatch.setenv('PIP_TIMEOUT', '0')
+                run_setup(test_setup_py, ['epcmd'])
+
+
+def make_trivial_sdist(dist_path, distname, version):
+    """
+    Create a simple sdist tarball at dist_path, containing just a simple
+    setup.py.
+    """
+
+    make_sdist(
+        dist_path,
+        [
+            (
+                'setup.py',
+                DALS(
+                    f"""\
+             import setuptools
+             setuptools.setup(
+                 name={distname!r},
+                 version={version!r}
+             )
+         """
+                ),
+            ),
+            ('setup.cfg', ''),
+        ],
+    )
+
+
+def make_nspkg_sdist(dist_path, distname, version):
+    """
+    Make an sdist tarball with distname and version which also contains one
+    package with the same name as distname.  The top-level package is
+    designated a namespace package).
+    """
+    # Assert that the distname contains at least one period
+    assert '.' in distname
+
+    parts = distname.split('.')
+    nspackage = parts[0]
+
+    packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)]
+
+    setup_py = DALS(
+        f"""\
+        import setuptools
+        setuptools.setup(
+            name={distname!r},
+            version={version!r},
+            packages={packages!r},
+            namespace_packages=[{nspackage!r}]
+        )
+    """
+    )
+
+    init = "__import__('pkg_resources').declare_namespace(__name__)"
+
+    files = [('setup.py', setup_py), (os.path.join(nspackage, '__init__.py'), init)]
+    for package in packages[1:]:
+        filename = os.path.join(*(package.split('.') + ['__init__.py']))
+        files.append((filename, ''))
+
+    make_sdist(dist_path, files)
+
+
+def make_python_requires_sdist(dist_path, distname, version, python_requires):
+    make_sdist(
+        dist_path,
+        [
+            (
+                'setup.py',
+                DALS(
+                    """\
+                import setuptools
+                setuptools.setup(
+                  name={name!r},
+                  version={version!r},
+                  python_requires={python_requires!r},
+                )
+                """
+                ).format(
+                    name=distname, version=version, python_requires=python_requires
+                ),
+            ),
+            ('setup.cfg', ''),
+        ],
+    )
+
+
+def make_sdist(dist_path, files):
+    """
+    Create a simple sdist tarball at dist_path, containing the files
+    listed in ``files`` as ``(filename, content)`` tuples.
+    """
+
+    # Distributions with only one file don't play well with pip.
+    assert len(files) > 1
+    with tarfile.open(dist_path, 'w:gz') as dist:
+        for filename, content in files:
+            file_bytes = io.BytesIO(content.encode('utf-8'))
+            file_info = tarfile.TarInfo(name=filename)
+            file_info.size = len(file_bytes.getvalue())
+            file_info.mtime = int(time.time())
+            dist.addfile(file_info, fileobj=file_bytes)
+
+
+def create_setup_requires_package(
+    path,
+    distname='foobar',
+    version='0.1',
+    make_package=make_trivial_sdist,
+    setup_py_template=None,
+    setup_attrs=None,
+    use_setup_cfg=(),
+):
+    """Creates a source tree under path for a trivial test package that has a
+    single requirement in setup_requires--a tarball for that requirement is
+    also created and added to the dependency_links argument.
+
+    ``distname`` and ``version`` refer to the name/version of the package that
+    the test package requires via ``setup_requires``.  The name of the test
+    package itself is just 'test_pkg'.
+    """
+
+    normalized_distname = safer_name(distname)
+    test_setup_attrs = {
+        'name': 'test_pkg',
+        'version': '0.0',
+        'setup_requires': [f'{normalized_distname}=={version}'],
+        'dependency_links': [os.path.abspath(path)],
+    }
+    if setup_attrs:
+        test_setup_attrs.update(setup_attrs)
+
+    test_pkg = os.path.join(path, 'test_pkg')
+    os.mkdir(test_pkg)
+
+    # setup.cfg
+    if use_setup_cfg:
+        options = []
+        metadata = []
+        for name in use_setup_cfg:
+            value = test_setup_attrs.pop(name)
+            if name in 'name version'.split():
+                section = metadata
+            else:
+                section = options
+            if isinstance(value, (tuple, list)):
+                value = ';'.join(value)
+            section.append(f'{name}: {value}')
+        test_setup_cfg_contents = DALS(
+            """
+            [metadata]
+            {metadata}
+            [options]
+            {options}
+            """
+        ).format(
+            options='\n'.join(options),
+            metadata='\n'.join(metadata),
+        )
+    else:
+        test_setup_cfg_contents = ''
+    with open(os.path.join(test_pkg, 'setup.cfg'), 'w', encoding="utf-8") as f:
+        f.write(test_setup_cfg_contents)
+
+    # setup.py
+    if setup_py_template is None:
+        setup_py_template = DALS(
+            """\
+            import setuptools
+            setuptools.setup(**%r)
+        """
+        )
+    with open(os.path.join(test_pkg, 'setup.py'), 'w', encoding="utf-8") as f:
+        f.write(setup_py_template % test_setup_attrs)
+
+    foobar_path = os.path.join(path, f'{normalized_distname}-{version}.tar.gz')
+    make_package(foobar_path, distname, version)
+
+    return test_pkg
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith('java') and ei.is_sh(sys.executable),
+    reason="Test cannot run under java when executable is sh",
+)
+class TestScriptHeader:
+    non_ascii_exe = '/Users/José/bin/python'
+    exe_with_spaces = r'C:\Program Files\Python36\python.exe'
+
+    def test_get_script_header(self):
+        expected = f'#!{ei.nt_quote_arg(os.path.normpath(sys.executable))}\n'
+        actual = ei.ScriptWriter.get_header('#!/usr/local/bin/python')
+        assert actual == expected
+
+    def test_get_script_header_args(self):
+        expected = f'#!{ei.nt_quote_arg(os.path.normpath(sys.executable))} -x\n'
+        actual = ei.ScriptWriter.get_header('#!/usr/bin/python -x')
+        assert actual == expected
+
+    def test_get_script_header_non_ascii_exe(self):
+        actual = ei.ScriptWriter.get_header(
+            '#!/usr/bin/python', executable=self.non_ascii_exe
+        )
+        expected = f'#!{self.non_ascii_exe} -x\n'
+        assert actual == expected
+
+    def test_get_script_header_exe_with_spaces(self):
+        actual = ei.ScriptWriter.get_header(
+            '#!/usr/bin/python', executable='"' + self.exe_with_spaces + '"'
+        )
+        expected = f'#!"{self.exe_with_spaces}"\n'
+        assert actual == expected
+
+
+class TestCommandSpec:
+    def test_custom_launch_command(self):
+        """
+        Show how a custom CommandSpec could be used to specify a #! executable
+        which takes parameters.
+        """
+        cmd = ei.CommandSpec(['/usr/bin/env', 'python3'])
+        assert cmd.as_header() == '#!/usr/bin/env python3\n'
+
+    def test_from_param_for_CommandSpec_is_passthrough(self):
+        """
+        from_param should return an instance of a CommandSpec
+        """
+        cmd = ei.CommandSpec(['python'])
+        cmd_new = ei.CommandSpec.from_param(cmd)
+        assert cmd is cmd_new
+
+    @mock.patch('sys.executable', TestScriptHeader.exe_with_spaces)
+    @mock.patch.dict(os.environ)
+    def test_from_environment_with_spaces_in_executable(self):
+        os.environ.pop('__PYVENV_LAUNCHER__', None)
+        cmd = ei.CommandSpec.from_environment()
+        assert len(cmd) == 1
+        assert cmd.as_header().startswith('#!"')
+
+    def test_from_simple_string_uses_shlex(self):
+        """
+        In order to support `executable = /usr/bin/env my-python`, make sure
+        from_param invokes shlex on that input.
+        """
+        cmd = ei.CommandSpec.from_param('/usr/bin/env my-python')
+        assert len(cmd) == 2
+        assert '"' not in cmd.as_header()
+
+    def test_from_param_raises_expected_error(self) -> None:
+        """
+        from_param should raise its own TypeError when the argument's type is unsupported
+        """
+        with pytest.raises(TypeError) as exc_info:
+            ei.CommandSpec.from_param(object())  # type: ignore[arg-type] # We want a type error here
+        assert (
+            str(exc_info.value) == "Argument has an unsupported type <class 'object'>"
+        ), exc_info.value
+
+
+class TestWindowsScriptWriter:
+    def test_header(self):
+        hdr = ei.WindowsScriptWriter.get_header('')
+        assert hdr.startswith('#!')
+        assert hdr.endswith('\n')
+        hdr = hdr.lstrip('#!')
+        hdr = hdr.rstrip('\n')
+        # header should not start with an escaped quote
+        assert not hdr.startswith('\\"')
+
+
+class VersionStub(NamedTuple):
+    major: int
+    minor: int
+    micro: int
+    releaselevel: str
+    serial: int
+
+
+def test_use_correct_python_version_string(tmpdir, tmpdir_cwd, monkeypatch):
+    # In issue #3001, easy_install wrongly uses the `python3.1` directory
+    # when the interpreter is `python3.10` and the `--user` option is given.
+    # See pypa/setuptools#3001.
+    dist = Distribution()
+    cmd = dist.get_command_obj('easy_install')
+    cmd.args = ['ok']
+    cmd.optimize = 0
+    cmd.user = True
+    cmd.install_userbase = str(tmpdir)
+    cmd.install_usersite = None
+    install_cmd = dist.get_command_obj('install')
+    install_cmd.install_userbase = str(tmpdir)
+    install_cmd.install_usersite = None
+
+    with monkeypatch.context() as patch, warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+        version = '3.10.1 (main, Dec 21 2021, 09:17:12) [GCC 10.2.1 20210110]'
+        info = VersionStub(3, 10, 1, "final", 0)
+        patch.setattr('site.ENABLE_USER_SITE', True)
+        patch.setattr('sys.version', version)
+        patch.setattr('sys.version_info', info)
+        patch.setattr(cmd, 'create_home_path', mock.Mock())
+        cmd.finalize_options()
+
+    name = "pypy" if hasattr(sys, 'pypy_version_info') else "python"
+    install_dir = cmd.install_dir.lower()
+
+    # In some platforms (e.g. Windows), install_dir is mostly determined
+    # via `sysconfig`, which define constants eagerly at module creation.
+    # This means that monkeypatching `sys.version` to emulate 3.10 for testing
+    # may have no effect.
+    # The safest test here is to rely on the fact that 3.1 is no longer
+    # supported/tested, and make sure that if 'python3.1' ever appears in the string
+    # it is followed by another digit (e.g. 'python3.10').
+    if re.search(name + r'3\.?1', install_dir):
+        assert re.search(name + r'3\.?1\d', install_dir)
+
+    # The following "variables" are used for interpolation in distutils
+    # installation schemes, so it should be fair to treat them as "semi-public",
+    # or at least public enough so we can have a test to make sure they are correct
+    assert cmd.config_vars['py_version'] == '3.10.1'
+    assert cmd.config_vars['py_version_short'] == '3.10'
+    assert cmd.config_vars['py_version_nodot'] == '310'
+
+
+@pytest.mark.xfail(
+    sys.platform == "darwin",
+    reason="https://github.com/pypa/setuptools/pull/4716#issuecomment-2447624418",
+)
+def test_editable_user_and_build_isolation(setup_context, monkeypatch, tmp_path):
+    """`setup.py develop` should honor `--user` even under build isolation"""
+
+    # == Arrange ==
+    # Pretend that build isolation was enabled
+    # e.g pip sets the environment variable PYTHONNOUSERSITE=1
+    monkeypatch.setattr('site.ENABLE_USER_SITE', False)
+
+    # Patching $HOME for 2 reasons:
+    # 1. setuptools/command/easy_install.py:create_home_path
+    #    tries creating directories in $HOME.
+    #    Given::
+    #        self.config_vars['DESTDIRS'] = (
+    #            "/home/user/.pyenv/versions/3.9.10 "
+    #            "/home/user/.pyenv/versions/3.9.10/lib "
+    #            "/home/user/.pyenv/versions/3.9.10/lib/python3.9 "
+    #            "/home/user/.pyenv/versions/3.9.10/lib/python3.9/lib-dynload")
+    #    `create_home_path` will::
+    #        makedirs(
+    #            "/home/user/.pyenv/versions/3.9.10 "
+    #            "/home/user/.pyenv/versions/3.9.10/lib "
+    #            "/home/user/.pyenv/versions/3.9.10/lib/python3.9 "
+    #            "/home/user/.pyenv/versions/3.9.10/lib/python3.9/lib-dynload")
+    #
+    # 2. We are going to force `site` to update site.USER_BASE and site.USER_SITE
+    #    To point inside our new home
+    monkeypatch.setenv('HOME', str(tmp_path / '.home'))
+    monkeypatch.setenv('USERPROFILE', str(tmp_path / '.home'))
+    monkeypatch.setenv('APPDATA', str(tmp_path / '.home'))
+    monkeypatch.setattr('site.USER_BASE', None)
+    monkeypatch.setattr('site.USER_SITE', None)
+    user_site = Path(site.getusersitepackages())
+    user_site.mkdir(parents=True, exist_ok=True)
+
+    sys_prefix = tmp_path / '.sys_prefix'
+    sys_prefix.mkdir(parents=True, exist_ok=True)
+    monkeypatch.setattr('sys.prefix', str(sys_prefix))
+
+    setup_script = (
+        "__import__('setuptools').setup(name='aproj', version=42, packages=[])\n"
+    )
+    (tmp_path / "setup.py").write_text(setup_script, encoding="utf-8")
+
+    # == Sanity check ==
+    assert list(sys_prefix.glob("*")) == []
+    assert list(user_site.glob("*")) == []
+
+    # == Act ==
+    run_setup('setup.py', ['develop', '--user'])
+
+    # == Assert ==
+    # Should not install to sys.prefix
+    assert list(sys_prefix.glob("*")) == []
+    # Should install to user site
+    installed = {f.name for f in user_site.glob("*")}
+    # sometimes easy-install.pth is created and sometimes not
+    installed = installed - {"easy-install.pth"}
+    assert installed == {'aproj.egg-link'}