Llama-3.1-8B-DALv0.1
/
venv
/lib
/python3.12
/site-packages
/setuptools
/tests
/test_easy_install.py
"""Easy install Tests""" | |
import sys | |
import os | |
import tempfile | |
import site | |
import contextlib | |
import tarfile | |
import logging | |
import itertools | |
import distutils.errors | |
import io | |
from typing import NamedTuple | |
import zipfile | |
import time | |
import re | |
import subprocess | |
import pathlib | |
import warnings | |
from pathlib import Path | |
from unittest import mock | |
import pytest | |
from jaraco import path | |
from setuptools import sandbox | |
from setuptools.sandbox import run_setup | |
import setuptools.command.easy_install as ei | |
from setuptools.command.easy_install import PthDistributions | |
from setuptools.dist import Distribution | |
from pkg_resources import normalize_path, working_set | |
from pkg_resources import Distribution as PRDistribution | |
from setuptools.tests.server import MockServer, path_to_url | |
from setuptools.tests import fail_on_ascii | |
import pkg_resources | |
from . import contexts | |
from .textwrap import DALS | |
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() | |
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) | |
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) | |
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) | |
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) | |
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 | |
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() | |
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" | |
) | |
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 | |
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 | |
def test_user_install_not_implied_user_site_enabled(self): | |
self.assert_not_user_site() | |
def test_user_install_not_implied_user_site_disabled(self): | |
self.assert_not_user_site() | |
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') | |
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) | |
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 | |
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, | |
) | |
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 | |
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 TestDistutilsPackage: | |
def test_bdist_egg_available_on_distutils_pkg(self, distutils_package): | |
run_setup('setup.py', ['bdist_egg']) | |
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/'] | |
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'), | |
) | |
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' | |
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: | |
self.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' | |
def test_setup_requires_with_attr_version(self, use_setup_cfg): | |
def make_dependency_sdist(dist_path, distname, version): | |
files = [ | |
( | |
'setup.py', | |
DALS( | |
""" | |
import setuptools | |
setuptools.setup( | |
name={name!r}, | |
version={version!r}, | |
py_modules=[{name!r}], | |
) | |
""".format(name=distname, version=version) | |
), | |
), | |
( | |
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='dependency @ %s' % 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 = '!=' + '.'.join(map(str, sys.version_info[:2])) + '.*' | |
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'] | |
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( | |
"""\ | |
import setuptools | |
setuptools.setup( | |
name=%r, | |
version=%r | |
) | |
""" | |
% (distname, version) | |
), | |
), | |
('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). | |
""" | |
parts = distname.split('.') | |
nspackage = parts[0] | |
packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)] | |
setup_py = DALS( | |
"""\ | |
import setuptools | |
setuptools.setup( | |
name=%r, | |
version=%r, | |
packages=%r, | |
namespace_packages=[%r] | |
) | |
""" | |
% (distname, version, packages, nspackage) | |
) | |
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'. | |
""" | |
test_setup_attrs = { | |
'name': 'test_pkg', | |
'version': '0.0', | |
'setup_requires': ['%s==%s' % (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('%s: %s' % (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, '%s-%s.tar.gz' % (distname, version)) | |
make_package(foobar_path, distname, version) | |
return test_pkg | |
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 = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable)) | |
actual = ei.ScriptWriter.get_header('#!/usr/local/bin/python') | |
assert actual == expected | |
def test_get_script_header_args(self): | |
expected = '#!%s -x\n' % ei.nt_quote_arg(os.path.normpath(sys.executable)) | |
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 = '#!%s -x\n' % self.non_ascii_exe | |
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 = '#!"%s"\n' % self.exe_with_spaces | |
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 | |
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() | |
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' | |
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'} | |