ci: add strict-no-cover to detect unnecessary coverage pragmas (#1897)

This commit is contained in:
Max Isbey 2026-01-23 20:00:20 +00:00 • committed by GitHub
commit a7ddfdae07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 355 additions and 358 deletions

View file

@ -335,9 +335,7 @@ async def test_progress_callback_exception_logging():
logged_errors.append(msg % args if args else msg)
# Create a progress callback that raises an exception
async def failing_progress_callback(
progress: float, total: float | None, message: str | None
) -> None: # pragma: no cover
async def failing_progress_callback(progress: float, total: float | None, message: str | None) -> None:
raise ValueError("Progress callback failed!")
# Create a server with a tool that sends progress notifications

View file

@ -97,8 +97,7 @@ async def test_request_cancellation():
)
# Give cancellation time to process
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
with anyio.fail_after(1): # pragma: no cover
with anyio.fail_after(1): # pragma: no branch
await ev_cancelled.wait()
@ -149,8 +148,7 @@ async def test_response_id_type_mismatch_string_to_int():
tg.start_soon(mock_server)
tg.start_soon(make_request, client_session)
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
with anyio.fail_after(2): # pragma: no cover
with anyio.fail_after(2): # pragma: no branch
await ev_response_received.wait()
assert len(result_holder) == 1
@ -205,8 +203,7 @@ async def test_error_response_id_type_mismatch_string_to_int():
tg.start_soon(mock_server)
tg.start_soon(make_request, client_session)
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
with anyio.fail_after(2): # pragma: no cover
with anyio.fail_after(2): # pragma: no branch
await ev_error_received.wait()
assert len(error_holder) == 1
@ -260,8 +257,7 @@ async def test_response_id_non_numeric_string_no_match():
tg.start_soon(mock_server)
tg.start_soon(make_request, client_session)
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
with anyio.fail_after(2): # pragma: no cover
with anyio.fail_after(2): # pragma: no branch
await ev_timeout.wait()
@ -305,8 +301,7 @@ async def test_connection_closed():
tg.start_soon(make_request, client_session)
tg.start_soon(mock_server)
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
with anyio.fail_after(1): # pragma: no cover
with anyio.fail_after(1):
await ev_closed.wait()
with anyio.fail_after(1): # pragma: no cover
with anyio.fail_after(1): # pragma: no branch
await ev_response.wait()

View file

