Skip to content

Commit

Permalink
Hashes from lines should intersect, not union
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr committed Sep 3, 2020
1 parent 567630b commit 700eb77
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 7 deletions.
3 changes: 3 additions & 0 deletions news/8839.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
New resolver: If a package appears multiple times in user specification with
different ``--hash`` options, only hashes that present in all specifications
should be allowed.
2 changes: 1 addition & 1 deletion src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def _iter_found_candidates(
extras = frozenset() # type: FrozenSet[str]
for ireq in ireqs:
specifier &= ireq.req.specifier
hashes |= ireq.hashes(trust_internet=False)
hashes &= ireq.hashes(trust_internet=False)
extras |= frozenset(ireq.extras)

# We use this to ensure that we only yield a single candidate for
Expand Down
20 changes: 14 additions & 6 deletions src/pip/_internal/utils/hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,24 @@ def __init__(self, hashes=None):
"""
self._allowed = {} if hashes is None else hashes

def __or__(self, other):
def __and__(self, other):
# type: (Hashes) -> Hashes
if not isinstance(other, Hashes):
return NotImplemented
new = self._allowed.copy()

# If either of the Hashes object is entirely empty (i.e. no hash
# specified at all), all hashes from the other object are allowed.
if not other:
return self
if not self:
return other

# Otherwise only hashes that present in both objects are allowed.
new = {}
for alg, values in iteritems(other._allowed):
try:
new[alg] += values
except KeyError:
new[alg] = values
if alg not in self._allowed:
continue
new[alg] = [v for v in values if v in self._allowed[alg]]
return Hashes(new)

@property
Expand Down
87 changes: 87 additions & 0 deletions tests/functional/test_new_resolver_hashes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import collections
import hashlib

import pytest

from pip._internal.utils.urls import path_to_url
from tests.lib import (
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):
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(
"""
<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,
)
)

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 'base' 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 'base' against 1 hashes "
"(1 matches, 0 no digest): discarding 1 non-matches",
),
],
ids=["identical", "intersect"],
)
def test_new_resolver_hash_intersect(script, requirements_template, message):
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",
"--use-feature=2020-resolver",
"--no-cache-dir",
"--no-deps",
"--no-index",
"--find-links", find_links.index_html,
"--verbose",
"--requirement", requirements_txt,
)

assert message in result.stdout, str(result)

0 comments on commit 700eb77

Please sign in to comment.