mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
1fd5098b24
The previous implementation uses pkg_resources.get_distribution(), which does not canonicalize the package name correctly, and fails when combined with pip's own get_distribution(), which does canonicalize names. This makes InstallRequirement.check_if_exists() only use pip's own canonicalization logic so different package name forms are matched as expected.
474 lines
15 KiB
Python
474 lines
15 KiB
Python
import itertools
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
|
|
import pytest
|
|
|
|
from tests.lib import pyversion # noqa: F401
|
|
from tests.lib import assert_all_changes
|
|
from tests.lib.local_repos import local_checkout
|
|
from tests.lib.wheel import make_wheel
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_no_upgrade_unless_requested(script):
|
|
"""
|
|
No upgrade if not specifically requested.
|
|
|
|
"""
|
|
script.pip('install', 'INITools==0.1')
|
|
result = script.pip('install', 'INITools')
|
|
assert not result.files_created, (
|
|
'pip install INITools upgraded when it should not have'
|
|
)
|
|
|
|
|
|
def test_invalid_upgrade_strategy_causes_error(script):
|
|
"""
|
|
It errors out when the upgrade-strategy is an invalid/unrecognised one
|
|
|
|
"""
|
|
result = script.pip_install_local(
|
|
'--upgrade', '--upgrade-strategy=bazinga', 'simple',
|
|
expect_error=True
|
|
)
|
|
|
|
assert result.returncode
|
|
assert "invalid choice" in result.stderr
|
|
|
|
|
|
def test_only_if_needed_does_not_upgrade_deps_when_satisfied(
|
|
script,
|
|
use_new_resolver,
|
|
with_wheel
|
|
):
|
|
"""
|
|
It doesn't upgrade a dependency if it already satisfies the requirements.
|
|
|
|
"""
|
|
script.pip_install_local('simple==2.0')
|
|
result = script.pip_install_local(
|
|
'--upgrade', '--upgrade-strategy=only-if-needed', 'require_simple'
|
|
)
|
|
|
|
assert (
|
|
(script.site_packages / 'require_simple-1.0.dist-info')
|
|
not in result.files_deleted
|
|
), "should have installed require_simple==1.0"
|
|
assert (
|
|
(script.site_packages / 'simple-2.0.dist-info')
|
|
not in result.files_deleted
|
|
), "should not have uninstalled simple==2.0"
|
|
|
|
msg = "Requirement already satisfied"
|
|
if not use_new_resolver:
|
|
msg = msg + ", skipping upgrade: simple"
|
|
assert (
|
|
msg in result.stdout
|
|
), "did not print correct message for not-upgraded requirement"
|
|
|
|
|
|
def test_only_if_needed_does_upgrade_deps_when_no_longer_satisfied(
|
|
script, with_wheel
|
|
):
|
|
"""
|
|
It does upgrade a dependency if it no longer satisfies the requirements.
|
|
|
|
"""
|
|
script.pip_install_local('simple==1.0')
|
|
result = script.pip_install_local(
|
|
'--upgrade', '--upgrade-strategy=only-if-needed', 'require_simple'
|
|
)
|
|
|
|
assert (
|
|
(script.site_packages / 'require_simple-1.0.dist-info')
|
|
not in result.files_deleted
|
|
), "should have installed require_simple==1.0"
|
|
expected = (
|
|
script.site_packages /
|
|
'simple-3.0.dist-info'
|
|
)
|
|
result.did_create(expected, message="should have installed simple==3.0")
|
|
expected = (
|
|
script.site_packages /
|
|
'simple-1.0.dist-info'
|
|
)
|
|
assert (
|
|
expected in result.files_deleted
|
|
), "should have uninstalled simple==1.0"
|
|
|
|
|
|
def test_eager_does_upgrade_dependecies_when_currently_satisfied(
|
|
script, with_wheel
|
|
):
|
|
"""
|
|
It does upgrade a dependency even if it already satisfies the requirements.
|
|
|
|
"""
|
|
script.pip_install_local('simple==2.0')
|
|
result = script.pip_install_local(
|
|
'--upgrade', '--upgrade-strategy=eager', 'require_simple'
|
|
)
|
|
|
|
assert (
|
|
(script.site_packages /
|
|
'require_simple-1.0.dist-info')
|
|
not in result.files_deleted
|
|
), "should have installed require_simple==1.0"
|
|
assert (
|
|
(script.site_packages /
|
|
'simple-2.0.dist-info')
|
|
in result.files_deleted
|
|
), "should have uninstalled simple==2.0"
|
|
|
|
|
|
def test_eager_does_upgrade_dependecies_when_no_longer_satisfied(
|
|
script, with_wheel
|
|
):
|
|
"""
|
|
It does upgrade a dependency if it no longer satisfies the requirements.
|
|
|
|
"""
|
|
script.pip_install_local('simple==1.0')
|
|
result = script.pip_install_local(
|
|
'--upgrade', '--upgrade-strategy=eager', 'require_simple'
|
|
)
|
|
|
|
assert (
|
|
(script.site_packages / 'require_simple-1.0.dist-info')
|
|
not in result.files_deleted
|
|
), "should have installed require_simple==1.0"
|
|
result.did_create(
|
|
script.site_packages / 'simple-3.0.dist-info',
|
|
message="should have installed simple==3.0"
|
|
)
|
|
assert (
|
|
script.site_packages / 'simple-1.0.dist-info'
|
|
in result.files_deleted
|
|
), "should have uninstalled simple==1.0"
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_to_specific_version(script, with_wheel):
|
|
"""
|
|
It does upgrade to specific version requested.
|
|
|
|
"""
|
|
script.pip('install', 'INITools==0.1')
|
|
result = script.pip('install', 'INITools==0.2')
|
|
assert result.files_created, (
|
|
'pip install with specific version did not upgrade'
|
|
)
|
|
assert (
|
|
script.site_packages / 'INITools-0.1.dist-info'
|
|
in result.files_deleted
|
|
)
|
|
result.did_create(
|
|
script.site_packages / 'INITools-0.2.dist-info'
|
|
)
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_if_requested(script, with_wheel):
|
|
"""
|
|
And it does upgrade if requested.
|
|
|
|
"""
|
|
script.pip('install', 'INITools==0.1')
|
|
result = script.pip('install', '--upgrade', 'INITools')
|
|
assert result.files_created, 'pip install --upgrade did not upgrade'
|
|
result.did_not_create(
|
|
script.site_packages /
|
|
'INITools-0.1.dist-info'
|
|
)
|
|
|
|
|
|
def test_upgrade_with_newest_already_installed(script, data, use_new_resolver):
|
|
"""
|
|
If the newest version of a package is already installed, the package should
|
|
not be reinstalled and the user should be informed.
|
|
"""
|
|
script.pip('install', '-f', data.find_links, '--no-index', 'simple')
|
|
result = script.pip(
|
|
'install', '--upgrade', '-f', data.find_links, '--no-index', 'simple'
|
|
)
|
|
assert not result.files_created, 'simple upgraded when it should not have'
|
|
if use_new_resolver:
|
|
msg = "Requirement already satisfied"
|
|
else:
|
|
msg = "already up-to-date"
|
|
assert msg in result.stdout, result.stdout
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_force_reinstall_newest(script):
|
|
"""
|
|
Force reinstallation of a package even if it is already at its newest
|
|
version if --force-reinstall is supplied.
|
|
"""
|
|
result = script.pip('install', 'INITools')
|
|
result.did_create(script.site_packages / 'initools')
|
|
result2 = script.pip(
|
|
'install', '--upgrade', '--force-reinstall', 'INITools'
|
|
)
|
|
assert result2.files_updated, 'upgrade to INITools 0.3 failed'
|
|
result3 = script.pip('uninstall', 'initools', '-y')
|
|
assert_all_changes(result, result3, [script.venv / 'build', 'cache'])
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_uninstall_before_upgrade(script):
|
|
"""
|
|
Automatic uninstall-before-upgrade.
|
|
|
|
"""
|
|
result = script.pip('install', 'INITools==0.2')
|
|
result.did_create(script.site_packages / 'initools')
|
|
result2 = script.pip('install', 'INITools==0.3')
|
|
assert result2.files_created, 'upgrade to INITools 0.3 failed'
|
|
result3 = script.pip('uninstall', 'initools', '-y')
|
|
assert_all_changes(result, result3, [script.venv / 'build', 'cache'])
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_uninstall_before_upgrade_from_url(script):
|
|
"""
|
|
Automatic uninstall-before-upgrade from URL.
|
|
|
|
"""
|
|
result = script.pip('install', 'INITools==0.2')
|
|
result.did_create(script.site_packages / 'initools')
|
|
result2 = script.pip(
|
|
'install',
|
|
'https://files.pythonhosted.org/packages/source/I/INITools/INITools-'
|
|
'0.3.tar.gz',
|
|
)
|
|
assert result2.files_created, 'upgrade to INITools 0.3 failed'
|
|
result3 = script.pip('uninstall', 'initools', '-y')
|
|
assert_all_changes(result, result3, [script.venv / 'build', 'cache'])
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_to_same_version_from_url(script):
|
|
"""
|
|
When installing from a URL the same version that is already installed, no
|
|
need to uninstall and reinstall if --upgrade is not specified.
|
|
|
|
"""
|
|
result = script.pip('install', 'INITools==0.3')
|
|
result.did_create(script.site_packages / 'initools')
|
|
result2 = script.pip(
|
|
'install',
|
|
'https://files.pythonhosted.org/packages/source/I/INITools/INITools-'
|
|
'0.3.tar.gz',
|
|
)
|
|
assert script.site_packages / 'initools' not in result2.files_updated, (
|
|
'INITools 0.3 reinstalled same version'
|
|
)
|
|
result3 = script.pip('uninstall', 'initools', '-y')
|
|
assert_all_changes(result, result3, [script.venv / 'build', 'cache'])
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_from_reqs_file(script):
|
|
"""
|
|
Upgrade from a requirements file.
|
|
|
|
"""
|
|
script.scratch_path.joinpath("test-req.txt").write_text(textwrap.dedent("""\
|
|
PyLogo<0.4
|
|
# and something else to test out:
|
|
INITools==0.3
|
|
"""))
|
|
install_result = script.pip(
|
|
'install', '-r', script.scratch_path / 'test-req.txt'
|
|
)
|
|
script.scratch_path.joinpath("test-req.txt").write_text(textwrap.dedent("""\
|
|
PyLogo
|
|
# and something else to test out:
|
|
INITools
|
|
"""))
|
|
script.pip(
|
|
'install', '--upgrade', '-r', script.scratch_path / 'test-req.txt'
|
|
)
|
|
uninstall_result = script.pip(
|
|
'uninstall', '-r', script.scratch_path / 'test-req.txt', '-y'
|
|
)
|
|
assert_all_changes(
|
|
install_result,
|
|
uninstall_result,
|
|
[script.venv / 'build', 'cache', script.scratch / 'test-req.txt'],
|
|
)
|
|
|
|
|
|
def test_uninstall_rollback(script, data):
|
|
"""
|
|
Test uninstall-rollback (using test package with a setup.py
|
|
crafted to fail on install).
|
|
|
|
"""
|
|
result = script.pip(
|
|
'install', '-f', data.find_links, '--no-index', 'broken==0.1'
|
|
)
|
|
result.did_create(script.site_packages / 'broken.py')
|
|
result2 = script.pip(
|
|
'install', '-f', data.find_links, '--no-index', 'broken===0.2broken',
|
|
expect_error=True,
|
|
)
|
|
assert result2.returncode == 1, str(result2)
|
|
assert script.run(
|
|
'python', '-c', "import broken; print(broken.VERSION)"
|
|
).stdout == '0.1\n'
|
|
assert_all_changes(
|
|
result.files_after,
|
|
result2,
|
|
[script.venv / 'build'],
|
|
)
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_should_not_install_always_from_cache(script, with_wheel):
|
|
"""
|
|
If there is an old cached package, pip should download the newer version
|
|
Related to issue #175
|
|
"""
|
|
script.pip('install', 'INITools==0.2')
|
|
script.pip('uninstall', '-y', 'INITools')
|
|
result = script.pip('install', 'INITools==0.1')
|
|
result.did_not_create(
|
|
script.site_packages /
|
|
'INITools-0.2.dist-info'
|
|
)
|
|
result.did_create(
|
|
script.site_packages /
|
|
'INITools-0.1.dist-info'
|
|
)
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_install_with_ignoreinstalled_requested(script, with_wheel):
|
|
"""
|
|
Test old conflicting package is completely ignored
|
|
"""
|
|
script.pip('install', 'INITools==0.1')
|
|
result = script.pip('install', '-I', 'INITools==0.3')
|
|
assert result.files_created, 'pip install -I did not install'
|
|
# both the old and new metadata should be present.
|
|
assert os.path.exists(
|
|
script.site_packages_path /
|
|
'INITools-0.1.dist-info'
|
|
)
|
|
assert os.path.exists(
|
|
script.site_packages_path /
|
|
'INITools-0.3.dist-info'
|
|
)
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_vcs_req_with_no_dists_found(script, tmpdir):
|
|
"""It can upgrade a VCS requirement that has no distributions otherwise."""
|
|
req = "{checkout}#egg=pip-test-package".format(
|
|
checkout=local_checkout(
|
|
"git+https://github.com/pypa/pip-test-package.git", tmpdir,
|
|
)
|
|
)
|
|
script.pip("install", req)
|
|
result = script.pip("install", "-U", req)
|
|
assert not result.returncode
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_upgrade_vcs_req_with_dist_found(script):
|
|
"""It can upgrade a VCS requirement that has distributions on the index."""
|
|
# TODO(pnasrat) Using local_checkout fails on windows - oddness with the
|
|
# test path urls/git.
|
|
req = (
|
|
"{url}#egg=pretend".format(
|
|
url=(
|
|
"git+git://github.com/alex/pretend@e7f26ad7dbcb4a02a4995aade4"
|
|
"743aad47656b27"
|
|
),
|
|
)
|
|
)
|
|
script.pip("install", req, expect_stderr=True)
|
|
result = script.pip("install", "-U", req, expect_stderr=True)
|
|
assert "pypi.org" not in result.stdout, result.stdout
|
|
|
|
|
|
class TestUpgradeDistributeToSetuptools(object):
|
|
"""
|
|
From pip1.4 to pip6, pip supported a set of "hacks" (see Issue #1122) to
|
|
allow distribute to conflict with setuptools, so that the following would
|
|
work to upgrade distribute:
|
|
|
|
``pip install -U setuptools``
|
|
|
|
In pip7, the hacks were removed. This test remains to at least confirm pip
|
|
can upgrade distribute to setuptools using:
|
|
|
|
``pip install -U distribute``
|
|
|
|
The reason this works is that a final version of distribute (v0.7.3) was
|
|
released that is simple wrapper with:
|
|
|
|
install_requires=['setuptools>=0.7']
|
|
|
|
The test use a fixed set of packages from our test packages dir. Note that
|
|
virtualenv-1.9.1 contains distribute-0.6.34 and virtualenv-1.10 contains
|
|
setuptools-0.9.7
|
|
"""
|
|
|
|
def prep_ve(self, script, version, pip_src, distribute=False):
|
|
self.script = script
|
|
self.script.pip_install_local(
|
|
'virtualenv=={version}'.format(**locals()))
|
|
args = ['virtualenv', self.script.scratch_path / 'VE']
|
|
if distribute:
|
|
args.insert(1, '--distribute')
|
|
if version == "1.9.1" and not distribute:
|
|
# setuptools 0.6 didn't support PYTHONDONTWRITEBYTECODE
|
|
del self.script.environ["PYTHONDONTWRITEBYTECODE"]
|
|
self.script.run(*args)
|
|
if sys.platform == 'win32':
|
|
bindir = "Scripts"
|
|
else:
|
|
bindir = "bin"
|
|
self.ve_bin = self.script.scratch_path / 'VE' / bindir
|
|
self.script.run(self.ve_bin / 'pip', 'uninstall', '-y', 'pip')
|
|
self.script.run(
|
|
self.ve_bin / 'python', 'setup.py', 'install',
|
|
cwd=pip_src,
|
|
expect_stderr=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("req1, req2", list(itertools.product(
|
|
["foo.bar", "foo_bar", "foo-bar"], ["foo.bar", "foo_bar", "foo-bar"],
|
|
)))
|
|
def test_install_find_existing_package_canonicalize(script, req1, req2):
|
|
"""Ensure an already-installed dist is found no matter how the dist name
|
|
was normalized on installation. (pypa/pip#8645)
|
|
"""
|
|
# Create and install a package that's not available in the later stage.
|
|
req_container = script.scratch_path.joinpath("foo-bar")
|
|
req_container.mkdir()
|
|
req_path = make_wheel("foo_bar", "1.0").save_to_dir(req_container)
|
|
script.pip("install", "--no-index", req_path)
|
|
|
|
# Depend on the previously installed, but now unavailable package.
|
|
pkg_container = script.scratch_path.joinpath("pkg")
|
|
pkg_container.mkdir()
|
|
make_wheel(
|
|
"pkg",
|
|
"1.0",
|
|
metadata_updates={"Requires-Dist": req2},
|
|
).save_to_dir(pkg_container)
|
|
|
|
# Ensure the previously installed package can be correctly used to match
|
|
# the dependency.
|
|
result = script.pip(
|
|
"install", "--no-index", "--find-links", pkg_container, "pkg",
|
|
)
|
|
satisfied_message = "Requirement already satisfied: {}".format(req2)
|
|
assert satisfied_message in result.stdout, str(result)
|