mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
535 lines
16 KiB
Python
535 lines
16 KiB
Python
import os
|
|
import ssl
|
|
import sys
|
|
import tempfile
|
|
import textwrap
|
|
from pathlib import Path
|
|
from typing import Callable, List
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import CertFactory, MockServer, ScriptFactory
|
|
from tests.lib import PipTestEnvironment, TestData
|
|
from tests.lib.server import (
|
|
authorization_response,
|
|
file_response,
|
|
make_mock_server,
|
|
package_page,
|
|
server_running,
|
|
)
|
|
from tests.lib.venv import VirtualEnvironment
|
|
|
|
TEST_PYPI_INITOOLS = "https://test.pypi.org/simple/initools/"
|
|
|
|
|
|
def test_options_from_env_vars(script: PipTestEnvironment) -> None:
|
|
"""
|
|
Test if ConfigOptionParser reads env vars (e.g. not using PyPI here)
|
|
|
|
"""
|
|
script.environ["PIP_NO_INDEX"] = "1"
|
|
result = script.pip("install", "-vvv", "INITools", expect_error=True)
|
|
assert "Ignoring indexes:" in result.stdout, str(result)
|
|
msg = "DistributionNotFound: No matching distribution found for INITools"
|
|
# Case insensitive as the new resolver canonicalizes the project name
|
|
assert msg.lower() in result.stdout.lower(), str(result)
|
|
|
|
|
|
def test_command_line_options_override_env_vars(
|
|
script: PipTestEnvironment, virtualenv: VirtualEnvironment
|
|
) -> None:
|
|
"""
|
|
Test that command line options override environmental variables.
|
|
|
|
"""
|
|
script.environ["PIP_INDEX_URL"] = "https://example.com/simple/"
|
|
result = script.pip("install", "-vvv", "INITools", expect_error=True)
|
|
assert "Getting page https://example.com/simple/initools" in result.stdout
|
|
virtualenv.clear()
|
|
result = script.pip(
|
|
"install",
|
|
"-vvv",
|
|
"--index-url",
|
|
"https://download.zope.org/ppix",
|
|
"INITools",
|
|
expect_error=True,
|
|
)
|
|
assert "example.com" not in result.stdout
|
|
assert "Getting page https://download.zope.org/ppix" in result.stdout
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_env_vars_override_config_file(
|
|
script: PipTestEnvironment, virtualenv: VirtualEnvironment
|
|
) -> None:
|
|
"""
|
|
Test that environmental variables override settings in config files.
|
|
"""
|
|
config_file = script.scratch_path / "test-pip.cfg"
|
|
# set this to make pip load it
|
|
script.environ["PIP_CONFIG_FILE"] = str(config_file)
|
|
# It's important that we test this particular config value ('no-index')
|
|
# because there is/was a bug which only shows up in cases in which
|
|
# 'config-item' and 'config_item' hash to the same value modulo the size
|
|
# of the config dictionary.
|
|
config_file.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
[global]
|
|
no-index = 1
|
|
"""
|
|
)
|
|
)
|
|
result = script.pip("install", "-vvv", "INITools", expect_error=True)
|
|
msg = "DistributionNotFound: No matching distribution found for INITools"
|
|
# Case insensitive as the new resolver canonicalizes the project name
|
|
assert msg.lower() in result.stdout.lower(), str(result)
|
|
script.environ["PIP_NO_INDEX"] = "0"
|
|
virtualenv.clear()
|
|
result = script.pip("install", "-vvv", "INITools")
|
|
assert "Successfully installed INITools" in result.stdout
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_command_line_append_flags(
|
|
script: PipTestEnvironment, virtualenv: VirtualEnvironment, data: TestData
|
|
) -> None:
|
|
"""
|
|
Test command line flags that append to defaults set by environmental
|
|
variables.
|
|
|
|
"""
|
|
script.environ["PIP_FIND_LINKS"] = TEST_PYPI_INITOOLS
|
|
result = script.pip(
|
|
"install",
|
|
"-vvv",
|
|
"INITools",
|
|
"--trusted-host",
|
|
"test.pypi.org",
|
|
)
|
|
assert (
|
|
"Fetching project page and analyzing links: https://test.pypi.org"
|
|
in result.stdout
|
|
), str(result)
|
|
virtualenv.clear()
|
|
result = script.pip(
|
|
"install",
|
|
"-vvv",
|
|
"--find-links",
|
|
data.find_links,
|
|
"INITools",
|
|
"--trusted-host",
|
|
"test.pypi.org",
|
|
)
|
|
assert (
|
|
"Fetching project page and analyzing links: https://test.pypi.org"
|
|
in result.stdout
|
|
)
|
|
assert (
|
|
f"Skipping link: not a file: {data.find_links}" in result.stdout
|
|
), f"stdout: {result.stdout}"
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_command_line_appends_correctly(
|
|
script: PipTestEnvironment, data: TestData
|
|
) -> None:
|
|
"""
|
|
Test multiple appending options set by environmental variables.
|
|
|
|
"""
|
|
script.environ["PIP_FIND_LINKS"] = f"{TEST_PYPI_INITOOLS} {data.find_links}"
|
|
result = script.pip(
|
|
"install",
|
|
"-vvv",
|
|
"INITools",
|
|
"--trusted-host",
|
|
"test.pypi.org",
|
|
)
|
|
|
|
assert (
|
|
"Fetching project page and analyzing links: https://test.pypi.org"
|
|
in result.stdout
|
|
), result.stdout
|
|
assert (
|
|
f"Skipping link: not a file: {data.find_links}" in result.stdout
|
|
), f"stdout: {result.stdout}"
|
|
|
|
|
|
def test_config_file_override_stack(
|
|
script: PipTestEnvironment,
|
|
virtualenv: VirtualEnvironment,
|
|
mock_server: MockServer,
|
|
shared_data: TestData,
|
|
) -> None:
|
|
"""
|
|
Test config files (global, overriding a global config with a
|
|
local, overriding all with a command line flag).
|
|
"""
|
|
mock_server.set_responses(
|
|
[
|
|
package_page({}),
|
|
package_page({}),
|
|
package_page({"INITools-0.2.tar.gz": "/files/INITools-0.2.tar.gz"}),
|
|
file_response(shared_data.packages.joinpath("INITools-0.2.tar.gz")),
|
|
]
|
|
)
|
|
mock_server.start()
|
|
base_address = f"http://{mock_server.host}:{mock_server.port}"
|
|
|
|
config_file = script.scratch_path / "test-pip.cfg"
|
|
|
|
# set this to make pip load it
|
|
script.environ["PIP_CONFIG_FILE"] = str(config_file)
|
|
|
|
config_file.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
[global]
|
|
index-url = {}/simple1
|
|
""".format(
|
|
base_address
|
|
)
|
|
)
|
|
)
|
|
script.pip("install", "-vvv", "INITools", expect_error=True)
|
|
virtualenv.clear()
|
|
|
|
config_file.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
[global]
|
|
index-url = {address}/simple1
|
|
[install]
|
|
index-url = {address}/simple2
|
|
""".format(
|
|
address=base_address
|
|
)
|
|
)
|
|
)
|
|
script.pip("install", "-vvv", "INITools", expect_error=True)
|
|
script.pip(
|
|
"install",
|
|
"-vvv",
|
|
"--index-url",
|
|
f"{base_address}/simple3",
|
|
"INITools",
|
|
)
|
|
|
|
mock_server.stop()
|
|
requests = mock_server.get_requests()
|
|
assert len(requests) == 4
|
|
assert requests[0]["PATH_INFO"] == "/simple1/initools/"
|
|
assert requests[1]["PATH_INFO"] == "/simple2/initools/"
|
|
assert requests[2]["PATH_INFO"] == "/simple3/initools/"
|
|
assert requests[3]["PATH_INFO"] == "/files/INITools-0.2.tar.gz"
|
|
|
|
|
|
def test_options_from_venv_config(
|
|
script: PipTestEnvironment, virtualenv: VirtualEnvironment
|
|
) -> None:
|
|
"""
|
|
Test if ConfigOptionParser reads a virtualenv-local config file
|
|
|
|
"""
|
|
from pip._internal.configuration import CONFIG_BASENAME
|
|
|
|
conf = "[global]\nno-index = true"
|
|
ini = virtualenv.location / CONFIG_BASENAME
|
|
with open(ini, "w") as f:
|
|
f.write(conf)
|
|
result = script.pip("install", "-vvv", "INITools", expect_error=True)
|
|
assert "Ignoring indexes:" in result.stdout, str(result)
|
|
msg = "DistributionNotFound: No matching distribution found for INITools"
|
|
# Case insensitive as the new resolver canonicalizes the project name
|
|
assert msg.lower() in result.stdout.lower(), str(result)
|
|
|
|
|
|
def test_install_no_binary_via_config_disables_cached_wheels(
|
|
script: PipTestEnvironment, data: TestData
|
|
) -> None:
|
|
config_file = tempfile.NamedTemporaryFile(mode="wt", delete=False)
|
|
try:
|
|
script.environ["PIP_CONFIG_FILE"] = config_file.name
|
|
config_file.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
[global]
|
|
no-binary = :all:
|
|
"""
|
|
)
|
|
)
|
|
config_file.close()
|
|
res = script.pip(
|
|
"install", "--no-index", "-f", data.find_links, "upper", expect_stderr=True
|
|
)
|
|
finally:
|
|
os.unlink(config_file.name)
|
|
assert "Successfully installed upper-2.0" in str(res), str(res)
|
|
# upper is built and not obtained from cache
|
|
assert "Building wheel for upper" in str(res), str(res)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "linux" and sys.version_info < (3, 8),
|
|
reason="Custom SSL certification not running well in CI",
|
|
)
|
|
def test_prompt_for_authentication(
|
|
script: PipTestEnvironment, data: TestData, cert_factory: CertFactory
|
|
) -> None:
|
|
"""Test behaviour while installing from a index url
|
|
requiring authentication
|
|
"""
|
|
cert_path = cert_factory()
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
ctx.load_cert_chain(cert_path, cert_path)
|
|
ctx.load_verify_locations(cafile=cert_path)
|
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
|
|
|
server = make_mock_server(ssl_context=ctx)
|
|
server.mock.side_effect = [
|
|
package_page(
|
|
{
|
|
"simple-3.0.tar.gz": "/files/simple-3.0.tar.gz",
|
|
}
|
|
),
|
|
authorization_response(data.packages / "simple-3.0.tar.gz"),
|
|
]
|
|
|
|
url = f"https://{server.host}:{server.port}/simple"
|
|
|
|
with server_running(server):
|
|
result = script.pip(
|
|
"install",
|
|
"--index-url",
|
|
url,
|
|
"--cert",
|
|
cert_path,
|
|
"--client-cert",
|
|
cert_path,
|
|
"simple",
|
|
expect_error=True,
|
|
)
|
|
|
|
assert f"User for {server.host}:{server.port}" in result.stdout, str(result)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "linux" and sys.version_info < (3, 8),
|
|
reason="Custom SSL certification not running well in CI",
|
|
)
|
|
def test_do_not_prompt_for_authentication(
|
|
script: PipTestEnvironment, data: TestData, cert_factory: CertFactory
|
|
) -> None:
|
|
"""Test behaviour if --no-input option is given while installing
|
|
from a index url requiring authentication
|
|
"""
|
|
cert_path = cert_factory()
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
ctx.load_cert_chain(cert_path, cert_path)
|
|
ctx.load_verify_locations(cafile=cert_path)
|
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
|
|
|
server = make_mock_server(ssl_context=ctx)
|
|
|
|
server.mock.side_effect = [
|
|
package_page(
|
|
{
|
|
"simple-3.0.tar.gz": "/files/simple-3.0.tar.gz",
|
|
}
|
|
),
|
|
authorization_response(data.packages / "simple-3.0.tar.gz"),
|
|
]
|
|
|
|
url = f"https://{server.host}:{server.port}/simple"
|
|
|
|
with server_running(server):
|
|
result = script.pip(
|
|
"install",
|
|
"--index-url",
|
|
url,
|
|
"--cert",
|
|
cert_path,
|
|
"--client-cert",
|
|
cert_path,
|
|
"--no-input",
|
|
"simple",
|
|
expect_error=True,
|
|
)
|
|
|
|
assert "ERROR: HTTP error 401" in result.stderr
|
|
|
|
|
|
@pytest.fixture(params=(True, False), ids=("interactive", "noninteractive"))
|
|
def interactive(request: pytest.FixtureRequest) -> bool:
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture(params=(True, False), ids=("auth_needed", "auth_not_needed"))
|
|
def auth_needed(request: pytest.FixtureRequest) -> bool:
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture(params=(None, "disabled", "import", "subprocess", "auto"))
|
|
def keyring_provider(request: pytest.FixtureRequest) -> str:
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture(params=("disabled", "import", "subprocess"))
|
|
def keyring_provider_implementation(request: pytest.FixtureRequest) -> str:
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture()
|
|
def flags(
|
|
request: pytest.FixtureRequest,
|
|
interactive: bool,
|
|
auth_needed: bool,
|
|
keyring_provider: str,
|
|
keyring_provider_implementation: str,
|
|
) -> List[str]:
|
|
if (
|
|
keyring_provider not in [None, "auto"]
|
|
and keyring_provider_implementation != keyring_provider
|
|
):
|
|
pytest.skip()
|
|
|
|
flags = []
|
|
if keyring_provider is not None:
|
|
flags.append("--keyring-provider")
|
|
flags.append(keyring_provider)
|
|
if not interactive:
|
|
flags.append("--no-input")
|
|
if auth_needed:
|
|
if keyring_provider_implementation == "disabled" or (
|
|
not interactive and keyring_provider in [None, "auto"]
|
|
):
|
|
request.applymarker(pytest.mark.xfail())
|
|
return flags
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "linux" and sys.version_info < (3, 8),
|
|
reason="Custom SSL certification not running well in CI",
|
|
)
|
|
def test_prompt_for_keyring_if_needed(
|
|
data: TestData,
|
|
cert_factory: CertFactory,
|
|
auth_needed: bool,
|
|
flags: List[str],
|
|
keyring_provider: str,
|
|
keyring_provider_implementation: str,
|
|
tmpdir: Path,
|
|
script_factory: ScriptFactory,
|
|
virtualenv_factory: Callable[[Path], VirtualEnvironment],
|
|
) -> None:
|
|
"""Test behaviour while installing from an index url
|
|
requiring authentication and keyring is possible.
|
|
"""
|
|
environ = os.environ.copy()
|
|
workspace = tmpdir.joinpath("workspace")
|
|
|
|
if keyring_provider_implementation == "subprocess":
|
|
keyring_virtualenv = virtualenv_factory(workspace.joinpath("keyring"))
|
|
keyring_script = script_factory(
|
|
workspace.joinpath("keyring"), keyring_virtualenv
|
|
)
|
|
keyring_script.pip(
|
|
"install",
|
|
"keyring",
|
|
)
|
|
|
|
environ["PATH"] = str(keyring_script.bin_path) + os.pathsep + environ["PATH"]
|
|
|
|
virtualenv = virtualenv_factory(workspace.joinpath("venv"))
|
|
script = script_factory(workspace.joinpath("venv"), virtualenv, environ=environ)
|
|
|
|
if (
|
|
keyring_provider not in [None, "auto"]
|
|
or keyring_provider_implementation != "subprocess"
|
|
):
|
|
script.pip(
|
|
"install",
|
|
"keyring",
|
|
)
|
|
|
|
if keyring_provider_implementation != "subprocess":
|
|
keyring_script = script
|
|
|
|
cert_path = cert_factory()
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
ctx.load_cert_chain(cert_path, cert_path)
|
|
ctx.load_verify_locations(cafile=cert_path)
|
|
ctx.verify_mode = ssl.CERT_REQUIRED
|
|
|
|
response = authorization_response if auth_needed else file_response
|
|
|
|
server = make_mock_server(ssl_context=ctx)
|
|
server.mock.side_effect = [
|
|
package_page(
|
|
{
|
|
"simple-3.0.tar.gz": "/files/simple-3.0.tar.gz",
|
|
}
|
|
),
|
|
response(data.packages / "simple-3.0.tar.gz"),
|
|
response(data.packages / "simple-3.0.tar.gz"),
|
|
]
|
|
|
|
url = f"https://USERNAME@{server.host}:{server.port}/simple"
|
|
|
|
keyring_content = textwrap.dedent(
|
|
"""\
|
|
import os
|
|
import sys
|
|
import keyring
|
|
from keyring.backend import KeyringBackend
|
|
from keyring.credentials import SimpleCredential
|
|
|
|
class TestBackend(KeyringBackend):
|
|
priority = 1
|
|
|
|
def get_credential(self, url, username):
|
|
sys.stderr.write("get_credential was called" + os.linesep)
|
|
return SimpleCredential(username="USERNAME", password="PASSWORD")
|
|
|
|
def get_password(self, url, username):
|
|
sys.stderr.write("get_password was called" + os.linesep)
|
|
return "PASSWORD"
|
|
|
|
def set_password(self, url, username):
|
|
pass
|
|
"""
|
|
)
|
|
keyring_path = keyring_script.site_packages_path / "keyring_test.py"
|
|
keyring_path.write_text(keyring_content)
|
|
|
|
keyring_content = (
|
|
"import keyring_test;"
|
|
" import keyring;"
|
|
" keyring.set_keyring(keyring_test.TestBackend())" + os.linesep
|
|
)
|
|
keyring_path = keyring_path.with_suffix(".pth")
|
|
keyring_path.write_text(keyring_content)
|
|
|
|
with server_running(server):
|
|
result = script.pip(
|
|
"install",
|
|
"--index-url",
|
|
url,
|
|
"--cert",
|
|
cert_path,
|
|
"--client-cert",
|
|
cert_path,
|
|
*flags,
|
|
"simple",
|
|
)
|
|
|
|
function_name = (
|
|
"get_credential"
|
|
if keyring_provider_implementation == "import"
|
|
else "get_password"
|
|
)
|
|
if auth_needed:
|
|
assert function_name + " was called" in result.stderr
|
|
else:
|
|
assert function_name + " was called" not in result.stderr
|