Skip to content

Commit fb2c51a

Browse files
committed
Add unit tests for urllib3 warning rewriting
Frankly, this is essentially guaranteed to break the next time we upgrade urllib3 (esp. as we're due for a major bump to the 2.x series), but these unit tests serve to ensure the rewriting filters works *now* and demonstrates in what situations it does work.
1 parent 15d8b10 commit fb2c51a

File tree

3 files changed

+79
-4
lines changed

3 files changed

+79
-4
lines changed

src/pip/_internal/network/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def raise_connection_error(error: requests.ConnectionError, *, timeout: float) -
153153
raise ConnectionFailedError(url, host, reason)
154154

155155

156-
class Urllib3RetryFilter:
156+
class Urllib3RetryFilter(logging.Filter):
157157
"""A logging filter which attempts to rewrite urllib3's retrying
158158
warnings to be more readable and less technical.
159159

tests/lib/server.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import threading
44
from base64 import b64encode
55
from contextlib import ExitStack, contextmanager
6+
from http.server import BaseHTTPRequestHandler, HTTPServer
67
from textwrap import dedent
7-
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List
8+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Union
89
from unittest.mock import Mock
910

1011
from werkzeug.serving import BaseWSGIServer, WSGIRequestHandler
@@ -104,7 +105,7 @@ def make_mock_server(**kwargs: Any) -> _MockServer:
104105

105106

106107
@contextmanager
107-
def server_running(server: BaseWSGIServer) -> Iterator[None]:
108+
def server_running(server: Union[BaseWSGIServer, HTTPServer]) -> Iterator[None]:
108109
"""Context manager for running the provided server in a separate thread."""
109110
thread = threading.Thread(target=server.serve_forever)
110111
thread.daemon = True
@@ -232,3 +233,13 @@ def get_requests(self) -> List[Dict[str, str]]:
232233
# Legacy: replace call[0][0] with call.args[0]
233234
# when pip drops support for python3.7
234235
return [call[0][0] for call in self._server.mock.call_args_list]
236+
237+
238+
class InstantCloseHTTPHandler(BaseHTTPRequestHandler):
239+
"""A HTTP request handler that closes the underlying request TCP
240+
socket immediately. Used for testing "connection reset by peer" and
241+
similar errors.
242+
"""
243+
244+
def handle(self) -> None:
245+
self.request.close()

tests/unit/test_network_session.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
import logging
22
import os
3+
from http.server import HTTPServer
34
from pathlib import Path
4-
from typing import Any, List, Optional
5+
from typing import Any, Iterator, List, Optional
56
from urllib.parse import urlparse
67
from urllib.request import getproxies
78

89
import pytest
910
from pip._vendor import requests
1011

1112
from pip import __version__
13+
from pip._internal.exceptions import DiagnosticPipError
1214
from pip._internal.models.link import Link
1315
from pip._internal.network.session import (
1416
CI_ENVIRONMENT_VARIABLES,
1517
PipSession,
1618
user_agent,
1719
)
20+
from pip._internal.utils.logging import VERBOSE
21+
from tests.lib.server import InstantCloseHTTPHandler, server_running
1822

1923

2024
def get_user_agent() -> str:
@@ -281,3 +285,63 @@ def test_proxy(self, proxy: Optional[str]) -> None:
281285
f"Invalid proxy {proxy} or session.proxies: "
282286
f"{session.proxies} is not correctly passed to session.request."
283287
)
288+
289+
290+
@pytest.mark.network
291+
class TestRetryWarningRewriting:
292+
@pytest.fixture(autouse=True)
293+
def setup_caplog_level(self, caplog: pytest.LogCaptureFixture) -> Iterator[None]:
294+
with caplog.at_level(logging.WARNING):
295+
yield
296+
297+
@pytest.mark.parametrize(
298+
"url, expected_message",
299+
[
300+
(
301+
"https://404.example.com",
302+
"failed to connect to 404.example.com via HTTPS",
303+
),
304+
("http://404.example.com", "failed to connect to 404.example.com via HTTP"),
305+
("https://expired.badssl.com", "SSL verification failed"),
306+
],
307+
)
308+
def test_simple_urls(
309+
self, caplog: pytest.LogCaptureFixture, url: str, expected_message: str
310+
) -> None:
311+
with PipSession(retries=1) as session:
312+
with pytest.raises(DiagnosticPipError):
313+
session.get(url)
314+
assert caplog.messages == [f"{expected_message}, retrying 1 last time"]
315+
316+
def test_timeout(self, caplog: pytest.LogCaptureFixture) -> None:
317+
with PipSession(retries=1) as session:
318+
with pytest.raises(DiagnosticPipError):
319+
session.get("https://httpstat.us/200?sleep=400", timeout=0.2)
320+
assert caplog.messages == [
321+
"server didn't respond within 0.2 seconds, retrying 1 last time"
322+
]
323+
324+
def test_connection_aborted(self, caplog: pytest.LogCaptureFixture) -> None:
325+
with HTTPServer(("localhost", 0), InstantCloseHTTPHandler) as server:
326+
with server_running(server), PipSession(retries=1) as session:
327+
with pytest.raises(DiagnosticPipError):
328+
session.get(f"http://{server.server_name}:{server.server_port}/")
329+
assert caplog.messages == [
330+
"the connection was closed unexpectedly, retrying 1 last time"
331+
]
332+
333+
def test_proxy(self, caplog: pytest.LogCaptureFixture) -> None:
334+
with PipSession(retries=1) as session:
335+
session.proxies = {"https": "https://404.example.com"}
336+
with pytest.raises(DiagnosticPipError):
337+
session.get("https://pypi.org")
338+
assert caplog.messages == ["failed to connect to proxy, retrying 1 last time"]
339+
340+
def test_verbose(self, caplog: pytest.LogCaptureFixture) -> None:
341+
caplog.set_level(VERBOSE)
342+
with PipSession(retries=1) as session:
343+
with pytest.raises(DiagnosticPipError):
344+
session.get("https://404.example.org")
345+
warnings = [r.message for r in caplog.records if r.levelno == logging.WARNING]
346+
assert len(warnings) == 1
347+
assert not warnings[0].endswith("retrying 1 last time")

0 commit comments

Comments
 (0)