@ -72,14 +72,14 @@ INIT_REQUEST = {
# Helper functions
def extract_protocol_version_from_sse(response: requests.Response) -> str: # pragma: no cover
def extract_protocol_version_from_sse(response: requests.Response) -> str:
"""Extract the negotiated protocol version from an SSE initialization response."""
assert response.headers.get("Content-Type") == "text/event-stream"
for line in response.text.splitlines():
if line.startswith("data: "):
init_data = json.loads(line[6:])
return init_data["result"]["protocolVersion"]
raise ValueError("Could not extract protocol version from SSE response")
raise ValueError("Could not extract protocol version from SSE response") # pragma: no cover
# Simple in-memory event store for testing
@ -90,9 +90,7 @@ class SimpleEventStore(EventStore):
self._events: list[tuple[StreamId, EventId, types.JSONRPCMessage | None]] = []
self._event_id_counter = 0
async def store_event( # pragma: no cover
self, stream_id: StreamId, message: types.JSONRPCMessage | None
) -> EventId:
async def store_event(self, stream_id: StreamId, message: types.JSONRPCMessage | None) -> EventId:
"""Store an event and return its ID."""
self._event_id_counter += 1
event_id = str(self._event_id_counter)
@ -871,7 +869,7 @@ def test_get_sse_stream(basic_server: None, basic_server_url: str):
init_data = None
assert init_response.headers.get("Content-Type") == "text/event-stream"
for line in init_response.text.splitlines(): # pragma: no branch
if line.startswith("data: "): # pragma: no cover
if line.startswith("data: "):
init_data = json.loads(line[6:])
break
assert init_data is not None
@ -931,7 +929,7 @@ def test_get_validation(basic_server: None, basic_server_url: str):
init_data = None
assert init_response.headers.get("Content-Type") == "text/event-stream"
for line in init_response.text.splitlines(): # pragma: no branch
if line.startswith("data: "): # pragma: no cover
if line.startswith("data: "):
init_data = json.loads(line[6:])
break
assert init_data is not None
@ -1155,8 +1153,8 @@ async def test_streamable_http_client_session_termination(basic_server: None, ba
tools = await session.list_tools()
assert len(tools.tools) == 10
headers: dict[str, str] = {} # pragma: no cover
if captured_session_id: # pragma: no cover
headers: dict[str, str] = {} # pragma: lax no cover
if captured_session_id: # pragma: lax no cover
headers[MCP_SESSION_ID_HEADER] = captured_session_id
async with create_mcp_http_client(headers=headers) as httpx_client:
@ -1219,8 +1217,8 @@ async def test_streamable_http_client_session_termination_204(
tools = await session.list_tools()
assert len(tools.tools) == 10
headers: dict[str, str] = {} # pragma: no cover
if captured_session_id: # pragma: no cover
headers: dict[str, str] = {} # pragma: lax no cover
if captured_session_id: # pragma: lax no cover
headers[MCP_SESSION_ID_HEADER] = captured_session_id
async with create_mcp_http_client(headers=headers) as httpx_client:
@ -1281,7 +1279,7 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
captured_protocol_version = result.protocol_version
# Start the tool that will wait on lock in a task
async with anyio.create_task_group() as tg:
async with anyio.create_task_group() as tg: # pragma: no branch
async def run_tool():
metadata = ClientMessageMetadata(
@ -1304,20 +1302,21 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
# Kill the client session while tool is waiting on lock
tg.cancel_scope.cancel()
# Verify we received exactly one notification
assert len(captured_notifications) == 1 # pragma: no cover
assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].params.data == "First notification before lock" # pragma: no cover
# Verify we received exactly one notification (inside ClientSession
# so coverage tracks these on Python 3.11, see PR #1897 for details)
assert len(captured_notifications) == 1 # pragma: lax no cover
assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: lax no cover
assert captured_notifications[0].params.data == "First notification before lock" # pragma: lax no cover
# Clear notifications for the second phase
captured_notifications = [] # pragma: no cover
# Now resume the session with the same mcp-session-id and protocol version
headers: dict[str, Any] = {} # pragma: no cover
if captured_session_id: # pragma: no cover
headers[MCP_SESSION_ID_HEADER] = captured_session_id
if captured_protocol_version: # pragma: no cover
headers[MCP_PROTOCOL_VERSION_HEADER] = captured_protocol_version
# Clear notifications and set up headers for phase 2 (between connections,
# not tracked by coverage on Python 3.11 due to cancel scope + sys.settrace bug)
captured_notifications = [] # pragma: lax no cover
assert captured_session_id is not None # pragma: lax no cover
assert captured_protocol_version is not None # pragma: lax no cover
headers: dict[str, Any] = { # pragma: lax no cover
MCP_SESSION_ID_HEADER: captured_session_id,
MCP_PROTOCOL_VERSION_HEADER: captured_protocol_version,
}
async with create_mcp_http_client(headers=headers) as httpx_client:
async with streamable_http_client(f"{server_url}/mcp", http_client=httpx_client) as (
@ -1325,7 +1324,9 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
write_stream,
_,
):
async with ClientSession(read_stream, write_stream, message_handler=message_handler) as session:
async with ClientSession(
read_stream, write_stream, message_handler=message_handler
) as session: # pragma: no branch
result = await session.send_request(
types.CallToolRequest(params=types.CallToolRequestParams(name="release_lock", arguments={})),
types.CallToolResult,
@ -1347,9 +1348,8 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
# We should have received the remaining notifications
assert len(captured_notifications) == 1
assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].params.data == "Second notification after lock" # pragma: no cover
assert isinstance(captured_notifications[0], types.LoggingMessageNotification)
assert captured_notifications[0].params.data == "Second notification after lock"
@pytest.mark.anyio
@ -1582,8 +1582,8 @@ async def test_streamablehttp_request_context_isolation(context_aware_server: No
contexts.append(context_data)
# Verify each request had its own context
assert len(contexts) == 3 # pragma: no cover
for i, ctx in enumerate(contexts): # pragma: no cover
assert len(contexts) == 3
for i, ctx in enumerate(contexts):
assert ctx["request_id"] == f"request-{i}"
assert ctx["headers"].get("x-request-id") == f"request-{i}"
assert ctx["headers"].get("x-custom-value") == f"value-{i}"
@ -2153,7 +2153,7 @@ async def test_streamable_http_multiple_reconnections(
assert "Completed 3 checkpoints" in result.content[0].text
# 4 priming + 3 notifications + 1 response = 8 tokens
assert len(resumption_tokens) == 8, ( # pragma: no cover
assert len(resumption_tokens) == 8, ( # pragma: lax no cover
f"Expected 8 resumption tokens (4 priming + 3 notifs + 1 response), "
f"got {len(resumption_tokens)}: {resumption_tokens}"
)

View file

@ -105,7 +105,7 @@ def run_server(server_port: int) -> None: # pragma: no cover
time.sleep(0.5)
@pytest.fixture() # pragma: no cover
@pytest.fixture()
def server(server_port: int) -> Generator[None, None, None]:
proc = multiprocessing.Process(target=run_server, kwargs={"server_port": server_port}, daemon=True)
print("starting process")