import datetime import json import os import sys import freezegun import pretend import pytest from pip._internal import self_outdated_check from pip._internal.models.candidate import InstallationCandidate from pip._internal.self_outdated_check import ( SelfCheckState, logger, pip_self_version_check, ) from tests.lib.path import Path class MockBestCandidateResult(object): def __init__(self, best): self.best_candidate = best class MockPackageFinder(object): BASE_URL = 'https://pypi.org/simple/pip-{0}.tar.gz' PIP_PROJECT_NAME = 'pip' INSTALLATION_CANDIDATES = [ InstallationCandidate(PIP_PROJECT_NAME, '6.9.0', BASE_URL.format('6.9.0')), InstallationCandidate(PIP_PROJECT_NAME, '3.3.1', BASE_URL.format('3.3.1')), InstallationCandidate(PIP_PROJECT_NAME, '1.0', BASE_URL.format('1.0')), ] @classmethod def create(cls, *args, **kwargs): return cls() def find_best_candidate(self, project_name): return MockBestCandidateResult(self.INSTALLATION_CANDIDATES[0]) class MockDistribution(object): def __init__(self, installer): self.installer = installer def has_metadata(self, name): return name == 'INSTALLER' def get_metadata_lines(self, name): if self.has_metadata(name): yield self.installer else: raise NotImplementedError('nope') def _options(): ''' Some default options that we pass to self_outdated_check.pip_self_version_check ''' return pretend.stub( find_links=[], index_url='default_url', extra_index_urls=[], no_index=False, pre=False, cache_dir='', ) @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, stored_time, installed_ver, new_ver, installer, check_if_upgrade_required, check_warn_logs): monkeypatch.setattr(self_outdated_check, 'get_installed_version', lambda name: installed_ver) monkeypatch.setattr(self_outdated_check, 'PackageFinder', MockPackageFinder) monkeypatch.setattr(logger, 'warning', pretend.call_recorder(lambda *a, **kw: None)) monkeypatch.setattr(logger, 'debug', pretend.call_recorder(lambda s, exc_info=None: None)) monkeypatch.setattr(self_outdated_check, 'get_distribution', lambda name: MockDistribution(installer)) fake_state = pretend.stub( state={"last_check": stored_time, 'pypi_version': installed_ver}, save=pretend.call_recorder(lambda v, t: None), ) 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", ] ): latest_pypi_version = pip_self_version_check(None, _options()) # See we return None if not installed_version if not installed_ver: assert not latest_pypi_version # See that we saved the correct version elif check_if_upgrade_required: assert fake_state.save.calls == [ pretend.call(new_ver, datetime.datetime(1970, 1, 9, 10, 00, 00)), ] else: # Make sure no Exceptions assert not logger.debug.calls # See that save was not called assert fake_state.save.calls == [] # Ensure we warn the user or not if check_warn_logs: assert len(logger.warning.calls) == 1 else: assert len(logger.warning.calls) == 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, expected): assert expected == self_outdated_check._get_statefile_name(key) def _get_statefile_path(cache_dir, key): return os.path.join( cache_dir, "selfcheck", self_outdated_check._get_statefile_name(key) ) def test_self_check_state_no_cache_dir(): state = SelfCheckState(cache_dir=False) assert state.state == {} assert state.statefile_path is None def test_self_check_state_key_uses_sys_prefix(monkeypatch): 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, tmpdir): 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, tmpdir): 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