pip/tests/functional/test_new_resolver_hashes.py

376 lines
11 KiB
Python

import collections
import hashlib
import pytest
from pip._internal.utils.urls import path_to_url
from tests.lib import (
PipTestEnvironment,
create_basic_sdist_for_package,
create_basic_wheel_for_package,
)
_FindLinks = collections.namedtuple(
"_FindLinks",
"index_html sdist_hash wheel_hash",
)
def _create_find_links(script: PipTestEnvironment) -> _FindLinks:
sdist_path = create_basic_sdist_for_package(script, "base", "0.1.0")
wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0")
sdist_hash = hashlib.sha256(sdist_path.read_bytes()).hexdigest()
wheel_hash = hashlib.sha256(wheel_path.read_bytes()).hexdigest()
index_html = script.scratch_path / "index.html"
index_html.write_text(
"""
<!DOCTYPE html>
<a href="{sdist_url}#sha256={sdist_hash}">{sdist_path.stem}</a>
<a href="{wheel_url}#sha256={wheel_hash}">{wheel_path.stem}</a>
""".format(
sdist_url=path_to_url(sdist_path),
sdist_hash=sdist_hash,
sdist_path=sdist_path,
wheel_url=path_to_url(wheel_path),
wheel_hash=wheel_hash,
wheel_path=wheel_path,
).strip()
)
return _FindLinks(index_html, sdist_hash, wheel_hash)
@pytest.mark.parametrize(
"requirements_template, message",
[
(
"""
base==0.1.0 --hash=sha256:{sdist_hash} --hash=sha256:{wheel_hash}
base==0.1.0 --hash=sha256:{sdist_hash} --hash=sha256:{wheel_hash}
""",
"Checked 2 links for project {name!r} against 2 hashes "
"(2 matches, 0 no digest): discarding no candidates",
),
(
# Different hash lists are intersected.
"""
base==0.1.0 --hash=sha256:{sdist_hash} --hash=sha256:{wheel_hash}
base==0.1.0 --hash=sha256:{sdist_hash}
""",
"Checked 2 links for project {name!r} against 1 hashes "
"(1 matches, 0 no digest): discarding 1 non-matches",
),
],
ids=["identical", "intersect"],
)
def test_new_resolver_hash_intersect(
script: PipTestEnvironment, requirements_template: str, message: str
) -> None:
find_links = _create_find_links(script)
requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text(
requirements_template.format(
sdist_hash=find_links.sdist_hash,
wheel_hash=find_links.wheel_hash,
),
)
result = script.pip(
"install",
"--no-cache-dir",
"--no-deps",
"--no-index",
"--find-links",
find_links.index_html,
"-vv",
"--requirement",
requirements_txt,
)
assert message.format(name="base") in result.stdout, str(result)
def test_new_resolver_hash_intersect_from_constraint(
script: PipTestEnvironment,
) -> None:
find_links = _create_find_links(script)
constraints_txt = script.scratch_path / "constraints.txt"
constraints_txt.write_text(
"base==0.1.0 --hash=sha256:{sdist_hash}".format(
sdist_hash=find_links.sdist_hash,
),
)
requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text(
"""
base==0.1.0 --hash=sha256:{sdist_hash} --hash=sha256:{wheel_hash}
""".format(
sdist_hash=find_links.sdist_hash,
wheel_hash=find_links.wheel_hash,
),
)
result = script.pip(
"install",
"--no-cache-dir",
"--no-deps",
"--no-index",
"--find-links",
find_links.index_html,
"-vv",
"--constraint",
constraints_txt,
"--requirement",
requirements_txt,
)
message = (
"Checked 2 links for project {name!r} against 1 hashes "
"(1 matches, 0 no digest): discarding 1 non-matches"
).format(name="base")
assert message in result.stdout, str(result)
@pytest.mark.parametrize(
"requirements_template, constraints_template",
[
(
"""
base==0.1.0 --hash=sha256:{sdist_hash}
base==0.1.0 --hash=sha256:{wheel_hash}
""",
"",
),
(
"base==0.1.0 --hash=sha256:{sdist_hash}",
"base==0.1.0 --hash=sha256:{wheel_hash}",
),
],
ids=["both-requirements", "one-each"],
)
def test_new_resolver_hash_intersect_empty(
script: PipTestEnvironment,
requirements_template: str,
constraints_template: str,
) -> None:
find_links = _create_find_links(script)
constraints_txt = script.scratch_path / "constraints.txt"
constraints_txt.write_text(
constraints_template.format(
sdist_hash=find_links.sdist_hash,
wheel_hash=find_links.wheel_hash,
),
)
requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text(
requirements_template.format(
sdist_hash=find_links.sdist_hash,
wheel_hash=find_links.wheel_hash,
),
)
result = script.pip(
"install",
"--no-cache-dir",
"--no-deps",
"--no-index",
"--find-links",
find_links.index_html,
"--constraint",
constraints_txt,
"--requirement",
requirements_txt,
expect_error=True,
)
assert (
"THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE."
) in result.stderr, str(result)
def test_new_resolver_hash_intersect_empty_from_constraint(
script: PipTestEnvironment,
) -> None:
find_links = _create_find_links(script)
constraints_txt = script.scratch_path / "constraints.txt"
constraints_txt.write_text(
"""
base==0.1.0 --hash=sha256:{sdist_hash}
base==0.1.0 --hash=sha256:{wheel_hash}
""".format(
sdist_hash=find_links.sdist_hash,
wheel_hash=find_links.wheel_hash,
),
)
result = script.pip(
"install",
"--no-cache-dir",
"--no-deps",
"--no-index",
"--find-links",
find_links.index_html,
"--constraint",
constraints_txt,
"base==0.1.0",
expect_error=True,
)
message = (
"Hashes are required in --require-hashes mode, but they are missing "
"from some requirements."
)
assert message in result.stderr, str(result)
@pytest.mark.parametrize("constrain_by_hash", [False, True])
def test_new_resolver_hash_requirement_and_url_constraint_can_succeed(
script: PipTestEnvironment,
constrain_by_hash: bool,
) -> None:
wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0")
wheel_hash = hashlib.sha256(wheel_path.read_bytes()).hexdigest()
requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text(
"""
base==0.1.0 --hash=sha256:{wheel_hash}
""".format(
wheel_hash=wheel_hash,
),
)
constraints_txt = script.scratch_path / "constraints.txt"
constraint_text = "base @ {wheel_url}\n".format(wheel_url=path_to_url(wheel_path))
if constrain_by_hash:
constraint_text += "base==0.1.0 --hash=sha256:{wheel_hash}\n".format(
wheel_hash=wheel_hash,
)
constraints_txt.write_text(constraint_text)
script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--constraint",
constraints_txt,
"--requirement",
requirements_txt,
)
script.assert_installed(base="0.1.0")
@pytest.mark.parametrize("constrain_by_hash", [False, True])
def test_new_resolver_hash_requirement_and_url_constraint_can_fail(
script: PipTestEnvironment,
constrain_by_hash: bool,
) -> None:
wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0")
other_path = create_basic_wheel_for_package(script, "other", "0.1.0")
other_hash = hashlib.sha256(other_path.read_bytes()).hexdigest()
requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text(
"""
base==0.1.0 --hash=sha256:{other_hash}
""".format(
other_hash=other_hash,
),
)
constraints_txt = script.scratch_path / "constraints.txt"
constraint_text = "base @ {wheel_url}\n".format(wheel_url=path_to_url(wheel_path))
if constrain_by_hash:
constraint_text += "base==0.1.0 --hash=sha256:{other_hash}\n".format(
other_hash=other_hash,
)
constraints_txt.write_text(constraint_text)
result = script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--constraint",
constraints_txt,
"--requirement",
requirements_txt,
expect_error=True,
)
assert (
"THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE."
) in result.stderr, str(result)
script.assert_not_installed("base", "other")
def test_new_resolver_hash_with_extras(script: PipTestEnvironment) -> None:
parent_with_extra_path = create_basic_wheel_for_package(
script, "parent_with_extra", "0.1.0", depends=["child[extra]"]
)
parent_with_extra_hash = hashlib.sha256(
parent_with_extra_path.read_bytes()
).hexdigest()
parent_without_extra_path = create_basic_wheel_for_package(
script, "parent_without_extra", "0.1.0", depends=["child"]
)
parent_without_extra_hash = hashlib.sha256(
parent_without_extra_path.read_bytes()
).hexdigest()
child_path = create_basic_wheel_for_package(
script, "child", "0.1.0", extras={"extra": ["extra"]}
)
child_hash = hashlib.sha256(child_path.read_bytes()).hexdigest()
# Newer release
create_basic_wheel_for_package(
script, "child", "0.2.0", extras={"extra": ["extra"]}
)
extra_path = create_basic_wheel_for_package(script, "extra", "0.1.0")
extra_hash = hashlib.sha256(extra_path.read_bytes()).hexdigest()
requirements_txt = script.scratch_path / "requirements.txt"
requirements_txt.write_text(
"""
child[extra]==0.1.0 --hash=sha256:{child_hash}
parent_with_extra==0.1.0 --hash=sha256:{parent_with_extra_hash}
parent_without_extra==0.1.0 --hash=sha256:{parent_without_extra_hash}
extra==0.1.0 --hash=sha256:{extra_hash}
""".format(
child_hash=child_hash,
parent_with_extra_hash=parent_with_extra_hash,
parent_without_extra_hash=parent_without_extra_hash,
extra_hash=extra_hash,
),
)
script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
script.scratch_path,
"--requirement",
requirements_txt,
)
script.assert_installed(
parent_with_extra="0.1.0",
parent_without_extra="0.1.0",
child="0.1.0",
extra="0.1.0",
)