summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Dufresne <jon.dufresne@gmail.com>2021-08-21 22:04:11 -0600
committerJon Dufresne <jon.dufresne@gmail.com>2021-08-22 09:57:26 -0600
commit943af79f2629e9e98260769582cde3c31d24db91 (patch)
tree719dff89cf162a43ae853d32ae6dd58fc2f18469
parent3e640bc0ad2c78506c4b70f2704ba7a6ec86a6c2 (diff)
downloadpip-943af79f2629e9e98260769582cde3c31d24db91.tar.gz
Run mypy on the tests directory
The tests are a large consumer of the pip API (both the internal API and otherwise). By running mypy on tests, we help to: 1. Ensure the internal API is consistent with regards to typing 2. Ensure the tests are representative of real life scenarios as the API are used correctly. 3. Helps to recognize unnecessary tests that simply pass junk data to functions which can be caught by the type checker. 4. Make sure test support code in tests/lib/ is correct and consistent. This is especially important when refactoring such code. For example, if we were to replace tests/lib/path.py with pathlib. As a first start, untyped defs are allowed. All existing typing issues have been resolved. Overtime, we can chip away at untyped defs and eventually remove the configuration option for stricter type checking of tests. The following changes were made to help make mypy pass: Remove unused record_callback argument from make_wheel() in tests. Unused since its introduction in 6d8a58f7e136df92704d2334288e4589d91a082b. Replace toml with tomli_w in tests/functional/test_pep517.py. Unlike the toml package, tomli_w contains inline typing annotations. Remove unnecessary make_no_network_finder(). Unnecessary since bab1e4f8a1a3ba5f5f08207c83a4e0a7a87ea615 where the _get_pages method was removed.
-rw-r--r--.pre-commit-config.yaml3
-rw-r--r--news/f231bb92-a022-4b48-b7fd-c83edefb4353.trivial.rst0
-rw-r--r--setup.cfg4
-rw-r--r--tests/conftest.py11
-rw-r--r--tests/functional/test_pep517.py9
-rw-r--r--tests/lib/local_repos.py1
-rw-r--r--tests/lib/server.py39
-rw-r--r--tests/lib/wheel.py38
-rw-r--r--tests/requirements.txt2
-rw-r--r--tests/unit/resolution_resolvelib/__init__.py0
-rw-r--r--tests/unit/test_finder.py35
-rw-r--r--tests/unit/test_self_check_outdated.py21
-rw-r--r--tests/unit/test_utils.py3
13 files changed, 78 insertions, 88 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e33aa41ef..b68022f94 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -42,12 +42,13 @@ repos:
rev: v0.910
hooks:
- id: mypy
- exclude: tests
+ exclude: tests/data
args: ["--pretty", "--show-error-codes"]
additional_dependencies: [
'keyring==23.0.1',
'nox==2021.6.12',
'types-docutils==0.1.8',
+ 'types-setuptools==57.0.2',
'types-six==0.1.9',
]
diff --git a/news/f231bb92-a022-4b48-b7fd-c83edefb4353.trivial.rst b/news/f231bb92-a022-4b48-b7fd-c83edefb4353.trivial.rst
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/news/f231bb92-a022-4b48-b7fd-c83edefb4353.trivial.rst
diff --git a/setup.cfg b/setup.cfg
index e32b723e9..96b7baf4a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,6 +51,10 @@ 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.
+allow_untyped_defs = True
+
[tool:pytest]
addopts = --ignore src/pip/_vendor --ignore tests/tests_cache -r aR --color=yes
markers =
diff --git a/tests/conftest.py b/tests/conftest.py
index 520d0cc72..390726d14 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,7 +8,7 @@ import subprocess
import sys
import time
from contextlib import ExitStack, contextmanager
-from typing import Dict, Iterable
+from typing import TYPE_CHECKING, Dict, Iterable, List
from unittest.mock import patch
import pytest
@@ -21,11 +21,14 @@ from tests.lib import DATA_DIR, SRC_DIR, PipTestEnvironment, TestData
from tests.lib.certs import make_tls_cert, serialize_cert, serialize_key
from tests.lib.path import Path
from tests.lib.server import MockServer as _MockServer
-from tests.lib.server import Responder, make_mock_server, server_running
+from tests.lib.server import make_mock_server, server_running
from tests.lib.venv import VirtualEnvironment
from .lib.compat import nullcontext
+if TYPE_CHECKING:
+ from wsgi import WSGIApplication
+
def pytest_addoption(parser):
parser.addoption(
@@ -521,7 +524,7 @@ class MockServer:
def host(self):
return self._server.host
- def set_responses(self, responses: Iterable[Responder]) -> None:
+ def set_responses(self, responses: Iterable["WSGIApplication"]) -> None:
assert not self._running, "responses cannot be set on running server"
self._server.mock.side_effect = responses
@@ -542,7 +545,7 @@ class MockServer:
assert self._running, "idle server cannot be stopped"
self.context.close()
- def get_requests(self) -> Dict[str, str]:
+ def get_requests(self) -> List[Dict[str, str]]:
"""Get environ for each received request."""
assert not self._running, "cannot get mock from running server"
# Legacy: replace call[0][0] with call.args[0]
diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py
index 121a738e1..7d1dd7400 100644
--- a/tests/functional/test_pep517.py
+++ b/tests/functional/test_pep517.py
@@ -1,8 +1,5 @@
import pytest
-
-# The vendored `tomli` package is not used here because it doesn't
-# have write capability
-import toml
+import tomli_w
from pip._internal.build_env import BuildEnvironment
from pip._internal.req import InstallRequirement
@@ -18,7 +15,7 @@ def make_project(tmpdir, requires=None, backend=None, backend_path=None):
buildsys["build-backend"] = backend
if backend_path:
buildsys["backend-path"] = backend_path
- data = toml.dumps({"build-system": buildsys})
+ data = tomli_w.dumps({"build-system": buildsys})
project_dir.joinpath("pyproject.toml").write_text(data)
return project_dir
@@ -189,7 +186,7 @@ def make_pyproject_with_setup(tmpdir, build_system=True, set_backend=True):
if set_backend:
buildsys["build-backend"] = "setuptools.build_meta"
expect_script_dir_on_path = False
- project_data = toml.dumps({"build-system": buildsys})
+ project_data = tomli_w.dumps({"build-system": buildsys})
else:
project_data = ""
diff --git a/tests/lib/local_repos.py b/tests/lib/local_repos.py
index ecc36475e..81a114fd0 100644
--- a/tests/lib/local_repos.py
+++ b/tests/lib/local_repos.py
@@ -54,6 +54,7 @@ def local_checkout(
repo_url_path = os.path.join(repo_url_path, "trunk")
else:
vcs_backend = vcs.get_backend(vcs_name)
+ assert vcs_backend is not None
vcs_backend.obtain(repo_url_path, url=hide_url(remote_repo))
return "{}+{}".format(vcs_name, path_to_url(repo_url_path))
diff --git a/tests/lib/server.py b/tests/lib/server.py
index 43aea2add..24a09a6d6 100644
--- a/tests/lib/server.py
+++ b/tests/lib/server.py
@@ -5,8 +5,7 @@ import threading
from base64 import b64encode
from contextlib import contextmanager
from textwrap import dedent
-from types import TracebackType
-from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
+from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator
from unittest.mock import Mock
from werkzeug.serving import BaseWSGIServer, WSGIRequestHandler
@@ -14,14 +13,10 @@ from werkzeug.serving import make_server as _make_server
from .compat import nullcontext
-Environ = Dict[str, str]
-Status = str
-Headers = Iterable[Tuple[str, str]]
-ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
-Write = Callable[[bytes], None]
-StartResponse = Callable[[Status, Headers, Optional[ExcInfo]], Write]
-Body = List[bytes]
-Responder = Callable[[Environ, StartResponse], Body]
+if TYPE_CHECKING:
+ from wsgi import StartResponse, WSGIApplication, WSGIEnvironment
+
+Body = Iterable[bytes]
class MockServer(BaseWSGIServer):
@@ -78,13 +73,13 @@ class _RequestHandler(WSGIRequestHandler):
def _mock_wsgi_adapter(
- mock: Callable[[Environ, StartResponse], Responder]
-) -> Responder:
+ mock: Callable[["WSGIEnvironment", "StartResponse"], "WSGIApplication"]
+) -> "WSGIApplication":
"""Uses a mock to record function arguments and provide
the actual function that should respond.
"""
- def adapter(environ: Environ, start_response: StartResponse) -> Body:
+ def adapter(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
try:
responder = mock(environ, start_response)
except StopIteration:
@@ -134,7 +129,7 @@ def make_mock_server(**kwargs: Any) -> MockServer:
@contextmanager
-def server_running(server: BaseWSGIServer) -> None:
+def server_running(server: BaseWSGIServer) -> Iterator[None]:
"""Context manager for running the provided server in a separate thread."""
thread = threading.Thread(target=server.serve_forever)
thread.daemon = True
@@ -150,8 +145,8 @@ def server_running(server: BaseWSGIServer) -> None:
# Helper functions for making responses in a declarative way.
-def text_html_response(text: str) -> Responder:
- def responder(environ: Environ, start_response: StartResponse) -> Body:
+def text_html_response(text: str) -> "WSGIApplication":
+ def responder(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
start_response(
"200 OK",
[
@@ -180,7 +175,7 @@ def html5_page(text: str) -> str:
)
-def index_page(spec: Dict[str, str]) -> Responder:
+def index_page(spec: Dict[str, str]) -> "WSGIApplication":
def link(name, value):
return '<a href="{}">{}</a>'.format(value, name)
@@ -188,7 +183,7 @@ def index_page(spec: Dict[str, str]) -> Responder:
return text_html_response(html5_page(links))
-def package_page(spec: Dict[str, str]) -> Responder:
+def package_page(spec: Dict[str, str]) -> "WSGIApplication":
def link(name, value):
return '<a href="{}">{}</a>'.format(value, name)
@@ -196,8 +191,8 @@ def package_page(spec: Dict[str, str]) -> Responder:
return text_html_response(html5_page(links))
-def file_response(path: str) -> Responder:
- def responder(environ: Environ, start_response: StartResponse) -> Body:
+def file_response(path: str) -> "WSGIApplication":
+ def responder(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
size = os.stat(path).st_size
start_response(
"200 OK",
@@ -213,10 +208,10 @@ def file_response(path: str) -> Responder:
return responder
-def authorization_response(path: str) -> Responder:
+def authorization_response(path: str) -> "WSGIApplication":
correct_auth = "Basic " + b64encode(b"USERNAME:PASSWORD").decode("ascii")
- def responder(environ: Environ, start_response: StartResponse) -> Body:
+ def responder(environ: "WSGIEnvironment", start_response: "StartResponse") -> Body:
if environ.get("HTTP_AUTHORIZATION") == correct_auth:
size = os.stat(path).st_size
diff --git a/tests/lib/wheel.py b/tests/lib/wheel.py
index 59cbc93e9..7facf8b2f 100644
--- a/tests/lib/wheel.py
+++ b/tests/lib/wheel.py
@@ -12,7 +12,6 @@ from hashlib import sha256
from io import BytesIO, StringIO
from typing import (
AnyStr,
- Callable,
Dict,
Iterable,
List,
@@ -28,9 +27,6 @@ from pip._vendor.requests.structures import CaseInsensitiveDict
from tests.lib.path import Path
-# path, digest, size
-RecordLike = Tuple[str, str, str]
-RecordCallback = Callable[[List["Record"]], Union[str, bytes, List[RecordLike]]]
# As would be used in metadata
HeaderValue = Union[str, List[str]]
@@ -82,7 +78,7 @@ def make_metadata_file(
value: Defaulted[Optional[AnyStr]],
updates: Defaulted[Dict[str, HeaderValue]],
body: Defaulted[AnyStr],
-) -> File:
+) -> Optional[File]:
if value is None:
return None
@@ -199,9 +195,8 @@ def digest(contents: bytes) -> str:
def record_file_maker_wrapper(
name: str,
version: str,
- files: List[File],
+ files: Iterable[File],
record: Defaulted[Optional[AnyStr]],
- record_callback: Defaulted[RecordCallback],
) -> Iterable[File]:
records: List[Record] = []
for file in files:
@@ -221,19 +216,22 @@ def record_file_maker_wrapper(
records.append(Record(record_path, "", ""))
- if record_callback is not _default:
- records = record_callback(records)
-
with StringIO(newline="") as buf:
writer = csv.writer(buf)
- for record in records:
- writer.writerow(record)
+ for r in records:
+ writer.writerow(r)
contents = buf.getvalue().encode("utf-8")
yield File(record_path, contents)
-def wheel_name(name: str, version: str, pythons: str, abis: str, platforms: str) -> str:
+def wheel_name(
+ name: str,
+ version: str,
+ pythons: Iterable[str],
+ abis: Iterable[str],
+ platforms: Iterable[str],
+) -> str:
stem = "-".join(
[
name,
@@ -249,7 +247,7 @@ def wheel_name(name: str, version: str, pythons: str, abis: str, platforms: str)
class WheelBuilder:
"""A wheel that can be saved or converted to several formats."""
- def __init__(self, name: str, files: List[File]) -> None:
+ def __init__(self, name: str, files: Iterable[File]) -> None:
self._name = name
self._files = files
@@ -259,9 +257,9 @@ class WheelBuilder:
:returns the wheel file path
"""
- path = Path(path) / self._name
- path.write_bytes(self.as_bytes())
- return str(path)
+ p = Path(path) / self._name
+ p.write_bytes(self.as_bytes())
+ return str(p)
def save_to(self, path: Union[Path, str]) -> str:
"""Generate wheel file, saving to the provided path. Any parent
@@ -298,7 +296,6 @@ def make_wheel(
console_scripts: Defaulted[List[str]] = _default,
entry_points: Defaulted[Dict[str, List[str]]] = _default,
record: Defaulted[Optional[AnyStr]] = _default,
- record_callback: Defaulted[RecordCallback] = _default,
) -> WheelBuilder:
"""
Helper function for generating test wheels which are compliant by default.
@@ -359,9 +356,6 @@ def make_wheel(
:param entry_points:
:param record: if provided and None, then no RECORD file is generated;
else if a string then sets the content of the RECORD file
- :param record_callback: callback function that receives and can edit the
- records before they are written to RECORD, ignored if record is
- provided
"""
pythons = ["py2", "py3"]
abis = ["none"]
@@ -388,7 +382,7 @@ def make_wheel(
actual_files = filter(None, possible_files)
files_and_record_file = record_file_maker_wrapper(
- name, version, actual_files, record, record_callback
+ name, version, actual_files, record
)
wheel_file_name = wheel_name(name, version, pythons, abis, platforms)
diff --git a/tests/requirements.txt b/tests/requirements.txt
index ee453e073..33eb8e523 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -10,4 +10,4 @@ setuptools
virtualenv < 20.0
werkzeug
wheel
-toml
+tomli-w
diff --git a/tests/unit/resolution_resolvelib/__init__.py b/tests/unit/resolution_resolvelib/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/unit/resolution_resolvelib/__init__.py
diff --git a/tests/unit/test_finder.py b/tests/unit/test_finder.py
index 6a1937a12..b4fde11d9 100644
--- a/tests/unit/test_finder.py
+++ b/tests/unit/test_finder.py
@@ -19,25 +19,6 @@ from pip._internal.req.constructors import install_req_from_line
from tests.lib import make_test_finder
-def make_no_network_finder(
- find_links,
- allow_all_prereleases: bool = False,
-):
- """
- Create and return a PackageFinder instance for test purposes that
- doesn't make any network requests when _get_pages() is called.
- """
- finder = make_test_finder(
- find_links=find_links,
- allow_all_prereleases=allow_all_prereleases,
- )
- # Replace the PackageFinder._link_collector's _get_pages() with a no-op.
- link_collector = finder._link_collector
- link_collector._get_pages = lambda locations: []
-
- return finder
-
-
def test_no_mpkg(data):
"""Finder skips zipfiles with "macosx10" in the name."""
finder = make_test_finder(find_links=[data.find_links])
@@ -342,7 +323,7 @@ def test_finder_priority_nonegg_over_eggfragments():
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"]
- finder = make_no_network_finder(links)
+ finder = make_test_finder(links)
all_versions = finder.find_all_candidates(req.name)
assert all_versions[0].link.url.endswith("tar.gz")
assert all_versions[1].link.url.endswith("#egg=bar-1.0")
@@ -353,7 +334,7 @@ def test_finder_priority_nonegg_over_eggfragments():
links.reverse()
- finder = make_no_network_finder(links)
+ finder = make_test_finder(links)
all_versions = finder.find_all_candidates(req.name)
assert all_versions[0].link.url.endswith("tar.gz")
assert all_versions[1].link.url.endswith("#egg=bar-1.0")
@@ -377,13 +358,13 @@ def test_finder_only_installs_stable_releases(data):
# using find-links
links = ["https://foo/bar-1.0.tar.gz", "https://foo/bar-2.0b1.tar.gz"]
- finder = make_no_network_finder(links)
+ finder = make_test_finder(links)
found = finder.find_requirement(req, False)
assert found.link.url == "https://foo/bar-1.0.tar.gz"
links.reverse()
- finder = make_no_network_finder(links)
+ finder = make_test_finder(links)
found = finder.find_requirement(req, False)
assert found.link.url == "https://foo/bar-1.0.tar.gz"
@@ -423,13 +404,13 @@ def test_finder_installs_pre_releases(data):
# using find-links
links = ["https://foo/bar-1.0.tar.gz", "https://foo/bar-2.0b1.tar.gz"]
- finder = make_no_network_finder(links, allow_all_prereleases=True)
+ finder = make_test_finder(links, allow_all_prereleases=True)
found = finder.find_requirement(req, False)
assert found.link.url == "https://foo/bar-2.0b1.tar.gz"
links.reverse()
- finder = make_no_network_finder(links, allow_all_prereleases=True)
+ finder = make_test_finder(links, allow_all_prereleases=True)
found = finder.find_requirement(req, False)
assert found.link.url == "https://foo/bar-2.0b1.tar.gz"
@@ -457,13 +438,13 @@ def test_finder_installs_pre_releases_with_version_spec():
req = install_req_from_line("bar>=0.0.dev0", None)
links = ["https://foo/bar-1.0.tar.gz", "https://foo/bar-2.0b1.tar.gz"]
- finder = make_no_network_finder(links)
+ finder = make_test_finder(links)
found = finder.find_requirement(req, False)
assert found.link.url == "https://foo/bar-2.0b1.tar.gz"
links.reverse()
- finder = make_no_network_finder(links)
+ finder = make_test_finder(links)
found = finder.find_requirement(req, False)
assert found.link.url == "https://foo/bar-2.0b1.tar.gz"
diff --git a/tests/unit/test_self_check_outdated.py b/tests/unit/test_self_check_outdated.py
index 81a2ee484..b19abf86f 100644
--- a/tests/unit/test_self_check_outdated.py
+++ b/tests/unit/test_self_check_outdated.py
@@ -4,13 +4,14 @@ import json
import os
import sys
-import freezegun
+import freezegun # type: ignore
import pretend
import pytest
from pip._vendor.packaging.version import parse as parse_version
from pip._internal import self_outdated_check
from pip._internal.models.candidate import InstallationCandidate
+from pip._internal.models.link import Link
from pip._internal.self_outdated_check import (
SelfCheckState,
logger,
@@ -29,9 +30,21 @@ class MockPackageFinder:
BASE_URL = "https://pypi.org/simple/pip-{0}.tar.gz"
PIP_PROJECT_NAME = "pip"
INSTALLATION_CANDIDATES = [
- InstallationCandidate(PIP_PROJECT_NAME, "6.9.0", BASE_URL.format("6.9.0")),
- InstallationCandidate(PIP_PROJECT_NAME, "3.3.1", BASE_URL.format("3.3.1")),
- InstallationCandidate(PIP_PROJECT_NAME, "1.0", BASE_URL.format("1.0")),
+ InstallationCandidate(
+ PIP_PROJECT_NAME,
+ "6.9.0",
+ Link(BASE_URL.format("6.9.0")),
+ ),
+ InstallationCandidate(
+ PIP_PROJECT_NAME,
+ "3.3.1",
+ Link(BASE_URL.format("3.3.1")),
+ ),
+ InstallationCandidate(
+ PIP_PROJECT_NAME,
+ "1.0",
+ Link(BASE_URL.format("1.0")),
+ ),
]
@classmethod
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index fcbd6b1b3..15b4367df 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -10,6 +10,7 @@ import stat
import sys
import time
from io import BytesIO
+from typing import List
from unittest.mock import Mock, patch
import pytest
@@ -190,7 +191,7 @@ class Tests_EgglinkPath:
class TestsGetDistributions:
"""Test get_installed_distributions() and get_distribution()."""
- class MockWorkingSet(list):
+ class MockWorkingSet(List[Mock]):
def require(self, name):
pass