1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00
pip/tests/unit/test_self_check_outdated.py
Jon Dufresne bf5f4008e7
Replace vendored html5lib with stdlib
The html5lib library isn't strictly required as the same functionality
can be achieved through the stdlib html.parser module.

The html5lib is one of the largest uses of the six library. By dropping
this unnecessary dependency, the pip project is closer to dropping the
six library.

Additionally, html5lib maintenance has slowed down and the project has
rejected pull requests to drop Python 2 support.

For now, the html5lib code remains, but is gated behind a command
line option: `--use-deprecated=html5lib`. After a sufficient amount of
time has passed without any reported bugs, the vendored library and this
flag can be removed completely.
2022-01-28 06:45:57 +00:00

258 lines
7.4 KiB
Python

import datetime
import functools
import json
import os
import sys
from typing import Any, Optional, cast
from unittest import mock
import freezegun # type: ignore
import pytest
from pip._vendor.packaging.version import parse as parse_version
from pip._internal import self_outdated_check
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.models.link import Link
from pip._internal.network.session import PipSession
from pip._internal.self_outdated_check import (
SelfCheckState,
logger,
pip_self_version_check,
)
from tests.lib.path import Path
class MockBestCandidateResult:
def __init__(self, best: InstallationCandidate) -> None:
self.best_candidate = best
class MockPackageFinder:
BASE_URL = "https://pypi.org/simple/pip-{0}.tar.gz"
PIP_PROJECT_NAME = "pip"
INSTALLATION_CANDIDATES = [
InstallationCandidate(
PIP_PROJECT_NAME,
"6.9.0",
Link(BASE_URL.format("6.9.0")),
),
InstallationCandidate(
PIP_PROJECT_NAME,
"3.3.1",
Link(BASE_URL.format("3.3.1")),
),
InstallationCandidate(
PIP_PROJECT_NAME,
"1.0",
Link(BASE_URL.format("1.0")),
),
]
@classmethod
def create(cls, *args: Any, **kwargs: Any) -> "MockPackageFinder":
return cls()
def find_best_candidate(self, project_name: str) -> MockBestCandidateResult:
return MockBestCandidateResult(self.INSTALLATION_CANDIDATES[0])
class MockDistribution:
def __init__(self, installer: str, version: str) -> None:
self.installer = installer
self.version = parse_version(version)
class MockEnvironment:
def __init__(self, installer: str, installed_version: Optional[str]) -> None:
self.installer = installer
self.installed_version = installed_version
def get_distribution(self, name: str) -> Optional[MockDistribution]:
if self.installed_version is None:
return None
return MockDistribution(self.installer, self.installed_version)
def _options() -> mock.Mock:
"""Some default options that we pass to
self_outdated_check.pip_self_version_check"""
return mock.Mock(
find_links=[],
index_url="default_url",
extra_index_urls=[],
no_index=False,
pre=False,
cache_dir="",
deprecated_features_enabled=[],
)
@pytest.mark.parametrize(
[
"stored_time",
"installed_ver",
"new_ver",
"installer",
"check_if_upgrade_required",
"check_warn_logs",
],
[
# Test we return None when installed version is None
("1970-01-01T10:00:00Z", None, "1.0", "pip", False, False),
# Need an upgrade - upgrade warning should print
("1970-01-01T10:00:00Z", "1.0", "6.9.0", "pip", True, True),
# Upgrade available, pip installed via rpm - warning should not print
("1970-01-01T10:00:00Z", "1.0", "6.9.0", "rpm", True, False),
# No upgrade - upgrade warning should not print
("1970-01-9T10:00:00Z", "6.9.0", "6.9.0", "pip", False, False),
],
)
def test_pip_self_version_check(
monkeypatch: pytest.MonkeyPatch,
stored_time: str,
installed_ver: Optional[str],
new_ver: str,
installer: str,
check_if_upgrade_required: bool,
check_warn_logs: bool,
) -> None:
monkeypatch.setattr(
self_outdated_check,
"get_default_environment",
functools.partial(MockEnvironment, installer, installed_ver),
)
monkeypatch.setattr(
self_outdated_check,
"PackageFinder",
MockPackageFinder,
)
monkeypatch.setattr(logger, "warning", mock.Mock())
monkeypatch.setattr(logger, "debug", mock.Mock())
fake_state = mock.Mock(
state={"last_check": stored_time, "pypi_version": installed_ver},
save=mock.Mock(),
)
monkeypatch.setattr(self_outdated_check, "SelfCheckState", lambda **kw: fake_state)
with freezegun.freeze_time(
"1970-01-09 10:00:00",
ignore=[
"six.moves",
"pip._vendor.six.moves",
"pip._vendor.requests.packages.urllib3.packages.six.moves",
],
):
pip_self_version_check(PipSession(), _options())
# See that we saved the correct version
if check_if_upgrade_required:
assert fake_state.save.call_args_list == [
mock.call(new_ver, datetime.datetime(1970, 1, 9, 10, 00, 00)),
]
elif installed_ver:
# Make sure no Exceptions
assert not cast(mock.Mock, logger.debug).call_args_list
# See that save was not called
assert fake_state.save.call_args_list == []
# Ensure we warn the user or not
if check_warn_logs:
assert cast(mock.Mock, logger.warning).call_count == 1
else:
assert cast(mock.Mock, logger.warning).call_count == 0
statefile_name_case_1 = "fcd2d5175dd33d5df759ee7b045264230205ef837bf9f582f7c3ada7"
statefile_name_case_2 = "902cecc0745b8ecf2509ba473f3556f0ba222fedc6df433acda24aa5"
@pytest.mark.parametrize(
"key,expected",
[
("/hello/world/venv", statefile_name_case_1),
("C:\\Users\\User\\Desktop\\venv", statefile_name_case_2),
],
)
def test_get_statefile_name_known_values(key: str, expected: str) -> None:
assert expected == self_outdated_check._get_statefile_name(key)
def _get_statefile_path(cache_dir: str, key: str) -> str:
return os.path.join(
cache_dir, "selfcheck", self_outdated_check._get_statefile_name(key)
)
def test_self_check_state_no_cache_dir() -> None:
state = SelfCheckState(cache_dir="")
assert state.state == {}
assert state.statefile_path is None
def test_self_check_state_key_uses_sys_prefix(monkeypatch: pytest.MonkeyPatch) -> None:
key = "helloworld"
monkeypatch.setattr(sys, "prefix", key)
state = self_outdated_check.SelfCheckState("")
assert state.key == key
def test_self_check_state_reads_expected_statefile(
monkeypatch: pytest.MonkeyPatch, tmpdir: Path
) -> None:
cache_dir = tmpdir / "cache_dir"
cache_dir.mkdir()
key = "helloworld"
statefile_path = _get_statefile_path(str(cache_dir), key)
last_check = "1970-01-02T11:00:00Z"
pypi_version = "1.0"
content = {
"key": key,
"last_check": last_check,
"pypi_version": pypi_version,
}
Path(statefile_path).parent.mkdir()
with open(statefile_path, "w") as f:
json.dump(content, f)
monkeypatch.setattr(sys, "prefix", key)
state = self_outdated_check.SelfCheckState(str(cache_dir))
assert state.state["last_check"] == last_check
assert state.state["pypi_version"] == pypi_version
def test_self_check_state_writes_expected_statefile(
monkeypatch: pytest.MonkeyPatch, tmpdir: Path
) -> None:
cache_dir = tmpdir / "cache_dir"
cache_dir.mkdir()
key = "helloworld"
statefile_path = _get_statefile_path(str(cache_dir), key)
last_check = datetime.datetime.strptime(
"1970-01-02T11:00:00Z", self_outdated_check.SELFCHECK_DATE_FMT
)
pypi_version = "1.0"
monkeypatch.setattr(sys, "prefix", key)
state = self_outdated_check.SelfCheckState(str(cache_dir))
state.save(pypi_version, last_check)
with open(statefile_path) as f:
saved = json.load(f)
expected = {
"key": key,
"last_check": last_check.strftime(self_outdated_check.SELFCHECK_DATE_FMT),
"pypi_version": pypi_version,
}
assert expected == saved