From 60c274be2d771b479439fb96affb1bc5f7e5e1ed Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 29 Aug 2021 15:43:28 -0700 Subject: [PATCH] Complete type annotations of tests/unit/ directory --- ...ff-0617-46ff-8b4a-4daa0011df2e.trivial.rst | 0 setup.cfg | 8 +- src/pip/_internal/req/req_file.py | 14 +- src/pip/_internal/req/req_install.py | 4 +- src/pip/_internal/resolution/base.py | 6 +- .../resolution/resolvelib/factory.py | 4 +- tests/lib/requests_mocks.py | 1 + tests/unit/resolution_resolvelib/conftest.py | 12 +- .../resolution_resolvelib/test_requirement.py | 34 +- .../resolution_resolvelib/test_resolver.py | 39 +- tests/unit/test_appdirs.py | 51 ++- tests/unit/test_base_command.py | 63 +-- tests/unit/test_cache.py | 25 +- tests/unit/test_cmdoptions.py | 6 +- tests/unit/test_collector.py | 152 ++++--- tests/unit/test_command_install.py | 34 +- tests/unit/test_commands.py | 38 +- tests/unit/test_compat.py | 11 +- tests/unit/test_configuration.py | 58 ++- tests/unit/test_direct_url.py | 24 +- tests/unit/test_direct_url_helpers.py | 33 +- tests/unit/test_finder.py | 93 +++-- tests/unit/test_format_control.py | 25 +- tests/unit/test_index.py | 145 ++++--- tests/unit/test_link.py | 40 +- tests/unit/test_locations.py | 30 +- tests/unit/test_logging.py | 26 +- tests/unit/test_metadata.py | 33 +- tests/unit/test_models.py | 17 +- tests/unit/test_models_wheel.py | 34 +- tests/unit/test_network_auth.py | 77 ++-- tests/unit/test_network_cache.py | 16 +- tests/unit/test_network_download.py | 27 +- tests/unit/test_network_lazy_wheel.py | 16 +- tests/unit/test_network_session.py | 43 +- tests/unit/test_network_utils.py | 7 +- tests/unit/test_operations_prepare.py | 36 +- tests/unit/test_options.py | 394 ++++++++++++------ tests/unit/test_packaging.py | 8 +- tests/unit/test_pep517.py | 8 +- tests/unit/test_req.py | 182 ++++---- tests/unit/test_req_file.py | 272 ++++++++---- tests/unit/test_req_install.py | 16 +- tests/unit/test_req_uninstall.py | 44 +- tests/unit/test_resolution_legacy_resolver.py | 64 ++- tests/unit/test_search_scope.py | 10 +- tests/unit/test_self_check_outdated.py | 65 +-- tests/unit/test_target_python.py | 33 +- tests/unit/test_urls.py | 11 +- tests/unit/test_utils.py | 230 +++++----- tests/unit/test_utils_compatibility_tags.py | 21 +- tests/unit/test_utils_distutils_args.py | 14 +- tests/unit/test_utils_filesystem.py | 15 +- tests/unit/test_utils_parallel.py | 25 +- tests/unit/test_utils_pkg_resources.py | 4 +- tests/unit/test_utils_subprocess.py | 93 +++-- tests/unit/test_utils_temp_dir.py | 41 +- tests/unit/test_utils_unpacking.py | 36 +- tests/unit/test_utils_virtualenv.py | 45 +- tests/unit/test_utils_wheel.py | 26 +- tests/unit/test_vcs.py | 174 ++++---- tests/unit/test_vcs_mercurial.py | 3 +- tests/unit/test_wheel.py | 141 ++++--- tests/unit/test_wheel_builder.py | 68 +-- 64 files changed, 2052 insertions(+), 1273 deletions(-) create mode 100644 news/872febff-0617-46ff-8b4a-4daa0011df2e.trivial.rst diff --git a/news/872febff-0617-46ff-8b4a-4daa0011df2e.trivial.rst b/news/872febff-0617-46ff-8b4a-4daa0011df2e.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/setup.cfg b/setup.cfg index 96b7baf4a05..6a3e8c7f5da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,8 +51,12 @@ follow_imports = skip [mypy-pip._vendor.requests.*] follow_imports = skip -[mypy-tests.*] -# TODO: The following option should be removed at some point in the future. +# TODO: The following options should be removed at some point in the future. +[mypy-tests.conftest] +allow_untyped_defs = True +[mypy-tests.lib.*] +allow_untyped_defs = True +[mypy-tests.functional.*] allow_untyped_defs = True [tool:pytest] diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index b392989bf8d..03ae50492c5 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -8,7 +8,17 @@ import shlex import urllib.parse from optparse import Values -from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Tuple +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Tuple, +) from pip._internal.cli import cmdoptions from pip._internal.exceptions import InstallationError, RequirementsFileParseError @@ -27,7 +37,7 @@ __all__ = ["parse_requirements"] -ReqFileLines = Iterator[Tuple[int, str]] +ReqFileLines = Iterable[Tuple[int, str]] LineParser = Callable[[str], Tuple[str, Values]] diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index add22b552cf..0df0ff6f438 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -7,7 +7,7 @@ import sys import uuid import zipfile -from typing import Any, Dict, Iterable, List, Optional, Sequence, Union +from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union from pip._vendor import pkg_resources from pip._vendor.packaging.markers import Marker @@ -103,7 +103,7 @@ def __init__( global_options: Optional[List[str]] = None, hash_options: Optional[Dict[str, List[str]]] = None, constraint: bool = False, - extras: Iterable[str] = (), + extras: Collection[str] = (), user_supplied: bool = False, ) -> None: assert req is None or isinstance(req, Requirement), req diff --git a/src/pip/_internal/resolution/base.py b/src/pip/_internal/resolution/base.py index 3f83ef0f533..42dade18c1e 100644 --- a/src/pip/_internal/resolution/base.py +++ b/src/pip/_internal/resolution/base.py @@ -1,9 +1,11 @@ -from typing import Callable, List +from typing import Callable, List, Optional from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_set import RequirementSet -InstallRequirementProvider = Callable[[str, InstallRequirement], InstallRequirement] +InstallRequirementProvider = Callable[ + [str, Optional[InstallRequirement]], InstallRequirement +] class BaseResolver: diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 2721bd979cc..e96764d31ea 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -347,7 +347,7 @@ def _iter_candidates_from_constraints( def find_candidates( self, identifier: str, - requirements: Mapping[str, Iterator[Requirement]], + requirements: Mapping[str, Iterable[Requirement]], incompatibilities: Mapping[str, Iterator[Candidate]], constraint: Constraint, prefers_installed: bool, @@ -484,7 +484,7 @@ def make_requirement_from_candidate( def make_requirement_from_spec( self, specifier: str, - comes_from: InstallRequirement, + comes_from: Optional[InstallRequirement], requested_extras: Iterable[str] = (), ) -> Optional[Requirement]: ireq = self._make_install_req_from_spec(specifier, comes_from) diff --git a/tests/lib/requests_mocks.py b/tests/lib/requests_mocks.py index 5db3970cbb2..1a77d271049 100644 --- a/tests/lib/requests_mocks.py +++ b/tests/lib/requests_mocks.py @@ -29,6 +29,7 @@ def __init__(self, contents): self.url = None self.headers = {"Content-Length": len(contents)} self.history = [] + self.from_cache = False class MockConnection: diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py index 99c847a662b..109b337ff20 100644 --- a/tests/unit/resolution_resolvelib/conftest.py +++ b/tests/unit/resolution_resolvelib/conftest.py @@ -1,3 +1,5 @@ +from typing import Iterator + import pytest from pip._internal.cli.req_command import RequirementCommand @@ -9,15 +11,17 @@ from pip._internal.models.search_scope import SearchScope from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.network.session import PipSession +from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req.constructors import install_req_from_line from pip._internal.req.req_tracker import get_requirement_tracker from pip._internal.resolution.resolvelib.factory import Factory from pip._internal.resolution.resolvelib.provider import PipProvider from pip._internal.utils.temp_dir import TempDirectory, global_tempdir_manager +from tests.lib import TestData @pytest.fixture -def finder(data): +def finder(data: TestData) -> Iterator[PackageFinder]: session = PipSession() scope = SearchScope([str(data.packages)], []) collector = LinkCollector(session, scope) @@ -27,7 +31,7 @@ def finder(data): @pytest.fixture -def preparer(finder): +def preparer(finder: PackageFinder) -> Iterator[RequirementPreparer]: session = PipSession() rc = InstallCommand("x", "y") o = rc.parse_args([]) @@ -48,7 +52,7 @@ def preparer(finder): @pytest.fixture -def factory(finder, preparer): +def factory(finder: PackageFinder, preparer: RequirementPreparer) -> Iterator[Factory]: yield Factory( finder=finder, preparer=preparer, @@ -63,7 +67,7 @@ def factory(finder, preparer): @pytest.fixture -def provider(factory): +def provider(factory: Factory) -> Iterator[PipProvider]: yield PipProvider( factory=factory, constraints={}, diff --git a/tests/unit/resolution_resolvelib/test_requirement.py b/tests/unit/resolution_resolvelib/test_requirement.py index 32cabcd3c42..387afbc2304 100644 --- a/tests/unit/resolution_resolvelib/test_requirement.py +++ b/tests/unit/resolution_resolvelib/test_requirement.py @@ -1,8 +1,14 @@ +from typing import Iterator, List, Tuple + import pytest from pip._vendor.resolvelib import BaseReporter, Resolver -from pip._internal.resolution.resolvelib.base import Candidate, Constraint +from pip._internal.resolution.resolvelib.base import Candidate, Constraint, Requirement +from pip._internal.resolution.resolvelib.factory import Factory +from pip._internal.resolution.resolvelib.provider import PipProvider from pip._internal.utils.urls import path_to_url +from tests.lib import TestData +from tests.lib.path import Path # NOTE: All tests are prefixed `test_rlr` (for "test resolvelib resolver"). # This helps select just these tests using pytest's `-k` option, and @@ -18,11 +24,11 @@ @pytest.fixture -def test_cases(data): - def data_file(name): +def test_cases(data: TestData) -> Iterator[List[Tuple[str, str, int]]]: + def data_file(name: str) -> Path: return data.packages.joinpath(name) - def data_url(name): + def data_url(name: str) -> str: return path_to_url(data_file(name)) test_cases = [ @@ -47,17 +53,23 @@ def data_url(name): yield test_cases -def test_new_resolver_requirement_has_name(test_cases, factory): +def test_new_resolver_requirement_has_name( + test_cases: List[Tuple[str, str, int]], factory: Factory +) -> None: """All requirements should have a name""" for spec, name, _ in test_cases: req = factory.make_requirement_from_spec(spec, comes_from=None) + assert req is not None assert req.name == name -def test_new_resolver_correct_number_of_matches(test_cases, factory): +def test_new_resolver_correct_number_of_matches( + test_cases: List[Tuple[str, str, int]], factory: Factory +) -> None: """Requirements should return the correct number of candidates""" for spec, _, match_count in test_cases: req = factory.make_requirement_from_spec(spec, comes_from=None) + assert req is not None matches = factory.find_candidates( req.name, {req.name: [req]}, @@ -68,10 +80,13 @@ def test_new_resolver_correct_number_of_matches(test_cases, factory): assert sum(1 for _ in matches) == match_count -def test_new_resolver_candidates_match_requirement(test_cases, factory): +def test_new_resolver_candidates_match_requirement( + test_cases: List[Tuple[str, str, int]], factory: Factory +) -> None: """Candidates returned from find_candidates should satisfy the requirement""" for spec, _, _ in test_cases: req = factory.make_requirement_from_spec(spec, comes_from=None) + assert req is not None candidates = factory.find_candidates( req.name, {req.name: [req]}, @@ -84,9 +99,10 @@ def test_new_resolver_candidates_match_requirement(test_cases, factory): assert req.is_satisfied_by(c) -def test_new_resolver_full_resolve(factory, provider): +def test_new_resolver_full_resolve(factory: Factory, provider: PipProvider) -> None: """A very basic full resolve""" req = factory.make_requirement_from_spec("simplewheel", comes_from=None) - r = Resolver(provider, BaseReporter()) + assert req is not None + r: Resolver[Requirement, Candidate, str] = Resolver(provider, BaseReporter()) result = r.resolve([req]) assert set(result.mapping.keys()) == {"simplewheel"} diff --git a/tests/unit/resolution_resolvelib/test_resolver.py b/tests/unit/resolution_resolvelib/test_resolver.py index f895eb27145..1fcde34a41e 100644 --- a/tests/unit/resolution_resolvelib/test_resolver.py +++ b/tests/unit/resolution_resolvelib/test_resolver.py @@ -1,3 +1,4 @@ +from typing import Dict, List, Optional, Tuple, cast from unittest import mock import pytest @@ -5,6 +6,8 @@ from pip._vendor.resolvelib.resolvers import Result from pip._vendor.resolvelib.structs import DirectedGraph +from pip._internal.index.package_finder import PackageFinder +from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req.constructors import install_req_from_line from pip._internal.req.req_set import RequirementSet from pip._internal.resolution.resolvelib.resolver import ( @@ -14,29 +17,31 @@ @pytest.fixture() -def resolver(preparer, finder): +def resolver(preparer: RequirementPreparer, finder: PackageFinder) -> Resolver: resolver = Resolver( preparer=preparer, finder=finder, wheel_cache=None, make_install_req=mock.Mock(), - use_user_site="not-used", - ignore_dependencies="not-used", - ignore_installed="not-used", - ignore_requires_python="not-used", - force_reinstall="not-used", + use_user_site=False, + ignore_dependencies=False, + ignore_installed=False, + ignore_requires_python=False, + force_reinstall=False, upgrade_strategy="to-satisfy-only", ) return resolver -def _make_graph(edges): +def _make_graph( + edges: List[Tuple[Optional[str], Optional[str]]] +) -> "DirectedGraph[Optional[str]]": """Build graph from edge declarations.""" - graph = DirectedGraph() + graph: "DirectedGraph[Optional[str]]" = DirectedGraph() for parent, child in edges: - parent = canonicalize_name(parent) if parent else None - child = canonicalize_name(child) if child else None + parent = cast(str, canonicalize_name(parent)) if parent else None + child = cast(str, canonicalize_name(child)) if child else None for v in (parent, child): if v not in graph: graph.add(v) @@ -76,12 +81,16 @@ def _make_graph(edges): ), ], ) -def test_new_resolver_get_installation_order(resolver, edges, ordered_reqs): +def test_new_resolver_get_installation_order( + resolver: Resolver, + edges: List[Tuple[Optional[str], Optional[str]]], + ordered_reqs: List[str], +) -> None: graph = _make_graph(edges) # Mapping values and criteria are not used in test, so we stub them out. mapping = {vertex: None for vertex in graph if vertex is not None} - resolver._result = Result(mapping, graph, criteria=None) + resolver._result = Result(mapping, graph, criteria=None) # type: ignore reqset = RequirementSet() for r in ordered_reqs: @@ -229,7 +238,11 @@ def test_new_resolver_get_installation_order(resolver, edges, ordered_reqs): ), ], ) -def test_new_resolver_topological_weights(name, edges, expected_weights): +def test_new_resolver_topological_weights( + name: str, + edges: List[Tuple[Optional[str], Optional[str]]], + expected_weights: Dict[Optional[str], int], +) -> None: graph = _make_graph(edges) weights = get_topological_weights(graph, len(expected_weights)) diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py index 6a3eb00e3b9..70453c2755c 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -4,13 +4,14 @@ import sys from unittest import mock +import pytest from pip._vendor import appdirs as _appdirs from pip._internal.utils import appdirs class TestUserCacheDir: - def test_user_cache_dir_win(self, monkeypatch): + def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( @@ -28,7 +29,7 @@ def test_user_cache_dir_win(self, monkeypatch): ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] - def test_user_cache_dir_osx(self, monkeypatch): + def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") @@ -36,7 +37,7 @@ def test_user_cache_dir_osx(self, monkeypatch): assert appdirs.user_cache_dir("pip") == "/home/test/Library/Caches/pip" - def test_user_cache_dir_linux(self, monkeypatch): + def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CACHE_HOME", raising=False) @@ -45,7 +46,9 @@ def test_user_cache_dir_linux(self, monkeypatch): assert appdirs.user_cache_dir("pip") == "/home/test/.cache/pip" - def test_user_cache_dir_linux_override(self, monkeypatch): + def test_user_cache_dir_linux_override( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_CACHE_HOME", "/home/test/.other-cache") @@ -54,7 +57,9 @@ def test_user_cache_dir_linux_override(self, monkeypatch): assert appdirs.user_cache_dir("pip") == "/home/test/.other-cache/pip" - def test_user_cache_dir_linux_home_slash(self, monkeypatch): + def test_user_cache_dir_linux_home_slash( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) # Verify that we are not affected by https://bugs.python.org/issue14768 @@ -64,7 +69,7 @@ def test_user_cache_dir_linux_home_slash(self, monkeypatch): assert appdirs.user_cache_dir("pip") == "/.cache/pip" - def test_user_cache_dir_unicode(self, monkeypatch): + def test_user_cache_dir_unicode(self, monkeypatch: pytest.MonkeyPatch) -> None: if sys.platform != "win32": return @@ -86,7 +91,7 @@ def my_get_win_folder(csidl_name): class TestSiteConfigDirs: - def test_site_config_dirs_win(self, monkeypatch): + def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch) -> None: _get_win_folder = mock.Mock(return_value="C:\\ProgramData") monkeypatch.setattr( @@ -101,7 +106,7 @@ def test_site_config_dirs_win(self, monkeypatch): assert appdirs.site_config_dirs("pip") == ["C:\\ProgramData\\pip"] assert _get_win_folder.call_args_list == [mock.call("CSIDL_COMMON_APPDATA")] - def test_site_config_dirs_osx(self, monkeypatch): + def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") @@ -109,7 +114,7 @@ def test_site_config_dirs_osx(self, monkeypatch): assert appdirs.site_config_dirs("pip") == ["/Library/Application Support/pip"] - def test_site_config_dirs_linux(self, monkeypatch): + def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) @@ -117,7 +122,9 @@ def test_site_config_dirs_linux(self, monkeypatch): assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"] - def test_site_config_dirs_linux_override(self, monkeypatch): + def test_site_config_dirs_linux_override( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setattr(os, "pathsep", ":") @@ -131,7 +138,9 @@ def test_site_config_dirs_linux_override(self, monkeypatch): "/etc", ] - def test_site_config_dirs_linux_empty(self, monkeypatch): + def test_site_config_dirs_linux_empty( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setattr(os, "pathsep", ":") @@ -141,7 +150,9 @@ def test_site_config_dirs_linux_empty(self, monkeypatch): class TestUserConfigDir: - def test_user_config_dir_win_no_roaming(self, monkeypatch): + def test_user_config_dir_win_no_roaming( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local") monkeypatch.setattr( @@ -159,7 +170,9 @@ def test_user_config_dir_win_no_roaming(self, monkeypatch): ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] - def test_user_config_dir_win_yes_roaming(self, monkeypatch): + def test_user_config_dir_win_yes_roaming( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: _get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Roaming") monkeypatch.setattr( @@ -176,7 +189,7 @@ def test_user_config_dir_win_yes_roaming(self, monkeypatch): ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_APPDATA")] - def test_user_config_dir_osx(self, monkeypatch): + def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_appdirs, "system", "darwin") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("HOME", "/home/test") @@ -190,7 +203,7 @@ def test_user_config_dir_osx(self, monkeypatch): else: assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" - def test_user_config_dir_linux(self, monkeypatch): + def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) @@ -199,7 +212,9 @@ def test_user_config_dir_linux(self, monkeypatch): assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" - def test_user_config_dir_linux_override(self, monkeypatch): + def test_user_config_dir_linux_override( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config") @@ -208,7 +223,9 @@ def test_user_config_dir_linux_override(self, monkeypatch): assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip" - def test_user_config_dir_linux_home_slash(self, monkeypatch): + def test_user_config_dir_linux_home_slash( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(_appdirs, "system", "linux2") monkeypatch.setattr(os, "path", posixpath) # Verify that we are not affected by https://bugs.python.org/issue14768 diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py index 8ce23ed6816..9a61ccc77b4 100644 --- a/tests/unit/test_base_command.py +++ b/tests/unit/test_base_command.py @@ -1,5 +1,7 @@ import logging import os +from optparse import Values +from typing import Callable, Iterator, List, NoReturn, Optional from unittest.mock import Mock, patch import pytest @@ -9,10 +11,11 @@ from pip._internal.utils import temp_dir from pip._internal.utils.logging import BrokenStdoutLoggingError from pip._internal.utils.temp_dir import TempDirectory +from tests.lib.path import Path @pytest.fixture -def fixed_time(utc): +def fixed_time(utc: None) -> Iterator[None]: with patch("time.time", lambda: 1547704837.040001): yield @@ -21,20 +24,22 @@ class FakeCommand(Command): _name = "fake" - def __init__(self, run_func=None, error=False): + def __init__( + self, run_func: Optional[Callable[[], int]] = None, error: bool = False + ) -> None: if error: - def run_func(): + def run_func() -> int: raise SystemExit(1) self.run_func = run_func super().__init__(self._name, self._name) - def main(self, args): + def main(self, args: List[str]) -> int: args.append("--disable-pip-version-check") return super().main(args) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: logging.getLogger("pip.tests").info("fake") # Return SUCCESS from run if run_func is not provided if self.run_func: @@ -46,18 +51,19 @@ def run(self, options, args): class FakeCommandWithUnicode(FakeCommand): _name = "fake_unicode" - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: logging.getLogger("pip.tests").info(b"bytes here \xE9") logging.getLogger("pip.tests").info(b"unicode here \xC3\xA9".decode("utf-8")) + return SUCCESS class TestCommand: - def call_main(self, capsys, args): + def call_main(self, capsys: pytest.CaptureFixture[str], args: List[str]) -> str: """ Call command.main(), and return the command's stderr. """ - def raise_broken_stdout(): + def raise_broken_stdout() -> NoReturn: raise BrokenStdoutLoggingError() cmd = FakeCommand(run_func=raise_broken_stdout) @@ -67,7 +73,7 @@ def raise_broken_stdout(): return stderr - def test_raise_broken_stdout(self, capsys): + def test_raise_broken_stdout(self, capsys: pytest.CaptureFixture[str]) -> None: """ Test raising BrokenStdoutLoggingError. """ @@ -75,7 +81,9 @@ def test_raise_broken_stdout(self, capsys): assert stderr.rstrip() == "ERROR: Pipe to stdout was broken" - def test_raise_broken_stdout__debug_logging(self, capsys): + def test_raise_broken_stdout__debug_logging( + self, capsys: pytest.CaptureFixture[str] + ) -> None: """ Test raising BrokenStdoutLoggingError with debug logging enabled. """ @@ -86,7 +94,7 @@ def test_raise_broken_stdout__debug_logging(self, capsys): @patch("pip._internal.cli.req_command.Command.handle_pip_version_check") -def test_handle_pip_version_check_called(mock_handle_version_check): +def test_handle_pip_version_check_called(mock_handle_version_check: Mock) -> None: """ Check that Command.handle_pip_version_check() is called. """ @@ -95,7 +103,7 @@ def test_handle_pip_version_check_called(mock_handle_version_check): mock_handle_version_check.assert_called_once() -def test_log_command_success(fixed_time, tmpdir): +def test_log_command_success(fixed_time: None, tmpdir: Path) -> None: """Test the --log option logs when command succeeds.""" cmd = FakeCommand() log_path = tmpdir.joinpath("log") @@ -104,7 +112,7 @@ def test_log_command_success(fixed_time, tmpdir): assert f.read().rstrip() == "2019-01-17T06:00:37,040 fake" -def test_log_command_error(fixed_time, tmpdir): +def test_log_command_error(fixed_time: None, tmpdir: Path) -> None: """Test the --log option logs when command fails.""" cmd = FakeCommand(error=True) log_path = tmpdir.joinpath("log") @@ -113,7 +121,7 @@ def test_log_command_error(fixed_time, tmpdir): assert f.read().startswith("2019-01-17T06:00:37,040 fake") -def test_log_file_command_error(fixed_time, tmpdir): +def test_log_file_command_error(fixed_time: None, tmpdir: Path) -> None: """Test the --log-file option logs (when there's an error).""" cmd = FakeCommand(error=True) log_file_path = tmpdir.joinpath("log_file") @@ -122,7 +130,7 @@ def test_log_file_command_error(fixed_time, tmpdir): assert f.read().startswith("2019-01-17T06:00:37,040 fake") -def test_log_unicode_messages(fixed_time, tmpdir): +def test_log_unicode_messages(fixed_time: None, tmpdir: Path) -> None: """Tests that logging bytestrings and unicode objects don't break logging. """ @@ -132,17 +140,18 @@ def test_log_unicode_messages(fixed_time, tmpdir): @pytest.mark.no_auto_tempdir_manager -def test_base_command_provides_tempdir_helpers(): +def test_base_command_provides_tempdir_helpers() -> None: assert temp_dir._tempdir_manager is None assert temp_dir._tempdir_registry is None - def assert_helpers_set(options, args): + def assert_helpers_set(options: Values, args: List[str]) -> int: assert temp_dir._tempdir_manager is not None assert temp_dir._tempdir_registry is not None return SUCCESS c = Command("fake", "fake") - c.run = Mock(side_effect=assert_helpers_set) + # https://github.com/python/mypy/issues/2427 + c.run = Mock(side_effect=assert_helpers_set) # type: ignore[assignment] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() @@ -152,20 +161,22 @@ def assert_helpers_set(options, args): @pytest.mark.parametrize("kind,exists", [(not_deleted, True), ("deleted", False)]) @pytest.mark.no_auto_tempdir_manager -def test_base_command_global_tempdir_cleanup(kind, exists): +def test_base_command_global_tempdir_cleanup(kind: str, exists: bool) -> None: assert temp_dir._tempdir_manager is None assert temp_dir._tempdir_registry is None class Holder: - value = None + value: str - def create_temp_dirs(options, args): + def create_temp_dirs(options: Values, args: List[str]) -> int: + assert c.tempdir_registry is not None c.tempdir_registry.set_delete(not_deleted, False) Holder.value = TempDirectory(kind=kind, globally_managed=True).path return SUCCESS c = Command("fake", "fake") - c.run = Mock(side_effect=create_temp_dirs) + # https://github.com/python/mypy/issues/2427 + c.run = Mock(side_effect=create_temp_dirs) # type: ignore[assignment] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() assert os.path.exists(Holder.value) == exists @@ -173,11 +184,12 @@ def create_temp_dirs(options, args): @pytest.mark.parametrize("kind,exists", [(not_deleted, True), ("deleted", False)]) @pytest.mark.no_auto_tempdir_manager -def test_base_command_local_tempdir_cleanup(kind, exists): +def test_base_command_local_tempdir_cleanup(kind: str, exists: bool) -> None: assert temp_dir._tempdir_manager is None assert temp_dir._tempdir_registry is None - def create_temp_dirs(options, args): + def create_temp_dirs(options: Values, args: List[str]) -> int: + assert c.tempdir_registry is not None c.tempdir_registry.set_delete(not_deleted, False) with TempDirectory(kind=kind) as d: @@ -187,6 +199,7 @@ def create_temp_dirs(options, args): return SUCCESS c = Command("fake", "fake") - c.run = Mock(side_effect=create_temp_dirs) + # https://github.com/python/mypy/issues/2427 + c.run = Mock(side_effect=create_temp_dirs) # type: ignore[assignment] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() diff --git a/tests/unit/test_cache.py b/tests/unit/test_cache.py index c47e42f1283..acb16034186 100644 --- a/tests/unit/test_cache.py +++ b/tests/unit/test_cache.py @@ -6,24 +6,25 @@ from pip._internal.models.format_control import FormatControl from pip._internal.models.link import Link from pip._internal.utils.misc import ensure_dir +from tests.lib.path import Path -def test_falsey_path_none(): - wc = WheelCache(False, None) +def test_falsey_path_none() -> None: + wc = WheelCache("", FormatControl()) assert wc.cache_dir is None -def test_subdirectory_fragment(): +def test_subdirectory_fragment() -> None: """ Test the subdirectory URL fragment is part of the cache key. """ - wc = WheelCache("/tmp/.foo/", None) + wc = WheelCache("/tmp/.foo/", FormatControl()) link1 = Link("git+https://g.c/o/r#subdirectory=d1") link2 = Link("git+https://g.c/o/r#subdirectory=d2") assert wc.get_path_for_link(link1) != wc.get_path_for_link(link2) -def test_wheel_name_filter(tmpdir): +def test_wheel_name_filter(tmpdir: Path) -> None: """ Test the wheel cache filters on wheel name when several wheels for different package are stored under the same cache directory. @@ -42,7 +43,7 @@ def test_wheel_name_filter(tmpdir): assert wc.get(link, "package2", [Tag("py3", "none", "any")]) is link -def test_cache_hash(): +def test_cache_hash() -> None: h = _hash_dict({"url": "https://g.c/o/r"}) assert h == "72aa79d3315c181d2cc23239d7109a782de663b6f89982624d8c1e86" h = _hash_dict({"url": "https://g.c/o/r", "subdirectory": "sd"}) @@ -51,7 +52,7 @@ def test_cache_hash(): assert h == "f83b32dfa27a426dec08c21bf006065dd003d0aac78e7fc493d9014d" -def test_get_cache_entry(tmpdir): +def test_get_cache_entry(tmpdir: Path) -> None: wc = WheelCache(tmpdir, FormatControl()) persi_link = Link("https://g.c/o/r/persi") persi_path = wc.get_path_for_link(persi_link) @@ -65,6 +66,12 @@ def test_get_cache_entry(tmpdir): pass other_link = Link("https://g.c/o/r/other") supported_tags = [Tag("py3", "none", "any")] - assert wc.get_cache_entry(persi_link, "persi", supported_tags).persistent - assert not wc.get_cache_entry(ephem_link, "ephem", supported_tags).persistent + entry = wc.get_cache_entry(persi_link, "persi", supported_tags) + assert entry is not None + assert entry.persistent + + entry = wc.get_cache_entry(ephem_link, "ephem", supported_tags) + assert entry is not None + assert not entry.persistent + assert wc.get_cache_entry(other_link, "other", supported_tags) is None diff --git a/tests/unit/test_cmdoptions.py b/tests/unit/test_cmdoptions.py index 7e067cb8ba2..1e5ef995cd0 100644 --- a/tests/unit/test_cmdoptions.py +++ b/tests/unit/test_cmdoptions.py @@ -1,3 +1,5 @@ +from typing import Optional, Tuple + import pytest from pip._internal.cli.cmdoptions import _convert_python_version @@ -22,6 +24,8 @@ ("3.7.3.1", ((), "at most three version parts are allowed")), ], ) -def test_convert_python_version(value, expected): +def test_convert_python_version( + value: str, expected: Tuple[Optional[Tuple[int, ...]], Optional[str]] +) -> None: actual = _convert_python_version(value) assert actual == expected, f"actual: {actual!r}" diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py index cda10b20d65..8b60c302915 100644 --- a/tests/unit/test_collector.py +++ b/tests/unit/test_collector.py @@ -5,8 +5,8 @@ import urllib.request import uuid from textwrap import dedent +from typing import List, Optional, Tuple from unittest import mock -from unittest.mock import Mock, patch import pytest from pip._vendor import html5lib, requests @@ -26,10 +26,12 @@ parse_links, ) from pip._internal.index.sources import _FlatDirectorySource, _IndexDirectorySource +from pip._internal.models.candidate import InstallationCandidate from pip._internal.models.index import PyPI from pip._internal.models.link import Link from pip._internal.network.session import PipSession -from tests.lib import make_test_link_collector +from tests.lib import TestData, make_test_link_collector +from tests.lib.path import Path @pytest.mark.parametrize( @@ -39,7 +41,7 @@ "file:///opt/data/pip-18.0.tar.gz", ], ) -def test_get_html_response_archive_to_naive_scheme(url): +def test_get_html_response_archive_to_naive_scheme(url: str) -> None: """ `_get_html_response()` should error on an archive-like URL if the scheme does not allow "poking" without getting data. @@ -57,8 +59,8 @@ def test_get_html_response_archive_to_naive_scheme(url): ) @mock.patch("pip._internal.index.collector.raise_for_status") def test_get_html_response_archive_to_http_scheme( - mock_raise_for_status, url, content_type -): + mock_raise_for_status: mock.Mock, url: str, content_type: str +) -> None: """ `_get_html_response()` should send a HEAD request on an archive-like URL if the scheme supports it, and raise `_NotHTML` if the response isn't HTML. @@ -90,7 +92,9 @@ def test_get_html_response_archive_to_http_scheme( ("file:///opt/data/pip-18.0.tar.gz"), ], ) -def test_get_html_page_invalid_content_type_archive(caplog, url): +def test_get_html_page_invalid_content_type_archive( + caplog: pytest.LogCaptureFixture, url: str +) -> None: """`_get_html_page()` should warn if an archive URL is not HTML and therefore cannot be used for a HEAD request. """ @@ -116,7 +120,9 @@ def test_get_html_page_invalid_content_type_archive(caplog, url): ], ) @mock.patch("pip._internal.index.collector.raise_for_status") -def test_get_html_response_archive_to_http_scheme_is_html(mock_raise_for_status, url): +def test_get_html_response_archive_to_http_scheme_is_html( + mock_raise_for_status: mock.Mock, url: str +) -> None: """ `_get_html_response()` should work with archive-like URLs if the HEAD request is responded with text/html. @@ -158,7 +164,7 @@ def test_get_html_response_archive_to_http_scheme_is_html(mock_raise_for_status, ], ) @mock.patch("pip._internal.index.collector.raise_for_status") -def test_get_html_response_no_head(mock_raise_for_status, url): +def test_get_html_response_no_head(mock_raise_for_status: mock.Mock, url: str) -> None: """ `_get_html_response()` shouldn't send a HEAD request if the URL does not look like an archive, only the GET request that retrieves data. @@ -192,7 +198,9 @@ def test_get_html_response_no_head(mock_raise_for_status, url): @mock.patch("pip._internal.index.collector.raise_for_status") -def test_get_html_response_dont_log_clear_text_password(mock_raise_for_status, caplog): +def test_get_html_response_dont_log_clear_text_password( + mock_raise_for_status: mock.Mock, caplog: pytest.LogCaptureFixture +) -> None: """ `_get_html_response()` should redact the password from the index URL in its DEBUG log message. @@ -243,7 +251,7 @@ def test_get_html_response_dont_log_clear_text_password(mock_raise_for_status, c ), ], ) -def test_determine_base_url(html, url, expected): +def test_determine_base_url(html: bytes, url: str, expected: str) -> None: document = html5lib.parse( html, transport_encoding=None, @@ -288,7 +296,7 @@ def test_determine_base_url(html, url, expected): ], ) @pytest.mark.parametrize("is_local_path", [True, False]) -def test_clean_url_path(path, expected, is_local_path): +def test_clean_url_path(path: str, expected: str, is_local_path: bool) -> None: assert _clean_url_path(path, is_local_path=is_local_path) == expected @@ -310,7 +318,7 @@ def test_clean_url_path(path, expected, is_local_path): ), ], ) -def test_clean_url_path_with_local_path(path, expected): +def test_clean_url_path_with_local_path(path: str, expected: str) -> None: actual = _clean_url_path(path, is_local_path=True) assert actual == expected @@ -406,11 +414,13 @@ def test_clean_url_path_with_local_path(path, expected): ), ], ) -def test_clean_link(url, clean_url): +def test_clean_link(url: str, clean_url: str) -> None: assert _clean_link(url) == clean_url -def _test_parse_links_data_attribute(anchor_html, attr, expected): +def _test_parse_links_data_attribute( + anchor_html: str, attr: str, expected: Optional[str] +) -> None: html = f'{anchor_html}' html_bytes = html.encode("utf-8") page = HTMLPage( @@ -445,7 +455,9 @@ def _test_parse_links_data_attribute(anchor_html, attr, expected): ), ], ) -def test_parse_links__requires_python(anchor_html, expected): +def test_parse_links__requires_python( + anchor_html: str, expected: Optional[str] +) -> None: _test_parse_links_data_attribute(anchor_html, "requires_python", expected) @@ -474,11 +486,11 @@ def test_parse_links__requires_python(anchor_html, expected): ), ], ) -def test_parse_links__yanked_reason(anchor_html, expected): +def test_parse_links__yanked_reason(anchor_html: str, expected: Optional[str]) -> None: _test_parse_links_data_attribute(anchor_html, "yanked_reason", expected) -def test_parse_links_caches_same_page_by_url(): +def test_parse_links_caches_same_page_by_url() -> None: html = ( '' '' @@ -523,28 +535,30 @@ def test_parse_links_caches_same_page_by_url(): @mock.patch("pip._internal.index.collector.raise_for_status") -def test_request_http_error(mock_raise_for_status, caplog): +def test_request_http_error( + mock_raise_for_status: mock.Mock, caplog: pytest.LogCaptureFixture +) -> None: caplog.set_level(logging.DEBUG) link = Link("http://localhost") - session = Mock(PipSession) - session.get.return_value = Mock() + session = mock.Mock(PipSession) + session.get.return_value = mock.Mock() mock_raise_for_status.side_effect = NetworkConnectionError("Http error") assert _get_html_page(link, session=session) is None assert "Could not fetch URL http://localhost: Http error - skipping" in caplog.text -def test_request_retries(caplog): +def test_request_retries(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.DEBUG) link = Link("http://localhost") - session = Mock(PipSession) + session = mock.Mock(PipSession) session.get.side_effect = requests.exceptions.RetryError("Retry error") assert _get_html_page(link, session=session) is None assert "Could not fetch URL http://localhost: Retry error - skipping" in caplog.text -def test_make_html_page(): +def test_make_html_page() -> None: headers = {"Content-Type": "text/html; charset=UTF-8"} - response = Mock( + response = mock.Mock( content=b"", url="https://example.com/index.html", headers=headers, @@ -563,7 +577,9 @@ def test_make_html_page(): ("git+https://github.com/pypa/pip.git", "git"), ], ) -def test_get_html_page_invalid_scheme(caplog, url, vcs_scheme): +def test_get_html_page_invalid_scheme( + caplog: pytest.LogCaptureFixture, url: str, vcs_scheme: str +) -> None: """`_get_html_page()` should error if an invalid scheme is given. Only file:, http:, https:, and ftp: are allowed. @@ -591,8 +607,10 @@ def test_get_html_page_invalid_scheme(caplog, url, vcs_scheme): ) @mock.patch("pip._internal.index.collector.raise_for_status") def test_get_html_page_invalid_content_type( - mock_raise_for_status, caplog, content_type -): + mock_raise_for_status: mock.Mock, + caplog: pytest.LogCaptureFixture, + content_type: str, +) -> None: """`_get_html_page()` should warn if an invalid content-type is given. Only text/html is allowed. """ @@ -617,7 +635,7 @@ def test_get_html_page_invalid_content_type( ) in caplog.record_tuples -def make_fake_html_response(url): +def make_fake_html_response(url: str) -> mock.Mock: """ Create a fake requests.Response object. """ @@ -630,10 +648,10 @@ def make_fake_html_response(url): """ ) content = html.encode("utf-8") - return Mock(content=content, url=url, headers={}) + return mock.Mock(content=content, url=url, headers={}) -def test_get_html_page_directory_append_index(tmpdir): +def test_get_html_page_directory_append_index(tmpdir: Path) -> None: """`_get_html_page()` should append "index.html" to a directory URL.""" dirpath = tmpdir / "something" dirpath.mkdir() @@ -652,18 +670,19 @@ def test_get_html_page_directory_append_index(tmpdir): mock.call(expected_url, session=session), ], f"actual calls: {mock_func.mock_calls}" + assert actual is not None assert actual.content == fake_response.content assert actual.encoding is None assert actual.url == expected_url -def test_collect_sources__file_expand_dir(data): +def test_collect_sources__file_expand_dir(data: TestData) -> None: """ Test that a file:// dir from --find-links becomes _FlatDirectorySource """ collector = LinkCollector.create( - session=Mock(is_secure_origin=None), # Shouldn't be used. - options=Mock( + session=mock.Mock(is_secure_origin=None), # Shouldn't be used. + options=mock.Mock( index_url="ignored-by-no-index", extra_index_urls=[], no_index=True, @@ -671,8 +690,9 @@ def test_collect_sources__file_expand_dir(data): ), ) sources = collector.collect_sources( - project_name=None, # Shouldn't be used. - candidates_from_page=None, # Shouldn't be used. + # Shouldn't be used. + project_name=None, # type: ignore[arg-type] + candidates_from_page=None, # type: ignore[arg-type] ) assert ( not sources.index_urls @@ -684,14 +704,14 @@ def test_collect_sources__file_expand_dir(data): ) -def test_collect_sources__file_not_find_link(data): +def test_collect_sources__file_not_find_link(data: TestData) -> None: """ Test that a file:// dir from --index-url doesn't become _FlatDirectorySource run """ collector = LinkCollector.create( - session=Mock(is_secure_origin=None), # Shouldn't be used. - options=Mock( + session=mock.Mock(is_secure_origin=None), # Shouldn't be used. + options=mock.Mock( index_url=data.index_url("empty_with_pkg"), extra_index_urls=[], no_index=False, @@ -700,7 +720,8 @@ def test_collect_sources__file_not_find_link(data): ) sources = collector.collect_sources( project_name="", - candidates_from_page=None, # Shouldn't be used. + # Shouldn't be used. + candidates_from_page=None, # type: ignore[arg-type] ) assert ( not sources.find_links @@ -709,13 +730,13 @@ def test_collect_sources__file_not_find_link(data): ), "Directory specified as index should be treated as a page" -def test_collect_sources__non_existing_path(): +def test_collect_sources__non_existing_path() -> None: """ Test that a non-existing path is ignored. """ collector = LinkCollector.create( - session=Mock(is_secure_origin=None), # Shouldn't be used. - options=Mock( + session=mock.Mock(is_secure_origin=None), # Shouldn't be used. + options=mock.Mock( index_url="ignored-by-no-index", extra_index_urls=[], no_index=True, @@ -723,15 +744,16 @@ def test_collect_sources__non_existing_path(): ), ) sources = collector.collect_sources( - project_name=None, # Shouldn't be used. - candidates_from_page=None, # Shouldn't be used. + # Shouldn't be used. + project_name=None, # type: ignore[arg-type] + candidates_from_page=None, # type: ignore[arg-type] ) assert not sources.index_urls and sources.find_links == [ None ], "Nothing should have been found" -def check_links_include(links, names): +def check_links_include(links: List[Link], names: List[str]) -> None: """ Assert that the given list of Link objects includes, for each of the given names, a link whose URL has a base name matching that name. @@ -743,8 +765,8 @@ def check_links_include(links, names): class TestLinkCollector: - @patch("pip._internal.index.collector._get_html_response") - def test_fetch_page(self, mock_get_html_response): + @mock.patch("pip._internal.index.collector._get_html_response") + def test_fetch_page(self, mock_get_html_response: mock.Mock) -> None: url = "https://pypi.org/simple/twine/" fake_response = make_fake_html_response(url) @@ -754,6 +776,7 @@ def test_fetch_page(self, mock_get_html_response): link_collector = make_test_link_collector() actual = link_collector.fetch_page(location) + assert actual is not None assert actual.content == fake_response.content assert actual.encoding is None assert actual.url == url @@ -766,7 +789,9 @@ def test_fetch_page(self, mock_get_html_response): session=link_collector.session, ) - def test_collect_sources(self, caplog, data): + def test_collect_sources( + self, caplog: pytest.LogCaptureFixture, data: TestData + ) -> None: caplog.set_level(logging.DEBUG) link_collector = make_test_link_collector( @@ -777,7 +802,9 @@ def test_collect_sources(self, caplog, data): ) collected_sources = link_collector.collect_sources( "twine", - candidates_from_page=lambda link: [link], + candidates_from_page=lambda link: [ + InstallationCandidate("twine", "1.0", link) + ], ) files_it = itertools.chain.from_iterable( @@ -799,9 +826,9 @@ def test_collect_sources(self, caplog, data): assert len(files) > 20 check_links_include(files, names=["simple-1.0.tar.gz"]) - assert pages == [Link("https://pypi.org/simple/twine/")] + assert [page.link for page in pages] == [Link("https://pypi.org/simple/twine/")] # Check that index URLs are marked as *un*cacheable. - assert not pages[0].cache_link_parsing + assert not pages[0].link.cache_link_parsing expected_message = dedent( """\ @@ -826,17 +853,17 @@ def test_collect_sources(self, caplog, data): ], ) def test_link_collector_create( - find_links, - no_index, - suppress_no_index, - expected, -): + find_links: List[str], + no_index: bool, + suppress_no_index: bool, + expected: Tuple[List[str], List[str]], +) -> None: """ :param expected: the expected (find_links, index_urls) values. """ expected_find_links, expected_index_urls = expected session = PipSession() - options = Mock( + options = mock.Mock( find_links=find_links, index_url="default_url", extra_index_urls=["url1", "url2"], @@ -855,16 +882,15 @@ def test_link_collector_create( assert search_scope.index_urls == expected_index_urls -@patch("os.path.expanduser") +@mock.patch("os.path.expanduser") def test_link_collector_create_find_links_expansion( - mock_expanduser, - tmpdir, -): + mock_expanduser: mock.Mock, tmpdir: Path +) -> None: """ Test "~" expansion in --find-links paths. """ # This is a mock version of expanduser() that expands "~" to the tmpdir. - def expand_path(path): + def expand_path(path: str) -> str: if path.startswith("~/"): path = os.path.join(tmpdir, path[2:]) return path @@ -872,7 +898,7 @@ def expand_path(path): mock_expanduser.side_effect = expand_path session = PipSession() - options = Mock( + options = mock.Mock( find_links=["~/temp1", "~/temp2"], index_url="default_url", extra_index_urls=[], diff --git a/tests/unit/test_command_install.py b/tests/unit/test_command_install.py index b3e11ef7e22..69792dd9839 100644 --- a/tests/unit/test_command_install.py +++ b/tests/unit/test_command_install.py @@ -1,5 +1,5 @@ import errno -from unittest.mock import patch +from unittest import mock import pytest from pip._vendor.packaging.requirements import Requirement @@ -15,9 +15,9 @@ class TestDecideUserInstall: - @patch("site.ENABLE_USER_SITE", True) - @patch("pip._internal.commands.install.site_packages_writable") - def test_prefix_and_target(self, sp_writable): + @mock.patch("site.ENABLE_USER_SITE", True) + @mock.patch("pip._internal.commands.install.site_packages_writable") + def test_prefix_and_target(self, sp_writable: mock.Mock) -> None: sp_writable.return_value = False assert decide_user_install(use_user_site=None, prefix_path="foo") is False @@ -35,11 +35,11 @@ def test_prefix_and_target(self, sp_writable): ) def test_most_cases( self, - enable_user_site, - site_packages_writable, - result, - monkeypatch, - ): + enable_user_site: bool, + site_packages_writable: bool, + result: bool, + monkeypatch: pytest.MonkeyPatch, + ) -> None: monkeypatch.setattr("site.ENABLE_USER_SITE", enable_user_site) monkeypatch.setattr( "pip._internal.commands.install.site_packages_writable", @@ -48,7 +48,7 @@ def test_most_cases( assert decide_user_install(use_user_site=None) is result -def test_rejection_for_pip_install_options(): +def test_rejection_for_pip_install_options() -> None: install_options = ["--prefix=/hello"] with pytest.raises(CommandError) as e: reject_location_related_install_options([], install_options) @@ -56,9 +56,7 @@ def test_rejection_for_pip_install_options(): assert "['--prefix'] from command line" in str(e.value) -def test_rejection_for_location_requirement_options(): - install_options = [] - +def test_rejection_for_location_requirement_options() -> None: bad_named_req_options = ["--home=/wow"] bad_named_req = InstallRequirement( Requirement("hello"), "requirements.txt", install_options=bad_named_req_options @@ -71,7 +69,7 @@ def test_rejection_for_location_requirement_options(): with pytest.raises(CommandError) as e: reject_location_related_install_options( - [bad_named_req, bad_unnamed_req], install_options + [bad_named_req, bad_unnamed_req], options=[] ) assert ( @@ -151,8 +149,12 @@ def test_rejection_for_location_requirement_options(): ], ) def test_create_os_error_message( - monkeypatch, error, show_traceback, using_user_site, expected -): + monkeypatch: pytest.MonkeyPatch, + error: OSError, + show_traceback: bool, + using_user_site: bool, + expected: str, +) -> None: monkeypatch.setattr(install, "running_under_virtualenv", lambda: False) msg = create_os_error_message(error, show_traceback, using_user_site) assert msg == expected diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index 60d702934a1..7a5c4e8319d 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -1,7 +1,9 @@ -from unittest.mock import patch +from typing import Callable, List +from unittest import mock import pytest +from pip._internal.cli.base_command import Command from pip._internal.cli.req_command import ( IndexGroupCommand, RequirementCommand, @@ -14,7 +16,7 @@ EXPECTED_INDEX_GROUP_COMMANDS = ["download", "index", "install", "list", "wheel"] -def check_commands(pred, expected): +def check_commands(pred: Callable[[Command], bool], expected: List[str]) -> None: """ Check the commands satisfying a predicate. """ @@ -23,7 +25,7 @@ def check_commands(pred, expected): assert actual == expected, f"actual: {actual}" -def test_commands_dict__order(): +def test_commands_dict__order() -> None: """ Check the ordering of commands_dict. """ @@ -35,38 +37,38 @@ def test_commands_dict__order(): @pytest.mark.parametrize("name", list(commands_dict)) -def test_create_command(name): +def test_create_command(name: str) -> None: """Test creating an instance of each available command.""" command = create_command(name) assert command.name == name assert command.summary == commands_dict[name].summary -def test_session_commands(): +def test_session_commands() -> None: """ Test which commands inherit from SessionCommandMixin. """ - def is_session_command(command): + def is_session_command(command: Command) -> bool: return isinstance(command, SessionCommandMixin) expected = ["download", "index", "install", "list", "search", "uninstall", "wheel"] check_commands(is_session_command, expected) -def test_index_group_commands(): +def test_index_group_commands() -> None: """ Test the commands inheriting from IndexGroupCommand. """ - def is_index_group_command(command): + def is_index_group_command(command: Command) -> bool: return isinstance(command, IndexGroupCommand) check_commands(is_index_group_command, EXPECTED_INDEX_GROUP_COMMANDS) # Also check that the commands inheriting from IndexGroupCommand are # exactly the commands with the --no-index option. - def has_option_no_index(command): + def has_option_no_index(command: Command) -> bool: return command.parser.has_option("--no-index") check_commands(has_option_no_index, EXPECTED_INDEX_GROUP_COMMANDS) @@ -84,14 +86,14 @@ def has_option_no_index(command): (True, True, False), ], ) -@patch("pip._internal.cli.req_command.pip_self_version_check") +@mock.patch("pip._internal.cli.req_command.pip_self_version_check") def test_index_group_handle_pip_version_check( - mock_version_check, - command_name, - disable_pip_version_check, - no_index, - expected_called, -): + mock_version_check: mock.Mock, + command_name: str, + disable_pip_version_check: bool, + no_index: bool, + expected_called: bool, +) -> None: """ Test whether pip_self_version_check() is called when handle_pip_version_check() is called, for each of the @@ -109,12 +111,12 @@ def test_index_group_handle_pip_version_check( mock_version_check.assert_not_called() -def test_requirement_commands(): +def test_requirement_commands() -> None: """ Test which commands inherit from RequirementCommand. """ - def is_requirement_command(command): + def is_requirement_command(command: Command) -> bool: return isinstance(command, RequirementCommand) check_commands(is_requirement_command, ["download", "install", "wheel"]) diff --git a/tests/unit/test_compat.py b/tests/unit/test_compat.py index 44dcc9c1758..da58cc8d3b3 100644 --- a/tests/unit/test_compat.py +++ b/tests/unit/test_compat.py @@ -3,15 +3,16 @@ import pytest from pip._internal.utils.compat import get_path_uid +from tests.lib.path import Path -def test_get_path_uid(): +def test_get_path_uid() -> None: path = os.getcwd() assert get_path_uid(path) == os.stat(path).st_uid @pytest.mark.skipif("not hasattr(os, 'O_NOFOLLOW')") -def test_get_path_uid_without_NOFOLLOW(monkeypatch): +def test_get_path_uid_without_NOFOLLOW(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delattr("os.O_NOFOLLOW") path = os.getcwd() assert get_path_uid(path) == os.stat(path).st_uid @@ -20,7 +21,7 @@ def test_get_path_uid_without_NOFOLLOW(monkeypatch): # Skip unconditionally on Windows, as symlinks need admin privs there @pytest.mark.skipif("sys.platform == 'win32'") @pytest.mark.skipif("not hasattr(os, 'symlink')") -def test_get_path_uid_symlink(tmpdir): +def test_get_path_uid_symlink(tmpdir: Path) -> None: f = tmpdir / "symlink" / "somefile" f.parent.mkdir() f.write_text("content") @@ -32,7 +33,9 @@ def test_get_path_uid_symlink(tmpdir): @pytest.mark.skipif("not hasattr(os, 'O_NOFOLLOW')") @pytest.mark.skipif("not hasattr(os, 'symlink')") -def test_get_path_uid_symlink_without_NOFOLLOW(tmpdir, monkeypatch): +def test_get_path_uid_symlink_without_NOFOLLOW( + tmpdir: Path, monkeypatch: pytest.MonkeyPatch +) -> None: monkeypatch.delattr("os.O_NOFOLLOW") f = tmpdir / "symlink" / "somefile" f.parent.mkdir() diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 18d9dddf504..6eb1f78ae56 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -11,25 +11,25 @@ class TestConfigurationLoading(ConfigurationMixin): - def test_global_loading(self): + def test_global_loading(self) -> None: self.patch_configuration(kinds.GLOBAL, {"test.hello": "1"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "1" - def test_user_loading(self): + def test_user_loading(self) -> None: self.patch_configuration(kinds.USER, {"test.hello": "2"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "2" - def test_site_loading(self): + def test_site_loading(self) -> None: self.patch_configuration(kinds.SITE, {"test.hello": "3"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "3" - def test_environment_config_loading(self, monkeypatch): + def test_environment_config_loading(self, monkeypatch: pytest.MonkeyPatch) -> None: contents = """ [test] hello = 4 @@ -43,21 +43,25 @@ def test_environment_config_loading(self, monkeypatch): self.configuration.get_value("test.hello") == "4" ), self.configuration._config - def test_environment_var_loading(self, monkeypatch): + def test_environment_var_loading(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PIP_HELLO", "5") self.configuration.load() assert self.configuration.get_value(":env:.hello") == "5" @pytest.mark.skipif("sys.platform == 'win32'") - def test_environment_var_does_not_load_lowercase(self, monkeypatch): + def test_environment_var_does_not_load_lowercase( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("pip_hello", "5") self.configuration.load() with pytest.raises(ConfigurationError): self.configuration.get_value(":env:.hello") - def test_environment_var_does_not_load_version(self, monkeypatch): + def test_environment_var_does_not_load_version( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("PIP_VERSION", "True") self.configuration.load() @@ -65,7 +69,9 @@ def test_environment_var_does_not_load_version(self, monkeypatch): with pytest.raises(ConfigurationError): self.configuration.get_value(":env:.version") - def test_environment_config_errors_if_malformed(self, monkeypatch): + def test_environment_config_errors_if_malformed( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: contents = """ test] hello = 4 @@ -86,49 +92,51 @@ class TestConfigurationPrecedence(ConfigurationMixin): # Tests for methods to that determine the order of precedence of # configuration options - def test_env_overides_site(self): + def test_env_overides_site(self) -> None: self.patch_configuration(kinds.SITE, {"test.hello": "1"}) self.patch_configuration(kinds.ENV, {"test.hello": "0"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "0" - def test_env_overides_user(self): + def test_env_overides_user(self) -> None: self.patch_configuration(kinds.USER, {"test.hello": "2"}) self.patch_configuration(kinds.ENV, {"test.hello": "0"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "0" - def test_env_overides_global(self): + def test_env_overides_global(self) -> None: self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"}) self.patch_configuration(kinds.ENV, {"test.hello": "0"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "0" - def test_site_overides_user(self): + def test_site_overides_user(self) -> None: self.patch_configuration(kinds.USER, {"test.hello": "2"}) self.patch_configuration(kinds.SITE, {"test.hello": "1"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "1" - def test_site_overides_global(self): + def test_site_overides_global(self) -> None: self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"}) self.patch_configuration(kinds.SITE, {"test.hello": "1"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "1" - def test_user_overides_global(self): + def test_user_overides_global(self) -> None: self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"}) self.patch_configuration(kinds.USER, {"test.hello": "2"}) self.configuration.load() assert self.configuration.get_value("test.hello") == "2" - def test_env_not_overriden_by_environment_var(self, monkeypatch): + def test_env_not_overriden_by_environment_var( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: self.patch_configuration(kinds.ENV, {"test.hello": "1"}) monkeypatch.setenv("PIP_HELLO", "5") @@ -137,7 +145,9 @@ def test_env_not_overriden_by_environment_var(self, monkeypatch): assert self.configuration.get_value("test.hello") == "1" assert self.configuration.get_value(":env:.hello") == "5" - def test_site_not_overriden_by_environment_var(self, monkeypatch): + def test_site_not_overriden_by_environment_var( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: self.patch_configuration(kinds.SITE, {"test.hello": "2"}) monkeypatch.setenv("PIP_HELLO", "5") @@ -146,7 +156,9 @@ def test_site_not_overriden_by_environment_var(self, monkeypatch): assert self.configuration.get_value("test.hello") == "2" assert self.configuration.get_value(":env:.hello") == "5" - def test_user_not_overriden_by_environment_var(self, monkeypatch): + def test_user_not_overriden_by_environment_var( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: self.patch_configuration(kinds.USER, {"test.hello": "3"}) monkeypatch.setenv("PIP_HELLO", "5") @@ -155,7 +167,9 @@ def test_user_not_overriden_by_environment_var(self, monkeypatch): assert self.configuration.get_value("test.hello") == "3" assert self.configuration.get_value(":env:.hello") == "5" - def test_global_not_overriden_by_environment_var(self, monkeypatch): + def test_global_not_overriden_by_environment_var( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: self.patch_configuration(kinds.GLOBAL, {"test.hello": "4"}) monkeypatch.setenv("PIP_HELLO", "5") @@ -168,7 +182,7 @@ def test_global_not_overriden_by_environment_var(self, monkeypatch): class TestConfigurationModification(ConfigurationMixin): # Tests for methods to that modify the state of a Configuration - def test_no_specific_given_modification(self): + def test_no_specific_given_modification(self) -> None: self.configuration.load() try: @@ -178,7 +192,7 @@ def test_no_specific_given_modification(self): else: assert False, "Should have raised an error." - def test_site_modification(self): + def test_site_modification(self) -> None: self.configuration.load_only = kinds.SITE self.configuration.load() @@ -192,7 +206,7 @@ def test_site_modification(self): assert mymock.call_count == 1 assert mymock.call_args[0][0] == (get_configuration_files()[kinds.SITE][0]) - def test_user_modification(self): + def test_user_modification(self) -> None: # get the path to local config file self.configuration.load_only = kinds.USER self.configuration.load() @@ -210,7 +224,7 @@ def test_user_modification(self): get_configuration_files()[kinds.USER][1] ) - def test_global_modification(self): + def test_global_modification(self) -> None: # get the path to local config file self.configuration.load_only = kinds.GLOBAL self.configuration.load() diff --git a/tests/unit/test_direct_url.py b/tests/unit/test_direct_url.py index 97b412aa8e7..c81e5129253 100644 --- a/tests/unit/test_direct_url.py +++ b/tests/unit/test_direct_url.py @@ -9,14 +9,15 @@ ) -def test_from_json(): +def test_from_json() -> None: json = '{"url": "file:///home/user/project", "dir_info": {}}' direct_url = DirectUrl.from_json(json) assert direct_url.url == "file:///home/user/project" + assert isinstance(direct_url.info, DirInfo) assert direct_url.info.editable is False -def test_to_json(): +def test_to_json() -> None: direct_url = DirectUrl( url="file:///home/user/archive.tgz", info=ArchiveInfo(), @@ -27,7 +28,7 @@ def test_to_json(): ) -def test_archive_info(): +def test_archive_info() -> None: direct_url_dict = { "url": "file:///home/user/archive.tgz", "archive_info": {"hash": "sha1=1b8c5bc61a86f377fea47b4276c8c8a5842d2220"}, @@ -35,11 +36,13 @@ def test_archive_info(): direct_url = DirectUrl.from_dict(direct_url_dict) assert isinstance(direct_url.info, ArchiveInfo) assert direct_url.url == direct_url_dict["url"] - assert direct_url.info.hash == direct_url_dict["archive_info"]["hash"] + assert ( + direct_url.info.hash == direct_url_dict["archive_info"]["hash"] # type: ignore + ) assert direct_url.to_dict() == direct_url_dict -def test_dir_info(): +def test_dir_info() -> None: direct_url_dict = { "url": "file:///home/user/project", "dir_info": {"editable": True}, @@ -52,10 +55,11 @@ def test_dir_info(): # test editable default to False direct_url_dict = {"url": "file:///home/user/project", "dir_info": {}} direct_url = DirectUrl.from_dict(direct_url_dict) + assert isinstance(direct_url.info, DirInfo) assert direct_url.info.editable is False -def test_vcs_info(): +def test_vcs_info() -> None: direct_url_dict = { "url": "https:///g.c/u/p.git", "vcs_info": { @@ -73,7 +77,7 @@ def test_vcs_info(): assert direct_url.to_dict() == direct_url_dict -def test_parsing_validation(): +def test_parsing_validation() -> None: with pytest.raises(DirectUrlValidationError, match="url must have a value"): DirectUrl.from_dict({"dir_info": {}}) with pytest.raises( @@ -96,15 +100,15 @@ def test_parsing_validation(): DirectUrl.from_dict({"url": "http://...", "dir_info": {}, "archive_info": {}}) -def test_redact_url(): - def _redact_git(url): +def test_redact_url() -> None: + def _redact_git(url: str) -> str: direct_url = DirectUrl( url=url, info=VcsInfo(vcs="git", commit_id="1"), ) return direct_url.redacted_url - def _redact_archive(url): + def _redact_archive(url: str) -> str: direct_url = DirectUrl( url=url, info=ArchiveInfo(), diff --git a/tests/unit/test_direct_url_helpers.py b/tests/unit/test_direct_url_helpers.py index 753afa9b9f6..8d94aeb50b6 100644 --- a/tests/unit/test_direct_url_helpers.py +++ b/tests/unit/test_direct_url_helpers.py @@ -1,5 +1,5 @@ from functools import partial -from unittest.mock import patch +from unittest import mock from pip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo from pip._internal.models.link import Link @@ -8,9 +8,11 @@ direct_url_from_link, ) from pip._internal.utils.urls import path_to_url +from tests.lib import PipTestEnvironment +from tests.lib.path import Path -def test_as_pep440_requirement_archive(): +def test_as_pep440_requirement_archive() -> None: direct_url = DirectUrl( url="file:///home/user/archive.tgz", info=ArchiveInfo(), @@ -26,6 +28,7 @@ def test_as_pep440_requirement_archive(): direct_url_as_pep440_direct_reference(direct_url, "pkg") == "pkg @ file:///home/user/archive.tgz#subdirectory=subdir" ) + assert isinstance(direct_url.info, ArchiveInfo) direct_url.info.hash = "sha1=1b8c5bc61a86f377fea47b4276c8c8a5842d2220" direct_url.validate() assert ( @@ -35,7 +38,7 @@ def test_as_pep440_requirement_archive(): ) -def test_as_pep440_requirement_dir(): +def test_as_pep440_requirement_dir() -> None: direct_url = DirectUrl( url="file:///home/user/project", info=DirInfo(editable=False), @@ -47,7 +50,7 @@ def test_as_pep440_requirement_dir(): ) -def test_as_pep440_requirement_editable_dir(): +def test_as_pep440_requirement_editable_dir() -> None: # direct_url_as_pep440_direct_reference behaves the same # irrespective of the editable flag. It's the responsibility of # callers to render it as editable @@ -62,7 +65,7 @@ def test_as_pep440_requirement_editable_dir(): ) -def test_as_pep440_requirement_vcs(): +def test_as_pep440_requirement_vcs() -> None: direct_url = DirectUrl( url="https:///g.c/u/p.git", info=VcsInfo(vcs="git", commit_id="1b8c5bc61a86f377fea47b4276c8c8a5842d2220"), @@ -82,8 +85,8 @@ def test_as_pep440_requirement_vcs(): ) -@patch("pip._internal.vcs.git.Git.get_revision") -def test_from_link_vcs(mock_get_backend_for_scheme): +@mock.patch("pip._internal.vcs.git.Git.get_revision") +def test_from_link_vcs(mock_get_backend_for_scheme: mock.Mock) -> None: _direct_url_from_link = partial(direct_url_from_link, source_dir="...") direct_url = _direct_url_from_link(Link("git+https://g.c/u/p.git")) assert direct_url.url == "https://g.c/u/p.git" @@ -98,15 +101,19 @@ def test_from_link_vcs(mock_get_backend_for_scheme): assert direct_url.subdirectory == "subdir" direct_url = _direct_url_from_link(Link("git+https://g.c/u/p.git@branch")) assert direct_url.url == "https://g.c/u/p.git" + assert isinstance(direct_url.info, VcsInfo) assert direct_url.info.requested_revision == "branch" direct_url = _direct_url_from_link(Link("git+https://g.c/u/p.git@branch#egg=pkg")) assert direct_url.url == "https://g.c/u/p.git" + assert isinstance(direct_url.info, VcsInfo) assert direct_url.info.requested_revision == "branch" direct_url = _direct_url_from_link(Link("git+https://token@g.c/u/p.git")) assert direct_url.to_dict()["url"] == "https://g.c/u/p.git" -def test_from_link_vcs_with_source_dir_obtains_commit_id(script, tmpdir): +def test_from_link_vcs_with_source_dir_obtains_commit_id( + script: PipTestEnvironment, tmpdir: Path +) -> None: repo_path = tmpdir / "test-repo" repo_path.mkdir() repo_dir = str(repo_path) @@ -119,18 +126,20 @@ def test_from_link_vcs_with_source_dir_obtains_commit_id(script, tmpdir): Link("git+https://g.c/u/p.git"), source_dir=repo_dir ) assert direct_url.url == "https://g.c/u/p.git" + assert isinstance(direct_url.info, VcsInfo) assert direct_url.info.commit_id == commit_id -def test_from_link_vcs_without_source_dir(script): +def test_from_link_vcs_without_source_dir(script: PipTestEnvironment) -> None: direct_url = direct_url_from_link( Link("git+https://g.c/u/p.git@1"), link_is_in_wheel_cache=True ) assert direct_url.url == "https://g.c/u/p.git" + assert isinstance(direct_url.info, VcsInfo) assert direct_url.info.commit_id == "1" -def test_from_link_archive(): +def test_from_link_archive() -> None: direct_url = direct_url_from_link(Link("https://g.c/archive.tgz")) assert direct_url.url == "https://g.c/archive.tgz" assert isinstance(direct_url.info, ArchiveInfo) @@ -141,14 +150,14 @@ def test_from_link_archive(): assert direct_url.info.hash == "sha1=1b8c5bc61a86f377fea47b4276c8c8a5842d2220" -def test_from_link_dir(tmpdir): +def test_from_link_dir(tmpdir: Path) -> None: dir_url = path_to_url(tmpdir) direct_url = direct_url_from_link(Link(dir_url)) assert direct_url.url == dir_url assert isinstance(direct_url.info, DirInfo) -def test_from_link_hide_user_password(): +def test_from_link_hide_user_password() -> None: # Basic test only here, other variants are covered by # direct_url.redact_url tests. direct_url = direct_url_from_link( diff --git a/tests/unit/test_finder.py b/tests/unit/test_finder.py index 9c164a2127f..34720d54ee8 100644 --- a/tests/unit/test_finder.py +++ b/tests/unit/test_finder.py @@ -1,4 +1,5 @@ import logging +from typing import Iterable from unittest.mock import Mock, patch import pytest @@ -16,28 +17,28 @@ ) from pip._internal.models.target_python import TargetPython from pip._internal.req.constructors import install_req_from_line -from tests.lib import make_test_finder +from tests.lib import TestData, make_test_finder -def test_no_mpkg(data): +def test_no_mpkg(data: TestData) -> None: """Finder skips zipfiles with "macosx10" in the name.""" finder = make_test_finder(find_links=[data.find_links]) req = install_req_from_line("pkgwithmpkg") found = finder.find_requirement(req, False) - + assert found is not None assert found.link.url.endswith("pkgwithmpkg-1.0.tar.gz"), found -def test_no_partial_name_match(data): +def test_no_partial_name_match(data: TestData) -> None: """Finder requires the full project name to match, not just beginning.""" finder = make_test_finder(find_links=[data.find_links]) req = install_req_from_line("gmpy") found = finder.find_requirement(req, False) - + assert found is not None assert found.link.url.endswith("gmpy-1.15.tar.gz"), found -def test_tilde(): +def test_tilde() -> None: """Finder can accept a path with ~ in it and will normalize it.""" patched_exists = patch( "pip._internal.index.collector.os.path.exists", return_value=True @@ -49,34 +50,36 @@ def test_tilde(): finder.find_requirement(req, False) -def test_duplicates_sort_ok(data): +def test_duplicates_sort_ok(data: TestData) -> None: """Finder successfully finds one of a set of duplicates in different locations""" finder = make_test_finder(find_links=[data.find_links, data.find_links2]) req = install_req_from_line("duplicate") found = finder.find_requirement(req, False) - + assert found is not None assert found.link.url.endswith("duplicate-1.0.tar.gz"), found -def test_finder_detects_latest_find_links(data): +def test_finder_detects_latest_find_links(data: TestData) -> None: """Test PackageFinder detects latest using find-links""" req = install_req_from_line("simple", None) finder = make_test_finder(find_links=[data.find_links]) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("simple-3.0.tar.gz") -def test_incorrect_case_file_index(data): +def test_incorrect_case_file_index(data: TestData) -> None: """Test PackageFinder detects latest using wrong case""" req = install_req_from_line("dinner", None) finder = make_test_finder(index_urls=[data.find_links3]) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("Dinner-2.0.tar.gz") @pytest.mark.network -def test_finder_detects_latest_already_satisfied_find_links(data): +def test_finder_detects_latest_already_satisfied_find_links(data: TestData) -> None: """Test PackageFinder detects latest already satisfied using find-links""" req = install_req_from_line("simple", None) # the latest simple in local pkgs is 3.0 @@ -94,7 +97,7 @@ def test_finder_detects_latest_already_satisfied_find_links(data): @pytest.mark.network -def test_finder_detects_latest_already_satisfied_pypi_links(): +def test_finder_detects_latest_already_satisfied_pypi_links() -> None: """Test PackageFinder detects latest already satisfied using pypi links""" req = install_req_from_line("initools", None) # the latest initools on PyPI is 0.3.1 @@ -112,7 +115,9 @@ def test_finder_detects_latest_already_satisfied_pypi_links(): class TestWheel: - def test_skip_invalid_wheel_link(self, caplog, data): + def test_skip_invalid_wheel_link( + self, caplog: pytest.LogCaptureFixture, data: TestData + ) -> None: """ Test if PackageFinder skips invalid wheel filenames """ @@ -126,7 +131,7 @@ def test_skip_invalid_wheel_link(self, caplog, data): assert "Skipping link: invalid wheel filename:" in caplog.text - def test_not_find_wheel_not_supported(self, data): + def test_not_find_wheel_not_supported(self, data: TestData) -> None: """ Test not finding an unsupported wheel. """ @@ -142,7 +147,9 @@ def test_not_find_wheel_not_supported(self, data): with pytest.raises(DistributionNotFound): finder.find_requirement(req, True) - def test_find_wheel_supported(self, data, monkeypatch): + def test_find_wheel_supported( + self, data: TestData, monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test finding supported wheel. """ @@ -155,9 +162,10 @@ def test_find_wheel_supported(self, data, monkeypatch): req = install_req_from_line("simple.dist") finder = make_test_finder(find_links=[data.find_links]) found = finder.find_requirement(req, True) + assert found is not None assert found.link.url.endswith("simple.dist-0.1-py2.py3-none-any.whl"), found - def test_wheel_over_sdist_priority(self, data): + def test_wheel_over_sdist_priority(self, data: TestData) -> None: """ Test wheels have priority over sdists. `test_link_sorting` also covers this at lower level @@ -165,9 +173,10 @@ def test_wheel_over_sdist_priority(self, data): req = install_req_from_line("priority") finder = make_test_finder(find_links=[data.find_links]) found = finder.find_requirement(req, True) + assert found is not None assert found.link.url.endswith("priority-1.0-py2.py3-none-any.whl"), found - def test_existing_over_wheel_priority(self, data): + def test_existing_over_wheel_priority(self, data: TestData) -> None: """ Test existing install has priority over wheels. `test_link_sorting` also covers this at a lower level @@ -187,7 +196,7 @@ def test_existing_over_wheel_priority(self, data): class TestCandidateEvaluator: - def test_link_sorting(self): + def test_link_sorting(self) -> None: """ Test link sorting """ @@ -232,7 +241,7 @@ def test_link_sorting(self): assert links == results, results assert links == results2, results2 - def test_link_sorting_wheels_with_build_tags(self): + def test_link_sorting_wheels_with_build_tags(self) -> None: """Verify build tags affect sorting.""" links = [ InstallationCandidate( @@ -259,7 +268,7 @@ def test_link_sorting_wheels_with_build_tags(self): assert links == results, results assert links == results2, results2 - def test_build_tag_is_less_important_than_other_tags(self): + def test_build_tag_is_less_important_than_other_tags(self) -> None: links = [ InstallationCandidate( "simple", @@ -300,7 +309,7 @@ def test_build_tag_is_less_important_than_other_tags(self): assert links == results2, results2 -def test_finder_priority_file_over_page(data): +def test_finder_priority_file_over_page(data: TestData) -> None: """Test PackageFinder prefers file links over equivalent page links""" req = install_req_from_line("gmpy==1.15", None) finder = make_test_finder( @@ -315,10 +324,11 @@ def test_finder_priority_file_over_page(data): ), all_versions found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.startswith("file://") -def test_finder_priority_nonegg_over_eggfragments(): +def test_finder_priority_nonegg_over_eggfragments() -> None: """Test PackageFinder prefers non-egg links over "#egg=" links""" req = install_req_from_line("bar==1.0", None) links = ["http://foo/bar.py#egg=bar-1.0", "http://foo/bar-1.0.tar.gz"] @@ -330,6 +340,7 @@ def test_finder_priority_nonegg_over_eggfragments(): found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("tar.gz") links.reverse() @@ -340,10 +351,11 @@ def test_finder_priority_nonegg_over_eggfragments(): assert all_versions[1].link.url.endswith("#egg=bar-1.0") found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("tar.gz") -def test_finder_only_installs_stable_releases(data): +def test_finder_only_installs_stable_releases(data: TestData) -> None: """ Test PackageFinder only accepts stable versioned releases by default. """ @@ -353,6 +365,7 @@ def test_finder_only_installs_stable_releases(data): # using a local index (that has pre & dev releases) finder = make_test_finder(index_urls=[data.index_url("pre")]) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("bar-1.0.tar.gz"), found.link.url # using find-links @@ -360,16 +373,18 @@ def test_finder_only_installs_stable_releases(data): finder = make_test_finder(links) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url == "https://foo/bar-1.0.tar.gz" links.reverse() finder = make_test_finder(links) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url == "https://foo/bar-1.0.tar.gz" -def test_finder_only_installs_data_require(data): +def test_finder_only_installs_data_require(data: TestData) -> None: """ Test whether the PackageFinder understand data-python-requires @@ -386,7 +401,7 @@ def test_finder_only_installs_data_require(data): assert {str(v.version) for v in links} == {"1.0.0", "3.3.0", "9.9.9"} -def test_finder_installs_pre_releases(data): +def test_finder_installs_pre_releases(data: TestData) -> None: """ Test PackageFinder finds pre-releases if asked to. """ @@ -399,6 +414,7 @@ def test_finder_installs_pre_releases(data): allow_all_prereleases=True, ) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("bar-2.0b1.tar.gz"), found.link.url # using find-links @@ -406,16 +422,18 @@ def test_finder_installs_pre_releases(data): finder = make_test_finder(links, allow_all_prereleases=True) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url == "https://foo/bar-2.0b1.tar.gz" links.reverse() finder = make_test_finder(links, allow_all_prereleases=True) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url == "https://foo/bar-2.0b1.tar.gz" -def test_finder_installs_dev_releases(data): +def test_finder_installs_dev_releases(data: TestData) -> None: """ Test PackageFinder finds dev releases if asked to. """ @@ -428,10 +446,11 @@ def test_finder_installs_dev_releases(data): allow_all_prereleases=True, ) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url.endswith("bar-2.0.dev1.tar.gz"), found.link.url -def test_finder_installs_pre_releases_with_version_spec(): +def test_finder_installs_pre_releases_with_version_spec() -> None: """ Test PackageFinder only accepts stable versioned releases by default. """ @@ -440,22 +459,24 @@ def test_finder_installs_pre_releases_with_version_spec(): finder = make_test_finder(links) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url == "https://foo/bar-2.0b1.tar.gz" links.reverse() finder = make_test_finder(links) found = finder.find_requirement(req, False) + assert found is not None assert found.link.url == "https://foo/bar-2.0b1.tar.gz" class TestLinkEvaluator: - def make_test_link_evaluator(self, formats): + def make_test_link_evaluator(self, formats: Iterable[str]) -> LinkEvaluator: target_python = TargetPython() return LinkEvaluator( project_name="pytest", canonical_name="pytest", - formats=formats, + formats=frozenset(formats), target_python=target_python, allow_yanked=True, ) @@ -467,7 +488,7 @@ def make_test_link_evaluator(self, formats): ("http:/yo/pytest-1.0-py2.py3-none-any.whl", "1.0"), ], ) - def test_evaluate_link__match(self, url, expected_version): + def test_evaluate_link__match(self, url: str, expected_version: str) -> None: """Test that 'pytest' archives match for 'pytest'""" link = Link(url) evaluator = self.make_test_link_evaluator(formats=["source", "binary"]) @@ -486,7 +507,7 @@ def test_evaluate_link__match(self, url, expected_version): ), ], ) - def test_evaluate_link__substring_fails(self, url, expected_msg): + def test_evaluate_link__substring_fails(self, url: str, expected_msg: str) -> None: """Test that 'pytest archives won't match for 'pytest'.""" link = Link(url) evaluator = self.make_test_link_evaluator(formats=["source", "binary"]) @@ -494,7 +515,7 @@ def test_evaluate_link__substring_fails(self, url, expected_msg): assert actual == (False, expected_msg) -def test_process_project_url(data): +def test_process_project_url(data: TestData) -> None: project_name = "simple" index_url = data.index_url("simple") project_url = Link(f"{index_url}/{project_name}") @@ -511,25 +532,25 @@ def test_process_project_url(data): assert str(package_link.version) == "1.0" -def test_find_all_candidates_nothing(): +def test_find_all_candidates_nothing() -> None: """Find nothing without anything""" finder = make_test_finder() assert not finder.find_all_candidates("pip") -def test_find_all_candidates_find_links(data): +def test_find_all_candidates_find_links(data: TestData) -> None: finder = make_test_finder(find_links=[data.find_links]) versions = finder.find_all_candidates("simple") assert [str(v.version) for v in versions] == ["3.0", "2.0", "1.0"] -def test_find_all_candidates_index(data): +def test_find_all_candidates_index(data: TestData) -> None: finder = make_test_finder(index_urls=[data.index_url("simple")]) versions = finder.find_all_candidates("simple") assert [str(v.version) for v in versions] == ["1.0"] -def test_find_all_candidates_find_links_and_index(data): +def test_find_all_candidates_find_links_and_index(data: TestData) -> None: finder = make_test_finder( find_links=[data.find_links], index_urls=[data.index_url("simple")], diff --git a/tests/unit/test_format_control.py b/tests/unit/test_format_control.py index a24fb39df58..33a03729db5 100644 --- a/tests/unit/test_format_control.py +++ b/tests/unit/test_format_control.py @@ -1,51 +1,56 @@ +from optparse import Values +from typing import FrozenSet, List, Set + import pytest from pip._internal.cli import cmdoptions from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS from pip._internal.models.format_control import FormatControl class SimpleCommand(Command): - def __init__(self): + def __init__(self) -> None: super().__init__("fake", "fake summary") - def add_options(self): + def add_options(self) -> None: self.cmd_opts.add_option(cmdoptions.no_binary()) self.cmd_opts.add_option(cmdoptions.only_binary()) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: self.options = options + return SUCCESS -def test_no_binary_overrides(): +def test_no_binary_overrides() -> None: cmd = SimpleCommand() cmd.main(["fake", "--only-binary=:all:", "--no-binary=fred"]) format_control = FormatControl({"fred"}, {":all:"}) assert cmd.options.format_control == format_control -def test_only_binary_overrides(): +def test_only_binary_overrides() -> None: cmd = SimpleCommand() cmd.main(["fake", "--no-binary=:all:", "--only-binary=fred"]) format_control = FormatControl({":all:"}, {"fred"}) assert cmd.options.format_control == format_control -def test_none_resets(): +def test_none_resets() -> None: cmd = SimpleCommand() cmd.main(["fake", "--no-binary=:all:", "--no-binary=:none:"]) format_control = FormatControl(set(), set()) assert cmd.options.format_control == format_control -def test_none_preserves_other_side(): +def test_none_preserves_other_side() -> None: cmd = SimpleCommand() cmd.main(["fake", "--no-binary=:all:", "--only-binary=fred", "--no-binary=:none:"]) format_control = FormatControl(set(), {"fred"}) assert cmd.options.format_control == format_control -def test_comma_separated_values(): +def test_comma_separated_values() -> None: cmd = SimpleCommand() cmd.main(["fake", "--no-binary=1,2,3"]) format_control = FormatControl({"1", "2", "3"}, set()) @@ -61,6 +66,8 @@ def test_comma_separated_values(): ({":all:"}, {"fred"}, "fred", frozenset(["binary"])), ], ) -def test_fmt_ctl_matches(no_binary, only_binary, argument, expected): +def test_fmt_ctl_matches( + no_binary: Set[str], only_binary: Set[str], argument: str, expected: FrozenSet[str] +) -> None: fmt = FormatControl(no_binary, only_binary) assert fmt.get_allowed_formats(argument) == expected diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py index 9eb3258d3c9..39106f63b23 100644 --- a/tests/unit/test_index.py +++ b/tests/unit/test_index.py @@ -1,7 +1,9 @@ import logging +from typing import FrozenSet, List, Optional, Set, Tuple import pytest from pip._vendor.packaging.specifiers import SpecifierSet +from pip._vendor.packaging.tags import Tag from pip._internal.index.collector import LinkCollector from pip._internal.index.package_finder import ( @@ -35,14 +37,16 @@ ("invalid", True), ], ) -def test_check_link_requires_python(requires_python, expected): +def test_check_link_requires_python(requires_python: str, expected: bool) -> None: version_info = (3, 6, 5) link = Link("https://example.com", requires_python=requires_python) actual = _check_link_requires_python(link, version_info) assert actual == expected -def check_caplog(caplog, expected_level, expected_message): +def check_caplog( + caplog: pytest.LogCaptureFixture, expected_level: str, expected_message: str +) -> None: assert len(caplog.records) == 1 record = caplog.records[0] assert record.levelname == expected_level @@ -53,7 +57,7 @@ def check_caplog(caplog, expected_level, expected_message): "ignore_requires_python, expected", [ ( - None, + False, ( False, "VERBOSE", @@ -73,10 +77,10 @@ def check_caplog(caplog, expected_level, expected_message): ], ) def test_check_link_requires_python__incompatible_python( - caplog, - ignore_requires_python, - expected, -): + caplog: pytest.LogCaptureFixture, + ignore_requires_python: bool, + expected: Tuple[bool, str, str], +) -> None: """ Test an incompatible Python. """ @@ -93,7 +97,9 @@ def test_check_link_requires_python__incompatible_python( check_caplog(caplog, expected_level, expected_message) -def test_check_link_requires_python__invalid_requires(caplog): +def test_check_link_requires_python__invalid_requires( + caplog: pytest.LogCaptureFixture, +) -> None: """ Test the log message for an invalid Requires-Python. """ @@ -112,24 +118,24 @@ class TestLinkEvaluator: @pytest.mark.parametrize( "py_version_info,ignore_requires_python,expected", [ - ((3, 6, 5), None, (True, "1.12")), + ((3, 6, 5), False, (True, "1.12")), # Test an incompatible Python. - ((3, 6, 4), None, (False, None)), + ((3, 6, 4), False, (False, None)), # Test an incompatible Python with ignore_requires_python=True. ((3, 6, 4), True, (True, "1.12")), ], ) def test_evaluate_link( self, - py_version_info, - ignore_requires_python, - expected, - ): + py_version_info: Tuple[int, int, int], + ignore_requires_python: bool, + expected: Tuple[bool, Optional[str]], + ) -> None: target_python = TargetPython(py_version_info=py_version_info) evaluator = LinkEvaluator( project_name="twine", canonical_name="twine", - formats={"source"}, + formats=frozenset(["source"]), target_python=target_python, allow_yanked=True, ignore_requires_python=ignore_requires_python, @@ -161,15 +167,15 @@ def test_evaluate_link( ) def test_evaluate_link__allow_yanked( self, - yanked_reason, - allow_yanked, - expected, - ): + yanked_reason: str, + allow_yanked: bool, + expected: Tuple[bool, str], + ) -> None: target_python = TargetPython(py_version_info=(3, 6, 4)) evaluator = LinkEvaluator( project_name="twine", canonical_name="twine", - formats={"source"}, + formats=frozenset(["source"]), target_python=target_python, allow_yanked=allow_yanked, ) @@ -180,7 +186,7 @@ def test_evaluate_link__allow_yanked( actual = evaluator.evaluate_link(link) assert actual == expected - def test_evaluate_link__incompatible_wheel(self): + def test_evaluate_link__incompatible_wheel(self) -> None: """ Test an incompatible wheel. """ @@ -190,7 +196,7 @@ def test_evaluate_link__incompatible_wheel(self): evaluator = LinkEvaluator( project_name="sample", canonical_name="sample", - formats={"binary"}, + formats=frozenset(["binary"]), target_python=target_python, allow_yanked=True, ) @@ -207,13 +213,12 @@ def test_evaluate_link__incompatible_wheel(self): @pytest.mark.parametrize( "hex_digest, expected_versions", [ - (None, ["1.0", "1.1", "1.2"]), (64 * "a", ["1.0", "1.1"]), (64 * "b", ["1.0", "1.2"]), (64 * "c", ["1.0", "1.1", "1.2"]), ], ) -def test_filter_unallowed_hashes(hex_digest, expected_versions): +def test_filter_unallowed_hashes(hex_digest: str, expected_versions: List[str]) -> None: candidates = [ make_mock_candidate("1.0"), make_mock_candidate("1.1", hex_digest=(64 * "a")), @@ -235,7 +240,7 @@ def test_filter_unallowed_hashes(hex_digest, expected_versions): assert actual is not candidates -def test_filter_unallowed_hashes__no_hashes(caplog): +def test_filter_unallowed_hashes__no_hashes(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.DEBUG) candidates = [ @@ -259,7 +264,9 @@ def test_filter_unallowed_hashes__no_hashes(caplog): check_caplog(caplog, "DEBUG", expected_message) -def test_filter_unallowed_hashes__log_message_with_match(caplog): +def test_filter_unallowed_hashes__log_message_with_match( + caplog: pytest.LogCaptureFixture, +) -> None: caplog.set_level(logging.DEBUG) # Test 1 match, 2 non-matches, 3 no hashes so all 3 values will be @@ -298,7 +305,9 @@ def test_filter_unallowed_hashes__log_message_with_match(caplog): check_caplog(caplog, "DEBUG", expected_message) -def test_filter_unallowed_hashes__log_message_with_no_match(caplog): +def test_filter_unallowed_hashes__log_message_with_no_match( + caplog: pytest.LogCaptureFixture, +) -> None: caplog.set_level(logging.DEBUG) candidates = [ @@ -334,9 +343,9 @@ class TestCandidateEvaluator: (True, True), ], ) - def test_create(self, allow_all_prereleases, prefer_binary): + def test_create(self, allow_all_prereleases: bool, prefer_binary: bool) -> None: target_python = TargetPython() - target_python._valid_tags = [("py36", "none", "any")] + target_python._valid_tags = [Tag("py36", "none", "any")] specifier = SpecifierSet() evaluator = CandidateEvaluator.create( project_name="my-project", @@ -348,9 +357,9 @@ def test_create(self, allow_all_prereleases, prefer_binary): assert evaluator._allow_all_prereleases == allow_all_prereleases assert evaluator._prefer_binary == prefer_binary assert evaluator._specifier is specifier - assert evaluator._supported_tags == [("py36", "none", "any")] + assert evaluator._supported_tags == [Tag("py36", "none", "any")] - def test_create__target_python_none(self): + def test_create__target_python_none(self) -> None: """ Test passing target_python=None. """ @@ -358,7 +367,7 @@ def test_create__target_python_none(self): expected_tags = get_supported() assert evaluator._supported_tags == expected_tags - def test_create__specifier_none(self): + def test_create__specifier_none(self) -> None: """ Test passing specifier=None. """ @@ -366,7 +375,7 @@ def test_create__specifier_none(self): expected_specifier = SpecifierSet() assert evaluator._specifier == expected_specifier - def test_get_applicable_candidates(self): + def test_get_applicable_candidates(self) -> None: specifier = SpecifierSet("<= 1.11") versions = ["1.10", "1.11", "1.12"] candidates = [make_mock_candidate(version) for version in versions] @@ -394,9 +403,9 @@ def test_get_applicable_candidates(self): ) def test_get_applicable_candidates__hashes( self, - specifier, - expected_versions, - ): + specifier: SpecifierSet, + expected_versions: List[str], + ) -> None: """ Test a non-None hashes value. """ @@ -418,7 +427,7 @@ def test_get_applicable_candidates__hashes( actual_versions = [str(c.version) for c in actual] assert actual_versions == expected_versions - def test_compute_best_candidate(self): + def test_compute_best_candidate(self) -> None: specifier = SpecifierSet("<= 1.11") versions = ["1.10", "1.11", "1.12"] candidates = [make_mock_candidate(version) for version in versions] @@ -438,7 +447,7 @@ def test_compute_best_candidate(self): assert result.best_candidate is expected_applicable[1] - def test_compute_best_candidate__none_best(self): + def test_compute_best_candidate__none_best(self) -> None: """ Test returning a None best candidate. """ @@ -466,7 +475,7 @@ def test_compute_best_candidate__none_best(self): (64 * "b", 0), ], ) - def test_sort_key__hash(self, hex_digest, expected): + def test_sort_key__hash(self, hex_digest: Optional[str], expected: int) -> None: """ Test the effect of the link's hash on _sort_key()'s return value. """ @@ -490,7 +499,9 @@ def test_sort_key__hash(self, hex_digest, expected): ("bad metadata", -1), ], ) - def test_sort_key__is_yanked(self, yanked_reason, expected): + def test_sort_key__is_yanked( + self, yanked_reason: Optional[str], expected: int + ) -> None: """ Test the effect of is_yanked on _sort_key()'s return value. """ @@ -501,7 +512,7 @@ def test_sort_key__is_yanked(self, yanked_reason, expected): actual = sort_value[1] assert actual == expected - def test_sort_best_candidate__no_candidates(self): + def test_sort_best_candidate__no_candidates(self) -> None: """ Test passing an empty list. """ @@ -511,8 +522,8 @@ def test_sort_best_candidate__no_candidates(self): def test_sort_best_candidate__best_yanked_but_not_all( self, - caplog, - ): + caplog: pytest.LogCaptureFixture, + ) -> None: """ Test the best candidates being yanked, but not all. """ @@ -546,9 +557,9 @@ class TestPackageFinder: ) def test_create__candidate_prefs( self, - allow_all_prereleases, - prefer_binary, - ): + allow_all_prereleases: bool, + prefer_binary: bool, + ) -> None: """ Test that the _candidate_prefs attribute is set correctly. """ @@ -569,7 +580,7 @@ def test_create__candidate_prefs( assert candidate_prefs.allow_all_prereleases == allow_all_prereleases assert candidate_prefs.prefer_binary == prefer_binary - def test_create__link_collector(self): + def test_create__link_collector(self) -> None: """ Test that the _link_collector attribute is set correctly. """ @@ -584,7 +595,7 @@ def test_create__link_collector(self): assert finder._link_collector is link_collector - def test_create__target_python(self): + def test_create__target_python(self) -> None: """ Test that the _target_python attribute is set correctly. """ @@ -604,7 +615,7 @@ def test_create__target_python(self): # Check that the attributes weren't reset. assert actual_target_python.py_version_info == (3, 7, 3) - def test_create__target_python_none(self): + def test_create__target_python_none(self) -> None: """ Test passing target_python=None. """ @@ -623,7 +634,7 @@ def test_create__target_python_none(self): assert actual_target_python.py_version_info == CURRENT_PY_VERSION_INFO @pytest.mark.parametrize("allow_yanked", [False, True]) - def test_create__allow_yanked(self, allow_yanked): + def test_create__allow_yanked(self, allow_yanked: bool) -> None: """ Test that the _allow_yanked attribute is set correctly. """ @@ -639,7 +650,7 @@ def test_create__allow_yanked(self, allow_yanked): assert finder._allow_yanked == allow_yanked @pytest.mark.parametrize("ignore_requires_python", [False, True]) - def test_create__ignore_requires_python(self, ignore_requires_python): + def test_create__ignore_requires_python(self, ignore_requires_python: bool) -> None: """ Test that the _ignore_requires_python attribute is set correctly. """ @@ -657,7 +668,7 @@ def test_create__ignore_requires_python(self, ignore_requires_python): ) assert finder._ignore_requires_python == ignore_requires_python - def test_create__format_control(self): + def test_create__format_control(self) -> None: """ Test that the format_control attribute is set correctly. """ @@ -693,11 +704,11 @@ def test_create__format_control(self): ) def test_make_link_evaluator( self, - allow_yanked, - ignore_requires_python, - only_binary, - expected_formats, - ): + allow_yanked: bool, + ignore_requires_python: bool, + only_binary: Set[str], + expected_formats: FrozenSet[str], + ) -> None: # Create a test TargetPython that we can check for. target_python = TargetPython(py_version_info=(3, 7)) format_control = FormatControl(set(), only_binary) @@ -743,11 +754,11 @@ def test_make_link_evaluator( ) def test_make_candidate_evaluator( self, - allow_all_prereleases, - prefer_binary, - ): + allow_all_prereleases: bool, + prefer_binary: bool, + ) -> None: target_python = TargetPython() - target_python._valid_tags = [("py36", "none", "any")] + target_python._valid_tags = [Tag("py36", "none", "any")] candidate_prefs = CandidatePreferences( prefer_binary=prefer_binary, allow_all_prereleases=allow_all_prereleases, @@ -776,7 +787,7 @@ def test_make_candidate_evaluator( assert evaluator._prefer_binary == prefer_binary assert evaluator._project_name == "my-project" assert evaluator._specifier is specifier - assert evaluator._supported_tags == [("py36", "none", "any")] + assert evaluator._supported_tags == [Tag("py36", "none", "any")] @pytest.mark.parametrize( @@ -804,7 +815,9 @@ def test_make_candidate_evaluator( ("zope.interface-", "zope-interface", 14), ], ) -def test_find_name_version_sep(fragment, canonical_name, expected): +def test_find_name_version_sep( + fragment: str, canonical_name: str, expected: int +) -> None: index = _find_name_version_sep(fragment, canonical_name) assert index == expected @@ -819,7 +832,7 @@ def test_find_name_version_sep(fragment, canonical_name, expected): ("zope.interface", "zope-interface"), ], ) -def test_find_name_version_sep_failure(fragment, canonical_name): +def test_find_name_version_sep_failure(fragment: str, canonical_name: str) -> None: with pytest.raises(ValueError) as ctx: _find_name_version_sep(fragment, canonical_name) message = f"{fragment} does not match {canonical_name}" @@ -855,6 +868,8 @@ def test_find_name_version_sep_failure(fragment, canonical_name): ("zope.interface", "zope-interface", None), ], ) -def test_extract_version_from_fragment(fragment, canonical_name, expected): +def test_extract_version_from_fragment( + fragment: str, canonical_name: str, expected: Optional[str] +) -> None: version = _extract_version_from_fragment(fragment, canonical_name) assert version == expected diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py index 901bf58e273..99ed0aba76e 100644 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -1,3 +1,5 @@ +from typing import Optional + import pytest from pip._internal.models.link import Link, links_equivalent @@ -14,7 +16,7 @@ class TestLink: ), ], ) - def test_repr(self, url, expected): + def test_repr(self, url: str, expected: str) -> None: link = Link(url) assert repr(link) == expected @@ -42,32 +44,32 @@ def test_repr(self, url, expected): ), ], ) - def test_filename(self, url, expected): + def test_filename(self, url: str, expected: str) -> None: link = Link(url) assert link.filename == expected - def test_splitext(self): + def test_splitext(self) -> None: assert ("wheel", ".whl") == Link("http://yo/wheel.whl").splitext() - def test_no_ext(self): + def test_no_ext(self) -> None: assert "" == Link("http://yo/wheel").ext - def test_ext(self): + def test_ext(self) -> None: assert ".whl" == Link("http://yo/wheel.whl").ext - def test_ext_fragment(self): + def test_ext_fragment(self) -> None: assert ".whl" == Link("http://yo/wheel.whl#frag").ext - def test_ext_query(self): + def test_ext_query(self) -> None: assert ".whl" == Link("http://yo/wheel.whl?a=b").ext - def test_is_wheel(self): + def test_is_wheel(self) -> None: assert Link("http://yo/wheel.whl").is_wheel - def test_is_wheel_false(self): + def test_is_wheel_false(self) -> None: assert not Link("http://yo/not_a_wheel").is_wheel - def test_fragments(self): + def test_fragments(self) -> None: url = "git+https://example.com/package#egg=eggname" assert "eggname" == Link(url).egg_fragment assert None is Link(url).subdirectory_fragment @@ -86,7 +88,7 @@ def test_fragments(self): ("there was a mistake", True), ], ) - def test_is_yanked(self, yanked_reason, expected): + def test_is_yanked(self, yanked_reason: Optional[str], expected: bool) -> None: link = Link( "https://example.com/wheel.whl", yanked_reason=yanked_reason, @@ -107,7 +109,9 @@ def test_is_yanked(self, yanked_reason, expected): ("sha512", "", False), ], ) - def test_is_hash_allowed(self, hash_name, hex_digest, expected): + def test_is_hash_allowed( + self, hash_name: str, hex_digest: str, expected: bool + ) -> None: url = "https://example.com/wheel.whl#{hash_name}={hex_digest}".format( hash_name=hash_name, hex_digest=hex_digest, @@ -119,7 +123,7 @@ def test_is_hash_allowed(self, hash_name, hex_digest, expected): hashes = Hashes(hashes_data) assert link.is_hash_allowed(hashes) == expected - def test_is_hash_allowed__no_hash(self): + def test_is_hash_allowed__no_hash(self) -> None: link = Link("https://example.com/wheel.whl") hashes_data = { "sha512": [128 * "a"], @@ -135,7 +139,9 @@ def test_is_hash_allowed__no_hash(self): (Hashes({"sha512": [128 * "a"]}), True), ], ) - def test_is_hash_allowed__none_hashes(self, hashes, expected): + def test_is_hash_allowed__none_hashes( + self, hashes: Optional[Hashes], expected: bool + ) -> None: url = "https://example.com/wheel.whl#sha512={}".format(128 * "a") link = Link(url) assert link.is_hash_allowed(hashes) == expected @@ -150,7 +156,7 @@ def test_is_hash_allowed__none_hashes(self, hashes, expected): ("file://home/foo/some.whl", False), ], ) - def test_is_vcs(self, url, expected): + def test_is_vcs(self, url: str, expected: bool) -> None: link = Link(url) assert link.is_vcs is expected @@ -180,7 +186,7 @@ def test_is_vcs(self, url, expected): ), ], ) -def test_links_equivalent(url1, url2): +def test_links_equivalent(url1: str, url2: str) -> None: assert links_equivalent(Link(url1), Link(url2)) @@ -204,5 +210,5 @@ def test_links_equivalent(url1, url2): ), ], ) -def test_links_equivalent_false(url1, url2): +def test_links_equivalent_false(url1: str, url2: str) -> None: assert not links_equivalent(Link(url1), Link(url2)) diff --git a/tests/unit/test_locations.py b/tests/unit/test_locations.py index f071faff52a..640be7f0df8 100644 --- a/tests/unit/test_locations.py +++ b/tests/unit/test_locations.py @@ -7,11 +7,13 @@ import shutil import sys import tempfile +from typing import Any, Dict from unittest.mock import Mock import pytest from pip._internal.locations import SCHEME_KEYS, get_scheme +from tests.lib.path import Path if sys.platform == "win32": pwd = Mock() @@ -19,23 +21,23 @@ import pwd -def _get_scheme_dict(*args, **kwargs): +def _get_scheme_dict(*args: Any, **kwargs: Any) -> Dict[str, str]: scheme = get_scheme(*args, **kwargs) return {k: getattr(scheme, k) for k in SCHEME_KEYS} class TestLocations: - def setup(self): + def setup(self) -> None: self.tempdir = tempfile.mkdtemp() self.st_uid = 9999 self.username = "example" self.patch() - def teardown(self): + def teardown(self) -> None: self.revert_patch() shutil.rmtree(self.tempdir, ignore_errors=True) - def patch(self): + def patch(self) -> None: """first store and then patch python methods pythons""" self.tempfile_gettempdir = tempfile.gettempdir self.old_os_fstat = os.fstat @@ -54,7 +56,7 @@ def patch(self): if sys.platform != "win32": pwd.getpwuid = lambda uid: self.get_mock_getpwuid(uid) - def revert_patch(self): + def revert_patch(self) -> None: """revert the patches to python methods""" tempfile.gettempdir = self.tempfile_gettempdir getpass.getuser = self.old_getpass_getuser @@ -64,7 +66,7 @@ def revert_patch(self): pwd.getpwuid = self.old_pwd_getpwuid os.fstat = self.old_os_fstat - def get_mock_fstat(self, fd): + def get_mock_fstat(self, fd: int) -> os.stat_result: """returns a basic mock fstat call result. Currently only the st_uid attribute has been set. """ @@ -72,7 +74,7 @@ def get_mock_fstat(self, fd): result.st_uid = self.st_uid return result - def get_mock_getpwuid(self, uid): + def get_mock_getpwuid(self, uid: int) -> pwd.struct_passwd: """returns a basic mock pwd.getpwuid call result. Currently only the pw_name attribute has been set. """ @@ -82,7 +84,7 @@ def get_mock_getpwuid(self, uid): class TestDistutilsScheme: - def test_root_modifies_appropriately(self): + def test_root_modifies_appropriately(self) -> None: # This deals with nt/posix path differences # root is c:\somewhere\else or /somewhere/else root = os.path.normcase( @@ -98,7 +100,9 @@ def test_root_modifies_appropriately(self): @pytest.mark.incompatible_with_sysconfig @pytest.mark.incompatible_with_venv - def test_distutils_config_file_read(self, tmpdir, monkeypatch): + def test_distutils_config_file_read( + self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: # This deals with nt/posix path differences install_scripts = os.path.normcase( os.path.abspath(os.path.join(os.path.sep, "somewhere", "else")) @@ -122,7 +126,9 @@ def test_distutils_config_file_read(self, tmpdir, monkeypatch): # when we request install-lib, we should install everything (.py & # .so) into that path; i.e. ensure platlib & purelib are set to # this path. sysconfig does not support this. - def test_install_lib_takes_precedence(self, tmpdir, monkeypatch): + def test_install_lib_takes_precedence( + self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: # This deals with nt/posix path differences install_lib = os.path.normcase( os.path.abspath(os.path.join(os.path.sep, "somewhere", "else")) @@ -142,13 +148,13 @@ def test_install_lib_takes_precedence(self, tmpdir, monkeypatch): assert scheme["platlib"] == install_lib + os.path.sep assert scheme["purelib"] == install_lib + os.path.sep - def test_prefix_modifies_appropriately(self): + def test_prefix_modifies_appropriately(self) -> None: prefix = os.path.abspath(os.path.join("somewhere", "else")) normal_scheme = _get_scheme_dict("example") prefix_scheme = _get_scheme_dict("example", prefix=prefix) - def _calculate_expected(value): + def _calculate_expected(value: str) -> str: path = os.path.join(prefix, os.path.relpath(value, sys.prefix)) return os.path.normpath(path) diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index e4ece7187f6..7b90d5dcc70 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -18,7 +18,7 @@ class TestIndentingFormatter: """Test ``pip._internal.utils.logging.IndentingFormatter``.""" - def make_record(self, msg, level_name): + def make_record(self, msg: str, level_name: str) -> logging.LogRecord: level_number = getattr(logging, level_name) attrs = dict( msg=msg, @@ -41,7 +41,7 @@ def make_record(self, msg, level_name): ("CRITICAL", "ERROR: hello\nworld"), ], ) - def test_format(self, level_name, expected, utc): + def test_format(self, level_name: str, expected: str, utc: None) -> None: """ Args: level_name: a logging level name (e.g. "WARNING"). @@ -61,7 +61,9 @@ def test_format(self, level_name, expected, utc): ), ], ) - def test_format_with_timestamp(self, level_name, expected, utc): + def test_format_with_timestamp( + self, level_name: str, expected: str, utc: None + ) -> None: record = self.make_record("hello\nworld", level_name=level_name) f = IndentingFormatter(fmt="%(message)s", add_timestamp=True) assert f.format(record) == expected @@ -74,7 +76,7 @@ def test_format_with_timestamp(self, level_name, expected, utc): ("CRITICAL", "DEPRECATION: hello\nworld"), ], ) - def test_format_deprecated(self, level_name, expected, utc): + def test_format_deprecated(self, level_name: str, expected: str, utc: None) -> None: """ Test that logged deprecation warnings coming from deprecated() don't get another prefix. @@ -86,7 +88,7 @@ def test_format_deprecated(self, level_name, expected, utc): f = IndentingFormatter(fmt="%(message)s") assert f.format(record) == expected - def test_thread_safety_base(self, utc): + def test_thread_safety_base(self, utc: None) -> None: record = self.make_record( "DEPRECATION: hello\nworld", level_name="WARNING", @@ -94,7 +96,7 @@ def test_thread_safety_base(self, utc): f = IndentingFormatter(fmt="%(message)s") results = [] - def thread_function(): + def thread_function() -> None: results.append(f.format(record)) thread_function() @@ -103,7 +105,7 @@ def thread_function(): thread.join() assert results[0] == results[1] - def test_thread_safety_indent_log(self, utc): + def test_thread_safety_indent_log(self, utc: None) -> None: record = self.make_record( "DEPRECATION: hello\nworld", level_name="WARNING", @@ -111,7 +113,7 @@ def test_thread_safety_indent_log(self, utc): f = IndentingFormatter(fmt="%(message)s") results = [] - def thread_function(): + def thread_function() -> None: with indent_log(): results.append(f.format(record)) @@ -123,7 +125,7 @@ def thread_function(): class TestColorizedStreamHandler: - def _make_log_record(self): + def _make_log_record(self) -> logging.LogRecord: attrs = { "msg": "my error", } @@ -131,7 +133,7 @@ def _make_log_record(self): return record - def test_broken_pipe_in_stderr_flush(self): + def test_broken_pipe_in_stderr_flush(self) -> None: """ Test sys.stderr.flush() raising BrokenPipeError. @@ -154,7 +156,7 @@ def test_broken_pipe_in_stderr_flush(self): assert "BrokenPipeError" in err_text assert "Message: 'my error'" in err_text - def test_broken_pipe_in_stdout_write(self): + def test_broken_pipe_in_stdout_write(self) -> None: """ Test sys.stdout.write() raising BrokenPipeError. @@ -169,7 +171,7 @@ def test_broken_pipe_in_stdout_write(self): with pytest.raises(BrokenStdoutLoggingError): handler.emit(record) - def test_broken_pipe_in_stdout_flush(self): + def test_broken_pipe_in_stdout_flush(self) -> None: """ Test sys.stdout.flush() raising BrokenPipeError. diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 3b980b36686..c519ba89a30 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -1,21 +1,30 @@ import logging -from unittest.mock import patch +from typing import cast +from unittest import mock + +import pytest +from pip._vendor.packaging.utils import NormalizedName from pip._internal.metadata import BaseDistribution from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, ArchiveInfo -@patch.object(BaseDistribution, "read_text", side_effect=FileNotFoundError) -def test_dist_get_direct_url_no_metadata(mock_read_text): - dist = BaseDistribution() +@mock.patch.object(BaseDistribution, "read_text", side_effect=FileNotFoundError) +def test_dist_get_direct_url_no_metadata(mock_read_text: mock.Mock) -> None: + class FakeDistribution(BaseDistribution): + pass + + dist = FakeDistribution() assert dist.direct_url is None mock_read_text.assert_called_once_with(DIRECT_URL_METADATA_NAME) -@patch.object(BaseDistribution, "read_text", return_value="{}") -def test_dist_get_direct_url_invalid_json(mock_read_text, caplog): +@mock.patch.object(BaseDistribution, "read_text", return_value="{}") +def test_dist_get_direct_url_invalid_json( + mock_read_text: mock.Mock, caplog: pytest.LogCaptureFixture +) -> None: class FakeDistribution(BaseDistribution): - canonical_name = "whatever" # Needed for error logging. + canonical_name = cast(NormalizedName, "whatever") # Needed for error logging. dist = FakeDistribution() with caplog.at_level(logging.WARNING): @@ -31,14 +40,18 @@ class FakeDistribution(BaseDistribution): ) -@patch.object( +@mock.patch.object( BaseDistribution, "read_text", return_value='{"url": "https://e.c/p.tgz", "archive_info": {}}', ) -def test_dist_get_direct_url_valid_metadata(mock_read_text): - dist = BaseDistribution() +def test_dist_get_direct_url_valid_metadata(mock_read_text: mock.Mock) -> None: + class FakeDistribution(BaseDistribution): + pass + + dist = FakeDistribution() direct_url = dist.direct_url + assert direct_url is not None mock_read_text.assert_called_once_with(DIRECT_URL_METADATA_NAME) assert direct_url.url == "https://e.c/p.tgz" assert isinstance(direct_url.info, ArchiveInfo) diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index 9a84fa22332..c5545e37d01 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -4,12 +4,13 @@ from pip._vendor.packaging.version import parse as parse_version from pip._internal.models import candidate, index +from pip._internal.models.link import Link class TestPackageIndex: """Tests for pip._internal.models.index.PackageIndex""" - def test_gives_right_urls(self): + def test_gives_right_urls(self) -> None: url = "https://mypypi.internal/path/" file_storage_domain = "files.mypypi.internal" pack_index = index.PackageIndex(url, file_storage_domain) @@ -21,7 +22,7 @@ def test_gives_right_urls(self): assert pack_index.simple_url == url + "simple" assert pack_index.pypi_url == url + "pypi" - def test_PyPI_urls_are_correct(self): + def test_PyPI_urls_are_correct(self) -> None: pack_index = index.PyPI assert pack_index.netloc == "pypi.org" @@ -30,7 +31,7 @@ def test_PyPI_urls_are_correct(self): assert pack_index.pypi_url == "https://pypi.org/pypi" assert pack_index.file_storage_domain == "files.pythonhosted.org" - def test_TestPyPI_urls_are_correct(self): + def test_TestPyPI_urls_are_correct(self) -> None: pack_index = index.TestPyPI assert pack_index.netloc == "test.pypi.org" @@ -41,18 +42,18 @@ def test_TestPyPI_urls_are_correct(self): class TestInstallationCandidate: - def test_sets_correct_variables(self): + def test_sets_correct_variables(self) -> None: obj = candidate.InstallationCandidate( - "A", "1.0.0", "https://somewhere.com/path/A-1.0.0.tar.gz" + "A", "1.0.0", Link("https://somewhere.com/path/A-1.0.0.tar.gz") ) assert obj.name == "A" assert obj.version == parse_version("1.0.0") - assert obj.link == "https://somewhere.com/path/A-1.0.0.tar.gz" + assert obj.link.url == "https://somewhere.com/path/A-1.0.0.tar.gz" # NOTE: This isn't checking the ordering logic; only the data provided to # it is correct. - def test_sets_the_right_key(self): + def test_sets_the_right_key(self) -> None: obj = candidate.InstallationCandidate( - "A", "1.0.0", "https://somewhere.com/path/A-1.0.0.tar.gz" + "A", "1.0.0", Link("https://somewhere.com/path/A-1.0.0.tar.gz") ) assert obj._compare_key == (obj.name, obj.version, obj.link) diff --git a/tests/unit/test_models_wheel.py b/tests/unit/test_models_wheel.py index fefb9b3a8f9..4ada81f70be 100644 --- a/tests/unit/test_models_wheel.py +++ b/tests/unit/test_models_wheel.py @@ -7,7 +7,7 @@ class TestWheelFile: - def test_std_wheel_pattern(self): + def test_std_wheel_pattern(self) -> None: w = Wheel("simple-1.1.1-py2-none-any.whl") assert w.name == "simple" assert w.version == "1.1.1" @@ -15,7 +15,7 @@ def test_std_wheel_pattern(self): assert w.abis == ["none"] assert w.plats == ["any"] - def test_wheel_pattern_multi_values(self): + def test_wheel_pattern_multi_values(self) -> None: w = Wheel("simple-1.1-py2.py3-abi1.abi2-any.whl") assert w.name == "simple" assert w.version == "1.1" @@ -23,7 +23,7 @@ def test_wheel_pattern_multi_values(self): assert w.abis == ["abi1", "abi2"] assert w.plats == ["any"] - def test_wheel_with_build_tag(self): + def test_wheel_with_build_tag(self) -> None: # pip doesn't do anything with build tags, but theoretically, we might # see one, in this case the build tag = '4' w = Wheel("simple-1.1-4-py2-none-any.whl") @@ -33,44 +33,44 @@ def test_wheel_with_build_tag(self): assert w.abis == ["none"] assert w.plats == ["any"] - def test_single_digit_version(self): + def test_single_digit_version(self) -> None: w = Wheel("simple-1-py2-none-any.whl") assert w.version == "1" - def test_non_pep440_version(self): + def test_non_pep440_version(self) -> None: w = Wheel("simple-_invalid_-py2-none-any.whl") assert w.version == "-invalid-" - def test_missing_version_raises(self): + def test_missing_version_raises(self) -> None: with pytest.raises(InvalidWheelFilename): Wheel("Cython-cp27-none-linux_x86_64.whl") - def test_invalid_filename_raises(self): + def test_invalid_filename_raises(self) -> None: with pytest.raises(InvalidWheelFilename): Wheel("invalid.whl") - def test_supported_single_version(self): + def test_supported_single_version(self) -> None: """ Test single-version wheel is known to be supported """ w = Wheel("simple-0.1-py2-none-any.whl") assert w.supported(tags=[Tag("py2", "none", "any")]) - def test_supported_multi_version(self): + def test_supported_multi_version(self) -> None: """ Test multi-version wheel is known to be supported """ w = Wheel("simple-0.1-py2.py3-none-any.whl") assert w.supported(tags=[Tag("py3", "none", "any")]) - def test_not_supported_version(self): + def test_not_supported_version(self) -> None: """ Test unsupported wheel is known to be unsupported """ w = Wheel("simple-0.1-py2-none-any.whl") assert not w.supported(tags=[Tag("py1", "none", "any")]) - def test_supported_osx_version(self): + def test_supported_osx_version(self) -> None: """ Wheels built for macOS 10.6 are supported on 10.9 """ @@ -82,7 +82,7 @@ def test_supported_osx_version(self): w = Wheel("simple-0.1-cp27-none-macosx_10_9_intel.whl") assert w.supported(tags=tags) - def test_not_supported_osx_version(self): + def test_not_supported_osx_version(self) -> None: """ Wheels built for macOS 10.9 are not supported on 10.6 """ @@ -100,7 +100,7 @@ def test_not_supported_osx_version(self): "https://github.com/pypa/packaging/pull/361 for further discussion." ) ) - def test_supported_multiarch_darwin(self): + def test_supported_multiarch_darwin(self) -> None: """ Multi-arch wheels (intel) are supported on components (i386, x86_64) """ @@ -138,7 +138,7 @@ def test_supported_multiarch_darwin(self): assert w.supported(tags=ppc) assert w.supported(tags=ppc64) - def test_not_supported_multiarch_darwin(self): + def test_not_supported_multiarch_darwin(self) -> None: """ Single-arch wheels (x86_64) are not supported on multi-arch (intel) """ @@ -156,7 +156,7 @@ def test_not_supported_multiarch_darwin(self): assert not w.supported(tags=intel) assert not w.supported(tags=universal) - def test_support_index_min(self): + def test_support_index_min(self) -> None: """ Test results from `support_index_min` """ @@ -170,7 +170,7 @@ def test_support_index_min(self): w = Wheel("simple-0.1-py2-none-TEST.whl") assert w.support_index_min(tags=tags) == 0 - def test_support_index_min__none_supported(self): + def test_support_index_min__none_supported(self) -> None: """ Test a wheel not supported by the given tags. """ @@ -178,7 +178,7 @@ def test_support_index_min__none_supported(self): with pytest.raises(ValueError): w.support_index_min(tags=[]) - def test_version_underscore_conversion(self): + def test_version_underscore_conversion(self) -> None: """ Test that we convert '_' to '-' for versions parsed out of wheel filenames diff --git a/tests/unit/test_network_auth.py b/tests/unit/test_network_auth.py index 7be8941febc..5c0e5746281 100644 --- a/tests/unit/test_network_auth.py +++ b/tests/unit/test_network_auth.py @@ -1,4 +1,5 @@ import functools +from typing import Any, List, Optional, Tuple import pytest @@ -36,7 +37,9 @@ ), ], ) -def test_get_credentials_parses_correctly(input_url, url, username, password): +def test_get_credentials_parses_correctly( + input_url: str, url: str, username: Optional[str], password: Optional[str] +) -> None: auth = MultiDomainBasicAuth() get = auth._get_url_and_credentials @@ -51,7 +54,7 @@ def test_get_credentials_parses_correctly(input_url, url, username, password): ) -def test_get_credentials_not_to_uses_cached_credentials(): +def test_get_credentials_not_to_uses_cached_credentials() -> None: auth = MultiDomainBasicAuth() auth.passwords["example.com"] = ("user", "pass") @@ -60,7 +63,7 @@ def test_get_credentials_not_to_uses_cached_credentials(): assert got == expected -def test_get_credentials_not_to_uses_cached_credentials_only_username(): +def test_get_credentials_not_to_uses_cached_credentials_only_username() -> None: auth = MultiDomainBasicAuth() auth.passwords["example.com"] = ("user", "pass") @@ -69,7 +72,7 @@ def test_get_credentials_not_to_uses_cached_credentials_only_username(): assert got == expected -def test_get_credentials_uses_cached_credentials(): +def test_get_credentials_uses_cached_credentials() -> None: auth = MultiDomainBasicAuth() auth.passwords["example.com"] = ("user", "pass") @@ -78,7 +81,7 @@ def test_get_credentials_uses_cached_credentials(): assert got == expected -def test_get_credentials_uses_cached_credentials_only_username(): +def test_get_credentials_uses_cached_credentials_only_username() -> None: auth = MultiDomainBasicAuth() auth.passwords["example.com"] = ("user", "pass") @@ -87,7 +90,7 @@ def test_get_credentials_uses_cached_credentials_only_username(): assert got == expected -def test_get_index_url_credentials(): +def test_get_index_url_credentials() -> None: auth = MultiDomainBasicAuth(index_urls=["http://foo:bar@example.com/path"]) get = functools.partial( auth._get_new_credentials, allow_netrc=False, allow_keyring=False @@ -103,17 +106,17 @@ class KeyringModuleV1: was added. """ - def __init__(self): - self.saved_passwords = [] + def __init__(self) -> None: + self.saved_passwords: List[Tuple[str, str, str]] = [] - def get_password(self, system, username): + def get_password(self, system: str, username: str) -> Optional[str]: if system == "example.com" and username: return username + "!netloc" if system == "http://example.com/path2" and username: return username + "!url" return None - def set_password(self, system, username, password): + def set_password(self, system: str, username: str, password: str) -> None: self.saved_passwords.append((system, username, password)) @@ -129,7 +132,11 @@ def set_password(self, system, username, password): ("http://foo@example.com/path2/path3", ("foo", "foo!url")), ), ) -def test_keyring_get_password(monkeypatch, url, expect): +def test_keyring_get_password( + monkeypatch: pytest.MonkeyPatch, + url: str, + expect: Tuple[Optional[str], Optional[str]], +) -> None: keyring = KeyringModuleV1() monkeypatch.setattr("pip._internal.network.auth.keyring", keyring) auth = MultiDomainBasicAuth(index_urls=["http://example.com/path2"]) @@ -138,12 +145,12 @@ def test_keyring_get_password(monkeypatch, url, expect): assert actual == expect -def test_keyring_get_password_after_prompt(monkeypatch): +def test_keyring_get_password_after_prompt(monkeypatch: pytest.MonkeyPatch) -> None: keyring = KeyringModuleV1() monkeypatch.setattr("pip._internal.network.auth.keyring", keyring) auth = MultiDomainBasicAuth() - def ask_input(prompt): + def ask_input(prompt: str) -> str: assert prompt == "User for example.com: " return "user" @@ -152,16 +159,18 @@ def ask_input(prompt): assert actual == ("user", "user!netloc", False) -def test_keyring_get_password_after_prompt_when_none(monkeypatch): +def test_keyring_get_password_after_prompt_when_none( + monkeypatch: pytest.MonkeyPatch, +) -> None: keyring = KeyringModuleV1() monkeypatch.setattr("pip._internal.network.auth.keyring", keyring) auth = MultiDomainBasicAuth() - def ask_input(prompt): + def ask_input(prompt: str) -> str: assert prompt == "User for unknown.com: " return "user" - def ask_password(prompt): + def ask_password(prompt: str) -> str: assert prompt == "Password: " return "fake_password" @@ -171,7 +180,9 @@ def ask_password(prompt): assert actual == ("user", "fake_password", True) -def test_keyring_get_password_username_in_index(monkeypatch): +def test_keyring_get_password_username_in_index( + monkeypatch: pytest.MonkeyPatch, +) -> None: keyring = KeyringModuleV1() monkeypatch.setattr("pip._internal.network.auth.keyring", keyring) auth = MultiDomainBasicAuth(index_urls=["http://user@example.com/path2"]) @@ -199,7 +210,12 @@ def test_keyring_get_password_username_in_index(monkeypatch): ), ), ) -def test_keyring_set_password(monkeypatch, response_status, creds, expect_save): +def test_keyring_set_password( + monkeypatch: pytest.MonkeyPatch, + response_status: int, + creds: Tuple[str, str, bool], + expect_save: bool, +) -> None: keyring = KeyringModuleV1() monkeypatch.setattr("pip._internal.network.auth.keyring", keyring) auth = MultiDomainBasicAuth(prompting=True) @@ -207,13 +223,13 @@ def test_keyring_set_password(monkeypatch, response_status, creds, expect_save): monkeypatch.setattr(auth, "_prompt_for_password", lambda *a: creds) if creds[2]: # when _prompt_for_password indicates to save, we should save - def should_save_password_to_keyring(*a): + def should_save_password_to_keyring(*a: Any) -> bool: return True else: # when _prompt_for_password indicates not to save, we should # never call this function - def should_save_password_to_keyring(*a): + def should_save_password_to_keyring(*a: Any) -> bool: assert False, "_should_save_password_to_keyring should not be called" monkeypatch.setattr( @@ -225,14 +241,15 @@ def should_save_password_to_keyring(*a): resp.url = req.url connection = MockConnection() - def _send(sent_req, **kwargs): + def _send(sent_req: MockRequest, **kwargs: Any) -> MockResponse: assert sent_req is req assert "Authorization" in sent_req.headers r = MockResponse(b"") r.status_code = response_status return r - connection._send = _send + # https://github.com/python/mypy/issues/2427 + connection._send = _send # type: ignore[assignment] resp.request = req resp.status_code = 401 @@ -250,14 +267,14 @@ class KeyringModuleV2: """Represents the current supported API of keyring""" class Credential: - def __init__(self, username, password): + def __init__(self, username: str, password: str) -> None: self.username = username self.password = password - def get_password(self, system, username): + def get_password(self, system: str, username: str) -> None: assert False, "get_password should not ever be called" - def get_credential(self, system, username): + def get_credential(self, system: str, username: str) -> Optional[Credential]: if system == "http://example.com/path2": return self.Credential("username", "url") if system == "example.com": @@ -273,7 +290,9 @@ def get_credential(self, system, username): ("http://user2@example.com/path2/path3", ("username", "url")), ), ) -def test_keyring_get_credential(monkeypatch, url, expect): +def test_keyring_get_credential( + monkeypatch: pytest.MonkeyPatch, url: str, expect: str +) -> None: monkeypatch.setattr(pip._internal.network.auth, "keyring", KeyringModuleV2()) auth = MultiDomainBasicAuth(index_urls=["http://example.com/path2"]) @@ -285,15 +304,15 @@ def test_keyring_get_credential(monkeypatch, url, expect): class KeyringModuleBroken: """Represents the current supported API of keyring, but broken""" - def __init__(self): + def __init__(self) -> None: self._call_count = 0 - def get_credential(self, system, username): + def get_credential(self, system: str, username: str) -> None: self._call_count += 1 raise Exception("This keyring is broken!") -def test_broken_keyring_disables_keyring(monkeypatch): +def test_broken_keyring_disables_keyring(monkeypatch: pytest.MonkeyPatch) -> None: keyring_broken = KeyringModuleBroken() monkeypatch.setattr(pip._internal.network.auth, "keyring", keyring_broken) diff --git a/tests/unit/test_network_cache.py b/tests/unit/test_network_cache.py index 41382506e38..c7e0e382b17 100644 --- a/tests/unit/test_network_cache.py +++ b/tests/unit/test_network_cache.py @@ -1,14 +1,16 @@ import os +from typing import Iterator from unittest.mock import Mock import pytest from pip._vendor.cachecontrol.caches import FileCache from pip._internal.network.cache import SafeFileCache +from tests.lib.path import Path @pytest.fixture(scope="function") -def cache_tmpdir(tmpdir): +def cache_tmpdir(tmpdir: Path) -> Iterator[Path]: cache_dir = tmpdir.joinpath("cache") cache_dir.mkdir(parents=True) yield cache_dir @@ -21,7 +23,7 @@ class TestSafeFileCache: os.geteuid which is absent on Windows. """ - def test_cache_roundtrip(self, cache_tmpdir): + def test_cache_roundtrip(self, cache_tmpdir: Path) -> None: cache = SafeFileCache(cache_tmpdir) assert cache.get("test key") is None @@ -31,7 +33,9 @@ def test_cache_roundtrip(self, cache_tmpdir): assert cache.get("test key") is None @pytest.mark.skipif("sys.platform == 'win32'") - def test_safe_get_no_perms(self, cache_tmpdir, monkeypatch): + def test_safe_get_no_perms( + self, cache_tmpdir: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: os.chmod(cache_tmpdir, 000) monkeypatch.setattr(os.path, "exists", lambda x: True) @@ -40,20 +44,20 @@ def test_safe_get_no_perms(self, cache_tmpdir, monkeypatch): cache.get("foo") @pytest.mark.skipif("sys.platform == 'win32'") - def test_safe_set_no_perms(self, cache_tmpdir): + def test_safe_set_no_perms(self, cache_tmpdir: Path) -> None: os.chmod(cache_tmpdir, 000) cache = SafeFileCache(cache_tmpdir) cache.set("foo", b"bar") @pytest.mark.skipif("sys.platform == 'win32'") - def test_safe_delete_no_perms(self, cache_tmpdir): + def test_safe_delete_no_perms(self, cache_tmpdir: Path) -> None: os.chmod(cache_tmpdir, 000) cache = SafeFileCache(cache_tmpdir) cache.delete("foo") - def test_cache_hashes_are_same(self, cache_tmpdir): + def test_cache_hashes_are_same(self, cache_tmpdir: Path) -> None: cache = SafeFileCache(cache_tmpdir) key = "test key" fake_cache = Mock(FileCache, directory=cache.directory, encode=FileCache.encode) diff --git a/tests/unit/test_network_download.py b/tests/unit/test_network_download.py index c1bc2ea1e1b..53200f2e511 100644 --- a/tests/unit/test_network_download.py +++ b/tests/unit/test_network_download.py @@ -1,5 +1,6 @@ import logging import sys +from typing import Dict import pytest @@ -23,32 +24,38 @@ ), ( "http://example.com/foo.tgz", - {"content-length": 2}, + {"content-length": "2"}, False, "Downloading http://example.com/foo.tgz (2 bytes)", ), ( "http://example.com/foo.tgz", - {"content-length": 2}, + {"content-length": "2"}, True, "Using cached http://example.com/foo.tgz (2 bytes)", ), ("https://files.pythonhosted.org/foo.tgz", {}, False, "Downloading foo.tgz"), ( "https://files.pythonhosted.org/foo.tgz", - {"content-length": 2}, + {"content-length": "2"}, False, "Downloading foo.tgz (2 bytes)", ), ( "https://files.pythonhosted.org/foo.tgz", - {"content-length": 2}, + {"content-length": "2"}, True, "Using cached foo.tgz", ), ], ) -def test_prepare_download__log(caplog, url, headers, from_cache, expected): +def test_prepare_download__log( + caplog: pytest.LogCaptureFixture, + url: str, + headers: Dict[str, str], + from_cache: bool, + expected: str, +) -> None: caplog.set_level(logging.INFO) resp = MockResponse(b"") resp.url = url @@ -75,7 +82,7 @@ def test_prepare_download__log(caplog, url, headers, from_cache, expected): ("/", ""), ], ) -def test_sanitize_content_filename(filename, expected): +def test_sanitize_content_filename(filename: str, expected: str) -> None: """ Test inputs where the result is the same for Windows and non-Windows. """ @@ -94,8 +101,8 @@ def test_sanitize_content_filename(filename, expected): ], ) def test_sanitize_content_filename__platform_dependent( - filename, win_expected, non_win_expected -): + filename: str, win_expected: str, non_win_expected: str +) -> None: """ Test inputs where the result is different for Windows and non-Windows. """ @@ -112,6 +119,8 @@ def test_sanitize_content_filename__platform_dependent( ('attachment;filename="../file"', "df", "file"), ], ) -def test_parse_content_disposition(content_disposition, default_filename, expected): +def test_parse_content_disposition( + content_disposition: str, default_filename: str, expected: str +) -> None: actual = parse_content_disposition(content_disposition, default_filename) assert actual == expected diff --git a/tests/unit/test_network_lazy_wheel.py b/tests/unit/test_network_lazy_wheel.py index 264bfcbd2f1..1d959d6b16a 100644 --- a/tests/unit/test_network_lazy_wheel.py +++ b/tests/unit/test_network_lazy_wheel.py @@ -1,3 +1,4 @@ +from typing import Iterator from zipfile import BadZipfile from pip._vendor.packaging.version import Version @@ -8,7 +9,8 @@ dist_from_wheel_url, ) from pip._internal.network.session import PipSession -from tests.lib.server import file_response +from tests.lib import TestData +from tests.lib.server import MockServer, file_response MYPY_0_782_WHL = ( "https://files.pythonhosted.org/packages/9d/65/" @@ -24,12 +26,12 @@ @fixture -def session(): +def session() -> PipSession: return PipSession() @fixture -def mypy_whl_no_range(mock_server, shared_data): +def mypy_whl_no_range(mock_server: MockServer, shared_data: TestData) -> Iterator[str]: mypy_whl = shared_data.packages / "mypy-0.782-py3-none-any.whl" mock_server.set_responses([file_response(mypy_whl)]) mock_server.start() @@ -39,7 +41,7 @@ def mypy_whl_no_range(mock_server, shared_data): @mark.network -def test_dist_from_wheel_url(session): +def test_dist_from_wheel_url(session: PipSession) -> None: """Test if the acquired distribution contain correct information.""" dist = dist_from_wheel_url("mypy", MYPY_0_782_WHL, session) assert dist.canonical_name == "mypy" @@ -49,14 +51,16 @@ def test_dist_from_wheel_url(session): assert {str(d) for d in dist.iter_dependencies(extras)} == MYPY_0_782_REQS -def test_dist_from_wheel_url_no_range(session, mypy_whl_no_range): +def test_dist_from_wheel_url_no_range( + session: PipSession, mypy_whl_no_range: str +) -> None: """Test handling when HTTP range requests are not supported.""" with raises(HTTPRangeRequestUnsupported): dist_from_wheel_url("mypy", mypy_whl_no_range, session) @mark.network -def test_dist_from_wheel_url_not_zip(session): +def test_dist_from_wheel_url_not_zip(session: PipSession) -> None: """Test handling with the given URL does not point to a ZIP.""" with raises(BadZipfile): dist_from_wheel_url("python", "https://www.python.org/", session) diff --git a/tests/unit/test_network_session.py b/tests/unit/test_network_session.py index 1f333fedf1b..f16843abfb1 100644 --- a/tests/unit/test_network_session.py +++ b/tests/unit/test_network_session.py @@ -1,16 +1,19 @@ import logging +from typing import Any, List import pytest from pip import __version__ +from pip._internal.models.link import Link from pip._internal.network.session import CI_ENVIRONMENT_VARIABLES, PipSession +from tests.lib.path import Path -def get_user_agent(): +def get_user_agent() -> str: return PipSession().headers["User-Agent"] -def test_user_agent(): +def test_user_agent() -> None: user_agent = get_user_agent() assert user_agent.startswith(f"pip/{__version__}") @@ -27,7 +30,9 @@ def test_user_agent(): ("BUILD", False), ], ) -def test_user_agent__ci(monkeypatch, name, expected_like_ci): +def test_user_agent__ci( + monkeypatch: pytest.MonkeyPatch, name: str, expected_like_ci: bool +) -> None: # Delete the variable names we use to check for CI to prevent the # detection from always returning True in case the tests are being run # under actual CI. It is okay to depend on CI_ENVIRONMENT_VARIABLES @@ -47,19 +52,19 @@ def test_user_agent__ci(monkeypatch, name, expected_like_ci): assert ('"ci":null' in user_agent) == (not expected_like_ci) -def test_user_agent_user_data(monkeypatch): +def test_user_agent_user_data(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PIP_USER_AGENT_USER_DATA", "some_string") assert "some_string" in PipSession().headers["User-Agent"] class TestPipSession: - def test_cache_defaults_off(self): + def test_cache_defaults_off(self) -> None: session = PipSession() assert not hasattr(session.adapters["http://"], "cache") assert not hasattr(session.adapters["https://"], "cache") - def test_cache_is_enabled(self, tmpdir): + def test_cache_is_enabled(self, tmpdir: Path) -> None: cache_directory = tmpdir.joinpath("test-cache") session = PipSession(cache=cache_directory) @@ -67,12 +72,12 @@ def test_cache_is_enabled(self, tmpdir): assert session.adapters["https://"].cache.directory == cache_directory - def test_http_cache_is_not_enabled(self, tmpdir): + def test_http_cache_is_not_enabled(self, tmpdir: Path) -> None: session = PipSession(cache=tmpdir.joinpath("test-cache")) assert not hasattr(session.adapters["http://"], "cache") - def test_trusted_hosts_adapter(self, tmpdir): + def test_trusted_hosts_adapter(self, tmpdir: Path) -> None: session = PipSession( cache=tmpdir.joinpath("test-cache"), trusted_hosts=["example.com"], @@ -85,7 +90,7 @@ def test_trusted_hosts_adapter(self, tmpdir): assert hasattr(session.adapters["http://example.com/"], "cache") assert hasattr(session.adapters["https://example.com/"], "cache") - def test_add_trusted_host(self): + def test_add_trusted_host(self) -> None: # Leave a gap to test how the ordering is affected. trusted_hosts = ["host1", "host3"] session = PipSession(trusted_hosts=trusted_hosts) @@ -141,7 +146,7 @@ def test_add_trusted_host(self): assert session.adapters[prefix4] is trusted_host_adapter assert session.adapters[prefix4_http] is trusted_host_adapter - def test_add_trusted_host__logging(self, caplog): + def test_add_trusted_host__logging(self, caplog: pytest.LogCaptureFixture) -> None: """ Test logging when add_trusted_host() is called. """ @@ -163,7 +168,7 @@ def test_add_trusted_host__logging(self, caplog): ] assert actual == expected - def test_iter_secure_origins(self): + def test_iter_secure_origins(self) -> None: trusted_hosts = ["host1", "host2", "host3:8080"] session = PipSession(trusted_hosts=trusted_hosts) @@ -177,7 +182,7 @@ def test_iter_secure_origins(self): ("*", "host3", 8080), ] - def test_iter_secure_origins__trusted_hosts_empty(self): + def test_iter_secure_origins__trusted_hosts_empty(self) -> None: """ Test iter_secure_origins() after passing trusted_hosts=[]. """ @@ -210,16 +215,22 @@ def test_iter_secure_origins__trusted_hosts_empty(self): ("http://example.com:8888/something/", ["example.com:8080"], False), ], ) - def test_is_secure_origin(self, caplog, location, trusted, expected): + def test_is_secure_origin( + self, + caplog: pytest.LogCaptureFixture, + location: str, + trusted: List[str], + expected: bool, + ) -> None: class MockLogger: - def __init__(self): + def __init__(self) -> None: self.called = False - def warning(self, *args, **kwargs): + def warning(self, *args: Any, **kwargs: Any) -> None: self.called = True session = PipSession(trusted_hosts=trusted) - actual = session.is_secure_origin(location) + actual = session.is_secure_origin(Link(location)) assert actual == expected log_records = [(r.levelname, r.message) for r in caplog.records] diff --git a/tests/unit/test_network_utils.py b/tests/unit/test_network_utils.py index 96a17808e3c..cdc10b2ba6e 100644 --- a/tests/unit/test_network_utils.py +++ b/tests/unit/test_network_utils.py @@ -12,7 +12,7 @@ (501, "Server Error"), ], ) -def test_raise_for_status_raises_exception(status_code, error_type): +def test_raise_for_status_raises_exception(status_code: int, error_type: str) -> None: contents = b"downloaded" resp = MockResponse(contents) resp.status_code = status_code @@ -26,11 +26,10 @@ def test_raise_for_status_raises_exception(status_code, error_type): ) -def test_raise_for_status_does_not_raises_exception(): +def test_raise_for_status_does_not_raises_exception() -> None: contents = b"downloaded" resp = MockResponse(contents) resp.status_code = 201 resp.url = "http://www.example.com/whatever.tgz" resp.reason = "No error" - return_value = raise_for_status(resp) - assert return_value is None + raise_for_status(resp) diff --git a/tests/unit/test_operations_prepare.py b/tests/unit/test_operations_prepare.py index 52432139293..961b96ca72a 100644 --- a/tests/unit/test_operations_prepare.py +++ b/tests/unit/test_operations_prepare.py @@ -2,6 +2,7 @@ import shutil from shutil import rmtree from tempfile import mkdtemp +from typing import Any, Dict from unittest.mock import Mock, patch import pytest @@ -13,18 +14,19 @@ from pip._internal.operations.prepare import _copy_source_tree, unpack_url from pip._internal.utils.hashes import Hashes from pip._internal.utils.urls import path_to_url +from tests.lib import TestData from tests.lib.filesystem import get_filelist, make_socket_file, make_unreadable_file from tests.lib.path import Path from tests.lib.requests_mocks import MockResponse -def test_unpack_url_with_urllib_response_without_content_type(data): +def test_unpack_url_with_urllib_response_without_content_type(data: TestData) -> None: """ It should download and unpack files even if no Content-Type header exists """ _real_session = PipSession() - def _fake_session_get(*args, **kwargs): + def _fake_session_get(*args: Any, **kwargs: Any) -> Dict[str, str]: resp = _real_session.get(*args, **kwargs) del resp.headers["Content-Type"] return resp @@ -55,7 +57,9 @@ def _fake_session_get(*args, **kwargs): @patch("pip._internal.network.download.raise_for_status") -def test_download_http_url__no_directory_traversal(mock_raise_for_status, tmpdir): +def test_download_http_url__no_directory_traversal( + mock_raise_for_status: Mock, tmpdir: Path +) -> None: """ Test that directory traversal doesn't happen on download when the Content-Disposition header contains a filename with a ".." path part. @@ -86,7 +90,7 @@ def test_download_http_url__no_directory_traversal(mock_raise_for_status, tmpdir @pytest.fixture -def clean_project(tmpdir_factory, data): +def clean_project(tmpdir_factory: pytest.TempdirFactory, data: TestData) -> Path: tmpdir = Path(str(tmpdir_factory.mktemp("clean_project"))) new_project_dir = tmpdir.joinpath("FSPkg") path = data.packages.joinpath("FSPkg") @@ -94,7 +98,7 @@ def clean_project(tmpdir_factory, data): return new_project_dir -def test_copy_source_tree(clean_project, tmpdir): +def test_copy_source_tree(clean_project: Path, tmpdir: Path) -> None: target = tmpdir.joinpath("target") expected_files = get_filelist(clean_project) assert len(expected_files) == 3 @@ -106,7 +110,9 @@ def test_copy_source_tree(clean_project, tmpdir): @pytest.mark.skipif("sys.platform == 'win32'") -def test_copy_source_tree_with_socket(clean_project, tmpdir, caplog): +def test_copy_source_tree_with_socket( + clean_project: Path, tmpdir: Path, caplog: pytest.LogCaptureFixture +) -> None: target = tmpdir.joinpath("target") expected_files = get_filelist(clean_project) socket_path = str(clean_project.joinpath("aaa")) @@ -125,7 +131,9 @@ def test_copy_source_tree_with_socket(clean_project, tmpdir, caplog): @pytest.mark.skipif("sys.platform == 'win32'") -def test_copy_source_tree_with_socket_fails_with_no_socket_error(clean_project, tmpdir): +def test_copy_source_tree_with_socket_fails_with_no_socket_error( + clean_project: Path, tmpdir: Path +) -> None: target = tmpdir.joinpath("target") expected_files = get_filelist(clean_project) make_socket_file(clean_project.joinpath("aaa")) @@ -144,7 +152,9 @@ def test_copy_source_tree_with_socket_fails_with_no_socket_error(clean_project, assert expected_files == copied_files -def test_copy_source_tree_with_unreadable_dir_fails(clean_project, tmpdir): +def test_copy_source_tree_with_unreadable_dir_fails( + clean_project: Path, tmpdir: Path +) -> None: target = tmpdir.joinpath("target") expected_files = get_filelist(clean_project) unreadable_file = clean_project.joinpath("bbb") @@ -163,7 +173,7 @@ def test_copy_source_tree_with_unreadable_dir_fails(clean_project, tmpdir): class Test_unpack_url: - def prep(self, tmpdir, data): + def prep(self, tmpdir: Path, data: TestData) -> None: self.build_dir = tmpdir.joinpath("build") self.download_dir = tmpdir.joinpath("download") os.mkdir(self.build_dir) @@ -176,13 +186,13 @@ def prep(self, tmpdir, data): self.dist_url2 = Link(path_to_url(self.dist_path2)) self.no_download = Mock(side_effect=AssertionError) - def test_unpack_url_no_download(self, tmpdir, data): + def test_unpack_url_no_download(self, tmpdir: Path, data: TestData) -> None: self.prep(tmpdir, data) unpack_url(self.dist_url, self.build_dir, self.no_download) assert os.path.isdir(os.path.join(self.build_dir, "simple")) assert not os.path.isfile(os.path.join(self.download_dir, self.dist_file)) - def test_unpack_url_bad_hash(self, tmpdir, data): + def test_unpack_url_bad_hash(self, tmpdir: Path, data: TestData) -> None: """ Test when the file url hash fragment is wrong """ @@ -197,7 +207,7 @@ def test_unpack_url_bad_hash(self, tmpdir, data): hashes=Hashes({"md5": ["bogus"]}), ) - def test_unpack_url_thats_a_dir(self, tmpdir, data): + def test_unpack_url_thats_a_dir(self, tmpdir: Path, data: TestData) -> None: self.prep(tmpdir, data) dist_path = data.packages.joinpath("FSPkg") dist_url = Link(path_to_url(dist_path)) @@ -211,7 +221,7 @@ def test_unpack_url_thats_a_dir(self, tmpdir, data): @pytest.mark.parametrize("exclude_dir", [".nox", ".tox"]) -def test_unpack_url_excludes_expected_dirs(tmpdir, exclude_dir): +def test_unpack_url_excludes_expected_dirs(tmpdir: Path, exclude_dir: str) -> None: src_dir = tmpdir / "src" dst_dir = tmpdir / "dst" src_included_file = src_dir.joinpath("file.txt") diff --git a/tests/unit/test_options.py b/tests/unit/test_options.py index 18523521746..ddcc8532cfd 100644 --- a/tests/unit/test_options.py +++ b/tests/unit/test_options.py @@ -1,18 +1,24 @@ import os from contextlib import contextmanager +from optparse import Values from tempfile import NamedTemporaryFile +from typing import Any, Dict, Iterator, List, Tuple, Union, cast import pytest import pip._internal.configuration from pip._internal.cli.main import main from pip._internal.commands import create_command +from pip._internal.commands.configuration import ConfigurationCommand from pip._internal.exceptions import PipError from tests.lib.options_helpers import AddFakeCommandMixin +from tests.lib.path import Path @contextmanager -def assert_option_error(capsys, expected): +def assert_option_error( + capsys: pytest.CaptureFixture[str], expected: str +) -> Iterator[None]: """ Assert that a SystemExit occurred because of a parsing error. @@ -27,7 +33,7 @@ def assert_option_error(capsys, expected): assert expected in stderr -def assert_is_default_cache_dir(value): +def assert_is_default_cache_dir(value: Path) -> None: # This path looks different on different platforms, but the path always # has the substring "pip". assert "pip" in value @@ -40,63 +46,76 @@ class TestOptionPrecedence(AddFakeCommandMixin): defaults """ - def get_config_section(self, section): + def get_config_section(self, section: str) -> List[Tuple[str, str]]: config = { "global": [("timeout", "-3")], "fake": [("timeout", "-2")], } return config[section] - def get_config_section_global(self, section): - config = { + def get_config_section_global(self, section: str) -> List[Tuple[str, str]]: + config: Dict[str, List[Tuple[str, str]]] = { "global": [("timeout", "-3")], "fake": [], } return config[section] - def test_env_override_default_int(self, monkeypatch): + def test_env_override_default_int(self, monkeypatch: pytest.MonkeyPatch) -> None: """ Test that environment variable overrides an int option default. """ monkeypatch.setenv("PIP_TIMEOUT", "-1") - options, args = main(["fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) assert options.timeout == -1 @pytest.mark.parametrize("values", (["F1"], ["F1", "F2"])) - def test_env_override_default_append(self, values, monkeypatch): + def test_env_override_default_append( + self, values: List[str], monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test that environment variable overrides an append option default. """ monkeypatch.setenv("PIP_FIND_LINKS", " ".join(values)) - options, args = main(["fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) assert options.find_links == values @pytest.mark.parametrize("choices", (["w"], ["s", "w"])) - def test_env_override_default_choice(self, choices, monkeypatch): + def test_env_override_default_choice( + self, choices: List[str], monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test that environment variable overrides a choice option default. """ monkeypatch.setenv("PIP_EXISTS_ACTION", " ".join(choices)) - options, args = main(["fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) assert options.exists_action == choices @pytest.mark.parametrize("name", ("PIP_LOG_FILE", "PIP_LOCAL_LOG")) - def test_env_alias_override_default(self, name, monkeypatch): + def test_env_alias_override_default( + self, name: str, monkeypatch: pytest.MonkeyPatch + ) -> None: """ When an option has multiple long forms, test that the technique of using the env variable, "PIP_" works for all cases. (e.g. PIP_LOG_FILE and PIP_LOCAL_LOG should all work) """ monkeypatch.setenv(name, "override.log") - options, args = main(["fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) assert options.log == "override.log" - def test_cli_override_environment(self, monkeypatch): + def test_cli_override_environment(self, monkeypatch: pytest.MonkeyPatch) -> None: """ Test the cli overrides and environment variable """ monkeypatch.setenv("PIP_TIMEOUT", "-1") - options, args = main(["fake", "--timeout", "-2"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["fake", "--timeout", "-2"]) + ) assert options.timeout == -2 @pytest.mark.parametrize( @@ -115,50 +134,57 @@ def test_cli_override_environment(self, monkeypatch): "no", ], ) - def test_cache_dir__PIP_NO_CACHE_DIR(self, pip_no_cache_dir, monkeypatch): + def test_cache_dir__PIP_NO_CACHE_DIR( + self, pip_no_cache_dir: str, monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test setting the PIP_NO_CACHE_DIR environment variable without passing any command-line flags. """ monkeypatch.setenv("PIP_NO_CACHE_DIR", pip_no_cache_dir) - options, args = main(["fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) assert options.cache_dir is False @pytest.mark.parametrize("pip_no_cache_dir", ["yes", "no"]) def test_cache_dir__PIP_NO_CACHE_DIR__with_cache_dir( self, - pip_no_cache_dir, - monkeypatch, - ): + pip_no_cache_dir: str, + monkeypatch: pytest.MonkeyPatch, + ) -> None: """ Test setting PIP_NO_CACHE_DIR while also passing an explicit --cache-dir value. """ monkeypatch.setenv("PIP_NO_CACHE_DIR", pip_no_cache_dir) - options, args = main(["--cache-dir", "/cache/dir", "fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--cache-dir", "/cache/dir", "fake"]) + ) # The command-line flag takes precedence. assert options.cache_dir == "/cache/dir" @pytest.mark.parametrize("pip_no_cache_dir", ["yes", "no"]) def test_cache_dir__PIP_NO_CACHE_DIR__with_no_cache_dir( self, - pip_no_cache_dir, - monkeypatch, - ): + pip_no_cache_dir: str, + monkeypatch: pytest.MonkeyPatch, + ) -> None: """ Test setting PIP_NO_CACHE_DIR while also passing --no-cache-dir. """ monkeypatch.setenv("PIP_NO_CACHE_DIR", pip_no_cache_dir) - options, args = main(["--no-cache-dir", "fake"]) + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["--no-cache-dir", "fake"])) # The command-line flag should take precedence (which has the same # value in this case). assert options.cache_dir is False def test_cache_dir__PIP_NO_CACHE_DIR_invalid__with_no_cache_dir( self, - monkeypatch, - capsys, - ): + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], + ) -> None: """ Test setting PIP_NO_CACHE_DIR to an invalid value while also passing --no-cache-dir. @@ -175,7 +201,7 @@ class TestUsePEP517Options: Test options related to using --use-pep517. """ - def parse_args(self, args): + def parse_args(self, args: List[str]) -> Values: # We use DownloadCommand since that is one of the few Command # classes with the use_pep517 options. command = create_command("download") @@ -183,28 +209,28 @@ def parse_args(self, args): return options - def test_no_option(self): + def test_no_option(self) -> None: """ Test passing no option. """ options = self.parse_args([]) assert options.use_pep517 is None - def test_use_pep517(self): + def test_use_pep517(self) -> None: """ Test passing --use-pep517. """ options = self.parse_args(["--use-pep517"]) assert options.use_pep517 is True - def test_no_use_pep517(self): + def test_no_use_pep517(self) -> None: """ Test passing --no-use-pep517. """ options = self.parse_args(["--no-use-pep517"]) assert options.use_pep517 is False - def test_PIP_USE_PEP517_true(self, monkeypatch): + def test_PIP_USE_PEP517_true(self, monkeypatch: pytest.MonkeyPatch) -> None: """ Test setting PIP_USE_PEP517 to "true". """ @@ -214,7 +240,7 @@ def test_PIP_USE_PEP517_true(self, monkeypatch): # configuration code returns an int. assert options.use_pep517 == 1 - def test_PIP_USE_PEP517_false(self, monkeypatch): + def test_PIP_USE_PEP517_false(self, monkeypatch: pytest.MonkeyPatch) -> None: """ Test setting PIP_USE_PEP517 to "false". """ @@ -224,7 +250,9 @@ def test_PIP_USE_PEP517_false(self, monkeypatch): # configuration code returns an int. assert options.use_pep517 == 0 - def test_use_pep517_and_PIP_USE_PEP517_false(self, monkeypatch): + def test_use_pep517_and_PIP_USE_PEP517_false( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test passing --use-pep517 and setting PIP_USE_PEP517 to "false". """ @@ -232,7 +260,9 @@ def test_use_pep517_and_PIP_USE_PEP517_false(self, monkeypatch): options = self.parse_args(["--use-pep517"]) assert options.use_pep517 is True - def test_no_use_pep517_and_PIP_USE_PEP517_true(self, monkeypatch): + def test_no_use_pep517_and_PIP_USE_PEP517_true( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test passing --no-use-pep517 and setting PIP_USE_PEP517 to "true". """ @@ -240,7 +270,9 @@ def test_no_use_pep517_and_PIP_USE_PEP517_true(self, monkeypatch): options = self.parse_args(["--no-use-pep517"]) assert options.use_pep517 is False - def test_PIP_NO_USE_PEP517(self, monkeypatch, capsys): + def test_PIP_NO_USE_PEP517( + self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] + ) -> None: """ Test setting PIP_NO_USE_PEP517, which isn't allowed. """ @@ -250,25 +282,32 @@ def test_PIP_NO_USE_PEP517(self, monkeypatch, capsys): class TestOptionsInterspersed(AddFakeCommandMixin): - def test_general_option_after_subcommand(self): - options, args = main(["fake", "--timeout", "-1"]) + def test_general_option_after_subcommand(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["fake", "--timeout", "-1"]) + ) assert options.timeout == -1 - def test_option_after_subcommand_arg(self): - options, args = main(["fake", "arg", "--timeout", "-1"]) + def test_option_after_subcommand_arg(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["fake", "arg", "--timeout", "-1"]) + ) assert options.timeout == -1 - def test_additive_before_after_subcommand(self): - options, args = main(["-v", "fake", "-v"]) + def test_additive_before_after_subcommand(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["-v", "fake", "-v"])) assert options.verbose == 2 - def test_subcommand_option_before_subcommand_fails(self): + def test_subcommand_option_before_subcommand_fails(self) -> None: with pytest.raises(SystemExit): main(["--find-links", "F1", "fake"]) @contextmanager -def tmpconfig(option, value, section="global"): +def tmpconfig(option: str, value: Any, section: str = "global") -> Iterator[str]: with NamedTemporaryFile(mode="w", delete=False) as f: f.write(f"[{section}]\n{option}={value}\n") name = f.name @@ -281,35 +320,51 @@ def tmpconfig(option, value, section="global"): class TestCountOptions(AddFakeCommandMixin): @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", range(4)) - def test_cli_long(self, option, value): + def test_cli_long(self, option: str, value: int) -> None: flags = [f"--{option}"] * value - opt1, args1 = main(flags + ["fake"]) - opt2, args2 = main(["fake"] + flags) + # FakeCommand intentionally returns the wrong type. + opt1, args1 = cast(Tuple[Values, List[str]], main(flags + ["fake"])) + opt2, args2 = cast(Tuple[Values, List[str]], main(["fake"] + flags)) assert getattr(opt1, option) == getattr(opt2, option) == value @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", range(1, 4)) - def test_cli_short(self, option, value): + def test_cli_short(self, option: str, value: int) -> None: flag = "-" + option[0] * value - opt1, args1 = main([flag, "fake"]) - opt2, args2 = main(["fake", flag]) + # FakeCommand intentionally returns the wrong type. + opt1, args1 = cast(Tuple[Values, List[str]], main([flag, "fake"])) + opt2, args2 = cast(Tuple[Values, List[str]], main(["fake", flag])) assert getattr(opt1, option) == getattr(opt2, option) == value @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", range(4)) - def test_env_var(self, option, value, monkeypatch): + def test_env_var( + self, option: str, value: int, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) - assert getattr(main(["fake"])[0], option) == value + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) + assert getattr(options, option) == value @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", range(3)) - def test_env_var_integrate_cli(self, option, value, monkeypatch): + def test_env_var_integrate_cli( + self, option: str, value: int, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) - assert getattr(main(["fake", "--" + option])[0], option) == value + 1 + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake", "--" + option])) + assert getattr(options, option) == value + 1 @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", (-1, "foobar")) - def test_env_var_invalid(self, option, value, monkeypatch, capsys): + def test_env_var_invalid( + self, + option: str, + value: Any, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], + ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) with assert_option_error(capsys, expected="a non-negative integer"): main(["fake"]) @@ -317,34 +372,58 @@ def test_env_var_invalid(self, option, value, monkeypatch, capsys): # Undocumented, support for backward compatibility @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", ("no", "false")) - def test_env_var_false(self, option, value, monkeypatch): + def test_env_var_false( + self, option: str, value: str, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) - assert getattr(main(["fake"])[0], option) == 0 + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) + assert getattr(options, option) == 0 # Undocumented, support for backward compatibility @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", ("yes", "true")) - def test_env_var_true(self, option, value, monkeypatch): + def test_env_var_true( + self, option: str, value: str, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) - assert getattr(main(["fake"])[0], option) == 1 + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) + assert getattr(options, option) == 1 @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", range(4)) - def test_config_file(self, option, value, monkeypatch): + def test_config_file( + self, option: str, value: int, monkeypatch: pytest.MonkeyPatch + ) -> None: with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) - assert getattr(main(["fake"])[0], option) == value + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) + assert getattr(options, option) == value @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", range(3)) - def test_config_file_integrate_cli(self, option, value, monkeypatch): + def test_config_file_integrate_cli( + self, option: str, value: int, monkeypatch: pytest.MonkeyPatch + ) -> None: with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) - assert getattr(main(["fake", "--" + option])[0], option) == value + 1 + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["fake", "--" + option]) + ) + assert getattr(options, option) == value + 1 @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", (-1, "foobar")) - def test_config_file_invalid(self, option, value, monkeypatch, capsys): + def test_config_file_invalid( + self, + option: str, + value: Any, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], + ) -> None: with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) with assert_option_error(capsys, expected="non-negative integer"): @@ -353,18 +432,26 @@ def test_config_file_invalid(self, option, value, monkeypatch, capsys): # Undocumented, support for backward compatibility @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", ("no", "false")) - def test_config_file_false(self, option, value, monkeypatch): + def test_config_file_false( + self, option: str, value: str, monkeypatch: pytest.MonkeyPatch + ) -> None: with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) - assert getattr(main(["fake"])[0], option) == 0 + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) + assert getattr(options, option) == 0 # Undocumented, support for backward compatibility @pytest.mark.parametrize("option", ("verbose", "quiet")) @pytest.mark.parametrize("value", ("yes", "true")) - def test_config_file_true(self, option, value, monkeypatch): + def test_config_file_true( + self, option: str, value: str, monkeypatch: pytest.MonkeyPatch + ) -> None: with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) - assert getattr(main(["fake"])[0], option) == 1 + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) + assert getattr(options, option) == 1 class TestGeneralOptions(AddFakeCommandMixin): @@ -372,74 +459,125 @@ class TestGeneralOptions(AddFakeCommandMixin): # the reason to specifically test general options is due to the # extra processing they receive, and the number of bugs we've had - def test_cache_dir__default(self): - options, args = main(["fake"]) + def test_cache_dir__default(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["fake"])) # With no options the default cache dir should be used. assert_is_default_cache_dir(options.cache_dir) - def test_cache_dir__provided(self): - options, args = main(["--cache-dir", "/cache/dir", "fake"]) + def test_cache_dir__provided(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--cache-dir", "/cache/dir", "fake"]) + ) assert options.cache_dir == "/cache/dir" - def test_no_cache_dir__provided(self): - options, args = main(["--no-cache-dir", "fake"]) + def test_no_cache_dir__provided(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast(Tuple[Values, List[str]], main(["--no-cache-dir", "fake"])) assert options.cache_dir is False - def test_require_virtualenv(self): - options1, args1 = main(["--require-virtualenv", "fake"]) - options2, args2 = main(["fake", "--require-virtualenv"]) + def test_require_virtualenv(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--require-virtualenv", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--require-virtualenv"]) + ) assert options1.require_venv assert options2.require_venv - def test_log(self): - options1, args1 = main(["--log", "path", "fake"]) - options2, args2 = main(["fake", "--log", "path"]) + def test_log(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--log", "path", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--log", "path"]) + ) assert options1.log == options2.log == "path" - def test_local_log(self): - options1, args1 = main(["--local-log", "path", "fake"]) - options2, args2 = main(["fake", "--local-log", "path"]) + def test_local_log(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--local-log", "path", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--local-log", "path"]) + ) assert options1.log == options2.log == "path" - def test_no_input(self): - options1, args1 = main(["--no-input", "fake"]) - options2, args2 = main(["fake", "--no-input"]) + def test_no_input(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast(Tuple[Values, List[str]], main(["--no-input", "fake"])) + options2, args2 = cast(Tuple[Values, List[str]], main(["fake", "--no-input"])) assert options1.no_input assert options2.no_input - def test_proxy(self): - options1, args1 = main(["--proxy", "path", "fake"]) - options2, args2 = main(["fake", "--proxy", "path"]) + def test_proxy(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--proxy", "path", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--proxy", "path"]) + ) assert options1.proxy == options2.proxy == "path" - def test_retries(self): - options1, args1 = main(["--retries", "-1", "fake"]) - options2, args2 = main(["fake", "--retries", "-1"]) + def test_retries(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--retries", "-1", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--retries", "-1"]) + ) assert options1.retries == options2.retries == -1 - def test_timeout(self): - options1, args1 = main(["--timeout", "-1", "fake"]) - options2, args2 = main(["fake", "--timeout", "-1"]) + def test_timeout(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--timeout", "-1", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--timeout", "-1"]) + ) assert options1.timeout == options2.timeout == -1 - def test_exists_action(self): - options1, args1 = main(["--exists-action", "w", "fake"]) - options2, args2 = main(["fake", "--exists-action", "w"]) + def test_exists_action(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--exists-action", "w", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--exists-action", "w"]) + ) assert options1.exists_action == options2.exists_action == ["w"] - def test_cert(self): - options1, args1 = main(["--cert", "path", "fake"]) - options2, args2 = main(["fake", "--cert", "path"]) + def test_cert(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--cert", "path", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--cert", "path"]) + ) assert options1.cert == options2.cert == "path" - def test_client_cert(self): - options1, args1 = main(["--client-cert", "path", "fake"]) - options2, args2 = main(["fake", "--client-cert", "path"]) + def test_client_cert(self) -> None: + # FakeCommand intentionally returns the wrong type. + options1, args1 = cast( + Tuple[Values, List[str]], main(["--client-cert", "path", "fake"]) + ) + options2, args2 = cast( + Tuple[Values, List[str]], main(["fake", "--client-cert", "path"]) + ) assert options1.client_cert == options2.client_cert == "path" class TestOptionsConfigFiles: - def test_venv_config_file_found(self, monkeypatch): + def test_venv_config_file_found(self, monkeypatch: pytest.MonkeyPatch) -> None: # strict limit on the global config files list monkeypatch.setattr( pip._internal.utils.appdirs, "site_config_dirs", lambda _: ["/a/place"] @@ -465,8 +603,13 @@ def test_venv_config_file_found(self, monkeypatch): (["--global", "--site", "--user"], PipError), ), ) - def test_config_file_options(self, monkeypatch, args, expect): - cmd = create_command("config") + def test_config_file_options( + self, + monkeypatch: pytest.MonkeyPatch, + args: List[str], + expect: Union[None, str, PipError], + ) -> None: + cmd = cast(ConfigurationCommand, create_command("config")) # Replace a handler with a no-op to avoid side effects monkeypatch.setattr(cmd, "get_name", lambda *a: None) @@ -479,22 +622,37 @@ def test_config_file_options(self, monkeypatch, args, expect): class TestOptionsExpandUser(AddFakeCommandMixin): - def test_cache_dir(self): - options, args = main(["--cache-dir", "~/cache/dir", "fake"]) + def test_cache_dir(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--cache-dir", "~/cache/dir", "fake"]) + ) assert options.cache_dir == os.path.expanduser("~/cache/dir") - def test_log(self): - options, args = main(["--log", "~/path", "fake"]) + def test_log(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--log", "~/path", "fake"]) + ) assert options.log == os.path.expanduser("~/path") - def test_local_log(self): - options, args = main(["--local-log", "~/path", "fake"]) + def test_local_log(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--local-log", "~/path", "fake"]) + ) assert options.log == os.path.expanduser("~/path") - def test_cert(self): - options, args = main(["--cert", "~/path", "fake"]) + def test_cert(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--cert", "~/path", "fake"]) + ) assert options.cert == os.path.expanduser("~/path") - def test_client_cert(self): - options, args = main(["--client-cert", "~/path", "fake"]) + def test_client_cert(self) -> None: + # FakeCommand intentionally returns the wrong type. + options, args = cast( + Tuple[Values, List[str]], main(["--client-cert", "~/path", "fake"]) + ) assert options.client_cert == os.path.expanduser("~/path") diff --git a/tests/unit/test_packaging.py b/tests/unit/test_packaging.py index 2386083dbd1..8750ca85338 100644 --- a/tests/unit/test_packaging.py +++ b/tests/unit/test_packaging.py @@ -1,3 +1,5 @@ +from typing import Optional, Tuple + import pytest from pip._vendor.packaging import specifiers @@ -12,12 +14,14 @@ ((3, 6, 5), None, True), ], ) -def test_check_requires_python(version_info, requires_python, expected): +def test_check_requires_python( + version_info: Tuple[int, int, int], requires_python: Optional[str], expected: bool +) -> None: actual = check_requires_python(requires_python, version_info) assert actual == expected -def test_check_requires_python__invalid(): +def test_check_requires_python__invalid() -> None: """ Test an invalid Requires-Python value. """ diff --git a/tests/unit/test_pep517.py b/tests/unit/test_pep517.py index 2242cf92d71..b18299d7039 100644 --- a/tests/unit/test_pep517.py +++ b/tests/unit/test_pep517.py @@ -4,6 +4,8 @@ from pip._internal.exceptions import InstallationError from pip._internal.req import InstallRequirement +from tests.lib import TestData +from tests.lib.path import Path @pytest.mark.parametrize( @@ -14,7 +16,7 @@ ("pep517_pyproject_only", True), ], ) -def test_use_pep517(shared_data, source, expected): +def test_use_pep517(shared_data: TestData, source: str, expected: bool) -> None: """ Test that we choose correctly between PEP 517 and legacy code paths """ @@ -32,7 +34,7 @@ def test_use_pep517(shared_data, source, expected): ("pep517_pyproject_only", "does not have a setup.py"), ], ) -def test_disabling_pep517_invalid(shared_data, source, msg): +def test_disabling_pep517_invalid(shared_data: TestData, source: str, msg: str) -> None: """ Test that we fail if we try to disable PEP 517 when it's not acceptable """ @@ -54,7 +56,7 @@ def test_disabling_pep517_invalid(shared_data, source, msg): @pytest.mark.parametrize( ("spec",), [("./foo",), ("git+https://example.com/pkg@dev#egg=myproj",)] ) -def test_pep517_parsing_checks_requirements(tmpdir, spec): +def test_pep517_parsing_checks_requirements(tmpdir: Path, spec: str) -> None: tmpdir.joinpath("pyproject.toml").write_text( dedent( """ diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index f6f552a8fe7..c4b97cf1c30 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -1,10 +1,12 @@ import contextlib +import email.message import os import shutil import sys import tempfile from functools import partial -from unittest.mock import patch +from typing import Iterator, Tuple, cast +from unittest import mock import pytest from pip._vendor import pkg_resources @@ -12,12 +14,14 @@ from pip._vendor.packaging.requirements import Requirement from pip._internal.commands import create_command +from pip._internal.commands.install import InstallCommand from pip._internal.exceptions import ( HashErrors, InstallationError, InvalidWheelFilename, PreviousBuildDirError, ) +from pip._internal.index.package_finder import PackageFinder from pip._internal.network.session import PipSession from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req import InstallRequirement, RequirementSet @@ -38,10 +42,13 @@ from pip._internal.req.req_tracker import get_requirement_tracker from pip._internal.resolution.legacy.resolver import Resolver from pip._internal.utils.urls import path_to_url -from tests.lib import make_test_finder, requirements_file +from tests.lib import TestData, make_test_finder, requirements_file +from tests.lib.path import Path -def get_processed_req_from_line(line, fname="file", lineno=1): +def get_processed_req_from_line( + line: str, fname: str = "file", lineno: int = 1 +) -> InstallRequirement: line_parser = get_line_parser(None) args_str, opts = line_parser(line) parsed_line = ParsedLine( @@ -61,14 +68,16 @@ def get_processed_req_from_line(line, fname="file", lineno=1): class TestRequirementSet: """RequirementSet tests""" - def setup(self): + def setup(self) -> None: self.tempdir = tempfile.mkdtemp() - def teardown(self): + def teardown(self) -> None: shutil.rmtree(self.tempdir, ignore_errors=True) @contextlib.contextmanager - def _basic_resolver(self, finder, require_hashes=False): + def _basic_resolver( + self, finder: PackageFinder, require_hashes: bool = False + ) -> Iterator[Resolver]: make_install_req = partial( install_req_from_req_string, isolated=False, @@ -104,7 +113,7 @@ def _basic_resolver(self, finder, require_hashes=False): force_reinstall=False, ) - def test_no_reuse_existing_build_dir(self, data): + def test_no_reuse_existing_build_dir(self, data: TestData) -> None: """Test prepare_files raise exception with previous build dir""" build_dir = os.path.join(self.tempdir, "build", "simple") @@ -127,7 +136,7 @@ def test_no_reuse_existing_build_dir(self, data): ): resolver.resolve(reqset.all_requirements, True) - def test_environment_marker_extras(self, data): + def test_environment_marker_extras(self, data: TestData) -> None: """ Test that the environment marker extras are used with non-wheel installs. @@ -141,7 +150,7 @@ def test_environment_marker_extras(self, data): reqset = resolver.resolve(reqset.all_requirements, True) assert not reqset.has_requirement("simple") - def test_missing_hash_with_require_hashes(self, data): + def test_missing_hash_with_require_hashes(self, data: TestData) -> None: """Setting --require-hashes explicitly should raise errors if hashes are missing. """ @@ -162,19 +171,21 @@ def test_missing_hash_with_require_hashes(self, data): ): resolver.resolve(reqset.all_requirements, True) - def test_missing_hash_with_require_hashes_in_reqs_file(self, data, tmpdir): + def test_missing_hash_with_require_hashes_in_reqs_file( + self, data: TestData, tmpdir: Path + ) -> None: """--require-hashes in a requirements file should make its way to the RequirementSet. """ finder = make_test_finder(find_links=[data.find_links]) session = finder._link_collector.session - command = create_command("install") + command = cast(InstallCommand, create_command("install")) with requirements_file("--require-hashes", tmpdir) as reqs_file: options, args = command.parse_args(["-r", reqs_file]) command.get_requirements(args, options, finder, session) assert options.require_hashes - def test_unsupported_hashes(self, data): + def test_unsupported_hashes(self, data: TestData) -> None: """VCS and dir links should raise errors when --require-hashes is on. @@ -218,7 +229,7 @@ def test_unsupported_hashes(self, data): ): resolver.resolve(reqset.all_requirements, True) - def test_unpinned_hash_checking(self, data): + def test_unpinned_hash_checking(self, data: TestData) -> None: """Make sure prepare_files() raises an error when a requirement is not version-pinned in hash-checking mode. """ @@ -252,7 +263,7 @@ def test_unpinned_hash_checking(self, data): ): resolver.resolve(reqset.all_requirements, True) - def test_hash_mismatch(self, data): + def test_hash_mismatch(self, data: TestData) -> None: """A hash mismatch should raise an error.""" file_url = path_to_url((data.packages / "simple-1.0.tar.gz").resolve()) reqset = RequirementSet() @@ -276,7 +287,7 @@ def test_hash_mismatch(self, data): ): resolver.resolve(reqset.all_requirements, True) - def test_unhashed_deps_on_require_hashes(self, data): + def test_unhashed_deps_on_require_hashes(self, data: TestData) -> None: """Make sure unhashed, unpinned, or otherwise unrepeatable dependencies get complained about when --require-hashes is on.""" reqset = RequirementSet() @@ -301,7 +312,7 @@ def test_unhashed_deps_on_require_hashes(self, data): ): resolver.resolve(reqset.all_requirements, True) - def test_hashed_deps_on_require_hashes(self): + def test_hashed_deps_on_require_hashes(self) -> None: """Make sure hashed dependencies get installed when --require-hashes is on. @@ -330,20 +341,21 @@ def test_hashed_deps_on_require_hashes(self): class TestInstallRequirement: - def setup(self): + def setup(self) -> None: self.tempdir = tempfile.mkdtemp() - def teardown(self): + def teardown(self) -> None: shutil.rmtree(self.tempdir, ignore_errors=True) - def test_url_with_query(self): + def test_url_with_query(self) -> None: """InstallRequirement should strip the fragment, but not the query.""" url = "http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz" fragment = "#egg=bar" req = install_req_from_line(url + fragment) + assert req.link is not None assert req.link.url == url + fragment, req.link - def test_pep440_wheel_link_requirement(self): + def test_pep440_wheel_link_requirement(self) -> None: url = "https://whatever.com/test-0.4-py2.py3-bogus-any.whl" line = "test @ https://whatever.com/test-0.4-py2.py3-bogus-any.whl" req = install_req_from_line(line) @@ -352,7 +364,7 @@ def test_pep440_wheel_link_requirement(self): assert parts[0].strip() == "test" assert parts[1].strip() == url - def test_pep440_url_link_requirement(self): + def test_pep440_url_link_requirement(self) -> None: url = "git+http://foo.com@ref#egg=foo" line = "foo @ git+http://foo.com@ref#egg=foo" req = install_req_from_line(line) @@ -361,7 +373,7 @@ def test_pep440_url_link_requirement(self): assert parts[0].strip() == "foo" assert parts[1].strip() == url - def test_url_with_authentication_link_requirement(self): + def test_url_with_authentication_link_requirement(self) -> None: url = "https://what@whatever.com/test-0.4-py2.py3-bogus-any.whl" line = "https://what@whatever.com/test-0.4-py2.py3-bogus-any.whl" req = install_req_from_line(line) @@ -370,7 +382,7 @@ def test_url_with_authentication_link_requirement(self): assert req.link.scheme == "https" assert req.link.url == url - def test_unsupported_wheel_link_requirement_raises(self): + def test_unsupported_wheel_link_requirement_raises(self) -> None: reqset = RequirementSet() req = install_req_from_line( "https://whatever.com/peppercorn-0.4-py2.py3-bogus-any.whl", @@ -382,7 +394,9 @@ def test_unsupported_wheel_link_requirement_raises(self): with pytest.raises(InstallationError): reqset.add_requirement(req) - def test_unsupported_wheel_local_file_requirement_raises(self, data): + def test_unsupported_wheel_local_file_requirement_raises( + self, data: TestData + ) -> None: reqset = RequirementSet() req = install_req_from_line( data.packages.joinpath("simple.dist-0.1-py1-none-invalid.whl"), @@ -394,33 +408,35 @@ def test_unsupported_wheel_local_file_requirement_raises(self, data): with pytest.raises(InstallationError): reqset.add_requirement(req) - def test_str(self): + def test_str(self) -> None: req = install_req_from_line("simple==0.1") assert str(req) == "simple==0.1" - def test_repr(self): + def test_repr(self) -> None: req = install_req_from_line("simple==0.1") assert repr(req) == ("") - def test_invalid_wheel_requirement_raises(self): + def test_invalid_wheel_requirement_raises(self) -> None: with pytest.raises(InvalidWheelFilename): install_req_from_line("invalid.whl") - def test_wheel_requirement_sets_req_attribute(self): + def test_wheel_requirement_sets_req_attribute(self) -> None: req = install_req_from_line("simple-0.1-py2.py3-none-any.whl") assert isinstance(req.req, Requirement) assert str(req.req) == "simple==0.1" - def test_url_preserved_line_req(self): + def test_url_preserved_line_req(self) -> None: """Confirm the url is preserved in a non-editable requirement""" url = "git+http://foo.com@ref#egg=foo" req = install_req_from_line(url) + assert req.link is not None assert req.link.url == url - def test_url_preserved_editable_req(self): + def test_url_preserved_editable_req(self) -> None: """Confirm the url is preserved in a editable requirement""" url = "git+http://foo.com@ref#egg=foo" req = install_req_from_editable(url) + assert req.link is not None assert req.link.url == url @pytest.mark.parametrize( @@ -431,7 +447,7 @@ def test_url_preserved_editable_req(self): "/path/to/foo.egg-info/".replace("/", os.path.sep), ), ) - def test_get_dist(self, path): + def test_get_dist(self, path: str) -> None: req = install_req_from_line("foo") req.metadata_directory = path dist = req.get_dist() @@ -439,7 +455,7 @@ def test_get_dist(self, path): assert dist.project_name == "foo" assert dist.location == "/path/to".replace("/", os.path.sep) - def test_markers(self): + def test_markers(self) -> None: for line in ( # recommended syntax 'mock3; python_version >= "3"', @@ -449,33 +465,37 @@ def test_markers(self): 'mock3;python_version >= "3"', ): req = install_req_from_line(line) + assert req.req is not None assert req.req.name == "mock3" assert str(req.req.specifier) == "" assert str(req.markers) == 'python_version >= "3"' - def test_markers_semicolon(self): + def test_markers_semicolon(self) -> None: # check that the markers can contain a semicolon req = install_req_from_line('semicolon; os_name == "a; b"') + assert req.req is not None assert req.req.name == "semicolon" assert str(req.req.specifier) == "" assert str(req.markers) == 'os_name == "a; b"' - def test_markers_url(self): + def test_markers_url(self) -> None: # test "URL; markers" syntax url = "http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz" line = f'{url}; python_version >= "3"' req = install_req_from_line(line) - assert req.link.url == url, req.url + assert req.link is not None + assert req.link.url == url, req.link.url assert str(req.markers) == 'python_version >= "3"' # without space, markers are part of the URL url = "http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz" line = f'{url};python_version >= "3"' req = install_req_from_line(line) - assert req.link.url == line, req.url + assert req.link is not None + assert req.link.url == line, req.link.url assert req.markers is None - def test_markers_match_from_line(self): + def test_markers_match_from_line(self) -> None: # match for markers in ( 'python_version >= "1.0"', @@ -496,7 +516,7 @@ def test_markers_match_from_line(self): assert str(req.markers) == str(Marker(markers)) assert not req.match_markers() - def test_markers_match(self): + def test_markers_match(self) -> None: # match for markers in ( 'python_version >= "1.0"', @@ -517,7 +537,7 @@ def test_markers_match(self): assert str(req.markers) == str(Marker(markers)) assert not req.match_markers() - def test_extras_for_line_path_requirement(self): + def test_extras_for_line_path_requirement(self) -> None: line = "SomeProject[ex1,ex2]" filename = "filename" comes_from = f"-r {filename} (line 1)" @@ -525,7 +545,7 @@ def test_extras_for_line_path_requirement(self): assert len(req.extras) == 2 assert req.extras == {"ex1", "ex2"} - def test_extras_for_line_url_requirement(self): + def test_extras_for_line_url_requirement(self) -> None: line = "git+https://url#egg=SomeProject[ex1,ex2]" filename = "filename" comes_from = f"-r {filename} (line 1)" @@ -533,7 +553,7 @@ def test_extras_for_line_url_requirement(self): assert len(req.extras) == 2 assert req.extras == {"ex1", "ex2"} - def test_extras_for_editable_path_requirement(self): + def test_extras_for_editable_path_requirement(self) -> None: url = ".[ex1,ex2]" filename = "filename" comes_from = f"-r {filename} (line 1)" @@ -541,7 +561,7 @@ def test_extras_for_editable_path_requirement(self): assert len(req.extras) == 2 assert req.extras == {"ex1", "ex2"} - def test_extras_for_editable_url_requirement(self): + def test_extras_for_editable_url_requirement(self) -> None: url = "git+https://url#egg=SomeProject[ex1,ex2]" filename = "filename" comes_from = f"-r {filename} (line 1)" @@ -549,28 +569,28 @@ def test_extras_for_editable_url_requirement(self): assert len(req.extras) == 2 assert req.extras == {"ex1", "ex2"} - def test_unexisting_path(self): + def test_unexisting_path(self) -> None: with pytest.raises(InstallationError) as e: install_req_from_line(os.path.join("this", "path", "does", "not", "exist")) err_msg = e.value.args[0] assert "Invalid requirement" in err_msg assert "It looks like a path." in err_msg - def test_single_equal_sign(self): + def test_single_equal_sign(self) -> None: with pytest.raises(InstallationError) as e: install_req_from_line("toto=42") err_msg = e.value.args[0] assert "Invalid requirement" in err_msg assert "= is not a valid operator. Did you mean == ?" in err_msg - def test_unidentifiable_name(self): + def test_unidentifiable_name(self) -> None: test_name = "-" with pytest.raises(InstallationError) as e: install_req_from_line(test_name) err_msg = e.value.args[0] assert f"Invalid requirement: '{test_name}'" == err_msg - def test_requirement_file(self): + def test_requirement_file(self) -> None: req_file_path = os.path.join(self.tempdir, "test.txt") with open(req_file_path, "w") as req_file: req_file.write("pip\nsetuptools") @@ -583,10 +603,12 @@ def test_requirement_file(self): assert "If that is the case, use the '-r' flag to install" in err_msg -@patch("pip._internal.req.req_install.os.path.abspath") -@patch("pip._internal.req.req_install.os.path.exists") -@patch("pip._internal.req.req_install.os.path.isdir") -def test_parse_editable_local(isdir_mock, exists_mock, abspath_mock): +@mock.patch("pip._internal.req.req_install.os.path.abspath") +@mock.patch("pip._internal.req.req_install.os.path.exists") +@mock.patch("pip._internal.req.req_install.os.path.isdir") +def test_parse_editable_local( + isdir_mock: mock.Mock, exists_mock: mock.Mock, abspath_mock: mock.Mock +) -> None: exists_mock.return_value = isdir_mock.return_value = True # mocks needed to support path operations on windows tests abspath_mock.return_value = "/some/path" @@ -599,7 +621,7 @@ def test_parse_editable_local(isdir_mock, exists_mock, abspath_mock): ) -def test_parse_editable_explicit_vcs(): +def test_parse_editable_explicit_vcs() -> None: assert parse_editable("svn+https://foo#egg=foo") == ( "foo", "svn+https://foo#egg=foo", @@ -607,7 +629,7 @@ def test_parse_editable_explicit_vcs(): ) -def test_parse_editable_vcs_extras(): +def test_parse_editable_vcs_extras() -> None: assert parse_editable("svn+https://foo#egg=foo[extras]") == ( "foo[extras]", "svn+https://foo#egg=foo[extras]", @@ -615,10 +637,12 @@ def test_parse_editable_vcs_extras(): ) -@patch("pip._internal.req.req_install.os.path.abspath") -@patch("pip._internal.req.req_install.os.path.exists") -@patch("pip._internal.req.req_install.os.path.isdir") -def test_parse_editable_local_extras(isdir_mock, exists_mock, abspath_mock): +@mock.patch("pip._internal.req.req_install.os.path.abspath") +@mock.patch("pip._internal.req.req_install.os.path.exists") +@mock.patch("pip._internal.req.req_install.os.path.isdir") +def test_parse_editable_local_extras( + isdir_mock: mock.Mock, exists_mock: mock.Mock, abspath_mock: mock.Mock +) -> None: exists_mock.return_value = isdir_mock.return_value = True abspath_mock.return_value = "/some/path" assert parse_editable(".[extras]") == ( @@ -634,7 +658,7 @@ def test_parse_editable_local_extras(isdir_mock, exists_mock, abspath_mock): ) -def test_exclusive_environment_markers(): +def test_exclusive_environment_markers() -> None: """Make sure RequirementSet accepts several excluding env markers""" eq36 = install_req_from_line("Django>=1.6.10,<1.7 ; python_version == '3.6'") eq36.user_supplied = True @@ -647,14 +671,18 @@ def test_exclusive_environment_markers(): assert req_set.has_requirement("Django") -def test_mismatched_versions(caplog): +def test_mismatched_versions(caplog: pytest.LogCaptureFixture) -> None: req = InstallRequirement( req=Requirement("simplewheel==2.0"), comes_from=None, ) req.source_dir = "/tmp/somewhere" # make req believe it has been unpacked # Monkeypatch! - req._metadata = {"name": "simplewheel", "version": "1.0"} + metadata = email.message.Message() + metadata["name"] = "simplewheel" + metadata["version"] = "1.0" + req._metadata = metadata + req.assert_source_matches_version() assert caplog.records[-1].message == ( "Requested simplewheel==2.0, but installing version 1.0" @@ -678,7 +706,7 @@ def test_mismatched_versions(caplog): (("simple-0.1-py2.py3-none-any.whl"), False), ], ) -def test_looks_like_path(args, expected): +def test_looks_like_path(args: str, expected: bool) -> None: assert _looks_like_path(args) == expected @@ -695,7 +723,7 @@ def test_looks_like_path(args, expected): (("C:\\absolute\\path"), True), ], ) -def test_looks_like_path_win(args, expected): +def test_looks_like_path_win(args: str, expected: bool) -> None: assert _looks_like_path(args) == expected @@ -733,17 +761,25 @@ def test_looks_like_path_win(args, expected): (("/path/to/simple==0.1", "simple==0.1"), (False, False), None), ], ) -@patch("pip._internal.req.req_install.os.path.isdir") -@patch("pip._internal.req.req_install.os.path.isfile") -def test_get_url_from_path(isdir_mock, isfile_mock, args, mock_returns, expected): +@mock.patch("pip._internal.req.req_install.os.path.isdir") +@mock.patch("pip._internal.req.req_install.os.path.isfile") +def test_get_url_from_path( + isdir_mock: mock.Mock, + isfile_mock: mock.Mock, + args: Tuple[str, str], + mock_returns: Tuple[bool, bool], + expected: None, +) -> None: isdir_mock.return_value = mock_returns[0] isfile_mock.return_value = mock_returns[1] assert _get_url_from_path(*args) is expected -@patch("pip._internal.req.req_install.os.path.isdir") -@patch("pip._internal.req.req_install.os.path.isfile") -def test_get_url_from_path__archive_file(isdir_mock, isfile_mock): +@mock.patch("pip._internal.req.req_install.os.path.isdir") +@mock.patch("pip._internal.req.req_install.os.path.isfile") +def test_get_url_from_path__archive_file( + isdir_mock: mock.Mock, isfile_mock: mock.Mock +) -> None: isdir_mock.return_value = False isfile_mock.return_value = True name = "simple-0.1-py2.py3-none-any.whl" @@ -752,9 +788,11 @@ def test_get_url_from_path__archive_file(isdir_mock, isfile_mock): assert _get_url_from_path(path, name) == url -@patch("pip._internal.req.req_install.os.path.isdir") -@patch("pip._internal.req.req_install.os.path.isfile") -def test_get_url_from_path__installable_dir(isdir_mock, isfile_mock): +@mock.patch("pip._internal.req.req_install.os.path.isdir") +@mock.patch("pip._internal.req.req_install.os.path.isfile") +def test_get_url_from_path__installable_dir( + isdir_mock: mock.Mock, isfile_mock: mock.Mock +) -> None: isdir_mock.return_value = True isfile_mock.return_value = True name = "some/setuptools/project" @@ -763,8 +801,8 @@ def test_get_url_from_path__installable_dir(isdir_mock, isfile_mock): assert _get_url_from_path(path, name) == url -@patch("pip._internal.req.req_install.os.path.isdir") -def test_get_url_from_path__installable_error(isdir_mock): +@mock.patch("pip._internal.req.req_install.os.path.isdir") +def test_get_url_from_path__installable_error(isdir_mock: mock.Mock) -> None: isdir_mock.return_value = True name = "some/setuptools/project" path = os.path.join("/path/to/" + name) diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py index b48def4d827..491877fb973 100644 --- a/tests/unit/test_req_file.py +++ b/tests/unit/test_req_file.py @@ -1,14 +1,18 @@ import collections import logging import os +import pathlib import subprocess import textwrap +from optparse import Values +from typing import TYPE_CHECKING, Any, Iterator, List, Optional, Tuple from unittest import mock import pytest import pip._internal.req.req_file # this will be monkeypatched from pip._internal.exceptions import InstallationError, RequirementsFileParseError +from pip._internal.index.package_finder import PackageFinder from pip._internal.models.format_control import FormatControl from pip._internal.network.session import PipSession from pip._internal.req.constructors import ( @@ -23,21 +27,29 @@ parse_requirements, preprocess, ) -from tests.lib import make_test_finder, requirements_file +from pip._internal.req.req_install import InstallRequirement +from tests.lib import TestData, make_test_finder, requirements_file +from tests.lib.path import Path + +if TYPE_CHECKING: + from typing import Protocol +else: + # Protocol was introduced in Python 3.8. + Protocol = object @pytest.fixture -def session(): +def session() -> PipSession: return PipSession() @pytest.fixture -def finder(session): +def finder(session: PipSession) -> PackageFinder: return make_test_finder(session=session) @pytest.fixture -def options(session): +def options(session: PipSession) -> mock.Mock: return mock.Mock( isolated_mode=False, index_url="default_url", @@ -47,13 +59,13 @@ def options(session): def parse_reqfile( - filename, - session, - finder=None, - options=None, - constraint=False, - isolated=False, -): + filename: str, + session: PipSession, + finder: PackageFinder = None, + options: Values = None, + constraint: bool = False, + isolated: bool = False, +) -> Iterator[InstallRequirement]: # Wrap parse_requirements/install_req_from_parsed_requirement to # avoid having to write the same chunk of code in lots of tests. for parsed_req in parse_requirements( @@ -66,7 +78,7 @@ def parse_reqfile( yield install_req_from_parsed_requirement(parsed_req, isolated=isolated) -def test_read_file_url(tmp_path, session): +def test_read_file_url(tmp_path: pathlib.Path, session: PipSession) -> None: reqs = tmp_path.joinpath("requirements.txt") reqs.write_text("foo") result = list(parse_requirements(reqs.as_posix(), session)) @@ -85,7 +97,7 @@ def test_read_file_url(tmp_path, session): class TestPreprocess: """tests for `preprocess`""" - def test_comments_and_joins_case1(self): + def test_comments_and_joins_case1(self) -> None: content = textwrap.dedent( """\ req1 \\ @@ -96,7 +108,7 @@ def test_comments_and_joins_case1(self): result = preprocess(content) assert list(result) == [(1, "req1"), (3, "req2")] - def test_comments_and_joins_case2(self): + def test_comments_and_joins_case2(self) -> None: content = textwrap.dedent( """\ req1\\ @@ -106,7 +118,7 @@ def test_comments_and_joins_case2(self): result = preprocess(content) assert list(result) == [(1, "req1")] - def test_comments_and_joins_case3(self): + def test_comments_and_joins_case3(self) -> None: content = textwrap.dedent( """\ req1 \\ @@ -121,17 +133,17 @@ def test_comments_and_joins_case3(self): class TestIgnoreComments: """tests for `ignore_comment`""" - def test_ignore_line(self): + def test_ignore_line(self) -> None: lines = [(1, ""), (2, "req1"), (3, "req2")] result = ignore_comments(lines) assert list(result) == [(2, "req1"), (3, "req2")] - def test_ignore_comment(self): + def test_ignore_comment(self) -> None: lines = [(1, "req1"), (2, "# comment"), (3, "req2")] result = ignore_comments(lines) assert list(result) == [(1, "req1"), (3, "req2")] - def test_strip_comment(self): + def test_strip_comment(self) -> None: lines = [(1, "req1"), (2, "req # comment"), (3, "req2")] result = ignore_comments(lines) assert list(result) == [(1, "req1"), (2, "req"), (3, "req2")] @@ -140,7 +152,7 @@ def test_strip_comment(self): class TestJoinLines: """tests for `join_lines`""" - def test_join_lines(self): + def test_join_lines(self) -> None: lines = enumerate( [ "line 1", @@ -161,7 +173,7 @@ def test_join_lines(self): ] assert expect == list(join_lines(lines)) - def test_last_line_with_escape(self): + def test_last_line_with_escape(self) -> None: lines = enumerate( [ "line 1", @@ -176,20 +188,31 @@ def test_last_line_with_escape(self): assert expect == list(join_lines(lines)) +class LineProcessor(Protocol): + def __call__( + self, + line: str, + filename: str, + line_number: int, + finder: Optional[PackageFinder] = None, + options: Optional[Values] = None, + session: Optional[PipSession] = None, + constraint: bool = False, + ) -> List[InstallRequirement]: + ... + + @pytest.fixture -def line_processor( - monkeypatch, - tmpdir, -): +def line_processor(monkeypatch: pytest.MonkeyPatch, tmpdir: Path) -> LineProcessor: def process_line( - line, - filename, - line_number, - finder=None, - options=None, - session=None, - constraint=False, - ): + line: str, + filename: str, + line_number: int, + finder: Optional[PackageFinder] = None, + options: Optional[Values] = None, + session: Optional[PipSession] = None, + constraint: bool = False, + ) -> List[InstallRequirement]: if session is None: session = PipSession() @@ -215,28 +238,28 @@ def process_line( class TestProcessLine: """tests for `process_line`""" - def test_parser_error(self, line_processor): + def test_parser_error(self, line_processor: LineProcessor) -> None: with pytest.raises(RequirementsFileParseError): line_processor("--bogus", "file", 1) - def test_parser_offending_line(self, line_processor): + def test_parser_offending_line(self, line_processor: LineProcessor) -> None: line = "pkg==1.0.0 --hash=somehash" with pytest.raises(RequirementsFileParseError) as err: line_processor(line, "file", 1) assert line in str(err.value) - def test_parser_non_offending_line(self, line_processor): + def test_parser_non_offending_line(self, line_processor: LineProcessor) -> None: try: line_processor("pkg==1.0.0 --hash=sha256:somehash", "file", 1) except RequirementsFileParseError: pytest.fail("Reported offending line where it should not.") - def test_only_one_req_per_line(self, line_processor): + def test_only_one_req_per_line(self, line_processor: LineProcessor) -> None: # pkg_resources raises the ValueError with pytest.raises(InstallationError): line_processor("req1 req2", "file", 1) - def test_error_message(self, line_processor): + def test_error_message(self, line_processor: LineProcessor) -> None: """ Test the error message if a parsing error occurs (all of path, line number, and hint). @@ -253,21 +276,21 @@ def test_error_message(self, line_processor): ) assert str(exc.value) == expected - def test_yield_line_requirement(self, line_processor): + def test_yield_line_requirement(self, line_processor: LineProcessor) -> None: line = "SomeProject" filename = "filename" comes_from = f"-r {filename} (line 1)" req = install_req_from_line(line, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) - def test_yield_pep440_line_requirement(self, line_processor): + def test_yield_pep440_line_requirement(self, line_processor: LineProcessor) -> None: line = "SomeProject @ https://url/SomeProject-py2-py3-none-any.whl" filename = "filename" comes_from = f"-r {filename} (line 1)" req = install_req_from_line(line, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) - def test_yield_line_constraint(self, line_processor): + def test_yield_line_constraint(self, line_processor: LineProcessor) -> None: line = "SomeProject" filename = "filename" comes_from = "-c {} (line {})".format(filename, 1) @@ -276,15 +299,18 @@ def test_yield_line_constraint(self, line_processor): assert repr(found_req) == repr(req) assert found_req.constraint is True - def test_yield_line_requirement_with_spaces_in_specifier(self, line_processor): + def test_yield_line_requirement_with_spaces_in_specifier( + self, line_processor: LineProcessor + ) -> None: line = "SomeProject >= 2" filename = "filename" comes_from = f"-r {filename} (line 1)" req = install_req_from_line(line, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) + assert req.req is not None assert str(req.req.specifier) == ">=2" - def test_yield_editable_requirement(self, line_processor): + def test_yield_editable_requirement(self, line_processor: LineProcessor) -> None: url = "git+https://url#egg=SomeProject" line = f"-e {url}" filename = "filename" @@ -292,7 +318,7 @@ def test_yield_editable_requirement(self, line_processor): req = install_req_from_editable(url, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) - def test_yield_editable_constraint(self, line_processor): + def test_yield_editable_constraint(self, line_processor: LineProcessor) -> None: url = "git+https://url#egg=SomeProject" line = f"-e {url}" filename = "filename" @@ -302,7 +328,9 @@ def test_yield_editable_constraint(self, line_processor): assert repr(found_req) == repr(req) assert found_req.constraint is True - def test_nested_constraints_file(self, monkeypatch, tmpdir, session): + def test_nested_constraints_file( + self, monkeypatch: pytest.MonkeyPatch, tmpdir: Path, session: PipSession + ) -> None: req_name = "hello" req_file = tmpdir / "parent" / "req_file.txt" req_file.parent.mkdir() @@ -316,7 +344,7 @@ def test_nested_constraints_file(self, monkeypatch, tmpdir, session): assert reqs[0].name == req_name assert reqs[0].constraint - def test_options_on_a_requirement_line(self, line_processor): + def test_options_on_a_requirement_line(self, line_processor: LineProcessor) -> None: line = ( "SomeProject --install-option=yo1 --install-option yo2 " '--global-option="yo3" --global-option "yo4"' @@ -326,7 +354,7 @@ def test_options_on_a_requirement_line(self, line_processor): assert req.global_options == ["yo3", "yo4"] assert req.install_options == ["yo1", "yo2"] - def test_hash_options(self, line_processor): + def test_hash_options(self, line_processor: LineProcessor) -> None: """Test the --hash option: mostly its value storage. Make sure it reads and preserve multiple hashes. @@ -353,34 +381,50 @@ def test_hash_options(self, line_processor): ], } - def test_set_isolated(self, line_processor, options): + def test_set_isolated( + self, line_processor: LineProcessor, options: mock.Mock + ) -> None: line = "SomeProject" filename = "filename" options.isolated_mode = True result = line_processor(line, filename, 1, options=options) assert result[0].isolated - def test_set_finder_no_index(self, line_processor, finder): + def test_set_finder_no_index( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("--no-index", "file", 1, finder=finder) assert finder.index_urls == [] - def test_set_finder_index_url(self, line_processor, finder, session): + def test_set_finder_index_url( + self, line_processor: LineProcessor, finder: PackageFinder, session: PipSession + ) -> None: line_processor("--index-url=url", "file", 1, finder=finder, session=session) assert finder.index_urls == ["url"] assert session.auth.index_urls == ["url"] - def test_set_finder_find_links(self, line_processor, finder): + def test_set_finder_find_links( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("--find-links=url", "file", 1, finder=finder) assert finder.find_links == ["url"] - def test_set_finder_extra_index_urls(self, line_processor, finder, session): + def test_set_finder_extra_index_urls( + self, line_processor: LineProcessor, finder: PackageFinder, session: PipSession + ) -> None: line_processor( "--extra-index-url=url", "file", 1, finder=finder, session=session ) assert finder.index_urls == ["url"] assert session.auth.index_urls == ["url"] - def test_set_finder_trusted_host(self, line_processor, caplog, session, finder): + def test_set_finder_trusted_host( + self, + line_processor: LineProcessor, + caplog: pytest.LogCaptureFixture, + session: PipSession, + finder: PackageFinder, + ) -> None: with caplog.at_level(logging.INFO): line_processor( "--trusted-host=host1 --trusted-host=host2:8080", @@ -399,24 +443,32 @@ def test_set_finder_trusted_host(self, line_processor, caplog, session, finder): expected = ("INFO", "adding trusted host: 'host1' (from line 1 of file.txt)") assert expected in actual - def test_set_finder_allow_all_prereleases(self, line_processor, finder): + def test_set_finder_allow_all_prereleases( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("--pre", "file", 1, finder=finder) assert finder.allow_all_prereleases - def test_use_feature(self, line_processor, options): + def test_use_feature( + self, line_processor: LineProcessor, options: mock.Mock + ) -> None: """--use-feature can be set in requirements files.""" line_processor("--use-feature=2020-resolver", "filename", 1, options=options) assert "2020-resolver" in options.features_enabled def test_relative_local_find_links( - self, line_processor, finder, monkeypatch, tmpdir - ): + self, + line_processor: LineProcessor, + finder: PackageFinder, + monkeypatch: pytest.MonkeyPatch, + tmpdir: Path, + ) -> None: """ Test a relative find_links path is joined with the req file directory """ base_path = tmpdir / "path" - def normalize(path): + def normalize(path: Path) -> str: return os.path.normcase(os.path.abspath(os.path.normpath(str(path)))) # Make sure the test also passes on windows @@ -424,24 +476,31 @@ def normalize(path): nested_link = normalize(base_path / "rel_path") exists_ = os.path.exists - def exists(path): + def exists(path: str) -> bool: if path == nested_link: return True else: - exists_(path) + return exists_(path) monkeypatch.setattr(os.path, "exists", exists) line_processor("--find-links=rel_path", req_file, 1, finder=finder) assert finder.find_links == [nested_link] - def test_relative_http_nested_req_files(self, finder, session, monkeypatch): + def test_relative_http_nested_req_files( + self, + finder: PackageFinder, + session: PipSession, + monkeypatch: pytest.MonkeyPatch, + ) -> None: """ Test a relative nested req file path is joined with the req file url """ req_name = "hello" req_file = "http://me.com/me/req_file.txt" - def get_file_content(filename, *args, **kwargs): + def get_file_content( + filename: str, *args: Any, **kwargs: Any + ) -> Tuple[None, str]: if filename == req_file: return None, "-r reqs.txt" elif filename == "http://me.com/me/reqs.txt": @@ -457,7 +516,9 @@ def get_file_content(filename, *args, **kwargs): assert result[0].name == req_name assert not result[0].constraint - def test_relative_local_nested_req_files(self, session, monkeypatch, tmpdir): + def test_relative_local_nested_req_files( + self, session: PipSession, monkeypatch: pytest.MonkeyPatch, tmpdir: Path + ) -> None: """ Test a relative nested req file path is joined with the req file dir """ @@ -474,7 +535,9 @@ def test_relative_local_nested_req_files(self, session, monkeypatch, tmpdir): assert reqs[0].name == req_name assert not reqs[0].constraint - def test_absolute_local_nested_req_files(self, session, tmpdir): + def test_absolute_local_nested_req_files( + self, session: PipSession, tmpdir: Path + ) -> None: """ Test an absolute nested req file path """ @@ -494,7 +557,9 @@ def test_absolute_local_nested_req_files(self, session, tmpdir): assert reqs[0].name == req_name assert not reqs[0].constraint - def test_absolute_http_nested_req_file_in_local(self, session, monkeypatch, tmpdir): + def test_absolute_http_nested_req_file_in_local( + self, session: PipSession, monkeypatch: pytest.MonkeyPatch, tmpdir: Path + ) -> None: """ Test a nested req file url in a local req file """ @@ -502,7 +567,9 @@ def test_absolute_http_nested_req_file_in_local(self, session, monkeypatch, tmpd req_file = tmpdir / "req_file.txt" nested_req_file = "http://me.com/me/req_file.txt" - def get_file_content(filename, *args, **kwargs): + def get_file_content( + filename: str, *args: Any, **kwargs: Any + ) -> Tuple[None, str]: if filename == str(req_file): return None, f"-r {nested_req_file}" elif filename == nested_req_file: @@ -520,17 +587,17 @@ def get_file_content(filename, *args, **kwargs): class TestBreakOptionsArgs: - def test_no_args(self): + def test_no_args(self) -> None: assert ("", "--option") == break_args_options("--option") - def test_no_options(self): + def test_no_options(self) -> None: assert ("arg arg", "") == break_args_options("arg arg") - def test_args_short_options(self): + def test_args_short_options(self) -> None: result = break_args_options("arg arg -s") assert ("arg arg", "-s") == result - def test_args_long_options(self): + def test_args_long_options(self) -> None: result = break_args_options("arg arg --long") assert ("arg arg", "--long") == result @@ -539,23 +606,33 @@ class TestOptionVariants: # this suite is really just testing optparse, but added it anyway - def test_variant1(self, line_processor, finder): + def test_variant1( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("-i url", "file", 1, finder=finder) assert finder.index_urls == ["url"] - def test_variant2(self, line_processor, finder): + def test_variant2( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("-i 'url'", "file", 1, finder=finder) assert finder.index_urls == ["url"] - def test_variant3(self, line_processor, finder): + def test_variant3( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("--index-url=url", "file", 1, finder=finder) assert finder.index_urls == ["url"] - def test_variant4(self, line_processor, finder): + def test_variant4( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("--index-url url", "file", 1, finder=finder) assert finder.index_urls == ["url"] - def test_variant5(self, line_processor, finder): + def test_variant5( + self, line_processor: LineProcessor, finder: PackageFinder + ) -> None: line_processor("--index-url='url'", "file", 1, finder=finder) assert finder.index_urls == ["url"] @@ -564,7 +641,7 @@ class TestParseRequirements: """tests for `parse_reqfile`""" @pytest.mark.network - def test_remote_reqs_parse(self): + def test_remote_reqs_parse(self) -> None: """ Test parsing a simple remote requirements file """ @@ -578,7 +655,9 @@ def test_remote_reqs_parse(self): ): pass - def test_multiple_appending_options(self, tmpdir, finder, options): + def test_multiple_appending_options( + self, tmpdir: Path, finder: PackageFinder, options: mock.Mock + ) -> None: with open(tmpdir.joinpath("req1.txt"), "w") as fp: fp.write("--extra-index-url url1 \n") fp.write("--extra-index-url url2 ") @@ -594,10 +673,12 @@ def test_multiple_appending_options(self, tmpdir, finder, options): assert finder.index_urls == ["url1", "url2"] - def test_expand_existing_env_variables(self, tmpdir, finder): + def test_expand_existing_env_variables( + self, tmpdir: Path, finder: PackageFinder + ) -> None: template = "https://{}:x-oauth-basic@github.com/user/{}/archive/master.zip" - def make_var(name): + def make_var(name: str) -> str: return f"${{{name}}}" env_vars = collections.OrderedDict( @@ -625,9 +706,12 @@ def make_var(name): assert len(reqs) == 1, "parsing requirement file with env variable failed" expected_url = template.format(*env_vars.values()) + assert reqs[0].link is not None assert reqs[0].link.url == expected_url, "variable expansion in req file failed" - def test_expand_missing_env_variables(self, tmpdir, finder): + def test_expand_missing_env_variables( + self, tmpdir: Path, finder: PackageFinder + ) -> None: req_url = ( "https://${NON_EXISTENT_VARIABLE}:$WRONG_FORMAT@" "%WINDOWS_FORMAT%github.com/user/repo/archive/master.zip" @@ -649,11 +733,12 @@ def test_expand_missing_env_variables(self, tmpdir, finder): ) assert len(reqs) == 1, "parsing requirement file with env variable failed" + assert reqs[0].link is not None assert ( reqs[0].link.url == req_url ), "ignoring invalid env variable in req file failed" - def test_join_lines(self, tmpdir, finder): + def test_join_lines(self, tmpdir: Path, finder: PackageFinder) -> None: with open(tmpdir.joinpath("req1.txt"), "w") as fp: fp.write("--extra-index-url url1 \\\n--extra-index-url url2") @@ -665,7 +750,9 @@ def test_join_lines(self, tmpdir, finder): assert finder.index_urls == ["url1", "url2"] - def test_req_file_parse_no_only_binary(self, data, finder): + def test_req_file_parse_no_only_binary( + self, data: TestData, finder: PackageFinder + ) -> None: list( parse_reqfile( data.reqfiles.joinpath("supported_options2.txt"), @@ -676,7 +763,9 @@ def test_req_file_parse_no_only_binary(self, data, finder): expected = FormatControl({"fred"}, {"wilma"}) assert finder.format_control == expected - def test_req_file_parse_comment_start_of_line(self, tmpdir, finder): + def test_req_file_parse_comment_start_of_line( + self, tmpdir: Path, finder: PackageFinder + ) -> None: """ Test parsing comments in a requirements file """ @@ -691,7 +780,9 @@ def test_req_file_parse_comment_start_of_line(self, tmpdir, finder): assert not reqs - def test_req_file_parse_comment_end_of_line_with_url(self, tmpdir, finder): + def test_req_file_parse_comment_end_of_line_with_url( + self, tmpdir: Path, finder: PackageFinder + ) -> None: """ Test parsing comments in a requirements file """ @@ -705,9 +796,12 @@ def test_req_file_parse_comment_end_of_line_with_url(self, tmpdir, finder): ) assert len(reqs) == 1 + assert reqs[0].link is not None assert reqs[0].link.url == "https://example.com/foo.tar.gz" - def test_req_file_parse_egginfo_end_of_line_with_url(self, tmpdir, finder): + def test_req_file_parse_egginfo_end_of_line_with_url( + self, tmpdir: Path, finder: PackageFinder + ) -> None: """ Test parsing comments in a requirements file """ @@ -723,7 +817,7 @@ def test_req_file_parse_egginfo_end_of_line_with_url(self, tmpdir, finder): assert len(reqs) == 1 assert reqs[0].name == "wat" - def test_req_file_no_finder(self, tmpdir): + def test_req_file_no_finder(self, tmpdir: Path) -> None: """ Test parsing a requirements file without a finder """ @@ -740,7 +834,13 @@ def test_req_file_no_finder(self, tmpdir): parse_reqfile(tmpdir.joinpath("req.txt"), session=PipSession()) - def test_install_requirements_with_options(self, tmpdir, finder, session, options): + def test_install_requirements_with_options( + self, + tmpdir: Path, + finder: PackageFinder, + session: PipSession, + options: mock.Mock, + ) -> None: global_option = "--dry-run" install_option = "--prefix=/opt" diff --git a/tests/unit/test_req_install.py b/tests/unit/test_req_install.py index 3301cac0621..ac2c0cdbb89 100644 --- a/tests/unit/test_req_install.py +++ b/tests/unit/test_req_install.py @@ -10,12 +10,13 @@ install_req_from_req_string, ) from pip._internal.req.req_install import InstallRequirement +from tests.lib.path import Path class TestInstallRequirementBuildDirectory: # no need to test symlinks on Windows @pytest.mark.skipif("sys.platform == 'win32'") - def test_tmp_build_directory(self): + def test_tmp_build_directory(self) -> None: # when req is None, we can produce a temporary directory # Make sure we're handling it correctly with real path. requirement = InstallRequirement(None, None) @@ -36,7 +37,7 @@ def test_tmp_build_directory(self): os.rmdir(tmp_dir) assert not os.path.exists(tmp_dir) - def test_forward_slash_results_in_a_link(self, tmpdir): + def test_forward_slash_results_in_a_link(self, tmpdir: Path) -> None: install_dir = tmpdir / "foo" / "bar" # Just create a file for letting the logic work @@ -53,7 +54,7 @@ def test_forward_slash_results_in_a_link(self, tmpdir): class TestInstallRequirementFrom: - def test_install_req_from_string_invalid_requirement(self): + def test_install_req_from_string_invalid_requirement(self) -> None: """ Requirement strings that cannot be parsed by packaging.requirements.Requirement raise an InstallationError. @@ -63,7 +64,7 @@ def test_install_req_from_string_invalid_requirement(self): assert str(excinfo.value) == ("Invalid requirement: 'http:/this/is/invalid'") - def test_install_req_from_string_without_comes_from(self): + def test_install_req_from_string_without_comes_from(self) -> None: """ Test to make sure that install_req_from_string succeeds when called with URL (PEP 508) but without comes_from. @@ -77,12 +78,14 @@ def test_install_req_from_string_without_comes_from(self): install_req = install_req_from_req_string(install_str) assert isinstance(install_req, InstallRequirement) + assert install_req.link is not None assert install_req.link.url == wheel_url + assert install_req.req is not None assert install_req.req.url == wheel_url assert install_req.comes_from is None assert install_req.is_wheel - def test_install_req_from_string_with_comes_from_without_link(self): + def test_install_req_from_string_with_comes_from_without_link(self) -> None: """ Test to make sure that install_req_from_string succeeds when called with URL (PEP 508) and comes_from @@ -102,7 +105,10 @@ def test_install_req_from_string_with_comes_from_without_link(self): install_req = install_req_from_req_string(install_str, comes_from=comes_from) assert isinstance(install_req, InstallRequirement) + assert isinstance(install_req.comes_from, InstallRequirement) assert install_req.comes_from.link is None + assert install_req.link is not None assert install_req.link.url == wheel_url + assert install_req.req is not None assert install_req.req.url == wheel_url assert install_req.is_wheel diff --git a/tests/unit/test_req_uninstall.py b/tests/unit/test_req_uninstall.py index 805a551aecd..ff154a5da99 100644 --- a/tests/unit/test_req_uninstall.py +++ b/tests/unit/test_req_uninstall.py @@ -1,5 +1,6 @@ import os import sys +from typing import Iterator, List, Tuple from unittest.mock import Mock import pytest @@ -15,17 +16,18 @@ uninstallation_paths, ) from tests.lib import create_file +from tests.lib.path import Path # Pretend all files are local, so UninstallPathSet accepts files in the tmpdir, # outside the virtualenv -def mock_is_local(path): +def mock_is_local(path: str) -> bool: return True -def test_uninstallation_paths(): +def test_uninstallation_paths() -> None: class dist: - def get_metadata_lines(self, record): + def get_metadata_lines(self, record: str) -> List[str]: return ["file.py,,", "file.pyc,,", "file.so,,", "nopyc.py"] location = "" @@ -52,8 +54,8 @@ def get_metadata_lines(self, record): assert paths2 == paths -def test_compressed_listing(tmpdir): - def in_tmpdir(paths): +def test_compressed_listing(tmpdir: Path) -> None: + def in_tmpdir(paths: List[str]) -> List[str]: li = [] for path in paths: li.append(str(os.path.join(tmpdir, path.replace("/", os.path.sep)))) @@ -123,7 +125,7 @@ def in_tmpdir(paths): class TestUninstallPathSet: - def test_add(self, tmpdir, monkeypatch): + def test_add(self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(pip._internal.req.req_uninstall, "is_local", mock_is_local) # Fix case for windows tests file_extant = os.path.normcase(os.path.join(tmpdir, "foo")) @@ -139,7 +141,7 @@ def test_add(self, tmpdir, monkeypatch): ups.add(file_nonexistent) assert ups.paths == {file_extant} - def test_add_pth(self, tmpdir, monkeypatch): + def test_add_pth(self, tmpdir: str, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(pip._internal.req.req_uninstall, "is_local", mock_is_local) # Fix case for windows tests tmpdir = os.path.normcase(tmpdir) @@ -169,7 +171,7 @@ def test_add_pth(self, tmpdir, monkeypatch): assert pth.entries == check @pytest.mark.skipif("sys.platform == 'win32'") - def test_add_symlink(self, tmpdir, monkeypatch): + def test_add_symlink(self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(pip._internal.req.req_uninstall, "is_local", mock_is_local) f = os.path.join(tmpdir, "foo") with open(f, "w"): @@ -181,7 +183,7 @@ def test_add_symlink(self, tmpdir, monkeypatch): ups.add(foo_link) assert ups.paths == {foo_link} - def test_compact_shorter_path(self, monkeypatch): + def test_compact_shorter_path(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(pip._internal.req.req_uninstall, "is_local", mock_is_local) monkeypatch.setattr("os.path.exists", lambda p: True) # This deals with nt/posix path differences @@ -194,7 +196,9 @@ def test_compact_shorter_path(self, monkeypatch): assert compact(ups.paths) == {short_path} @pytest.mark.skipif("sys.platform == 'win32'") - def test_detect_symlink_dirs(self, monkeypatch, tmpdir): + def test_detect_symlink_dirs( + self, monkeypatch: pytest.MonkeyPatch, tmpdir: Path + ) -> None: monkeypatch.setattr(pip._internal.req.req_uninstall, "is_local", mock_is_local) # construct 2 paths: @@ -215,7 +219,7 @@ def test_detect_symlink_dirs(self, monkeypatch, tmpdir): class TestStashedUninstallPathSet: - WALK_RESULT = [ + WALK_RESULT: List[Tuple[str, List[str], List[str]]] = [ ("A", ["B", "C"], ["a.py"]), ("A/B", ["D"], ["b.py"]), ("A/B/D", [], ["c.py"]), @@ -227,13 +231,13 @@ class TestStashedUninstallPathSet: ] @classmethod - def mock_walk(cls, root): + def mock_walk(cls, root: str) -> Iterator[Tuple[str, List[str], List[str]]]: for dirname, subdirs, files in cls.WALK_RESULT: dirname = os.path.sep.join(dirname.split("/")) if dirname.startswith(root): yield dirname[len(root) + 1 :], subdirs, files - def test_compress_for_rename(self, monkeypatch): + def test_compress_for_rename(self, monkeypatch: pytest.MonkeyPatch) -> None: paths = [ os.path.sep.join(p.split("/")) for p in [ @@ -261,7 +265,9 @@ def test_compress_for_rename(self, monkeypatch): assert set(expected_paths) == set(actual_paths) @classmethod - def make_stash(cls, tmpdir, paths): + def make_stash( + cls, tmpdir: Path, paths: List[str] + ) -> Tuple[StashedUninstallPathSet, List[Tuple[str, str]]]: for dirname, subdirs, files in cls.WALK_RESULT: root = os.path.join(tmpdir, *dirname.split("/")) if not os.path.exists(root): @@ -279,7 +285,7 @@ def make_stash(cls, tmpdir, paths): return pathset, stashed_paths - def test_stash(self, tmpdir): + def test_stash(self, tmpdir: Path) -> None: pathset, stashed_paths = self.make_stash( tmpdir, [ @@ -296,7 +302,7 @@ def test_stash(self, tmpdir): assert stashed_paths == pathset._moves - def test_commit(self, tmpdir): + def test_commit(self, tmpdir: Path) -> None: pathset, stashed_paths = self.make_stash( tmpdir, [ @@ -313,7 +319,7 @@ def test_commit(self, tmpdir): assert not os.path.exists(old_path) assert not os.path.exists(new_path) - def test_rollback(self, tmpdir): + def test_rollback(self, tmpdir: Path) -> None: pathset, stashed_paths = self.make_stash( tmpdir, [ @@ -331,7 +337,7 @@ def test_rollback(self, tmpdir): assert not os.path.exists(new_path) @pytest.mark.skipif("sys.platform == 'win32'") - def test_commit_symlinks(self, tmpdir): + def test_commit_symlinks(self, tmpdir: Path) -> None: adir = tmpdir / "dir" adir.mkdir() dirlink = tmpdir / "dirlink" @@ -363,7 +369,7 @@ def test_commit_symlinks(self, tmpdir): assert os.path.isfile(afile) @pytest.mark.skipif("sys.platform == 'win32'") - def test_rollback_symlinks(self, tmpdir): + def test_rollback_symlinks(self, tmpdir: Path) -> None: adir = tmpdir / "dir" adir.mkdir() dirlink = tmpdir / "dirlink" diff --git a/tests/unit/test_resolution_legacy_resolver.py b/tests/unit/test_resolution_legacy_resolver.py index c43fe455afa..7b5ba6372cc 100644 --- a/tests/unit/test_resolution_legacy_resolver.py +++ b/tests/unit/test_resolution_legacy_resolver.py @@ -1,12 +1,15 @@ import email.message import logging +from typing import List, Optional, Type, TypeVar, cast from unittest import mock import pytest from pip._vendor.packaging.specifiers import SpecifierSet +from pip._vendor.packaging.utils import NormalizedName from pip._internal.exceptions import NoneMetadataError, UnsupportedPythonVersion from pip._internal.metadata import BaseDistribution +from pip._internal.models.candidate import InstallationCandidate from pip._internal.req.constructors import install_req_from_line from pip._internal.resolution.legacy.resolver import ( Resolver, @@ -15,31 +18,36 @@ from tests.lib import make_test_finder from tests.lib.index import make_mock_candidate +T = TypeVar("T") + class FakeDist(BaseDistribution): - def __init__(self, metadata): - self._canonical_name = "my-project" + def __init__(self, metadata: email.message.Message) -> None: + self._canonical_name = cast(NormalizedName, "my-project") self._metadata = metadata - def __str__(self): + def __str__(self) -> str: return f"" @property - def canonical_name(self): + def canonical_name(self) -> NormalizedName: return self._canonical_name @property - def metadata(self): + def metadata(self) -> email.message.Message: return self._metadata -def make_fake_dist(*, klass=FakeDist, requires_python=None): +def make_fake_dist( + *, klass: Type[BaseDistribution] = FakeDist, requires_python: Optional[str] = None +) -> BaseDistribution: metadata = email.message.Message() metadata["Name"] = "my-project" if requires_python is not None: metadata["Requires-Python"] = requires_python - return klass(metadata) + # Too many arguments for "BaseDistribution" + return klass(metadata) # type: ignore[call-arg] class TestCheckDistRequiresPython: @@ -48,7 +56,7 @@ class TestCheckDistRequiresPython: Test _check_dist_requires_python(). """ - def test_compatible(self, caplog): + def test_compatible(self, caplog: pytest.LogCaptureFixture) -> None: """ Test a Python version compatible with the dist's Requires-Python. """ @@ -62,7 +70,7 @@ def test_compatible(self, caplog): ) assert not len(caplog.records) - def test_incompatible(self): + def test_incompatible(self) -> None: """ Test a Python version incompatible with the dist's Requires-Python. """ @@ -78,7 +86,9 @@ def test_incompatible(self): "3.6.5 not in '==3.6.4'" ) - def test_incompatible_with_ignore_requires(self, caplog): + def test_incompatible_with_ignore_requires( + self, caplog: pytest.LogCaptureFixture + ) -> None: """ Test a Python version incompatible with the dist's Requires-Python while passing ignore_requires_python=True. @@ -98,7 +108,7 @@ def test_incompatible_with_ignore_requires(self, caplog): "3.6.5 not in '==3.6.4'" ) - def test_none_requires_python(self, caplog): + def test_none_requires_python(self, caplog: pytest.LogCaptureFixture) -> None: """ Test a dist with Requires-Python None. """ @@ -116,7 +126,7 @@ def test_none_requires_python(self, caplog): ) assert len(caplog.records) == 0 - def test_invalid_requires_python(self, caplog): + def test_invalid_requires_python(self, caplog: pytest.LogCaptureFixture) -> None: """ Test a dist with an invalid Requires-Python. """ @@ -142,12 +152,12 @@ def test_invalid_requires_python(self, caplog): "PKG-INFO", ], ) - def test_empty_metadata_error(self, metadata_name): + def test_empty_metadata_error(self, metadata_name: str) -> None: """Test dist.metadata raises FileNotFoundError.""" class NotWorkingFakeDist(FakeDist): @property - def metadata(self): + def metadata(self) -> email.message.Message: raise FileNotFoundError(metadata_name) dist = make_fake_dist(klass=NotWorkingFakeDist) @@ -169,8 +179,12 @@ class TestYankedWarning: Test _populate_link() emits warning if one or more candidates are yanked. """ - def _make_test_resolver(self, monkeypatch, mock_candidates): - def _find_candidates(project_name): + def _make_test_resolver( + self, + monkeypatch: pytest.MonkeyPatch, + mock_candidates: List[InstallationCandidate], + ) -> Resolver: + def _find_candidates(project_name: str) -> List[InstallationCandidate]: return mock_candidates finder = make_test_finder() @@ -189,7 +203,9 @@ def _find_candidates(project_name): upgrade_strategy="to-satisfy-only", ) - def test_sort_best_candidate__has_non_yanked(self, caplog, monkeypatch): + def test_sort_best_candidate__has_non_yanked( + self, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test unyanked candidate preferred over yanked. """ @@ -209,7 +225,9 @@ def test_sort_best_candidate__has_non_yanked(self, caplog, monkeypatch): assert ireq.link == candidates[0].link assert len(caplog.records) == 0 - def test_sort_best_candidate__all_yanked(self, caplog, monkeypatch): + def test_sort_best_candidate__all_yanked( + self, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch + ) -> None: """ Test all candidates yanked. """ @@ -252,11 +270,11 @@ def test_sort_best_candidate__all_yanked(self, caplog, monkeypatch): ) def test_sort_best_candidate__yanked_reason( self, - caplog, - monkeypatch, - yanked_reason, - expected_reason, - ): + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, + yanked_reason: str, + expected_reason: str, + ) -> None: """ Test the log message with various reason strings. """ diff --git a/tests/unit/test_search_scope.py b/tests/unit/test_search_scope.py index b7c86b020e6..ef21c10b820 100644 --- a/tests/unit/test_search_scope.py +++ b/tests/unit/test_search_scope.py @@ -3,7 +3,7 @@ class TestSearchScope: - def test_get_formatted_locations_basic_auth(self): + def test_get_formatted_locations_basic_auth(self) -> None: """ Test that basic authentication credentials defined in URL is not included in formatted output. @@ -24,15 +24,15 @@ def test_get_formatted_locations_basic_auth(self): assert "links-user:****@page.domain.com" in result assert "links-pass" not in result - def test_get_index_urls_locations(self): + def test_get_index_urls_locations(self) -> None: """Check that the canonical name is on all indexes""" search_scope = SearchScope( find_links=[], index_urls=["file://index1/", "file://index2"], ) - actual = search_scope.get_index_urls_locations( - install_req_from_line("Complex_Name").name - ) + req = install_req_from_line("Complex_Name") + assert req.name is not None + actual = search_scope.get_index_urls_locations(req.name) assert actual == [ "file://index1/complex-name/", "file://index2/complex-name/", diff --git a/tests/unit/test_self_check_outdated.py b/tests/unit/test_self_check_outdated.py index e4bac727246..22214fbcfbd 100644 --- a/tests/unit/test_self_check_outdated.py +++ b/tests/unit/test_self_check_outdated.py @@ -3,6 +3,7 @@ import json import os import sys +from typing import Any, Optional, cast from unittest import mock import freezegun # type: ignore @@ -12,6 +13,7 @@ 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, @@ -21,7 +23,7 @@ class MockBestCandidateResult: - def __init__(self, best): + def __init__(self, best: InstallationCandidate) -> None: self.best_candidate = best @@ -48,31 +50,31 @@ class MockPackageFinder: ] @classmethod - def create(cls, *args, **kwargs): + def create(cls, *args: Any, **kwargs: Any) -> "MockPackageFinder": return cls() - def find_best_candidate(self, project_name): + def find_best_candidate(self, project_name: str) -> MockBestCandidateResult: return MockBestCandidateResult(self.INSTALLATION_CANDIDATES[0]) class MockDistribution: - def __init__(self, installer, version): + def __init__(self, installer: str, version: str) -> None: self.installer = installer self.version = parse_version(version) class MockEnvironment: - def __init__(self, installer, installed_version): + def __init__(self, installer: str, installed_version: Optional[str]) -> None: self.installer = installer self.installed_version = installed_version - def get_distribution(self, name): + 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(): +def _options() -> mock.Mock: """Some default options that we pass to self_outdated_check.pip_self_version_check""" return mock.Mock( @@ -106,14 +108,14 @@ def _options(): ], ) def test_pip_self_version_check( - monkeypatch, - stored_time, - installed_ver, - new_ver, - installer, - check_if_upgrade_required, - check_warn_logs, -): + 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", @@ -141,27 +143,24 @@ def test_pip_self_version_check( "pip._vendor.requests.packages.urllib3.packages.six.moves", ], ): - latest_pypi_version = pip_self_version_check(None, _options()) + pip_self_version_check(PipSession(), _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: + if check_if_upgrade_required: assert fake_state.save.call_args_list == [ mock.call(new_ver, datetime.datetime(1970, 1, 9, 10, 00, 00)), ] - else: + elif installed_ver: # Make sure no Exceptions - assert not logger.debug.call_args_list + 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 logger.warning.call_count == 1 + assert cast(mock.Mock, logger.warning).call_count == 1 else: - assert logger.warning.call_count == 0 + assert cast(mock.Mock, logger.warning).call_count == 0 statefile_name_case_1 = "fcd2d5175dd33d5df759ee7b045264230205ef837bf9f582f7c3ada7" @@ -176,23 +175,23 @@ def test_pip_self_version_check( ("C:\\Users\\User\\Desktop\\venv", statefile_name_case_2), ], ) -def test_get_statefile_name_known_values(key, expected): +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, 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(): - state = SelfCheckState(cache_dir=False) +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): +def test_self_check_state_key_uses_sys_prefix(monkeypatch: pytest.MonkeyPatch) -> None: key = "helloworld" monkeypatch.setattr(sys, "prefix", key) @@ -201,7 +200,9 @@ def test_self_check_state_key_uses_sys_prefix(monkeypatch): assert state.key == key -def test_self_check_state_reads_expected_statefile(monkeypatch, tmpdir): +def test_self_check_state_reads_expected_statefile( + monkeypatch: pytest.MonkeyPatch, tmpdir: Path +) -> None: cache_dir = tmpdir / "cache_dir" cache_dir.mkdir() key = "helloworld" @@ -227,7 +228,9 @@ def test_self_check_state_reads_expected_statefile(monkeypatch, tmpdir): assert state.state["pypi_version"] == pypi_version -def test_self_check_state_writes_expected_statefile(monkeypatch, tmpdir): +def test_self_check_state_writes_expected_statefile( + monkeypatch: pytest.MonkeyPatch, tmpdir: Path +) -> None: cache_dir = tmpdir / "cache_dir" cache_dir.mkdir() key = "helloworld" diff --git a/tests/unit/test_target_python.py b/tests/unit/test_target_python.py index 233c4c19369..d3e27e39ae8 100644 --- a/tests/unit/test_target_python.py +++ b/tests/unit/test_target_python.py @@ -1,6 +1,8 @@ -from unittest.mock import patch +from typing import Any, Dict, Optional, Tuple +from unittest import mock import pytest +from pip._vendor.packaging.tags import Tag from pip._internal.models.target_python import TargetPython from tests.lib import CURRENT_PY_VERSION_INFO, pyversion @@ -19,7 +21,11 @@ class TestTargetPython: ((3, 10, 1), ((3, 10, 1), "3.10")), ], ) - def test_init__py_version_info(self, py_version_info, expected): + def test_init__py_version_info( + self, + py_version_info: Tuple[int, ...], + expected: Tuple[Tuple[int, int, int], str], + ) -> None: """ Test passing the py_version_info argument. """ @@ -33,7 +39,7 @@ def test_init__py_version_info(self, py_version_info, expected): assert target_python.py_version_info == expected_py_version_info assert target_python.py_version == expected_py_version - def test_init__py_version_info_none(self): + def test_init__py_version_info_none(self) -> None: """ Test passing py_version_info=None. """ @@ -67,7 +73,7 @@ def test_init__py_version_info_none(self): ), ], ) - def test_format_given(self, kwargs, expected): + def test_format_given(self, kwargs: Dict[str, Any], expected: str) -> None: target_python = TargetPython(**kwargs) actual = target_python.format_given() assert actual == expected @@ -86,13 +92,13 @@ def test_format_given(self, kwargs, expected): (None, None), ], ) - @patch("pip._internal.models.target_python.get_supported") + @mock.patch("pip._internal.models.target_python.get_supported") def test_get_tags( self, - mock_get_supported, - py_version_info, - expected_version, - ): + mock_get_supported: mock.Mock, + py_version_info: Optional[Tuple[int, ...]], + expected_version: Optional[str], + ) -> None: mock_get_supported.return_value = ["tag-1", "tag-2"] target_python = TargetPython(py_version_info=py_version_info) @@ -105,11 +111,14 @@ def test_get_tags( # Check that the value was cached. assert target_python._valid_tags == ["tag-1", "tag-2"] - def test_get_tags__uses_cached_value(self): + def test_get_tags__uses_cached_value(self) -> None: """ Test that get_tags() uses the cached value. """ target_python = TargetPython(py_version_info=None) - target_python._valid_tags = ["tag-1", "tag-2"] + target_python._valid_tags = [ + Tag("py2", "none", "any"), + Tag("py3", "none", "any"), + ] actual = target_python.get_tags() - assert actual == ["tag-1", "tag-2"] + assert actual == [Tag("py2", "none", "any"), Tag("py3", "none", "any")] diff --git a/tests/unit/test_urls.py b/tests/unit/test_urls.py index c598daa4281..56ee80aa802 100644 --- a/tests/unit/test_urls.py +++ b/tests/unit/test_urls.py @@ -1,6 +1,7 @@ import os import sys import urllib.request +from typing import Optional import pytest @@ -16,19 +17,19 @@ ("", None), ], ) -def test_get_url_scheme(url, expected): +def test_get_url_scheme(url: str, expected: Optional[str]) -> None: assert get_url_scheme(url) == expected @pytest.mark.skipif("sys.platform == 'win32'") -def test_path_to_url_unix(): +def test_path_to_url_unix() -> None: assert path_to_url("/tmp/file") == "file:///tmp/file" path = os.path.join(os.getcwd(), "file") assert path_to_url("file") == "file://" + urllib.request.pathname2url(path) @pytest.mark.skipif("sys.platform != 'win32'") -def test_path_to_url_win(): +def test_path_to_url_win() -> None: assert path_to_url("c:/tmp/file") == "file:///C:/tmp/file" assert path_to_url("c:\\tmp\\file") == "file:///C:/tmp/file" assert path_to_url(r"\\unc\as\path") == "file://unc/as/path" @@ -49,7 +50,7 @@ def test_path_to_url_win(): ("file:///c:/tmp/file", r"C:\tmp\file", "/c:/tmp/file"), ], ) -def test_url_to_path(url, win_expected, non_win_expected): +def test_url_to_path(url: str, win_expected: str, non_win_expected: str) -> None: if sys.platform == "win32": expected_path = win_expected else: @@ -63,7 +64,7 @@ def test_url_to_path(url, win_expected, non_win_expected): @pytest.mark.skipif("sys.platform != 'win32'") -def test_url_to_path_path_to_url_symmetry_win(): +def test_url_to_path_path_to_url_symmetry_win() -> None: path = r"C:\tmp\file" assert url_to_path(path_to_url(path)) == path diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 182a13ea0ed..a70e3eea48b 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -10,7 +10,7 @@ import sys import time from io import BytesIO -from typing import List +from typing import Any, Callable, Iterator, List, NoReturn, Optional, Tuple, Type from unittest.mock import Mock, patch import pytest @@ -48,12 +48,13 @@ tabulate, ) from pip._internal.utils.setuptools_build import make_setuptools_shim_args +from tests.lib.path import Path class Tests_EgglinkPath: "util.egg_link_path_from_location() tests" - def setup(self): + def setup(self) -> None: project = "foo" @@ -82,8 +83,8 @@ def setup(self): self.old_isfile = path.isfile self.mock_isfile = path.isfile = Mock() - def teardown(self): - from pip._internal.utils import misc as utils + def teardown(self) -> None: + from pip._internal.utils import egg_link as utils utils.site_packages = self.old_site_packages utils.running_under_virtualenv = self.old_running_under_virtualenv @@ -93,16 +94,16 @@ def teardown(self): path.isfile = self.old_isfile - def eggLinkInUserSite(self, egglink): + def eggLinkInUserSite(self, egglink: str) -> bool: return egglink == self.user_site_egglink - def eggLinkInSitePackages(self, egglink): + def eggLinkInSitePackages(self, egglink: str) -> bool: return egglink == self.site_packages_egglink # ####################### # # # egglink in usersite # # # ####################### # - def test_egglink_in_usersite_notvenv(self): + def test_egglink_in_usersite_notvenv(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = False self.mock_isfile.side_effect = self.eggLinkInUserSite @@ -111,13 +112,13 @@ def test_egglink_in_usersite_notvenv(self): == self.user_site_egglink ) - def test_egglink_in_usersite_venv_noglobal(self): + def test_egglink_in_usersite_venv_noglobal(self) -> None: self.mock_virtualenv_no_global.return_value = True self.mock_running_under_virtualenv.return_value = True self.mock_isfile.side_effect = self.eggLinkInUserSite assert egg_link_path_from_location(self.mock_dist.project_name) is None - def test_egglink_in_usersite_venv_global(self): + def test_egglink_in_usersite_venv_global(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = True self.mock_isfile.side_effect = self.eggLinkInUserSite @@ -129,7 +130,7 @@ def test_egglink_in_usersite_venv_global(self): # ####################### # # # egglink in sitepkgs # # # ####################### # - def test_egglink_in_sitepkgs_notvenv(self): + def test_egglink_in_sitepkgs_notvenv(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = False self.mock_isfile.side_effect = self.eggLinkInSitePackages @@ -138,7 +139,7 @@ def test_egglink_in_sitepkgs_notvenv(self): == self.site_packages_egglink ) - def test_egglink_in_sitepkgs_venv_noglobal(self): + def test_egglink_in_sitepkgs_venv_noglobal(self) -> None: self.mock_virtualenv_no_global.return_value = True self.mock_running_under_virtualenv.return_value = True self.mock_isfile.side_effect = self.eggLinkInSitePackages @@ -147,7 +148,7 @@ def test_egglink_in_sitepkgs_venv_noglobal(self): == self.site_packages_egglink ) - def test_egglink_in_sitepkgs_venv_global(self): + def test_egglink_in_sitepkgs_venv_global(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = True self.mock_isfile.side_effect = self.eggLinkInSitePackages @@ -159,7 +160,7 @@ def test_egglink_in_sitepkgs_venv_global(self): # ################################## # # # egglink in usersite & sitepkgs # # # ################################## # - def test_egglink_in_both_notvenv(self): + def test_egglink_in_both_notvenv(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = False self.mock_isfile.return_value = True @@ -168,7 +169,7 @@ def test_egglink_in_both_notvenv(self): == self.user_site_egglink ) - def test_egglink_in_both_venv_noglobal(self): + def test_egglink_in_both_venv_noglobal(self) -> None: self.mock_virtualenv_no_global.return_value = True self.mock_running_under_virtualenv.return_value = True self.mock_isfile.return_value = True @@ -177,7 +178,7 @@ def test_egglink_in_both_venv_noglobal(self): == self.site_packages_egglink ) - def test_egglink_in_both_venv_global(self): + def test_egglink_in_both_venv_global(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = True self.mock_isfile.return_value = True @@ -189,19 +190,19 @@ def test_egglink_in_both_venv_global(self): # ############## # # # no egglink # # # ############## # - def test_noegglink_in_sitepkgs_notvenv(self): + def test_noegglink_in_sitepkgs_notvenv(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = False self.mock_isfile.return_value = False assert egg_link_path_from_location(self.mock_dist.project_name) is None - def test_noegglink_in_sitepkgs_venv_noglobal(self): + def test_noegglink_in_sitepkgs_venv_noglobal(self) -> None: self.mock_virtualenv_no_global.return_value = True self.mock_running_under_virtualenv.return_value = True self.mock_isfile.return_value = False assert egg_link_path_from_location(self.mock_dist.project_name) is None - def test_noegglink_in_sitepkgs_venv_global(self): + def test_noegglink_in_sitepkgs_venv_global(self) -> None: self.mock_virtualenv_no_global.return_value = False self.mock_running_under_virtualenv.return_value = True self.mock_isfile.return_value = False @@ -214,7 +215,7 @@ class TestsGetDistributions: """Test get_distribution().""" class MockWorkingSet(List[Mock]): - def require(self, name): + def require(self, name: str) -> None: pass workingset = MockWorkingSet( @@ -241,10 +242,10 @@ def require(self, name): ) ) - def dist_is_local(self, dist): + def dist_is_local(self, dist: Mock) -> bool: return dist.test_name != "global" and dist.test_name != "user" - def dist_in_usersite(self, dist): + def dist_in_usersite(self, dist: Mock) -> bool: return dist.test_name == "user" @pytest.mark.parametrize( @@ -262,11 +263,11 @@ def dist_in_usersite(self, dist): ) def test_get_distribution( self, - mock_dist_is_local, - mock_dist_in_usersite, - working_set, - req_name, - ): + mock_dist_is_local: Mock, + mock_dist_in_usersite: Mock, + working_set: MockWorkingSet, + req_name: str, + ) -> None: """Ensure get_distribution() finds all kinds of distributions.""" mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite @@ -278,26 +279,28 @@ def test_get_distribution( @patch("pip._vendor.pkg_resources.working_set", workingset) def test_get_distribution_nonexist( self, - mock_dist_is_local, - mock_dist_in_usersite, - ): + mock_dist_is_local: Mock, + mock_dist_in_usersite: Mock, + ) -> None: mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite dist = get_distribution("non-exist") assert dist is None -def test_rmtree_errorhandler_nonexistent_directory(tmpdir): +def test_rmtree_errorhandler_nonexistent_directory(tmpdir: Path) -> None: """ Test rmtree_errorhandler ignores the given non-existing directory. """ nonexistent_path = str(tmpdir / "foo") mock_func = Mock() - rmtree_errorhandler(mock_func, nonexistent_path, None) + # Argument 3 to "rmtree_errorhandler" has incompatible type "None"; expected + # "Tuple[Type[BaseException], BaseException, TracebackType]" + rmtree_errorhandler(mock_func, nonexistent_path, None) # type: ignore[arg-type] mock_func.assert_not_called() -def test_rmtree_errorhandler_readonly_directory(tmpdir): +def test_rmtree_errorhandler_readonly_directory(tmpdir: Path) -> None: """ Test rmtree_errorhandler makes the given read-only directory writable. """ @@ -309,14 +312,16 @@ def test_rmtree_errorhandler_readonly_directory(tmpdir): # Make sure mock_func is called with the given path mock_func = Mock() - rmtree_errorhandler(mock_func, path, None) + # Argument 3 to "rmtree_errorhandler" has incompatible type "None"; expected + # "Tuple[Type[BaseException], BaseException, TracebackType]" + rmtree_errorhandler(mock_func, path, None) # type: ignore[arg-type] mock_func.assert_called_with(path) # Make sure the path is now writable assert os.stat(path).st_mode & stat.S_IWRITE -def test_rmtree_errorhandler_reraises_error(tmpdir): +def test_rmtree_errorhandler_reraises_error(tmpdir: Path) -> None: """ Test rmtree_errorhandler reraises an exception by the given unreadable directory. @@ -334,30 +339,32 @@ def test_rmtree_errorhandler_reraises_error(tmpdir): except RuntimeError: # Make sure the handler reraises an exception with pytest.raises(RuntimeError, match="test message"): - rmtree_errorhandler(mock_func, path, None) + # Argument 3 to "rmtree_errorhandler" has incompatible type "None"; expected + # "Tuple[Type[BaseException], BaseException, TracebackType]" + rmtree_errorhandler(mock_func, path, None) # type: ignore[arg-type] mock_func.assert_not_called() -def test_rmtree_skips_nonexistent_directory(): +def test_rmtree_skips_nonexistent_directory() -> None: """ Test wrapped rmtree doesn't raise an error by the given nonexistent directory. """ - rmtree.__wrapped__("nonexistent-subdir") + rmtree.__wrapped__("nonexistent-subdir") # type: ignore[attr-defined] class Failer: - def __init__(self, duration=1): + def __init__(self, duration: int = 1) -> None: self.succeed_after = time.time() + duration - def call(self, *args, **kw): + def call(self, *args: Any, **kw: Any) -> None: """Fail with OSError self.max_fails times""" if time.time() < self.succeed_after: raise OSError("Failed") -def test_rmtree_retries(monkeypatch): +def test_rmtree_retries(monkeypatch: pytest.MonkeyPatch) -> None: """ Test pip._internal.utils.rmtree will retry failures """ @@ -365,7 +372,7 @@ def test_rmtree_retries(monkeypatch): rmtree("foo") -def test_rmtree_retries_for_3sec(monkeypatch): +def test_rmtree_retries_for_3sec(monkeypatch: pytest.MonkeyPatch) -> None: """ Test pip._internal.utils.rmtree will retry failures for no more than 3 sec """ @@ -389,7 +396,7 @@ class Test_normalize_path: # permission bit to create them, and Python 2 doesn't support it anyway, so # it's easiest just to skip this test on Windows altogether. @pytest.mark.skipif("sys.platform == 'win32'") - def test_resolve_symlinks(self, tmpdir): + def test_resolve_symlinks(self, tmpdir: Path) -> None: print(type(tmpdir)) print(dir(tmpdir)) orig_working_dir = os.getcwd() @@ -436,14 +443,16 @@ class TestHashes: ("sha512", 128 * "c", False), ], ) - def test_is_hash_allowed(self, hash_name, hex_digest, expected): + def test_is_hash_allowed( + self, hash_name: str, hex_digest: str, expected: bool + ) -> None: hashes_data = { "sha512": [128 * "a", 128 * "b"], } hashes = Hashes(hashes_data) assert hashes.is_hash_allowed(hash_name, hex_digest) == expected - def test_success(self, tmpdir): + def test_success(self, tmpdir: Path) -> None: """Make sure no error is raised when at least one hash matches. Test check_against_path because it calls everything else. @@ -463,37 +472,37 @@ def test_success(self, tmpdir): ) hashes.check_against_path(file) - def test_failure(self): + def test_failure(self) -> None: """Hashes should raise HashMismatch when no hashes match.""" hashes = Hashes({"sha256": ["wrongwrong"]}) with pytest.raises(HashMismatch): hashes.check_against_file(BytesIO(b"hello")) - def test_missing_hashes(self): + def test_missing_hashes(self) -> None: """MissingHashes should raise HashMissing when any check is done.""" with pytest.raises(HashMissing): MissingHashes().check_against_file(BytesIO(b"hello")) - def test_unknown_hash(self): + def test_unknown_hash(self) -> None: """Hashes should raise InstallationError when it encounters an unknown hash.""" hashes = Hashes({"badbad": ["dummy"]}) with pytest.raises(InstallationError): hashes.check_against_file(BytesIO(b"hello")) - def test_non_zero(self): + def test_non_zero(self) -> None: """Test that truthiness tests tell whether any known-good hashes exist.""" - assert Hashes({"sha256": "dummy"}) + assert Hashes({"sha256": ["dummy"]}) assert not Hashes() assert not Hashes({}) - def test_equality(self): + def test_equality(self) -> None: assert Hashes() == Hashes() assert Hashes({"sha256": ["abcd"]}) == Hashes({"sha256": ["abcd"]}) assert Hashes({"sha256": ["ab", "cd"]}) == Hashes({"sha256": ["cd", "ab"]}) - def test_hash(self): + def test_hash(self) -> None: cache = {} cache[Hashes({"sha256": ["ab", "cd"]})] = 42 assert cache[Hashes({"sha256": ["ab", "cd"]})] == 42 @@ -502,7 +511,7 @@ def test_hash(self): class TestEncoding: """Tests for pip._internal.utils.encoding""" - def test_auto_decode_utf_16_le(self): + def test_auto_decode_utf_16_le(self) -> None: data = ( b"\xff\xfeD\x00j\x00a\x00n\x00g\x00o\x00=\x00" b"=\x001\x00.\x004\x00.\x002\x00" @@ -510,7 +519,7 @@ def test_auto_decode_utf_16_le(self): assert data.startswith(codecs.BOM_UTF16_LE) assert auto_decode(data) == "Django==1.4.2" - def test_auto_decode_utf_16_be(self): + def test_auto_decode_utf_16_be(self) -> None: data = ( b"\xfe\xff\x00D\x00j\x00a\x00n\x00g\x00o\x00=" b"\x00=\x001\x00.\x004\x00.\x002" @@ -518,14 +527,14 @@ def test_auto_decode_utf_16_be(self): assert data.startswith(codecs.BOM_UTF16_BE) assert auto_decode(data) == "Django==1.4.2" - def test_auto_decode_no_bom(self): + def test_auto_decode_no_bom(self) -> None: assert auto_decode(b"foobar") == "foobar" - def test_auto_decode_pep263_headers(self): + def test_auto_decode_pep263_headers(self) -> None: latin1_req = "# coding=latin1\n# Pas trop de café" assert auto_decode(latin1_req.encode("latin1")) == latin1_req - def test_auto_decode_no_preferred_encoding(self): + def test_auto_decode_no_preferred_encoding(self) -> None: om, em = Mock(), Mock() om.return_value = "ascii" em.return_value = None @@ -536,18 +545,18 @@ def test_auto_decode_no_preferred_encoding(self): assert ret == data @pytest.mark.parametrize("encoding", [encoding for bom, encoding in BOMS]) - def test_all_encodings_are_valid(self, encoding): + def test_all_encodings_are_valid(self, encoding: str) -> None: # we really only care that there is no LookupError assert "".encode(encoding).decode(encoding) == "" -def raises(error): +def raises(error: Type[Exception]) -> NoReturn: raise error class TestGlibc: @pytest.mark.skipif("sys.platform == 'win32'") - def test_glibc_version_string(self, monkeypatch): + def test_glibc_version_string(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( os, "confstr", @@ -557,7 +566,9 @@ def test_glibc_version_string(self, monkeypatch): assert glibc_version_string() == "2.20" @pytest.mark.skipif("sys.platform == 'win32'") - def test_glibc_version_string_confstr(self, monkeypatch): + def test_glibc_version_string_confstr( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr( os, "confstr", @@ -574,15 +585,21 @@ def test_glibc_version_string_confstr(self, monkeypatch): lambda x: "XXX", ], ) - def test_glibc_version_string_confstr_fail(self, monkeypatch, failure): + def test_glibc_version_string_confstr_fail( + self, monkeypatch: pytest.MonkeyPatch, failure: Callable[[Any], Any] + ) -> None: monkeypatch.setattr(os, "confstr", failure, raising=False) assert glibc_version_string_confstr() is None - def test_glibc_version_string_confstr_missing(self, monkeypatch): + def test_glibc_version_string_confstr_missing( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.delattr(os, "confstr", raising=False) assert glibc_version_string_confstr() is None - def test_glibc_version_string_ctypes_missing(self, monkeypatch): + def test_glibc_version_string_ctypes_missing( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setitem(sys.modules, "ctypes", None) assert glibc_version_string_ctypes() is None @@ -597,7 +614,9 @@ def test_glibc_version_string_ctypes_missing(self, monkeypatch): ((3, 6, 2, 4), (3, 6, 2)), ], ) -def test_normalize_version_info(version_info, expected): +def test_normalize_version_info( + version_info: Tuple[int, ...], expected: Tuple[int, int, int] +) -> None: actual = normalize_version_info(version_info) assert actual == expected @@ -612,7 +631,9 @@ class TestGetProg: ("/usr/bin/pip3", "", "pip3"), ], ) - def test_get_prog(self, monkeypatch, argv, executable, expected): + def test_get_prog( + self, monkeypatch: pytest.MonkeyPatch, argv: str, executable: str, expected: str + ) -> None: monkeypatch.setattr("pip._internal.utils.misc.sys.argv", [argv]) monkeypatch.setattr("pip._internal.utils.misc.sys.executable", executable) assert get_prog() == expected @@ -633,7 +654,9 @@ def test_get_prog(self, monkeypatch, argv, executable, expected): (("2001:db6::1", 5000), "[2001:db6::1]:5000"), ], ) -def test_build_netloc(host_port, expected_netloc): +def test_build_netloc( + host_port: Tuple[str, Optional[int]], expected_netloc: str +) -> None: assert build_netloc(*host_port) == expected_netloc @@ -659,10 +682,10 @@ def test_build_netloc(host_port, expected_netloc): ], ) def test_build_url_from_netloc_and_parse_netloc( - netloc, - expected_url, - expected_host_port, -): + netloc: str, + expected_url: str, + expected_host_port: Tuple[str, Optional[int]], +) -> None: assert build_url_from_netloc(netloc) == expected_url assert parse_netloc(netloc) == expected_host_port @@ -686,7 +709,9 @@ def test_build_url_from_netloc_and_parse_netloc( ("user%3Aname:%23%40%5E@example.com", ("example.com", ("user:name", "#@^"))), ], ) -def test_split_auth_from_netloc(netloc, expected): +def test_split_auth_from_netloc( + netloc: str, expected: Tuple[str, Tuple[Optional[str], Optional[str]]] +) -> None: actual = split_auth_from_netloc(netloc) assert actual == expected @@ -731,7 +756,9 @@ def test_split_auth_from_netloc(netloc, expected): ), ], ) -def test_split_auth_netloc_from_url(url, expected): +def test_split_auth_netloc_from_url( + url: str, expected: Tuple[str, str, Tuple[Optional[str], Optional[str]]] +) -> None: actual = split_auth_netloc_from_url(url) assert actual == expected @@ -755,7 +782,7 @@ def test_split_auth_netloc_from_url(url, expected): ("user%3Aname:%23%40%5E@example.com", "user%3Aname:****@example.com"), ], ) -def test_redact_netloc(netloc, expected): +def test_redact_netloc(netloc: str, expected: str) -> None: actual = redact_netloc(netloc) assert actual == expected @@ -784,7 +811,7 @@ def test_redact_netloc(netloc, expected): ("git+ssh://git@pypi.org/something", "git+ssh://pypi.org/something"), ], ) -def test_remove_auth_from_url(auth_url, expected_url): +def test_remove_auth_from_url(auth_url: str, expected_url: str) -> None: url = remove_auth_from_url(auth_url) assert url == expected_url @@ -803,13 +830,13 @@ def test_remove_auth_from_url(auth_url, expected_url): ), ], ) -def test_redact_auth_from_url(auth_url, expected_url): +def test_redact_auth_from_url(auth_url: str, expected_url: str) -> None: url = redact_auth_from_url(auth_url) assert url == expected_url class TestHiddenText: - def test_basic(self): + def test_basic(self) -> None: """ Test str(), repr(), and attribute access. """ @@ -819,7 +846,7 @@ def test_basic(self): assert hidden.redacted == "######" assert hidden.secret == "my-secret" - def test_equality_with_str(self): + def test_equality_with_str(self) -> None: """ Test equality (and inequality) with str objects. """ @@ -833,7 +860,7 @@ def test_equality_with_str(self): assert hidden != hidden.redacted assert hidden.redacted != hidden - def test_equality_same_secret(self): + def test_equality_same_secret(self) -> None: """ Test equality with an object having the same secret. """ @@ -845,7 +872,7 @@ def test_equality_same_secret(self): # Also test __ne__. assert not hidden1 != hidden2 - def test_equality_different_secret(self): + def test_equality_different_secret(self) -> None: """ Test equality with an object having a different secret. """ @@ -857,7 +884,7 @@ def test_equality_different_secret(self): assert not hidden1 == hidden2 -def test_hide_value(): +def test_hide_value() -> None: hidden = hide_value("my-secret") assert repr(hidden) == "" assert str(hidden) == "****" @@ -865,7 +892,7 @@ def test_hide_value(): assert hidden.secret == "my-secret" -def test_hide_url(): +def test_hide_url() -> None: hidden_url = hide_url("https://user:password@example.com") assert repr(hidden_url) == "" assert str(hidden_url) == "https://user:****@example.com" @@ -874,7 +901,7 @@ def test_hide_url(): @pytest.fixture() -def patch_deprecation_check_version(): +def patch_deprecation_check_version() -> Iterator[None]: # We do this, so that the deprecation tests are easier to write. import pip._internal.utils.deprecation as d @@ -890,8 +917,11 @@ def patch_deprecation_check_version(): @pytest.mark.parametrize("issue", [None, 988]) @pytest.mark.parametrize("feature_flag", [None, "magic-8-ball"]) def test_deprecated_message_contains_information( - gone_in, replacement, issue, feature_flag -): + gone_in: Optional[str], + replacement: Optional[str], + issue: Optional[int], + feature_flag: Optional[str], +) -> None: with pytest.warns(PipDeprecationWarning) as record: deprecated( reason="Stop doing this!", @@ -902,6 +932,7 @@ def test_deprecated_message_contains_information( ) assert len(record) == 1 + assert isinstance(record[0].message, PipDeprecationWarning) message = record[0].message.args[0] assert "DEPRECATION: Stop doing this!" in message @@ -915,7 +946,9 @@ def test_deprecated_message_contains_information( @pytest.mark.parametrize("replacement", [None, "a magic 8 ball"]) @pytest.mark.parametrize("issue", [None, 988]) @pytest.mark.parametrize("feature_flag", [None, "magic-8-ball"]) -def test_deprecated_raises_error_if_too_old(replacement, issue, feature_flag): +def test_deprecated_raises_error_if_too_old( + replacement: Optional[str], issue: Optional[int], feature_flag: Optional[str] +) -> None: with pytest.raises(PipDeprecationWarning) as exception: deprecated( reason="Stop doing this!", @@ -937,14 +970,14 @@ def test_deprecated_raises_error_if_too_old(replacement, issue, feature_flag): @pytest.mark.usefixtures("patch_deprecation_check_version") -def test_deprecated_message_reads_well_past(): +def test_deprecated_message_reads_well_past() -> None: with pytest.raises(PipDeprecationWarning) as exception: deprecated( reason="Stop doing this!", gone_in="1.0", # this matches the patched version. replacement="to be nicer", feature_flag="magic-8-ball", - issue="100000", + issue=100000, ) message = exception.value.args[0] @@ -958,17 +991,18 @@ def test_deprecated_message_reads_well_past(): @pytest.mark.usefixtures("patch_deprecation_check_version") -def test_deprecated_message_reads_well_future(): +def test_deprecated_message_reads_well_future() -> None: with pytest.warns(PipDeprecationWarning) as record: deprecated( reason="Stop doing this!", gone_in="2.0", # this is greater than the patched version. replacement="to be nicer", feature_flag="crisis", - issue="100000", + issue=100000, ) assert len(record) == 1 + assert isinstance(record[0].message, PipDeprecationWarning) message = record[0].message.args[0] assert message == ( @@ -980,7 +1014,7 @@ def test_deprecated_message_reads_well_future(): ) -def test_make_setuptools_shim_args(): +def test_make_setuptools_shim_args() -> None: # Test all arguments at once, including the overall ordering. args = make_setuptools_shim_args( "/dir/path/setup.py", @@ -997,7 +1031,9 @@ def test_make_setuptools_shim_args(): @pytest.mark.parametrize("global_options", [None, [], ["--some", "--option"]]) -def test_make_setuptools_shim_args__global_options(global_options): +def test_make_setuptools_shim_args__global_options( + global_options: Optional[List[str]], +) -> None: args = make_setuptools_shim_args( "/dir/path/setup.py", global_options=global_options, @@ -1012,7 +1048,7 @@ def test_make_setuptools_shim_args__global_options(global_options): @pytest.mark.parametrize("no_user_config", [False, True]) -def test_make_setuptools_shim_args__no_user_config(no_user_config): +def test_make_setuptools_shim_args__no_user_config(no_user_config: bool) -> None: args = make_setuptools_shim_args( "/dir/path/setup.py", no_user_config=no_user_config, @@ -1021,7 +1057,7 @@ def test_make_setuptools_shim_args__no_user_config(no_user_config): @pytest.mark.parametrize("unbuffered_output", [False, True]) -def test_make_setuptools_shim_args__unbuffered_output(unbuffered_output): +def test_make_setuptools_shim_args__unbuffered_output(unbuffered_output: bool) -> None: args = make_setuptools_shim_args( "/dir/path/setup.py", unbuffered_output=unbuffered_output ) @@ -1037,7 +1073,9 @@ def test_make_setuptools_shim_args__unbuffered_output(unbuffered_output): (False, True, False), ], ) -def test_is_console_interactive(monkeypatch, isatty, no_stdin, expected): +def test_is_console_interactive( + monkeypatch: pytest.MonkeyPatch, isatty: bool, no_stdin: bool, expected: bool +) -> None: monkeypatch.setattr(sys.stdin, "isatty", Mock(return_value=isatty)) if no_stdin: @@ -1055,7 +1093,7 @@ def test_is_console_interactive(monkeypatch, isatty, no_stdin, expected): (1234567890, "1234.6 MB"), ], ) -def test_format_size(size, expected): +def test_format_size(size: int, expected: str) -> None: assert format_size(size) == expected @@ -1083,5 +1121,5 @@ def test_format_size(size, expected): ), ], ) -def test_tabulate(rows, table, sizes): +def test_tabulate(rows: List[Tuple[str]], table: List[str], sizes: List[int]) -> None: assert tabulate(rows) == (table, sizes) diff --git a/tests/unit/test_utils_compatibility_tags.py b/tests/unit/test_utils_compatibility_tags.py index 3b8a73daacd..f09c451b8ee 100644 --- a/tests/unit/test_utils_compatibility_tags.py +++ b/tests/unit/test_utils_compatibility_tags.py @@ -1,4 +1,5 @@ import sysconfig +from typing import Any, Callable, Dict, List, Tuple from unittest.mock import patch import pytest @@ -19,26 +20,26 @@ ((3, 10), "310"), ], ) -def test_version_info_to_nodot(version_info, expected): +def test_version_info_to_nodot(version_info: Tuple[int], expected: str) -> None: actual = compatibility_tags.version_info_to_nodot(version_info) assert actual == expected class Testcompatibility_tags: - def mock_get_config_var(self, **kwd): + def mock_get_config_var(self, **kwd: str) -> Callable[[str], Any]: """ Patch sysconfig.get_config_var for arbitrary keys. """ get_config_var = sysconfig.get_config_var - def _mock_get_config_var(var): + def _mock_get_config_var(var: str) -> Any: if var in kwd: return kwd[var] return get_config_var(var) return _mock_get_config_var - def test_no_hyphen_tag(self): + def test_no_hyphen_tag(self) -> None: """ Test that no tag contains a hyphen. """ @@ -63,11 +64,13 @@ class TestManylinux2010Tags: ("manylinux2010_i686", "manylinux1_i686"), ], ) - def test_manylinux2010_implies_manylinux1(self, manylinux2010, manylinux1): + def test_manylinux2010_implies_manylinux1( + self, manylinux2010: str, manylinux1: str + ) -> None: """ Specifying manylinux2010 implies manylinux1. """ - groups = {} + groups: Dict[Tuple[str, str], List[str]] = {} supported = compatibility_tags.get_supported(platforms=[manylinux2010]) for tag in supported: groups.setdefault((tag.interpreter, tag.abi), []).append(tag.platform) @@ -86,11 +89,13 @@ class TestManylinux2014Tags: ("manylinux2014_i686", ["manylinux2010_i686", "manylinux1_i686"]), ], ) - def test_manylinuxA_implies_manylinuxB(self, manylinuxA, manylinuxB): + def test_manylinuxA_implies_manylinuxB( + self, manylinuxA: str, manylinuxB: List[str] + ) -> None: """ Specifying manylinux2014 implies manylinux2010/manylinux1. """ - groups = {} + groups: Dict[Tuple[str, str], List[str]] = {} supported = compatibility_tags.get_supported(platforms=[manylinuxA]) for tag in supported: groups.setdefault((tag.interpreter, tag.abi), []).append(tag.platform) diff --git a/tests/unit/test_utils_distutils_args.py b/tests/unit/test_utils_distutils_args.py index cf648b10511..e63c565a12f 100644 --- a/tests/unit/test_utils_distutils_args.py +++ b/tests/unit/test_utils_distutils_args.py @@ -3,30 +3,30 @@ from pip._internal.utils.distutils_args import parse_distutils_args -def test_unknown_option_is_ok(): +def test_unknown_option_is_ok() -> None: result = parse_distutils_args(["--foo"]) assert not result -def test_option_is_returned(): +def test_option_is_returned() -> None: result = parse_distutils_args(["--prefix=hello"]) assert result["prefix"] == "hello" -def test_options_are_clobbered(): +def test_options_are_clobbered() -> None: # Matches the current setuptools behavior that the last argument # wins. result = parse_distutils_args(["--prefix=hello", "--prefix=world"]) assert result["prefix"] == "world" -def test_multiple_options_work(): +def test_multiple_options_work() -> None: result = parse_distutils_args(["--prefix=hello", "--root=world"]) assert result["prefix"] == "hello" assert result["root"] == "world" -def test_multiple_invocations_do_not_keep_options(): +def test_multiple_invocations_do_not_keep_options() -> None: result = parse_distutils_args(["--prefix=hello1"]) assert len(result) == 1 assert result["prefix"] == "hello1" @@ -52,12 +52,12 @@ def test_multiple_invocations_do_not_keep_options(): ("root", "11"), ], ) -def test_all_value_options_work(name, value): +def test_all_value_options_work(name: str, value: str) -> None: result = parse_distutils_args([f"--{name}={value}"]) key_name = name.replace("-", "_") assert result[key_name] == value -def test_user_option_works(): +def test_user_option_works() -> None: result = parse_distutils_args(["--user"]) assert result["user"] == 1 diff --git a/tests/unit/test_utils_filesystem.py b/tests/unit/test_utils_filesystem.py index c7b3b90d4e8..b15c3141ad0 100644 --- a/tests/unit/test_utils_filesystem.py +++ b/tests/unit/test_utils_filesystem.py @@ -1,5 +1,6 @@ import os import shutil +from typing import Callable, Type import pytest @@ -8,21 +9,21 @@ from tests.lib.path import Path -def make_file(path): +def make_file(path: str) -> None: Path(path).touch() -def make_valid_symlink(path): +def make_valid_symlink(path: str) -> None: target = path + "1" make_file(target) os.symlink(target, path) -def make_broken_symlink(path): +def make_broken_symlink(path: str) -> None: os.symlink("foo", path) -def make_dir(path): +def make_dir(path: str) -> None: os.mkdir(path) @@ -40,7 +41,7 @@ def make_dir(path): (make_dir, False), ], ) -def test_is_socket(create, result, tmpdir): +def test_is_socket(create: Callable[[str], None], result: bool, tmpdir: Path) -> None: target = tmpdir.joinpath("target") create(target) assert os.path.lexists(target) @@ -54,7 +55,9 @@ def test_is_socket(create, result, tmpdir): (make_unreadable_file, OSError), ], ) -def test_copy2_fixed_raises_appropriate_errors(create, error_type, tmpdir): +def test_copy2_fixed_raises_appropriate_errors( + create: Callable[[str], None], error_type: Type[Exception], tmpdir: Path +) -> None: src = tmpdir.joinpath("src") create(src) dest = tmpdir.joinpath("dest") diff --git a/tests/unit/test_utils_parallel.py b/tests/unit/test_utils_parallel.py index 8e6e26b8de4..b6c3e1fbfaa 100644 --- a/tests/unit/test_utils_parallel.py +++ b/tests/unit/test_utils_parallel.py @@ -4,8 +4,9 @@ from importlib import import_module from math import factorial from sys import modules +from typing import Any, Iterator -from pytest import mark +import pytest DUNDER_IMPORT = "builtins.__import__" FUNC, ITERABLE = factorial, range(42) @@ -13,7 +14,7 @@ _import = __import__ -def unload_parallel(): +def unload_parallel() -> None: try: del modules["pip._internal.utils.parallel"] except KeyError: @@ -21,7 +22,7 @@ def unload_parallel(): @contextmanager -def tmp_import_parallel(): +def tmp_import_parallel() -> Iterator[Any]: unload_parallel() try: yield import_module("pip._internal.utils.parallel") @@ -29,24 +30,24 @@ def tmp_import_parallel(): unload_parallel() -def lack_sem_open(name, *args, **kwargs): +def lack_sem_open(name: str, *args: Any, **kwargs: Any) -> Any: """Raise ImportError on import of multiprocessing.synchronize.""" if name.endswith("synchronize"): raise ImportError return _import(name, *args, **kwargs) -def have_sem_open(name, *args, **kwargs): +def have_sem_open(name: str, *args: Any, **kwargs: Any) -> Any: """Make sure multiprocessing.synchronize import is successful.""" # We don't care about the return value # since we don't use the pool with this import. if name.endswith("synchronize"): - return + return None return _import(name, *args, **kwargs) -@mark.parametrize("name", MAPS) -def test_lack_sem_open(name, monkeypatch): +@pytest.mark.parametrize("name", MAPS) +def test_lack_sem_open(name: str, monkeypatch: pytest.MonkeyPatch) -> None: """Test fallback when sem_open is not available. If so, multiprocessing[.dummy].Pool will fail to be created and @@ -57,16 +58,16 @@ def test_lack_sem_open(name, monkeypatch): assert getattr(parallel, name) is parallel._map_fallback -@mark.parametrize("name", MAPS) -def test_have_sem_open(name, monkeypatch): +@pytest.mark.parametrize("name", MAPS) +def test_have_sem_open(name: str, monkeypatch: pytest.MonkeyPatch) -> None: """Test fallback when sem_open is available.""" monkeypatch.setattr(DUNDER_IMPORT, have_sem_open) with tmp_import_parallel() as parallel: assert getattr(parallel, name) is getattr(parallel, f"_{name}") -@mark.parametrize("name", MAPS) -def test_map(name): +@pytest.mark.parametrize("name", MAPS) +def test_map(name: str) -> None: """Test correctness of result of asynchronous maps.""" map_async = getattr(import_module("pip._internal.utils.parallel"), name) assert set(map_async(FUNC, ITERABLE)) == set(map(FUNC, ITERABLE)) diff --git a/tests/unit/test_utils_pkg_resources.py b/tests/unit/test_utils_pkg_resources.py index 37e49fa51d6..a4bb9349384 100644 --- a/tests/unit/test_utils_pkg_resources.py +++ b/tests/unit/test_utils_pkg_resources.py @@ -11,7 +11,7 @@ from pip._internal.utils.pkg_resources import DictMetadata -def test_dict_metadata_works(): +def test_dict_metadata_works() -> None: name = "simple" version = "0.1.0" require_a = "a==1.0" @@ -48,7 +48,7 @@ def test_dict_metadata_works(): assert SpecifierSet(requires_python) == dist.requires_python -def test_dict_metadata_throws_on_bad_unicode(): +def test_dict_metadata_throws_on_bad_unicode() -> None: metadata = DictMetadata({"METADATA": b"\xff"}) with pytest.raises(UnicodeDecodeError) as e: diff --git a/tests/unit/test_utils_subprocess.py b/tests/unit/test_utils_subprocess.py index 9c7cd03772c..5d0a9ba8c3b 100644 --- a/tests/unit/test_utils_subprocess.py +++ b/tests/unit/test_utils_subprocess.py @@ -2,6 +2,7 @@ import sys from logging import DEBUG, ERROR, INFO, WARNING from textwrap import dedent +from typing import List, Optional, Tuple, Type import pytest @@ -10,6 +11,7 @@ from pip._internal.utils.logging import VERBOSE from pip._internal.utils.misc import hide_value from pip._internal.utils.subprocess import ( + CommandArgs, call_subprocess, format_command_args, make_command, @@ -33,12 +35,12 @@ ), ], ) -def test_format_command_args(args, expected): +def test_format_command_args(args: CommandArgs, expected: str) -> None: actual = format_command_args(args) assert actual == expected -def test_make_subprocess_output_error(): +def test_make_subprocess_output_error() -> None: cmd_args = ["test", "has space"] cwd = "/path/to/cwd" lines = ["line1\n", "line2\n", "line3\n"] @@ -62,7 +64,9 @@ def test_make_subprocess_output_error(): assert actual == expected, f"actual: {actual}" -def test_make_subprocess_output_error__non_ascii_command_arg(monkeypatch): +def test_make_subprocess_output_error__non_ascii_command_arg( + monkeypatch: pytest.MonkeyPatch, +) -> None: """ Test a command argument with a non-ascii character. """ @@ -87,7 +91,7 @@ def test_make_subprocess_output_error__non_ascii_command_arg(monkeypatch): assert actual == expected, f"actual: {actual}" -def test_make_subprocess_output_error__non_ascii_cwd_python_3(): +def test_make_subprocess_output_error__non_ascii_cwd_python_3() -> None: """ Test a str (text) cwd with a non-ascii character in Python 3. """ @@ -111,7 +115,7 @@ def test_make_subprocess_output_error__non_ascii_cwd_python_3(): # This test is mainly important for checking unicode in Python 2. -def test_make_subprocess_output_error__non_ascii_line(): +def test_make_subprocess_output_error__non_ascii_line() -> None: """ Test a line with a non-ascii character. """ @@ -141,7 +145,12 @@ def test_make_subprocess_output_error__non_ascii_line(): (False, ("out\nerr\n", "out\r\nerr\r\n", "err\nout\n", "err\r\nout\r\n")), ], ) -def test_call_subprocess_stdout_only(capfd, monkeypatch, stdout_only, expected): +def test_call_subprocess_stdout_only( + capfd: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, + stdout_only: bool, + expected: Tuple[str, ...], +) -> None: log = [] monkeypatch.setattr( subprocess_logger, @@ -167,14 +176,14 @@ def test_call_subprocess_stdout_only(capfd, monkeypatch, stdout_only, expected): class FakeSpinner(SpinnerInterface): - def __init__(self): + def __init__(self) -> None: self.spin_count = 0 - self.final_status = None + self.final_status: Optional[str] = None - def spin(self): + def spin(self) -> None: self.spin_count += 1 - def finish(self, final_status): + def finish(self, final_status: str) -> None: self.final_status = final_status @@ -186,14 +195,14 @@ class TestCallSubprocess: def check_result( self, - capfd, - caplog, - log_level, - spinner, - result, - expected, - expected_spinner, - ): + capfd: pytest.CaptureFixture[str], + caplog: pytest.LogCaptureFixture, + log_level: int, + spinner: FakeSpinner, + result: Optional[str], + expected: Tuple[Optional[List[str]], List[Tuple[str, int, str]]], + expected_spinner: Tuple[int, Optional[str]], + ) -> None: """ Check the result of calling call_subprocess(). @@ -215,6 +224,7 @@ def check_result( if expected_proc is None: assert result is None else: + assert result is not None assert result.splitlines() == expected_proc # Confirm that stdout and stderr haven't been written to. @@ -238,7 +248,12 @@ def check_result( assert (spinner.spin_count, spinner.final_status) == expected_spinner - def prepare_call(self, caplog, log_level, command=None): + def prepare_call( + self, + caplog: pytest.LogCaptureFixture, + log_level: int, + command: Optional[str] = None, + ) -> Tuple[List[str], FakeSpinner]: if command is None: command = 'print("Hello"); print("world")' @@ -248,7 +263,9 @@ def prepare_call(self, caplog, log_level, command=None): return (args, spinner) - def test_debug_logging(self, capfd, caplog): + def test_debug_logging( + self, capfd: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture + ) -> None: """ Test DEBUG logging (and without passing show_stdout=True). """ @@ -276,7 +293,9 @@ def test_debug_logging(self, capfd, caplog): expected_spinner=(0, None), ) - def test_info_logging(self, capfd, caplog): + def test_info_logging( + self, capfd: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture + ) -> None: """ Test INFO logging (and without passing show_stdout=True). """ @@ -284,7 +303,10 @@ def test_info_logging(self, capfd, caplog): args, spinner = self.prepare_call(caplog, log_level) result = call_subprocess(args, spinner=spinner) - expected = (["Hello", "world"], []) + expected: Tuple[List[str], List[Tuple[str, int, str]]] = ( + ["Hello", "world"], + [], + ) # The spinner should spin twice in this case since the subprocess # output isn't being written to the console. self.check_result( @@ -297,7 +319,9 @@ def test_info_logging(self, capfd, caplog): expected_spinner=(2, "done"), ) - def test_info_logging__subprocess_error(self, capfd, caplog): + def test_info_logging__subprocess_error( + self, capfd: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture + ) -> None: """ Test INFO logging of a subprocess with an error (and without passing show_stdout=True). @@ -358,7 +382,9 @@ def test_info_logging__subprocess_error(self, capfd, caplog): assert command_line.startswith(" command: ") assert command_line.endswith('print("world"); exit("fail")\'') - def test_info_logging_with_show_stdout_true(self, capfd, caplog): + def test_info_logging_with_show_stdout_true( + self, capfd: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture + ) -> None: """ Test INFO logging with show_stdout=True. """ @@ -410,13 +436,13 @@ def test_info_logging_with_show_stdout_true(self, capfd, caplog): ) def test_spinner_finish( self, - exit_status, - show_stdout, - extra_ok_returncodes, - log_level, - caplog, - expected, - ): + exit_status: int, + show_stdout: bool, + extra_ok_returncodes: Optional[Tuple[int, ...]], + log_level: int, + caplog: pytest.LogCaptureFixture, + expected: Tuple[Optional[Type[Exception]], Optional[str], int], + ) -> None: """ Test that the spinner finishes correctly. """ @@ -426,6 +452,7 @@ def test_spinner_finish( command = f'print("Hello"); print("world"); exit({exit_status})' args, spinner = self.prepare_call(caplog, log_level, command=command) + exc_type: Optional[Type[Exception]] try: call_subprocess( args, @@ -442,7 +469,7 @@ def test_spinner_finish( assert spinner.final_status == expected_final_status assert spinner.spin_count == expected_spin_count - def test_closes_stdin(self): + def test_closes_stdin(self) -> None: with pytest.raises(InstallationSubprocessError): call_subprocess( [sys.executable, "-c", "input()"], @@ -450,7 +477,7 @@ def test_closes_stdin(self): ) -def test_unicode_decode_error(caplog): +def test_unicode_decode_error(caplog: pytest.LogCaptureFixture) -> None: if locale.getpreferredencoding() != "UTF-8": pytest.skip("locale.getpreferredencoding() is not UTF-8") caplog.set_level(INFO) diff --git a/tests/unit/test_utils_temp_dir.py b/tests/unit/test_utils_temp_dir.py index 187d449a2b0..6b3571ff71c 100644 --- a/tests/unit/test_utils_temp_dir.py +++ b/tests/unit/test_utils_temp_dir.py @@ -2,6 +2,7 @@ import os import stat import tempfile +from typing import Any, Iterator, Optional, Union import pytest @@ -10,15 +11,17 @@ from pip._internal.utils.temp_dir import ( AdjacentTempDirectory, TempDirectory, + _Default, _default, global_tempdir_manager, tempdir_registry, ) +from tests.lib.path import Path # No need to test symlinked directories on Windows @pytest.mark.skipif("sys.platform == 'win32'") -def test_symlinked_path(): +def test_symlinked_path() -> None: with TempDirectory() as tmp_dir: assert os.path.exists(tmp_dir.path) @@ -35,14 +38,14 @@ def test_symlinked_path(): assert not os.path.exists(tmp_dir.path) -def test_deletes_readonly_files(): - def create_file(*args): +def test_deletes_readonly_files() -> None: + def create_file(*args: str) -> None: fpath = os.path.join(*args) ensure_dir(os.path.dirname(fpath)) with open(fpath, "w") as f: f.write("Holla!") - def readonly_file(*args): + def readonly_file(*args: str) -> None: fpath = os.path.join(*args) os.chmod(fpath, stat.S_IREAD) @@ -56,7 +59,7 @@ def readonly_file(*args): readonly_file(tmp_dir.path, "subfolder", "readonly-file") -def test_path_access_after_context_raises(): +def test_path_access_after_context_raises() -> None: with TempDirectory() as tmp_dir: path = tmp_dir.path @@ -66,7 +69,7 @@ def test_path_access_after_context_raises(): assert path in str(e.value) -def test_path_access_after_clean_raises(): +def test_path_access_after_clean_raises() -> None: tmp_dir = TempDirectory() path = tmp_dir.path tmp_dir.cleanup() @@ -77,7 +80,7 @@ def test_path_access_after_clean_raises(): assert path in str(e.value) -def test_create_and_cleanup_work(): +def test_create_and_cleanup_work() -> None: tmp_dir = TempDirectory() created_path = tmp_dir.path @@ -101,8 +104,8 @@ def test_create_and_cleanup_work(): "2", ], ) -def test_adjacent_directory_names(name): - def names(): +def test_adjacent_directory_names(name: str) -> None: + def names() -> Iterator[str]: return AdjacentTempDirectory._generate_names(name) chars = AdjacentTempDirectory.LEADING_CHARS @@ -162,7 +165,7 @@ def names(): "_package", ], ) -def test_adjacent_directory_exists(name, tmpdir): +def test_adjacent_directory_exists(name: str, tmpdir: Path) -> None: block_name, expect_name = itertools.islice( AdjacentTempDirectory._generate_names(name), 2 ) @@ -177,10 +180,10 @@ def test_adjacent_directory_exists(name, tmpdir): assert expect_name == os.path.split(atmp_dir.path)[1] -def test_adjacent_directory_permission_error(monkeypatch): +def test_adjacent_directory_permission_error(monkeypatch: pytest.MonkeyPatch) -> None: name = "ABC" - def raising_mkdir(*args, **kwargs): + def raising_mkdir(*args: Any, **kwargs: Any) -> None: raise OSError("Unknown OSError") with TempDirectory() as tmp_dir: @@ -194,7 +197,7 @@ def raising_mkdir(*args, **kwargs): pass -def test_global_tempdir_manager(): +def test_global_tempdir_manager() -> None: with global_tempdir_manager(): d = TempDirectory(globally_managed=True) path = d.path @@ -202,7 +205,7 @@ def test_global_tempdir_manager(): assert not os.path.exists(path) -def test_tempdirectory_asserts_global_tempdir(monkeypatch): +def test_tempdirectory_asserts_global_tempdir(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(temp_dir, "_tempdir_manager", None) with pytest.raises(AssertionError): TempDirectory(globally_managed=True) @@ -229,7 +232,9 @@ def test_tempdirectory_asserts_global_tempdir(monkeypatch): (False, "unspecified", True), ], ) -def test_tempdir_registry(kind, delete, exists): +def test_tempdir_registry( + delete: Union[bool, _Default], kind: str, exists: bool +) -> None: with tempdir_registry() as registry: registry.set_delete(deleted_kind, True) registry.set_delete(not_deleted_kind, False) @@ -241,7 +246,9 @@ def test_tempdir_registry(kind, delete, exists): @pytest.mark.parametrize("delete,exists", [(_default, True), (None, False)]) -def test_temp_dir_does_not_delete_explicit_paths_by_default(tmpdir, delete, exists): +def test_temp_dir_does_not_delete_explicit_paths_by_default( + tmpdir: Path, delete: Optional[_Default], exists: bool +) -> None: path = tmpdir / "example" path.mkdir() @@ -255,7 +262,7 @@ def test_temp_dir_does_not_delete_explicit_paths_by_default(tmpdir, delete, exis @pytest.mark.parametrize("should_delete", [True, False]) -def test_tempdir_registry_lazy(should_delete): +def test_tempdir_registry_lazy(should_delete: bool) -> None: """ Test the registry entry can be updated after a temp dir is created, to change whether a kind should be deleted or not. diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 4ea34c757f6..ccb7a304925 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -1,3 +1,4 @@ +import io import os import shutil import stat @@ -6,11 +7,14 @@ import tempfile import time import zipfile +from typing import List, Tuple import pytest from pip._internal.exceptions import InstallationError from pip._internal.utils.unpacking import is_within_directory, untar_file, unzip_file +from tests.lib import TestData +from tests.lib.path import Path class TestUnpackArchives: @@ -33,19 +37,19 @@ class TestUnpackArchives: """ - def setup(self): + def setup(self) -> None: self.tempdir = tempfile.mkdtemp() self.old_mask = os.umask(0o022) self.symlink_expected_mode = None - def teardown(self): + def teardown(self) -> None: os.umask(self.old_mask) shutil.rmtree(self.tempdir, ignore_errors=True) - def mode(self, path): + def mode(self, path: str) -> int: return stat.S_IMODE(os.stat(path).st_mode) - def confirm_files(self): + def confirm_files(self) -> None: # expectations based on 022 umask set above and the unpack logic that # sets execute permissions, not preservation for fname, expected_mode, test, expected_contents in [ @@ -76,7 +80,7 @@ def confirm_files(self): mode == expected_mode ), f"mode: {mode}, expected mode: {expected_mode}" - def make_zip_file(self, filename, file_list): + def make_zip_file(self, filename: str, file_list: List[str]) -> str: """ Create a zip file for test case """ @@ -86,7 +90,7 @@ def make_zip_file(self, filename, file_list): myzip.writestr(item, "file content") return test_zip - def make_tar_file(self, filename, file_list): + def make_tar_file(self, filename: str, file_list: List[str]) -> str: """ Create a tar file for test case """ @@ -94,10 +98,10 @@ def make_tar_file(self, filename, file_list): with tarfile.open(test_tar, "w") as mytar: for item in file_list: file_tarinfo = tarfile.TarInfo(item) - mytar.addfile(file_tarinfo, "file content") + mytar.addfile(file_tarinfo, io.BytesIO(b"file content")) return test_tar - def test_unpack_tgz(self, data): + def test_unpack_tgz(self, data: TestData) -> None: """ Test unpacking a *.tgz, and setting execute permissions """ @@ -109,7 +113,7 @@ def test_unpack_tgz(self, data): mtime = time.gmtime(os.stat(file_txt_path).st_mtime) assert mtime[0:6] == (2013, 8, 16, 5, 13, 37), mtime - def test_unpack_zip(self, data): + def test_unpack_zip(self, data: TestData) -> None: """ Test unpacking a *.zip, and setting execute permissions """ @@ -117,7 +121,7 @@ def test_unpack_zip(self, data): unzip_file(test_file, self.tempdir) self.confirm_files() - def test_unpack_zip_failure(self): + def test_unpack_zip_failure(self) -> None: """ Test unpacking a *.zip with file containing .. path and expect exception @@ -128,7 +132,7 @@ def test_unpack_zip_failure(self): unzip_file(test_zip, self.tempdir) assert "trying to install outside target directory" in str(e.value) - def test_unpack_zip_success(self): + def test_unpack_zip_success(self) -> None: """ Test unpacking a *.zip with regular files, no file will be installed outside target directory after unpack @@ -142,7 +146,7 @@ def test_unpack_zip_success(self): test_zip = self.make_zip_file("test_zip.zip", files) unzip_file(test_zip, self.tempdir) - def test_unpack_tar_failure(self): + def test_unpack_tar_failure(self) -> None: """ Test unpacking a *.tar with file containing .. path and expect exception @@ -153,7 +157,7 @@ def test_unpack_tar_failure(self): untar_file(test_tar, self.tempdir) assert "trying to install outside target directory" in str(e.value) - def test_unpack_tar_success(self): + def test_unpack_tar_success(self) -> None: """ Test unpacking a *.tar with regular files, no file will be installed outside target directory after unpack @@ -168,12 +172,12 @@ def test_unpack_tar_success(self): untar_file(test_tar, self.tempdir) -def test_unpack_tar_unicode(tmpdir): +def test_unpack_tar_unicode(tmpdir: Path) -> None: test_tar = tmpdir / "test.tar" # tarfile tries to decode incoming with tarfile.open(test_tar, "w", format=tarfile.PAX_FORMAT, encoding="utf-8") as f: metadata = tarfile.TarInfo("dir/åäö_日本語.py") - f.addfile(metadata, "hello world") + f.addfile(metadata, io.BytesIO(b"hello world")) output_dir = tmpdir / "output" output_dir.mkdir() @@ -200,6 +204,6 @@ def test_unpack_tar_unicode(tmpdir): (("parent/", "parent/../sub"), False), ], ) -def test_is_within_directory(args, expected): +def test_is_within_directory(args: Tuple[str, str], expected: bool) -> None: result = is_within_directory(*args) assert result == expected diff --git a/tests/unit/test_utils_virtualenv.py b/tests/unit/test_utils_virtualenv.py index 207c087a823..8f517d24d36 100644 --- a/tests/unit/test_utils_virtualenv.py +++ b/tests/unit/test_utils_virtualenv.py @@ -1,10 +1,12 @@ import logging import site import sys +from typing import List, Optional import pytest from pip._internal.utils import virtualenv +from tests.lib.path import Path @pytest.mark.parametrize( @@ -21,7 +23,12 @@ ("not_sys_prefix", "not_sys_prefix", True), # Unknown case ], ) -def test_running_under_virtualenv(monkeypatch, real_prefix, base_prefix, expected): +def test_running_under_virtualenv( + monkeypatch: pytest.MonkeyPatch, + real_prefix: Optional[str], + base_prefix: Optional[str], + expected: bool, +) -> None: # Use raising=False to prevent AttributeError on missing attribute if real_prefix is None: monkeypatch.delattr(sys, "real_prefix", raising=False) @@ -44,12 +51,12 @@ def test_running_under_virtualenv(monkeypatch, real_prefix, base_prefix, expecte ], ) def test_virtualenv_no_global_with_regular_virtualenv( - monkeypatch, - tmpdir, - under_virtualenv, - no_global_file, - expected, -): + monkeypatch: pytest.MonkeyPatch, + tmpdir: Path, + under_virtualenv: bool, + no_global_file: bool, + expected: bool, +) -> None: monkeypatch.setattr(virtualenv, "_running_under_venv", lambda: False) monkeypatch.setattr(site, "__file__", tmpdir / "site.py") @@ -92,13 +99,13 @@ def test_virtualenv_no_global_with_regular_virtualenv( ], ) def test_virtualenv_no_global_with_pep_405_virtual_environment( - monkeypatch, - caplog, - pyvenv_cfg_lines, - under_venv, - expected, - expect_warning, -): + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, + pyvenv_cfg_lines: Optional[List[str]], + under_venv: bool, + expected: bool, + expect_warning: bool, +) -> None: monkeypatch.setattr(virtualenv, "_running_under_regular_virtualenv", lambda: False) monkeypatch.setattr(virtualenv, "_get_pyvenv_cfg_lines", lambda: pyvenv_cfg_lines) monkeypatch.setattr(virtualenv, "_running_under_venv", lambda: under_venv) @@ -125,11 +132,11 @@ def test_virtualenv_no_global_with_pep_405_virtual_environment( ], ) def test_get_pyvenv_cfg_lines_for_pep_405_virtual_environment( - monkeypatch, - tmpdir, - contents, - expected, -): + monkeypatch: pytest.MonkeyPatch, + tmpdir: Path, + contents: Optional[str], + expected: Optional[List[str]], +) -> None: monkeypatch.setattr(sys, "prefix", str(tmpdir)) if contents is not None: tmpdir.joinpath("pyvenv.cfg").write_text(contents) diff --git a/tests/unit/test_utils_wheel.py b/tests/unit/test_utils_wheel.py index 89409ae822f..53e149f9493 100644 --- a/tests/unit/test_utils_wheel.py +++ b/tests/unit/test_utils_wheel.py @@ -2,17 +2,21 @@ from contextlib import ExitStack from email import message_from_string from io import BytesIO +from typing import Callable, Iterator from zipfile import ZipFile import pytest from pip._internal.exceptions import UnsupportedWheel from pip._internal.utils import wheel +from tests.lib import TestData from tests.lib.path import Path +_ZipDir = Callable[[Path], ZipFile] + @pytest.fixture -def zip_dir(): +def zip_dir() -> Iterator[_ZipDir]: def make_zip(path: Path) -> ZipFile: buf = BytesIO() with ZipFile(buf, "w", allowZip64=True) as z: @@ -32,7 +36,7 @@ def make_zip(path: Path) -> ZipFile: yield make_zip -def test_wheel_dist_info_dir_found(tmpdir, zip_dir): +def test_wheel_dist_info_dir_found(tmpdir: Path, zip_dir: _ZipDir) -> None: expected = "simple-0.1.dist-info" dist_info_dir = tmpdir / expected dist_info_dir.mkdir() @@ -40,7 +44,7 @@ def test_wheel_dist_info_dir_found(tmpdir, zip_dir): assert wheel.wheel_dist_info_dir(zip_dir(tmpdir), "simple") == expected -def test_wheel_dist_info_dir_multiple(tmpdir, zip_dir): +def test_wheel_dist_info_dir_multiple(tmpdir: Path, zip_dir: _ZipDir) -> None: dist_info_dir_1 = tmpdir / "simple-0.1.dist-info" dist_info_dir_1.mkdir() dist_info_dir_1.joinpath("WHEEL").touch() @@ -52,13 +56,13 @@ def test_wheel_dist_info_dir_multiple(tmpdir, zip_dir): assert "multiple .dist-info directories found" in str(e.value) -def test_wheel_dist_info_dir_none(tmpdir, zip_dir): +def test_wheel_dist_info_dir_none(tmpdir: Path, zip_dir: _ZipDir) -> None: with pytest.raises(UnsupportedWheel) as e: wheel.wheel_dist_info_dir(zip_dir(tmpdir), "simple") assert "directory not found" in str(e.value) -def test_wheel_dist_info_dir_wrong_name(tmpdir, zip_dir): +def test_wheel_dist_info_dir_wrong_name(tmpdir: Path, zip_dir: _ZipDir) -> None: dist_info_dir = tmpdir / "unrelated-0.1.dist-info" dist_info_dir.mkdir() dist_info_dir.joinpath("WHEEL").touch() @@ -67,11 +71,11 @@ def test_wheel_dist_info_dir_wrong_name(tmpdir, zip_dir): assert "does not start with 'simple'" in str(e.value) -def test_wheel_version_ok(data): +def test_wheel_version_ok(data: TestData) -> None: assert wheel.wheel_version(message_from_string("Wheel-Version: 1.9")) == (1, 9) -def test_wheel_metadata_fails_missing_wheel(tmpdir, zip_dir): +def test_wheel_metadata_fails_missing_wheel(tmpdir: Path, zip_dir: _ZipDir) -> None: dist_info_dir = tmpdir / "simple-0.1.0.dist-info" dist_info_dir.mkdir() dist_info_dir.joinpath("METADATA").touch() @@ -81,7 +85,7 @@ def test_wheel_metadata_fails_missing_wheel(tmpdir, zip_dir): assert "could not read" in str(e.value) -def test_wheel_metadata_fails_on_bad_encoding(tmpdir, zip_dir): +def test_wheel_metadata_fails_on_bad_encoding(tmpdir: Path, zip_dir: _ZipDir) -> None: dist_info_dir = tmpdir / "simple-0.1.0.dist-info" dist_info_dir.mkdir() dist_info_dir.joinpath("METADATA").touch() @@ -92,7 +96,7 @@ def test_wheel_metadata_fails_on_bad_encoding(tmpdir, zip_dir): assert "error decoding" in str(e.value) -def test_wheel_version_fails_on_no_wheel_version(): +def test_wheel_version_fails_on_no_wheel_version() -> None: with pytest.raises(UnsupportedWheel) as e: wheel.wheel_version(message_from_string("")) assert "missing Wheel-Version" in str(e.value) @@ -106,13 +110,13 @@ def test_wheel_version_fails_on_no_wheel_version(): ("1.",), ], ) -def test_wheel_version_fails_on_bad_wheel_version(version): +def test_wheel_version_fails_on_bad_wheel_version(version: str) -> None: with pytest.raises(UnsupportedWheel) as e: wheel.wheel_version(message_from_string(f"Wheel-Version: {version}")) assert "invalid Wheel-Version" in str(e.value) -def test_check_compatibility(): +def test_check_compatibility() -> None: name = "test" vc = wheel.VERSION_COMPATIBLE diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py index 0f41ba1d7c1..67e3273c789 100644 --- a/tests/unit/test_vcs.py +++ b/tests/unit/test_vcs.py @@ -1,12 +1,13 @@ import os import pathlib -from unittest import TestCase -from unittest.mock import patch +from typing import Any, Dict, List, Optional, Tuple, Type +from unittest import TestCase, mock import pytest from pip._internal.exceptions import BadCommand, InstallationError -from pip._internal.utils.misc import hide_url, hide_value +from pip._internal.utils.misc import HiddenText, hide_url, hide_value +from pip._internal.utils.subprocess import CommandArgs from pip._internal.vcs import make_vcs_requirement_url from pip._internal.vcs.bazaar import Bazaar from pip._internal.vcs.git import Git, RemoteNotValidError, looks_like_hash @@ -14,12 +15,13 @@ from pip._internal.vcs.subversion import Subversion from pip._internal.vcs.versioncontrol import RevOptions, VersionControl from tests.lib import is_svn_installed, need_svn +from tests.lib.path import Path @pytest.mark.skipif( "CI" not in os.environ, reason="Subversion is only required under CI" ) -def test_ensure_svn_available(): +def test_ensure_svn_available() -> None: """Make sure that svn is available when running in CI.""" assert is_svn_installed() @@ -49,12 +51,12 @@ def test_ensure_svn_available(): ), ], ) -def test_make_vcs_requirement_url(args, expected): +def test_make_vcs_requirement_url(args: Tuple[Any, ...], expected: str) -> None: actual = make_vcs_requirement_url(*args) assert actual == expected -def test_rev_options_repr(): +def test_rev_options_repr() -> None: rev_options = RevOptions(Git, "develop") assert repr(rev_options) == "" @@ -76,7 +78,12 @@ def test_rev_options_repr(): ), ], ) -def test_rev_options_to_args(vc_class, expected1, expected2, kwargs): +def test_rev_options_to_args( + vc_class: Type[VersionControl], + expected1: List[str], + expected2: List[str], + kwargs: Dict[str, Any], +) -> None: """ Test RevOptions.to_args(). """ @@ -84,7 +91,7 @@ def test_rev_options_to_args(vc_class, expected1, expected2, kwargs): assert RevOptions(vc_class, "123", **kwargs).to_args() == expected2 -def test_rev_options_to_display(): +def test_rev_options_to_display() -> None: """ Test RevOptions.to_display(). """ @@ -97,7 +104,7 @@ def test_rev_options_to_display(): assert rev_options.to_display() == " (to revision master)" -def test_rev_options_make_new(): +def test_rev_options_make_new() -> None: """ Test RevOptions.make_new(). """ @@ -124,7 +131,7 @@ def test_rev_options_make_new(): ((41 * "a"), False), ], ) -def test_looks_like_hash(sha, expected): +def test_looks_like_hash(sha: str, expected: bool) -> None: assert looks_like_hash(sha) == expected @@ -142,7 +149,9 @@ def test_looks_like_hash(sha, expected): (Subversion, "svn://example.com/MyProject", True), ], ) -def test_should_add_vcs_url_prefix(vcs_cls, remote_url, expected): +def test_should_add_vcs_url_prefix( + vcs_cls: Type[VersionControl], remote_url: str, expected: bool +) -> None: actual = vcs_cls.should_add_vcs_url_prefix(remote_url) assert actual == expected @@ -166,7 +175,7 @@ def test_should_add_vcs_url_prefix(vcs_cls, remote_url, expected): ("https://bob@example.com/foo", "https://bob@example.com/foo"), ], ) -def test_git_remote_url_to_pip(url, target): +def test_git_remote_url_to_pip(url: str, target: str) -> None: assert Git._git_remote_to_pip_url(url) == target @@ -180,7 +189,7 @@ def test_git_remote_url_to_pip(url, target): ("/muffle/fuffle/pufffle/fluffle.git", "posix"), ], ) -def test_paths_are_not_mistaken_for_scp_shorthand(url, platform): +def test_paths_are_not_mistaken_for_scp_shorthand(url: str, platform: str) -> None: # File paths should not be mistaken for SCP shorthand. If they do then # 'c:/piffle/wiffle' would end up as 'ssh://c/piffle/wiffle'. from pip._internal.vcs.git import SCP_REGEX @@ -192,16 +201,16 @@ def test_paths_are_not_mistaken_for_scp_shorthand(url, platform): Git._git_remote_to_pip_url(url) -def test_git_remote_local_path(tmpdir): +def test_git_remote_local_path(tmpdir: Path) -> None: path = pathlib.Path(tmpdir, "project.git") path.mkdir() # Path must exist to be recognised as a local git remote. assert Git._git_remote_to_pip_url(str(path)) == path.as_uri() -@patch("pip._internal.vcs.git.Git.get_remote_url") -@patch("pip._internal.vcs.git.Git.get_revision") -@patch("pip._internal.vcs.git.Git.get_subdirectory") +@mock.patch("pip._internal.vcs.git.Git.get_remote_url") +@mock.patch("pip._internal.vcs.git.Git.get_revision") +@mock.patch("pip._internal.vcs.git.Git.get_subdirectory") @pytest.mark.parametrize( "git_url, target_url_prefix", [ @@ -218,12 +227,12 @@ def test_git_remote_local_path(tmpdir): ) @pytest.mark.network def test_git_get_src_requirements( - mock_get_subdirectory, - mock_get_revision, - mock_get_remote_url, - git_url, - target_url_prefix, -): + mock_get_subdirectory: mock.Mock, + mock_get_revision: mock.Mock, + mock_get_remote_url: mock.Mock, + git_url: str, + target_url_prefix: str, +) -> None: sha = "5547fa909e83df8bd743d3978d6667497983a4b7" mock_get_remote_url.return_value = Git._git_remote_to_pip_url(git_url) @@ -236,30 +245,32 @@ def test_git_get_src_requirements( assert ret == target -@patch("pip._internal.vcs.git.Git.get_revision_sha") -def test_git_resolve_revision_rev_exists(get_sha_mock): +@mock.patch("pip._internal.vcs.git.Git.get_revision_sha") +def test_git_resolve_revision_rev_exists(get_sha_mock: mock.Mock) -> None: get_sha_mock.return_value = ("123456", False) - url = "git+https://git.example.com" + url = HiddenText("git+https://git.example.com", redacted="*") rev_options = Git.make_rev_options("develop") new_options = Git.resolve_revision(".", url, rev_options) assert new_options.rev == "123456" -@patch("pip._internal.vcs.git.Git.get_revision_sha") -def test_git_resolve_revision_rev_not_found(get_sha_mock): +@mock.patch("pip._internal.vcs.git.Git.get_revision_sha") +def test_git_resolve_revision_rev_not_found(get_sha_mock: mock.Mock) -> None: get_sha_mock.return_value = (None, False) - url = "git+https://git.example.com" + url = HiddenText("git+https://git.example.com", redacted="*") rev_options = Git.make_rev_options("develop") new_options = Git.resolve_revision(".", url, rev_options) assert new_options.rev == "develop" -@patch("pip._internal.vcs.git.Git.get_revision_sha") -def test_git_resolve_revision_not_found_warning(get_sha_mock, caplog): +@mock.patch("pip._internal.vcs.git.Git.get_revision_sha") +def test_git_resolve_revision_not_found_warning( + get_sha_mock: mock.Mock, caplog: pytest.LogCaptureFixture +) -> None: get_sha_mock.return_value = (None, False) - url = "git+https://git.example.com" + url = HiddenText("git+https://git.example.com", redacted="*") sha = 40 * "a" rev_options = Git.make_rev_options(sha) @@ -290,8 +301,10 @@ def test_git_resolve_revision_not_found_warning(get_sha_mock, caplog): (None, False), ), ) -@patch("pip._internal.vcs.git.Git.get_revision") -def test_git_is_commit_id_equal(mock_get_revision, rev_name, result): +@mock.patch("pip._internal.vcs.git.Git.get_revision") +def test_git_is_commit_id_equal( + mock_get_revision: mock.Mock, rev_name: Optional[str], result: bool +) -> None: """ Test Git.is_commit_id_equal(). """ @@ -310,7 +323,9 @@ def test_git_is_commit_id_equal(mock_get_revision, rev_name, result): (("user:pass@example.com", "https"), ("user:pass@example.com", (None, None))), ], ) -def test_git__get_netloc_and_auth(args, expected): +def test_git__get_netloc_and_auth( + args: Tuple[str, str], expected: Tuple[str, Tuple[None, None]] +) -> None: """ Test VersionControl.get_netloc_and_auth(). """ @@ -337,7 +352,9 @@ def test_git__get_netloc_and_auth(args, expected): (("user:pass@example.com", "ssh"), ("user:pass@example.com", (None, None))), ], ) -def test_subversion__get_netloc_and_auth(args, expected): +def test_subversion__get_netloc_and_auth( + args: Tuple[str, str], expected: Tuple[str, Tuple[Optional[str], Optional[str]]] +) -> None: """ Test Subversion.get_netloc_and_auth(). """ @@ -346,7 +363,7 @@ def test_subversion__get_netloc_and_auth(args, expected): assert actual == expected -def test_git__get_url_rev__idempotent(): +def test_git__get_url_rev__idempotent() -> None: """ Check that Git.get_url_rev_and_auth() is idempotent for what the code calls "stub URLs" (i.e. URLs that don't contain "://"). @@ -375,7 +392,9 @@ def test_git__get_url_rev__idempotent(): ), ], ) -def test_version_control__get_url_rev_and_auth(url, expected): +def test_version_control__get_url_rev_and_auth( + url: str, expected: Tuple[str, None, Tuple[None, None]] +) -> None: """ Test the basic case of VersionControl.get_url_rev_and_auth(). """ @@ -391,7 +410,7 @@ def test_version_control__get_url_rev_and_auth(url, expected): "https://svn.example.com/My+Project", ], ) -def test_version_control__get_url_rev_and_auth__missing_plus(url): +def test_version_control__get_url_rev_and_auth__missing_plus(url: str) -> None: """ Test passing a URL to VersionControl.get_url_rev_and_auth() with a "+" missing from the scheme. @@ -409,7 +428,7 @@ def test_version_control__get_url_rev_and_auth__missing_plus(url): "git+https://github.com/MyUser/myProject.git@#egg=py_pkg", ], ) -def test_version_control__get_url_rev_and_auth__no_revision(url): +def test_version_control__get_url_rev_and_auth__no_revision(url: str) -> None: """ Test passing a URL to VersionControl.get_url_rev_and_auth() with empty revision @@ -429,16 +448,19 @@ def test_version_control__get_url_rev_and_auth__no_revision(url): ], ids=["FileNotFoundError", "PermissionError"], ) -def test_version_control__run_command__fails(vcs_cls, exc_cls, msg_re): +def test_version_control__run_command__fails( + vcs_cls: Type[VersionControl], exc_cls: Type[Exception], msg_re: str +) -> None: """ Test that ``VersionControl.run_command()`` raises ``BadCommand`` when the command is not found or when the user have no permission to execute it. The error message must contains the command name. """ - with patch("pip._internal.vcs.versioncontrol.call_subprocess") as call: + with mock.patch("pip._internal.vcs.versioncontrol.call_subprocess") as call: call.side_effect = exc_cls with pytest.raises(BadCommand, match=msg_re.format(name=vcs_cls.name)): - vcs_cls.run_command([]) + # https://github.com/python/mypy/issues/3283 + vcs_cls.run_command([]) # type: ignore[arg-type] @pytest.mark.parametrize( @@ -473,7 +495,7 @@ def test_version_control__run_command__fails(vcs_cls, exc_cls, msg_re): ), ], ) -def test_bazaar__get_url_rev_and_auth(url, expected): +def test_bazaar__get_url_rev_and_auth(url: str, expected: str) -> None: """ Test Bazaar.get_url_rev_and_auth(). """ @@ -506,7 +528,9 @@ def test_bazaar__get_url_rev_and_auth(url, expected): ), ], ) -def test_subversion__get_url_rev_and_auth(url, expected): +def test_subversion__get_url_rev_and_auth( + url: str, expected: Tuple[str, None, Tuple[Optional[str], Optional[str]]] +) -> None: """ Test Subversion.get_url_rev_and_auth(). """ @@ -524,7 +548,9 @@ def test_subversion__get_url_rev_and_auth(url, expected): ("user", hide_value("pass"), []), ], ) -def test_git__make_rev_args(username, password, expected): +def test_git__make_rev_args( + username: Optional[str], password: Optional[HiddenText], expected: CommandArgs +) -> None: """ Test VersionControl.make_rev_args(). """ @@ -544,7 +570,9 @@ def test_git__make_rev_args(username, password, expected): ), ], ) -def test_subversion__make_rev_args(username, password, expected): +def test_subversion__make_rev_args( + username: Optional[str], password: Optional[HiddenText], expected: CommandArgs +) -> None: """ Test Subversion.make_rev_args(). """ @@ -552,7 +580,7 @@ def test_subversion__make_rev_args(username, password, expected): assert actual == expected -def test_subversion__get_url_rev_options(): +def test_subversion__get_url_rev_options() -> None: """ Test Subversion.get_url_rev_options(). """ @@ -566,7 +594,7 @@ def test_subversion__get_url_rev_options(): ) -def test_get_git_version(): +def test_get_git_version() -> None: git_version = Git().get_git_version() assert git_version >= (1, 0, 0) @@ -582,10 +610,10 @@ def test_get_git_version(): (True, True, True), ], ) -@patch("sys.stdin.isatty") +@mock.patch("sys.stdin.isatty") def test_subversion__init_use_interactive( - mock_isatty, use_interactive, is_atty, expected -): + mock_isatty: mock.Mock, use_interactive: bool, is_atty: bool, expected: bool +) -> None: """ Test Subversion.__init__() with mocked sys.stdin.isatty() output. """ @@ -595,7 +623,7 @@ def test_subversion__init_use_interactive( @need_svn -def test_subversion__call_vcs_version(): +def test_subversion__call_vcs_version() -> None: """ Test Subversion.call_vcs_version() against local ``svn``. """ @@ -630,10 +658,10 @@ def test_subversion__call_vcs_version(): ("", ()), ], ) -@patch("pip._internal.vcs.subversion.Subversion.run_command") +@mock.patch("pip._internal.vcs.subversion.Subversion.run_command") def test_subversion__call_vcs_version_patched( - mock_run_command, svn_output, expected_version -): + mock_run_command: mock.Mock, svn_output: str, expected_version: Tuple[int, ...] +) -> None: """ Test Subversion.call_vcs_version() against patched output. """ @@ -642,8 +670,10 @@ def test_subversion__call_vcs_version_patched( assert version == expected_version -@patch("pip._internal.vcs.subversion.Subversion.run_command") -def test_subversion__call_vcs_version_svn_not_installed(mock_run_command): +@mock.patch("pip._internal.vcs.subversion.Subversion.run_command") +def test_subversion__call_vcs_version_svn_not_installed( + mock_run_command: mock.Mock, +) -> None: """ Test Subversion.call_vcs_version() when svn is not installed. """ @@ -661,7 +691,7 @@ def test_subversion__call_vcs_version_svn_not_installed(mock_run_command): (1, 8, 0), ], ) -def test_subversion__get_vcs_version_cached(version): +def test_subversion__get_vcs_version_cached(version: Tuple[int, ...]) -> None: """ Test Subversion.get_vcs_version() with previously cached result. """ @@ -678,8 +708,10 @@ def test_subversion__get_vcs_version_cached(version): (1, 8, 0), ], ) -@patch("pip._internal.vcs.subversion.Subversion.call_vcs_version") -def test_subversion__get_vcs_version_call_vcs(mock_call_vcs, vcs_version): +@mock.patch("pip._internal.vcs.subversion.Subversion.call_vcs_version") +def test_subversion__get_vcs_version_call_vcs( + mock_call_vcs: mock.Mock, vcs_version: Tuple[int, ...] +) -> None: """ Test Subversion.get_vcs_version() with mocked output from call_vcs_version(). @@ -704,8 +736,8 @@ def test_subversion__get_vcs_version_call_vcs(mock_call_vcs, vcs_version): ], ) def test_subversion__get_remote_call_options( - use_interactive, vcs_version, expected_options -): + use_interactive: bool, vcs_version: Tuple[int, ...], expected_options: List[str] +) -> None: """ Test Subversion.get_remote_call_options(). """ @@ -715,8 +747,8 @@ def test_subversion__get_remote_call_options( class TestSubversionArgs(TestCase): - def setUp(self): - patcher = patch("pip._internal.vcs.versioncontrol.call_subprocess") + def setUp(self) -> None: + patcher = mock.patch("pip._internal.vcs.versioncontrol.call_subprocess") self.addCleanup(patcher.stop) self.call_subprocess_mock = patcher.start() @@ -728,10 +760,10 @@ def setUp(self): self.rev_options = RevOptions(Subversion) self.dest = "/tmp/test" - def assert_call_args(self, args): + def assert_call_args(self, args: CommandArgs) -> None: assert self.call_subprocess_mock.call_args[0][0] == args - def test_obtain(self): + def test_obtain(self) -> None: self.svn.obtain(self.dest, hide_url(self.url)) self.assert_call_args( [ @@ -748,7 +780,7 @@ def test_obtain(self): ] ) - def test_fetch_new(self): + def test_fetch_new(self) -> None: self.svn.fetch_new(self.dest, hide_url(self.url), self.rev_options) self.assert_call_args( [ @@ -761,7 +793,7 @@ def test_fetch_new(self): ] ) - def test_fetch_new_revision(self): + def test_fetch_new_revision(self) -> None: rev_options = RevOptions(Subversion, "123") self.svn.fetch_new(self.dest, hide_url(self.url), rev_options) self.assert_call_args( @@ -777,7 +809,7 @@ def test_fetch_new_revision(self): ] ) - def test_switch(self): + def test_switch(self) -> None: self.svn.switch(self.dest, hide_url(self.url), self.rev_options) self.assert_call_args( [ @@ -789,7 +821,7 @@ def test_switch(self): ] ) - def test_update(self): + def test_update(self) -> None: self.svn.update(self.dest, hide_url(self.url), self.rev_options) self.assert_call_args( [ diff --git a/tests/unit/test_vcs_mercurial.py b/tests/unit/test_vcs_mercurial.py index 2c42189818b..22ec2b60ed4 100644 --- a/tests/unit/test_vcs_mercurial.py +++ b/tests/unit/test_vcs_mercurial.py @@ -8,10 +8,11 @@ from pip._internal.utils.misc import hide_url from pip._internal.vcs.mercurial import Mercurial from tests.lib import need_mercurial +from tests.lib.path import Path @need_mercurial -def test_mercurial_switch_updates_config_file_when_found(tmpdir): +def test_mercurial_switch_updates_config_file_when_found(tmpdir: Path) -> None: hg = Mercurial() options = hg.make_rev_options() hg_dir = os.path.join(tmpdir, ".hg") diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 9cc720543c1..37b5974eb39 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -2,8 +2,10 @@ import csv import logging import os +import pathlib import textwrap from email import message_from_string +from typing import Dict, List, Optional, Tuple, cast from unittest.mock import patch import pytest @@ -19,14 +21,18 @@ from pip._internal.models.scheme import Scheme from pip._internal.operations.build.wheel_legacy import get_legacy_build_wheel_path from pip._internal.operations.install import wheel +from pip._internal.operations.install.wheel import InstalledCSVRow, RecordPath from pip._internal.utils.compat import WINDOWS from pip._internal.utils.misc import hash_file from pip._internal.utils.unpacking import unpack_file -from tests.lib import DATA_DIR, assert_paths_equal +from tests.lib import DATA_DIR, TestData, assert_paths_equal +from tests.lib.path import Path from tests.lib.wheel import make_wheel -def call_get_legacy_build_wheel_path(caplog, names): +def call_get_legacy_build_wheel_path( + caplog: pytest.LogCaptureFixture, names: List[str] +) -> Optional[str]: wheel_path = get_legacy_build_wheel_path( names=names, temp_dir="/tmp/abcd", @@ -37,13 +43,16 @@ def call_get_legacy_build_wheel_path(caplog, names): return wheel_path -def test_get_legacy_build_wheel_path(caplog): +def test_get_legacy_build_wheel_path(caplog: pytest.LogCaptureFixture) -> None: actual = call_get_legacy_build_wheel_path(caplog, names=["name"]) + assert actual is not None assert_paths_equal(actual, "/tmp/abcd/name") assert not caplog.records -def test_get_legacy_build_wheel_path__no_names(caplog): +def test_get_legacy_build_wheel_path__no_names( + caplog: pytest.LogCaptureFixture, +) -> None: caplog.set_level(logging.INFO) actual = call_get_legacy_build_wheel_path(caplog, names=[]) assert actual is None @@ -57,13 +66,16 @@ def test_get_legacy_build_wheel_path__no_names(caplog): ] -def test_get_legacy_build_wheel_path__multiple_names(caplog): +def test_get_legacy_build_wheel_path__multiple_names( + caplog: pytest.LogCaptureFixture, +) -> None: caplog.set_level(logging.INFO) # Deliberately pass the names in non-sorted order. actual = call_get_legacy_build_wheel_path( caplog, names=["name2", "name1"], ) + assert actual is not None assert_paths_equal(actual, "/tmp/abcd/name1") assert len(caplog.records) == 1 record = caplog.records[0] @@ -84,7 +96,7 @@ def test_get_legacy_build_wheel_path__multiple_names(caplog): "進入點 = 套件.模組:函式", ], ) -def test_get_entrypoints(tmp_path, console_scripts): +def test_get_entrypoints(tmp_path: pathlib.Path, console_scripts: str) -> None: entry_points_text = """ [console_scripts] {} @@ -103,13 +115,11 @@ def test_get_entrypoints(tmp_path, console_scripts): }, ).as_distribution("simple") - assert wheel.get_entrypoints(distribution) == ( - dict([console_scripts.split(" = ")]), - {}, - ) + entry_point, entry_point_value = console_scripts.split(" = ") + assert wheel.get_entrypoints(distribution) == ({entry_point: entry_point_value}, {}) -def test_get_entrypoints_no_entrypoints(tmp_path): +def test_get_entrypoints_no_entrypoints(tmp_path: pathlib.Path) -> None: distribution = make_wheel("simple", "0.1.0").as_distribution("simple") console, gui = wheel.get_entrypoints(distribution) @@ -155,20 +165,20 @@ def test_get_entrypoints_no_entrypoints(tmp_path): ), ], ) -def test_normalized_outrows(outrows, expected): +def test_normalized_outrows( + outrows: List[Tuple[RecordPath, str, str]], expected: List[Tuple[str, str, str]] +) -> None: actual = wheel._normalized_outrows(outrows) assert actual == expected -def call_get_csv_rows_for_installed(tmpdir, text): +def call_get_csv_rows_for_installed(tmpdir: Path, text: str) -> List[InstalledCSVRow]: path = tmpdir.joinpath("temp.txt") path.write_text(text) # Test that an installed file appearing in RECORD has its filename # updated in the new RECORD file. - installed = {"a": "z"} - changed = set() - generated = [] + installed = cast(Dict[RecordPath, RecordPath], {"a": "z"}) lib_dir = "/lib/dir" with open(path, **wheel.csv_io_kwargs("r")) as f: @@ -176,14 +186,16 @@ def call_get_csv_rows_for_installed(tmpdir, text): outrows = wheel.get_csv_rows_for_installed( record_rows, installed=installed, - changed=changed, - generated=generated, + changed=set(), + generated=[], lib_dir=lib_dir, ) return outrows -def test_get_csv_rows_for_installed(tmpdir, caplog): +def test_get_csv_rows_for_installed( + tmpdir: Path, caplog: pytest.LogCaptureFixture +) -> None: text = textwrap.dedent( """\ a,b,c @@ -201,7 +213,9 @@ def test_get_csv_rows_for_installed(tmpdir, caplog): assert len(caplog.records) == 0 -def test_get_csv_rows_for_installed__long_lines(tmpdir, caplog): +def test_get_csv_rows_for_installed__long_lines( + tmpdir: Path, caplog: pytest.LogCaptureFixture +) -> None: text = textwrap.dedent( """\ a,b,c,d @@ -210,20 +224,17 @@ def test_get_csv_rows_for_installed__long_lines(tmpdir, caplog): """ ) outrows = call_get_csv_rows_for_installed(tmpdir, text) - - expected = [ + assert outrows == [ ("z", "b", "c"), ("e", "f", "g"), ("h", "i", "j"), ] - assert outrows == expected messages = [rec.message for rec in caplog.records] - expected = [ + assert messages == [ "RECORD line has more than three elements: ['a', 'b', 'c', 'd']", "RECORD line has more than three elements: ['h', 'i', 'j', 'k']", ] - assert messages == expected @pytest.mark.parametrize( @@ -237,12 +248,12 @@ def test_get_csv_rows_for_installed__long_lines(tmpdir, caplog): ("root-is-purelib: True", True), ], ) -def test_wheel_root_is_purelib(text, expected): +def test_wheel_root_is_purelib(text: str, expected: bool) -> None: assert wheel.wheel_root_is_purelib(message_from_string(text)) == expected class TestWheelFile: - def test_unpack_wheel_no_flatten(self, tmpdir): + def test_unpack_wheel_no_flatten(self, tmpdir: Path) -> None: filepath = os.path.join(DATA_DIR, "packages", "meta-1.0-py2.py3-none-any.whl") unpack_file(filepath, tmpdir) assert os.path.isdir(os.path.join(tmpdir, "meta-1.0.dist-info")) @@ -253,7 +264,7 @@ class TestInstallUnpackedWheel: Tests for moving files from wheel src to scheme paths """ - def prep(self, data, tmpdir): + def prep(self, data: TestData, tmpdir: str) -> None: # Since Path implements __add__, os.path.join returns a Path object. # Passing Path objects to interfaces expecting str (like # `compileall.compile_file`) can cause failures, so we normalize it @@ -321,11 +332,11 @@ def main(): self.scheme.purelib, "sample-1.2.0.dist-info" ) - def assert_permission(self, path, mode): + def assert_permission(self, path: str, mode: int) -> None: target_mode = os.stat(path).st_mode & 0o777 assert (target_mode & mode) == mode, oct(target_mode) - def assert_installed(self, expected_permission): + def assert_installed(self, expected_permission: int) -> None: # lib assert os.path.isdir(os.path.join(self.scheme.purelib, "sample")) # dist-info @@ -340,7 +351,7 @@ def assert_installed(self, expected_permission): pkg_data = os.path.join(self.scheme.purelib, "sample", "package_data.dat") assert os.path.isfile(pkg_data) - def test_std_install(self, data, tmpdir): + def test_std_install(self, data: TestData, tmpdir: Path) -> None: self.prep(data, tmpdir) wheel.install_wheel( self.name, @@ -352,8 +363,8 @@ def test_std_install(self, data, tmpdir): @pytest.mark.parametrize("user_mask, expected_permission", [(0o27, 0o640)]) def test_std_install_with_custom_umask( - self, data, tmpdir, user_mask, expected_permission - ): + self, data: TestData, tmpdir: Path, user_mask: int, expected_permission: int + ) -> None: """Test that the files created after install honor the permissions set when the user sets a custom umask""" @@ -370,7 +381,7 @@ def test_std_install_with_custom_umask( finally: os.umask(prev_umask) - def test_std_install_requested(self, data, tmpdir): + def test_std_install_requested(self, data: TestData, tmpdir: Path) -> None: self.prep(data, tmpdir) wheel.install_wheel( self.name, @@ -383,7 +394,7 @@ def test_std_install_requested(self, data, tmpdir): requested_path = os.path.join(self.dest_dist_info, "REQUESTED") assert os.path.isfile(requested_path) - def test_std_install_with_direct_url(self, data, tmpdir): + def test_std_install_with_direct_url(self, data: TestData, tmpdir: Path) -> None: """Test that install_wheel creates direct_url.json metadata when provided with a direct_url argument. Also test that the RECORDS file contains an entry for direct_url.json in that case. @@ -404,15 +415,15 @@ def test_std_install_with_direct_url(self, data, tmpdir): ) direct_url_path = os.path.join(self.dest_dist_info, DIRECT_URL_METADATA_NAME) self.assert_permission(direct_url_path, 0o644) - with open(direct_url_path, "rb") as f: + with open(direct_url_path, "rb") as f1: expected_direct_url_json = direct_url.to_json() - direct_url_json = f.read().decode("utf-8") + direct_url_json = f1.read().decode("utf-8") assert direct_url_json == expected_direct_url_json # check that the direc_url file is part of RECORDS - with open(os.path.join(self.dest_dist_info, "RECORD")) as f: - assert DIRECT_URL_METADATA_NAME in f.read() + with open(os.path.join(self.dest_dist_info, "RECORD")) as f2: + assert DIRECT_URL_METADATA_NAME in f2.read() - def test_install_prefix(self, data, tmpdir): + def test_install_prefix(self, data: TestData, tmpdir: Path) -> None: prefix = os.path.join(os.path.sep, "some", "path") self.prep(data, tmpdir) scheme = get_scheme( @@ -434,7 +445,7 @@ def test_install_prefix(self, data, tmpdir): assert os.path.exists(os.path.join(tmpdir, "some", "path", bin_dir)) assert os.path.exists(os.path.join(tmpdir, "some", "path", "my_data")) - def test_dist_info_contains_empty_dir(self, data, tmpdir): + def test_dist_info_contains_empty_dir(self, data: TestData, tmpdir: Path) -> None: """ Test that empty dirs are not installed """ @@ -450,7 +461,9 @@ def test_dist_info_contains_empty_dir(self, data, tmpdir): assert not os.path.isdir(os.path.join(self.dest_dist_info, "empty_dir")) @pytest.mark.parametrize("path", ["/tmp/example", "../example", "./../example"]) - def test_wheel_install_rejects_bad_paths(self, data, tmpdir, path): + def test_wheel_install_rejects_bad_paths( + self, data: TestData, tmpdir: Path, path: str + ) -> None: self.prep(data, tmpdir) wheel_path = make_wheel( "simple", "0.1.0", extra_files={path: "example contents\n"} @@ -470,7 +483,9 @@ def test_wheel_install_rejects_bad_paths(self, data, tmpdir, path): @pytest.mark.xfail(strict=True) @pytest.mark.parametrize("entrypoint", ["hello = hello", "hello = hello:"]) @pytest.mark.parametrize("entrypoint_type", ["console_scripts", "gui_scripts"]) - def test_invalid_entrypoints_fail(self, data, tmpdir, entrypoint, entrypoint_type): + def test_invalid_entrypoints_fail( + self, data: TestData, tmpdir: Path, entrypoint: str, entrypoint_type: str + ) -> None: self.prep(data, tmpdir) wheel_path = make_wheel( "simple", "0.1.0", entry_points={entrypoint_type: [entrypoint]} @@ -495,22 +510,22 @@ class TestMessageAboutScriptsNotOnPATH: "which may not be expanded by all applications." ) - def _template(self, paths, scripts): + def _template(self, paths: List[str], scripts: List[str]) -> Optional[str]: with patch.dict("os.environ", {"PATH": os.pathsep.join(paths)}): return wheel.message_about_scripts_not_on_PATH(scripts) - def test_no_script(self): + def test_no_script(self) -> None: retval = self._template(paths=["/a/b", "/c/d/bin"], scripts=[]) assert retval is None - def test_single_script__single_dir_not_on_PATH(self): + def test_single_script__single_dir_not_on_PATH(self) -> None: retval = self._template(paths=["/a/b", "/c/d/bin"], scripts=["/c/d/foo"]) assert retval is not None assert "--no-warn-script-location" in retval assert "foo is installed in '/c/d'" in retval assert self.tilde_warning_msg not in retval - def test_two_script__single_dir_not_on_PATH(self): + def test_two_script__single_dir_not_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin"], scripts=["/c/d/foo", "/c/d/baz"] ) @@ -519,7 +534,7 @@ def test_two_script__single_dir_not_on_PATH(self): assert "baz and foo are installed in '/c/d'" in retval assert self.tilde_warning_msg not in retval - def test_multi_script__multi_dir_not_on_PATH(self): + def test_multi_script__multi_dir_not_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin"], scripts=["/c/d/foo", "/c/d/bar", "/c/d/baz", "/a/b/c/spam"], @@ -530,7 +545,7 @@ def test_multi_script__multi_dir_not_on_PATH(self): assert "spam is installed in '/a/b/c'" in retval assert self.tilde_warning_msg not in retval - def test_multi_script_all__multi_dir_not_on_PATH(self): + def test_multi_script_all__multi_dir_not_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin"], scripts=["/c/d/foo", "/c/d/bar", "/c/d/baz", "/a/b/c/spam", "/a/b/c/eggs"], @@ -541,30 +556,30 @@ def test_multi_script_all__multi_dir_not_on_PATH(self): assert "eggs and spam are installed in '/a/b/c'" in retval assert self.tilde_warning_msg not in retval - def test_two_script__single_dir_on_PATH(self): + def test_two_script__single_dir_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin"], scripts=["/a/b/foo", "/a/b/baz"] ) assert retval is None - def test_multi_script__multi_dir_on_PATH(self): + def test_multi_script__multi_dir_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin"], scripts=["/a/b/foo", "/a/b/bar", "/a/b/baz", "/c/d/bin/spam"], ) assert retval is None - def test_multi_script__single_dir_on_PATH(self): + def test_multi_script__single_dir_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin"], scripts=["/a/b/foo", "/a/b/bar", "/a/b/baz"] ) assert retval is None - def test_single_script__single_dir_on_PATH(self): + def test_single_script__single_dir_on_PATH(self) -> None: retval = self._template(paths=["/a/b", "/c/d/bin"], scripts=["/a/b/foo"]) assert retval is None - def test_PATH_check_case_insensitive_on_windows(self): + def test_PATH_check_case_insensitive_on_windows(self) -> None: retval = self._template(paths=["C:\\A\\b"], scripts=["c:\\a\\b\\c", "C:/A/b/d"]) if WINDOWS: assert retval is None @@ -572,13 +587,15 @@ def test_PATH_check_case_insensitive_on_windows(self): assert retval is not None assert self.tilde_warning_msg not in retval - def test_trailing_ossep_removal(self): + def test_trailing_ossep_removal(self) -> None: retval = self._template( paths=[os.path.join("a", "b", "")], scripts=[os.path.join("a", "b", "c")] ) assert retval is None - def test_missing_PATH_env_treated_as_empty_PATH_env(self, monkeypatch): + def test_missing_PATH_env_treated_as_empty_PATH_env( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: scripts = ["a/b/foo"] monkeypatch.delenv("PATH") @@ -589,11 +606,11 @@ def test_missing_PATH_env_treated_as_empty_PATH_env(self, monkeypatch): assert retval_missing == retval_empty - def test_no_script_tilde_in_path(self): + def test_no_script_tilde_in_path(self) -> None: retval = self._template(paths=["/a/b", "/c/d/bin", "~/e", "/f/g~g"], scripts=[]) assert retval is None - def test_multi_script_all_tilde__multi_dir_not_on_PATH(self): + def test_multi_script_all_tilde__multi_dir_not_on_PATH(self) -> None: retval = self._template( paths=["/a/b", "/c/d/bin", "~e/f"], scripts=[ @@ -612,7 +629,7 @@ def test_multi_script_all_tilde__multi_dir_not_on_PATH(self): assert "tilde is installed in '/e/f'" in retval assert self.tilde_warning_msg in retval - def test_multi_script_all_tilde_not_at_start__multi_dir_not_on_PATH(self): + def test_multi_script_all_tilde_not_at_start__multi_dir_not_on_PATH(self) -> None: retval = self._template( paths=["/e/f~f", "/c/d/bin"], scripts=[ @@ -631,7 +648,7 @@ def test_multi_script_all_tilde_not_at_start__multi_dir_not_on_PATH(self): class TestWheelHashCalculators: - def prep(self, tmpdir): + def prep(self, tmpdir: Path) -> None: self.test_file = tmpdir.joinpath("hash.file") # Want this big enough to trigger the internal read loops. self.test_file_len = 2 * 1024 * 1024 @@ -644,13 +661,13 @@ def prep(self, tmpdir): "sha256=VkfwXsGJWJR9ModO63iPo5agXQurfBtx8RLOt-mzHu4" ) - def test_hash_file(self, tmpdir): + def test_hash_file(self, tmpdir: Path) -> None: self.prep(tmpdir) h, length = hash_file(self.test_file) assert length == self.test_file_len assert h.hexdigest() == self.test_file_hash - def test_rehash(self, tmpdir): + def test_rehash(self, tmpdir: Path) -> None: self.prep(tmpdir) h, length = wheel.rehash(self.test_file) assert length == str(self.test_file_len) diff --git a/tests/unit/test_wheel_builder.py b/tests/unit/test_wheel_builder.py index 3ddd9e66a7b..30b4060212d 100644 --- a/tests/unit/test_wheel_builder.py +++ b/tests/unit/test_wheel_builder.py @@ -1,12 +1,14 @@ import logging -from unittest.mock import patch +from typing import Optional, cast +from unittest import mock import pytest from pip._internal import wheel_builder from pip._internal.models.link import Link from pip._internal.operations.build.wheel_legacy import format_command_result -from tests.lib import _create_test_package +from pip._internal.req.req_install import InstallRequirement +from tests.lib import PipTestEnvironment, _create_test_package @pytest.mark.parametrize( @@ -22,7 +24,7 @@ ("im_invalid", False), ], ) -def test_contains_egg_info(s, expected): +def test_contains_egg_info(s: str, expected: bool) -> None: result = wheel_builder._contains_egg_info(s) assert result == expected @@ -30,14 +32,14 @@ def test_contains_egg_info(s, expected): class ReqMock: def __init__( self, - name="pendulum", - is_wheel=False, - editable=False, - link=None, - constraint=False, - source_dir="/tmp/pip-install-123/pendulum", - use_pep517=True, - ): + name: str = "pendulum", + is_wheel: bool = False, + editable: bool = False, + link: Optional[Link] = None, + constraint: bool = False, + source_dir: Optional[str] = "/tmp/pip-install-123/pendulum", + use_pep517: bool = True, + ) -> None: self.name = name self.is_wheel = is_wheel self.editable = editable @@ -90,9 +92,11 @@ def __init__( ), ], ) -def test_should_build_for_install_command(req, disallow_binaries, expected): +def test_should_build_for_install_command( + req: ReqMock, disallow_binaries: bool, expected: bool +) -> None: should_build = wheel_builder.should_build_for_install_command( - req, + cast(InstallRequirement, req), check_binary_allowed=lambda req: not disallow_binaries, ) assert should_build is expected @@ -109,28 +113,30 @@ def test_should_build_for_install_command(req, disallow_binaries, expected): (ReqMock(link=Link("git+https://g.c/org/repo")), True), ], ) -def test_should_build_for_wheel_command(req, expected): - should_build = wheel_builder.should_build_for_wheel_command(req) +def test_should_build_for_wheel_command(req: ReqMock, expected: bool) -> None: + should_build = wheel_builder.should_build_for_wheel_command( + cast(InstallRequirement, req) + ) assert should_build is expected -@patch("pip._internal.wheel_builder.is_wheel_installed") -def test_should_build_legacy_wheel_not_installed(is_wheel_installed): +@mock.patch("pip._internal.wheel_builder.is_wheel_installed") +def test_should_build_legacy_wheel_not_installed(is_wheel_installed: mock.Mock) -> None: is_wheel_installed.return_value = False legacy_req = ReqMock(use_pep517=False) should_build = wheel_builder.should_build_for_install_command( - legacy_req, + cast(InstallRequirement, legacy_req), check_binary_allowed=lambda req: True, ) assert not should_build -@patch("pip._internal.wheel_builder.is_wheel_installed") -def test_should_build_legacy_wheel_installed(is_wheel_installed): +@mock.patch("pip._internal.wheel_builder.is_wheel_installed") +def test_should_build_legacy_wheel_installed(is_wheel_installed: mock.Mock) -> None: is_wheel_installed.return_value = True legacy_req = ReqMock(use_pep517=False) should_build = wheel_builder.should_build_for_install_command( - legacy_req, + cast(InstallRequirement, legacy_req), check_binary_allowed=lambda req: True, ) assert should_build @@ -146,26 +152,26 @@ def test_should_build_legacy_wheel_installed(is_wheel_installed): (ReqMock(link=Link("https://g.c/dist-2.0.4.tgz")), True), ], ) -def test_should_cache(req, expected): - assert wheel_builder._should_cache(req) is expected +def test_should_cache(req: ReqMock, expected: bool) -> None: + assert wheel_builder._should_cache(cast(InstallRequirement, req)) is expected -def test_should_cache_git_sha(script): +def test_should_cache_git_sha(script: PipTestEnvironment) -> None: repo_path = _create_test_package(script, name="mypkg") commit = script.run("git", "rev-parse", "HEAD", cwd=repo_path).stdout.strip() # a link referencing a sha should be cached url = "git+https://g.c/o/r@" + commit + "#egg=mypkg" req = ReqMock(link=Link(url), source_dir=repo_path) - assert wheel_builder._should_cache(req) + assert wheel_builder._should_cache(cast(InstallRequirement, req)) # a link not referencing a sha should not be cached url = "git+https://g.c/o/r@master#egg=mypkg" req = ReqMock(link=Link(url), source_dir=repo_path) - assert not wheel_builder._should_cache(req) + assert not wheel_builder._should_cache(cast(InstallRequirement, req)) -def test_format_command_result__INFO(caplog): +def test_format_command_result__INFO(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.INFO) actual = format_command_result( # Include an argument with a space to test argument quoting. @@ -187,7 +193,9 @@ def test_format_command_result__INFO(caplog): "output line 1\noutput line 2", ], ) -def test_format_command_result__DEBUG(caplog, command_output): +def test_format_command_result__DEBUG( + caplog: pytest.LogCaptureFixture, command_output: str +) -> None: caplog.set_level(logging.DEBUG) actual = format_command_result( command_args=["arg1", "arg2"], @@ -203,7 +211,9 @@ def test_format_command_result__DEBUG(caplog, command_output): @pytest.mark.parametrize("log_level", ["DEBUG", "INFO"]) -def test_format_command_result__empty_output(caplog, log_level): +def test_format_command_result__empty_output( + caplog: pytest.LogCaptureFixture, log_level: str +) -> None: caplog.set_level(log_level) actual = format_command_result( command_args=["arg1", "arg2"],