Skip to content

Commit

Permalink
Add debug logging to filter_unallowed_hashes().
Browse files Browse the repository at this point in the history
  • Loading branch information
cjerdonek committed Jul 14, 2019
1 parent 0d96a49 commit 3cf192f
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 14 deletions.
64 changes: 51 additions & 13 deletions src/pip/_internal/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,9 @@ def evaluate_link(self, link):


def filter_unallowed_hashes(
candidates, # type: List[InstallationCandidate]
hashes, # type: Hashes
candidates, # type: List[InstallationCandidate]
hashes, # type: Hashes
project_name, # type: str
):
# type: (...) -> List[InstallationCandidate]
"""
Expand All @@ -461,23 +462,58 @@ def filter_unallowed_hashes(
have been installed (e.g. permitting the user to more easily update
their requirements file with the desired hash).
"""
applicable = []
found_allowed_hash = False
if not hashes:
logger.debug(
'Given no hashes to check %s links for project %r: '
'discarding no candidates',
len(candidates),
project_name,
)
# Make sure we're not returning back the given value.
return list(candidates)

matches_or_no_digest = []
# Collect the non-matches for logging purposes.
non_matches = []
match_count = 0
for candidate in candidates:
link = candidate.location
if not link.has_hash:
applicable.append(candidate)
pass
elif link.is_hash_allowed(hashes=hashes):
match_count += 1
else:
non_matches.append(candidate)
continue

if link.is_hash_allowed(hashes=hashes):
found_allowed_hash = True
applicable.append(candidate)
matches_or_no_digest.append(candidate)

if match_count:
filtered = matches_or_no_digest
else:
# Make sure we're not returning back the given value.
filtered = list(candidates)

if found_allowed_hash:
return applicable
if len(filtered) == len(candidates):
discard_message = 'discarding no candidates'
else:
discard_message = 'discarding {} non-matches:\n {}'.format(
len(non_matches),
'\n '.join(str(candidate.location) for candidate in non_matches)
)

logger.debug(
'Checked %s links for project %r against %s hashes '
'(%s matches, %s no digest): %s',
len(candidates),
project_name,
hashes.digest_count,
match_count,
len(matches_or_no_digest) - match_count,
discard_message
)

# Make sure we're not returning back the given value.
return list(candidates)
return filtered


class CandidatePreferences(object):
Expand Down Expand Up @@ -587,7 +623,9 @@ def get_applicable_candidates(
]

return filter_unallowed_hashes(
candidates=applicable_candidates, hashes=self._hashes,
candidates=applicable_candidates,
hashes=self._hashes,
project_name=self._project_name,
)

def make_found_candidates(
Expand Down
5 changes: 5 additions & 0 deletions src/pip/_internal/utils/hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ def __init__(self, hashes=None):
"""
self._allowed = {} if hashes is None else hashes

@property
def digest_count(self):
# type: () -> int
return sum(len(digests) for digests in self._allowed.values())

def is_hash_allowed(
self,
hash_name, # type: str
Expand Down
83 changes: 82 additions & 1 deletion tests/unit/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,95 @@ def test_filter_unallowed_hashes(hex_digest, expected_versions):
'sha256': [hex_digest],
}
hashes = Hashes(hashes_data)
actual = filter_unallowed_hashes(candidates, hashes=hashes)
actual = filter_unallowed_hashes(
candidates, hashes=hashes, project_name='my-project',
)

actual_versions = [str(candidate.version) for candidate in actual]
assert actual_versions == expected_versions
# Check that the return value is always different from the given value.
assert actual is not candidates


def test_filter_unallowed_hashes__no_hashes(caplog):
caplog.set_level(logging.DEBUG)

candidates = [
make_mock_candidate('1.0'),
make_mock_candidate('1.1'),
]
actual = filter_unallowed_hashes(
candidates, hashes=Hashes(), project_name='my-project',
)

# Check that the return value is a copy.
assert actual == candidates
assert actual is not candidates

expected_message = (
"Given no hashes to check 2 links for project 'my-project': "
"discarding no candidates"
)
check_caplog(caplog, 'DEBUG', expected_message)


def test_filter_unallowed_hashes__log_message_with_match(caplog):
caplog.set_level(logging.DEBUG)

# Test 1 match, 2 non-matches, 3 no hashes so all 3 values will be
# different.
candidates = [
make_mock_candidate('1.0'),
make_mock_candidate('1.1',),
make_mock_candidate('1.2',),
make_mock_candidate('1.3', hex_digest=(64 * 'a')),
make_mock_candidate('1.4', hex_digest=(64 * 'b')),
make_mock_candidate('1.5', hex_digest=(64 * 'c')),
]
hashes_data = {
'sha256': [64 * 'a', 64 * 'd'],
}
hashes = Hashes(hashes_data)
actual = filter_unallowed_hashes(
candidates, hashes=hashes, project_name='my-project',
)
assert len(actual) == 4

expected_message = (
"Checked 6 links for project 'my-project' against 2 hashes "
"(1 matches, 3 no digest): discarding 2 non-matches:\n"
" https://example.com/pkg-1.4.tar.gz#sha256="
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"
" https://example.com/pkg-1.5.tar.gz#sha256="
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
)
check_caplog(caplog, 'DEBUG', expected_message)


def test_filter_unallowed_hashes__log_message_with_no_match(caplog):
caplog.set_level(logging.DEBUG)

candidates = [
make_mock_candidate('1.0'),
make_mock_candidate('1.1', hex_digest=(64 * 'b')),
make_mock_candidate('1.2', hex_digest=(64 * 'c')),
]
hashes_data = {
'sha256': [64 * 'a', 64 * 'd'],
}
hashes = Hashes(hashes_data)
actual = filter_unallowed_hashes(
candidates, hashes=hashes, project_name='my-project',
)
assert len(actual) == 3

expected_message = (
"Checked 3 links for project 'my-project' against 2 hashes "
"(0 matches, 1 no digest): discarding no candidates"
)
check_caplog(caplog, 'DEBUG', expected_message)


class TestCandidateEvaluator:

@pytest.mark.parametrize('allow_all_prereleases, prefer_binary', [
Expand Down

0 comments on commit 3cf192f

Please sign in to comment.