diff options
Diffstat (limited to 'setuptools/tests/test_easy_install.py')
-rw-r--r-- | setuptools/tests/test_easy_install.py | 577 |
1 files changed, 464 insertions, 113 deletions
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 57339c8..5831b26 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """Easy install Tests """ -from __future__ import absolute_import import sys import os @@ -15,21 +13,27 @@ import distutils.errors import io import zipfile import mock - import time -from setuptools.extern.six.moves import urllib +import re +import subprocess +import pathlib +import warnings +from collections import namedtuple 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.command import easy_install as easy_install_pkg +from setuptools.command.easy_install import ( + EasyInstallDeprecationWarning, ScriptWriter, PthDistributions, + WindowsScriptWriter, +) from setuptools.dist import Distribution from pkg_resources import normalize_path, working_set from pkg_resources import Distribution as PRDistribution -import setuptools.tests.server +from setuptools.tests.server import MockServer, path_to_url from setuptools.tests import fail_on_ascii import pkg_resources @@ -37,11 +41,21 @@ from . import contexts from .textwrap import DALS -class FakeDist(object): +@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'} + return {str('name'): 'ep'} def as_requirement(self): return 'spec' @@ -50,40 +64,22 @@ class FakeDist(object): SETUP_PY = DALS(""" from setuptools import setup - setup(name='foo') + setup() """) class TestEasyInstallTest: - def test_install_site_py(self, tmpdir): - dist = Distribution() - cmd = ei.easy_install(dist) - cmd.sitepy_installed = False - cmd.install_dir = str(tmpdir) - cmd.install_site_py() - assert (tmpdir / 'site.py').exists() - def test_get_script_args(self): header = ei.CommandSpec.best().from_environment().as_header() - expected = header + DALS(r""" - # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name' - __requires__ = 'spec' - import re - import sys - from pkg_resources import load_entry_point - - if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit( - load_entry_point('spec', 'console_scripts', 'name')() - ) - """) dist = FakeDist() - args = next(ei.ScriptWriter.get_args(dist)) name, script = itertools.islice(args, 2) - - assert script == expected + 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 @@ -124,7 +120,9 @@ class TestEasyInstallTest: site.getsitepackages. """ path = normalize_path('/setuptools/test/site-packages') - mock_gsp = lambda: [path] + + def mock_gsp(): + return [path] monkeypatch.setattr(site, 'getsitepackages', mock_gsp, raising=False) assert path in ei.get_site_dirs() @@ -152,7 +150,7 @@ class TestEasyInstallTest: "", ), ( - u'mypkg/\u2603.txt', + 'mypkg/☃.txt', "", ), ] @@ -167,7 +165,8 @@ class TestEasyInstallTest: return str(sdist) @fail_on_ascii - def test_unicode_filename_in_sdist(self, sdist_unicode, tmpdir, monkeypatch): + def test_unicode_filename_in_sdist( + self, sdist_unicode, tmpdir, monkeypatch): """ The install command should execute correctly even if the package has unicode filenames. @@ -184,6 +183,59 @@ class TestEasyInstallTest: 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 = [ ( @@ -198,7 +250,7 @@ class TestEasyInstallTest: """), ), ( - u'mypkg_script', + 'mypkg_script', DALS(""" #/usr/bin/python print('mypkg_script') @@ -228,7 +280,24 @@ class TestEasyInstallTest: cmd.easy_install(sdist_script) assert (target / 'mypkg_script').exists() + def test_dist_get_script_args_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + ScriptWriter.get_script_args(None, None) + + def test_dist_get_script_header_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + ScriptWriter.get_script_header("") + def test_dist_get_writer_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + ScriptWriter.get_writer(None) + + def test_dist_WindowsScriptWriter_get_writer_deprecated(self): + with pytest.warns(EasyInstallDeprecationWarning): + WindowsScriptWriter.get_writer() + + +@pytest.mark.filterwarnings('ignore:Unbuilt egg') class TestPTHFileWriter: def test_add_from_cwd_site_sets_dirty(self): '''a pth file manager should set dirty @@ -249,7 +318,7 @@ class TestPTHFileWriter: assert not pth.dirty -@pytest.yield_fixture +@pytest.fixture def setup_context(tmpdir): with (tmpdir / 'setup.py').open('w') as f: f.write(SETUP_PY) @@ -305,7 +374,7 @@ class TestUserInstallTest: f.write('Name: foo\n') return str(tmpdir) - @pytest.yield_fixture() + @pytest.fixture() def install_target(self, tmpdir): target = str(tmpdir) with mock.patch('sys.path', sys.path + [target]): @@ -350,7 +419,7 @@ class TestUserInstallTest: ) -@pytest.yield_fixture +@pytest.fixture def distutils_package(): distutils_setup_py = SETUP_PY.replace( 'from setuptools import setup', @@ -362,48 +431,51 @@ def distutils_package(): yield +@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 TestDistutilsPackage: def test_bdist_egg_available_on_distutils_pkg(self, distutils_package): run_setup('setup.py', ['bdist_egg']) class TestSetupRequires: - def test_setup_requires_honors_fetch_params(self): + + 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 - allow-hosts, index-url, and find-links). + index-url, and find-links). """ - # set up a server which will simulate an alternate package index. - p_index = setuptools.tests.server.MockServer() - p_index.start() - netloc = 1 - p_index_loc = urllib.parse.urlparse(p_index.url)[netloc] - if p_index_loc.endswith(':0'): - # Some platforms (Jython) don't find a port to which to bind, - # so skip this test for them. - return + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('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): - ei_params = [ - '--index-url', p_index.url, - '--allow-hosts', p_index_loc, + cmd = [ + sys.executable, + '-m', 'setup', + 'easy_install', + '--index-url', mock_index.url, '--exclude-scripts', '--install-dir', temp_install_dir, dist_file, ] - with sandbox.save_argv(['easy_install']): - # attempt to install the dist. It should fail because - # it doesn't exist. - with pytest.raises(SystemExit): - easy_install_pkg.main(ei_params) - # there should have been two or three requests to the server - # (three happens on Python 3.3a) - assert 2 <= len(p_index.requests) <= 3 - assert p_index.requests[0].path == '/does-not-exist/' + 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 @@ -422,7 +494,9 @@ class TestSetupRequires: version="1.0", setup_requires = ['does-not-exist'], ) - """))]) + """)), + ('setup.cfg', ''), + ]) yield dist_path use_setup_cfg = ( @@ -449,12 +523,13 @@ class TestSetupRequires: 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_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']) + run_setup(test_setup_py, [str('--name')]) lines = stdout.readlines() assert len(lines) > 0 @@ -508,9 +583,10 @@ class TestSetupRequires: try: # Don't even need to install the package, just # running the setup.py at all is sufficient - run_setup(test_setup_py, ['--name']) + run_setup(test_setup_py, [str('--name')]) except pkg_resources.VersionConflict: - self.fail('Installing setup.py requirements ' + self.fail( + 'Installing setup.py requirements ' 'caused a VersionConflict') assert 'FAIL' not in stdout.getvalue() @@ -521,35 +597,236 @@ class TestSetupRequires: @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): - make_sdist(dist_path, [ - ('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 - """ - ))]) + 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',), + 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']) + run_setup(test_setup_py, [str('--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(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv('PIP_NO_INDEX', 'false') + monkeypatch.setenv(str('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') 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, [str('--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(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('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, [str('--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, [str('--version')]) + assert len(mock_index.requests) == 0 + + def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir): + ''' Check `python_requires` is honored. ''' + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) + monkeypatch.setenv(str('PIP_VERBOSE'), str('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( # noqa + 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, [str('--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(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('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') 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') 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, [str('--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'), [str('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') as fp: + fp.write(DALS( + ''' + from setuptools import installer, setup + setup(setup_requires='dep[extra]') + ''')) + # Check... + monkeypatch.setenv(str('PIP_FIND_LINKS'), str(temp_dir)) + monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + run_setup(test_setup_py, [str('--version')]) + def make_trivial_sdist(dist_path, distname, version): """ @@ -565,7 +842,9 @@ def make_trivial_sdist(dist_path, distname, version): name=%r, version=%r ) - """ % (distname, version)))]) + """ % (distname, version))), + ('setup.cfg', ''), + ]) def make_nspkg_sdist(dist_path, distname, version): @@ -601,12 +880,32 @@ def make_nspkg_sdist(dist_path, distname, version): 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')) @@ -639,8 +938,8 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', test_pkg = os.path.join(path, 'test_pkg') os.mkdir(test_pkg) + # setup.cfg if use_setup_cfg: - test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') options = [] metadata = [] for name in use_setup_cfg: @@ -652,28 +951,29 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', if isinstance(value, (tuple, list)): value = ';'.join(value) section.append('%s: %s' % (name, value)) - with open(test_setup_cfg, 'w') as f: - f.write(DALS( - """ - [metadata] - {metadata} - [options] - {options} - """ - ).format( - options='\n'.join(options), - metadata='\n'.join(metadata), - )) - - test_setup_py = os.path.join(test_pkg, 'setup.py') + 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') 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(test_setup_py, 'w') as f: + with open(os.path.join(test_pkg, 'setup.py'), 'w') as f: f.write(setup_py_template % test_setup_attrs) foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version)) @@ -688,27 +988,29 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', ) class TestScriptHeader: non_ascii_exe = '/Users/José/bin/python' - exe_with_spaces = r'C:\Program Files\Python33\python.exe' + 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_script_header('#!/usr/local/bin/python') + 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_script_header('#!/usr/bin/python -x') + 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_script_header('#!/usr/bin/python', + actual = ei.ScriptWriter.get_header( + '#!/usr/bin/python', executable=self.non_ascii_exe) - expected = '#!%s -x\n' % self.non_ascii_exe + expected = str('#!%s -x\n') % self.non_ascii_exe assert actual == expected def test_get_script_header_exe_with_spaces(self): - actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python', + actual = ei.ScriptWriter.get_header( + '#!/usr/bin/python', executable='"' + self.exe_with_spaces + '"') expected = '#!"%s"\n' % self.exe_with_spaces assert actual == expected @@ -751,10 +1053,59 @@ class TestCommandSpec: class TestWindowsScriptWriter: def test_header(self): - hdr = ei.WindowsScriptWriter.get_script_header('') + 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('\\"') + + +VersionStub = namedtuple("VersionStub", "major, minor, micro, releaselevel, serial") + + +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' |