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
30 changed files with 355 additions and 358 deletions

View file

@ -23,7 +23,7 @@ class MockTokenStorage:
self._tokens: OAuthToken | None = None
self._client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None: # pragma: no cover
async def get_tokens(self) -> OAuthToken | None:
return self._tokens
async def set_tokens(self, tokens: OAuthToken) -> None: # pragma: no cover

View file

@ -40,7 +40,7 @@ class StreamSpyCollection:
self.client.sent_messages.clear()
self.server.sent_messages.clear()
def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]: # pragma: no cover
def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]:
"""Get client-sent requests, optionally filtered by method."""
return [
req.message

View file

@ -1696,7 +1696,7 @@ class TestSEP985Discovery:
final_response = httpx.Response(200, request=final_request)
try:
await auth_flow.asend(final_response)
except StopAsyncIteration: # pragma: no cover
except StopAsyncIteration:
pass
@pytest.mark.anyio

View file

@ -94,7 +94,7 @@ def non_sdk_server(non_sdk_server_port: int) -> Generator[None, None, None]:
proc.start()
# Wait for server to be ready
try: # pragma: no cover
try:
wait_for_server(non_sdk_server_port, timeout=10.0)
except TimeoutError: # pragma: no cover
proc.kill()

View file

@ -157,7 +157,7 @@ async def test_stdio_client_universal_cleanup():
@pytest.mark.anyio
@pytest.mark.skipif(sys.platform == "win32", reason="Windows signal handling is different")
async def test_stdio_client_sigint_only_process(): # pragma: no cover
async def test_stdio_client_sigint_only_process(): # pragma: lax no cover
"""Test cleanup with a process that ignores SIGTERM but responds to SIGINT."""
# Create a Python script that ignores SIGTERM but handles SIGINT
script_content = textwrap.dedent(
@ -481,7 +481,7 @@ class TestChildProcessCleanup:
await anyio.sleep(0.5)
# Verify child is writing
if os.path.exists(marker_file): # pragma: no cover
if os.path.exists(marker_file): # pragma: no branch
size1 = os.path.getsize(marker_file)
await anyio.sleep(0.3)
size2 = os.path.getsize(marker_file)

View file

@ -34,10 +34,8 @@ def fastmcp_server() -> FastMCP:
"""Create a FastMCP server for testing."""
server = FastMCP("test")
# pragma: no cover on handlers below - they exist only to register capabilities.
# Transport tests verify stream creation and basic protocol, not handler invocation.
@server.tool()
def greet(name: str) -> str: # pragma: no cover
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"

View file

@ -310,8 +310,7 @@ async def test_task_metadata_in_call_tool_request() -> None:
async with anyio.create_task_group() as tg:
async def handle_messages():
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
async for message in server_session.incoming_messages: # pragma: no cover
async for message in server_session.incoming_messages: # pragma: no branch
await server._handle_message(message, server_session, {}, False)
tg.start_soon(handle_messages)
@ -388,9 +387,9 @@ async def test_task_metadata_is_task_property() -> None:
),
) as server_session:
async with anyio.create_task_group() as tg:
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
async def handle_messages(): # pragma: no cover
async for message in server_session.incoming_messages:
async def handle_messages():
async for message in server_session.incoming_messages: # pragma: no branch
await server._handle_message(message, server_session, {}, False)
tg.start_soon(handle_messages)
@ -573,7 +572,7 @@ async def test_build_elicit_form_request() -> None:
assert (
request_with_task.params["_meta"]["io.modelcontextprotocol/related-task"]["taskId"] == "test-task-123"
)
finally: # pragma: no cover
finally:
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -619,7 +618,7 @@ async def test_build_elicit_url_request() -> None:
assert (
request_with_task.params["_meta"]["io.modelcontextprotocol/related-task"]["taskId"] == "test-task-789"
)
finally: # pragma: no cover
finally:
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -670,7 +669,7 @@ async def test_build_create_message_request() -> None:
request_with_task.params["_meta"]["io.modelcontextprotocol/related-task"]["taskId"]
== "sampling-task-456"
)
finally: # pragma: no cover
finally:
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -707,7 +706,7 @@ async def test_send_message() -> None:
received = await server_to_client_receive.receive()
assert isinstance(received.message, JSONRPCNotification)
assert received.message.method == "test/notification"
finally: # pragma: no cover
finally: # pragma: lax no cover
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -761,7 +760,7 @@ async def test_response_routing_success() -> None:
assert len(routed_responses) == 1
assert routed_responses[0]["id"] == "test-req-1"
assert routed_responses[0]["response"]["status"] == "ok"
finally: # pragma: no cover
finally: # pragma: lax no cover
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -816,7 +815,7 @@ async def test_response_routing_error() -> None:
assert len(routed_errors) == 1
assert routed_errors[0]["id"] == "test-req-2"
assert routed_errors[0]["error"].message == "Test error"
finally: # pragma: no cover
finally: # pragma: lax no cover
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -874,7 +873,7 @@ async def test_response_routing_skips_non_matching_routers() -> None:
# Verify both routers were called (first returned False, second returned True)
assert router_calls == ["non_matching_response", "matching_response"]
finally: # pragma: no cover
finally: # pragma: lax no cover
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()
@ -933,7 +932,7 @@ async def test_error_routing_skips_non_matching_routers() -> None:
# Verify both routers were called (first returned False, second returned True)
assert router_calls == ["non_matching_error", "matching_error"]
finally: # pragma: no cover
finally: # pragma: lax no cover
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
await client_to_server_send.aclose()

View file

@ -102,7 +102,7 @@ async def test_lifespan_cleanup_executed():
# Give server a moment to complete cleanup
with anyio.move_on_after(5.0):
while not Path(cleanup_marker).exists(): # pragma: no cover
while not Path(cleanup_marker).exists(): # pragma: lax no cover
await anyio.sleep(0.1)
# Verify cleanup marker was created - this works now that stdio_client
@ -113,9 +113,9 @@ async def test_lifespan_cleanup_executed():
finally:
# Clean up files
for path in [server_script, startup_marker, cleanup_marker]:
try: # pragma: no cover
try: # pragma: lax no cover
Path(path).unlink()
except FileNotFoundError: # pragma: no cover
except FileNotFoundError: # pragma: lax no cover
pass
@ -204,7 +204,7 @@ async def test_stdin_close_triggers_cleanup():
await anyio.sleep(0.1)
# Check if process is still running
if hasattr(process, "returncode") and process.returncode is not None: # pragma: no cover
if hasattr(process, "returncode") and process.returncode is not None: # pragma: lax no cover
pytest.fail(f"Server process exited with code {process.returncode}")
assert Path(startup_marker).exists(), "Server startup marker not created"
@ -217,14 +217,14 @@ async def test_stdin_close_triggers_cleanup():
try:
with anyio.fail_after(5.0): # Increased from 2.0 to 5.0
await process.wait()
except TimeoutError: # pragma: no cover
except TimeoutError: # pragma: lax no cover
# If it doesn't exit after stdin close, terminate it
process.terminate()
await process.wait()
# Check if cleanup ran
with anyio.move_on_after(5.0):
while not Path(cleanup_marker).exists(): # pragma: no cover
while not Path(cleanup_marker).exists(): # pragma: lax no cover
await anyio.sleep(0.1)
# Verify the cleanup ran - stdin closure enables graceful shutdown
@ -234,7 +234,7 @@ async def test_stdin_close_triggers_cleanup():
finally:
# Clean up files
for path in [server_script, startup_marker, cleanup_marker]:
try: # pragma: no cover
try: # pragma: lax no cover
Path(path).unlink()
except FileNotFoundError: # pragma: no cover
except FileNotFoundError: # pragma: lax no cover
pass

View file

@ -33,10 +33,10 @@ async def test_resource_templates():
assert len(templates) == 2
# Verify template details
greeting_template = next(t for t in templates if t.name == "get_greeting") # pragma: no cover
greeting_template = next(t for t in templates if t.name == "get_greeting")
assert greeting_template.uri_template == "greeting://{name}"
assert greeting_template.description == "Get a personalized greeting"
profile_template = next(t for t in templates if t.name == "get_user_profile") # pragma: no cover
profile_template = next(t for t in templates if t.name == "get_user_profile")
assert profile_template.uri_template == "users://{user_id}/profile"
assert profile_template.description == "Dynamic user data"

View file

@ -99,7 +99,7 @@ def check_logs_for_race_condition_errors(caplog: pytest.LogCaptureFixture, test_
# Check for specific race condition errors in logs
errors_found: list[str] = []
for record in caplog.records: # pragma: no cover
for record in caplog.records: # pragma: lax no cover
message = record.getMessage()
if "ClosedResourceError" in message:
errors_found.append("ClosedResourceError")

View file

@ -104,7 +104,7 @@ async def test_notification_validation_error(tmp_path: Path):
# proving server is still responsive
result = await session.call_tool("fast", read_timeout_seconds=None)
assert result.content == [TextContent(type="text", text="fast 3")]
scope.cancel() # pragma: no cover
scope.cancel() # pragma: lax no cover
# Run server and client in separate task groups to avoid cancellation
server_writer, server_reader = anyio.create_memory_object_stream[SessionMessage](1)

View file

@ -82,7 +82,7 @@ async def test_malformed_initialize_request_does_not_crash_server():
except anyio.WouldBlock: # pragma: no cover
pytest.fail("No response received - server likely crashed")
finally: # pragma: no cover
finally: # pragma: lax no cover
# Close all streams to ensure proper cleanup
await read_send_stream.aclose()
await write_send_stream.aclose()
@ -143,7 +143,7 @@ async def test_multiple_concurrent_malformed_requests():
assert isinstance(response, JSONRPCError)
assert response.id == f"malformed_{i}"
assert response.error.code == INVALID_PARAMS
finally: # pragma: no cover
finally: # pragma: lax no cover
# Close all streams to ensure proper cleanup
await read_send_stream.aclose()
await write_send_stream.aclose()

View file

@ -172,7 +172,7 @@ class MockOAuthProvider(OAuthAuthorizationServerProvider[AuthorizationCode, Refr
async def revoke_token(self, token: AccessToken | RefreshToken) -> None:
match token:
case RefreshToken(): # pragma: no cover
case RefreshToken(): # pragma: lax no cover
# Remove the refresh token
del self.refresh_tokens[token.token]

View file

@ -18,9 +18,9 @@ def temp_file():
f.write(content)
path = Path(f.name).resolve()
yield path
try: # pragma: no cover
try: # pragma: lax no cover
path.unlink()
except FileNotFoundError: # pragma: no cover
except FileNotFoundError: # pragma: lax no cover
pass # File was already deleted by the test
@ -101,7 +101,7 @@ class TestFileResource:
@pytest.mark.skipif(os.name == "nt", reason="File permissions behave differently on Windows")
@pytest.mark.anyio
async def test_permission_error(self, temp_file: Path): # pragma: no cover
async def test_permission_error(self, temp_file: Path): # pragma: lax no cover
"""Test reading a file without permissions."""
temp_file.chmod(0o000) # Remove all permissions
try:

View file

@ -18,9 +18,9 @@ def temp_file():
f.write(content)
path = Path(f.name).resolve()
yield path
try: # pragma: no cover
try: # pragma: lax no cover
path.unlink()
except FileNotFoundError: # pragma: no cover
except FileNotFoundError: # pragma: lax no cover
pass # File was already deleted by the test

View file

@ -15,7 +15,7 @@ class TestResourceTemplate:
def test_template_creation(self):
"""Test creating a template from a function."""
def my_func(key: str, value: int) -> dict[str, Any]: # pragma: no cover
def my_func(key: str, value: int) -> dict[str, Any]:
return {"key": key, "value": value}
template = ResourceTemplate.from_function(
@ -239,7 +239,7 @@ class TestResourceTemplateAnnotations:
async def test_template_created_resources_inherit_annotations(self):
"""Test that resources created from templates inherit annotations."""
def get_item(item_id: str) -> str: # pragma: no cover
def get_item(item_id: str) -> str:
return f"Item {item_id}"
annotations = Annotations(priority=0.6)

View file

@ -65,12 +65,10 @@ async def test_stdio_elicitation():
create_ask_user_tool(mcp)
# Create a custom handler for elicitation requests
async def elicitation_callback(
context: RequestContext[ClientSession, None], params: ElicitRequestParams
): # pragma: no cover
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
if params.message == "Tool wants to ask: What is your name?":
return ElicitResult(action="accept", content={"answer": "Test User"})
else:
else: # pragma: no cover
raise ValueError(f"Unexpected elicitation message: {params.message}")
await call_tool_and_assert(
@ -99,10 +97,10 @@ async def test_elicitation_schema_validation():
def create_validation_tool(name: str, schema_class: type[BaseModel]):
@mcp.tool(name=name, description=f"Tool testing {name}")
async def tool(ctx: Context[ServerSession, None]) -> str: # pragma: no cover
async def tool(ctx: Context[ServerSession, None]) -> str:
try:
await ctx.elicit(message="This should fail validation", schema=schema_class)
return "Should not reach here"
return "Should not reach here" # pragma: no cover
except TypeError as e:
return f"Validation failed as expected: {str(e)}"
@ -190,10 +188,10 @@ async def test_elicitation_with_optional_fields():
optional_list: list[int] | None = Field(default=None, description="Invalid optional list")
@mcp.tool(description="Tool with invalid optional field")
async def invalid_optional_tool(ctx: Context[ServerSession, None]) -> str: # pragma: no cover
async def invalid_optional_tool(ctx: Context[ServerSession, None]) -> str:
try:
await ctx.elicit(message="This should fail", schema=InvalidOptionalSchema)
return "Should not reach here"
return "Should not reach here" # pragma: no cover
except TypeError as e:
return f"Validation failed: {str(e)}"

View file

@ -516,7 +516,7 @@ async def test_str_annotation_runtime_validation():
containing valid JSON to ensure they are passed as strings, not parsed objects.
"""
def handle_json_payload(payload: str, strict_mode: bool = False) -> str: # pragma: no cover
def handle_json_payload(payload: str, strict_mode: bool = False) -> str:
"""Function that processes a JSON payload as a string."""
# This function expects to receive the raw JSON string
# It might parse it later after validation or logging
@ -833,7 +833,7 @@ def test_unstructured_output_unannotated_class():
def test_tool_call_result_is_unstructured_and_not_converted():
def func_returning_call_tool_result() -> CallToolResult: # pragma: no cover
def func_returning_call_tool_result() -> CallToolResult:
return CallToolResult(content=[])
meta = func_metadata(func_returning_call_tool_result)
@ -846,7 +846,7 @@ def test_tool_call_result_annotated_is_structured_and_converted():
class PersonClass(BaseModel):
name: str
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]:
return CallToolResult(content=[], structured_content={"name": "Brandon"})
meta = func_metadata(func_returning_annotated_tool_call_result)
@ -866,7 +866,7 @@ def test_tool_call_result_annotated_is_structured_and_invalid():
class PersonClass(BaseModel):
name: str
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]:
return CallToolResult(content=[], structured_content={"person": "Brandon"})
meta = func_metadata(func_returning_annotated_tool_call_result)
@ -1092,7 +1092,7 @@ def test_basemodel_reserved_names():
async def test_basemodel_reserved_names_validation():
"""Test that validation and calling works with reserved parameter names"""
def func_with_reserved_names( # pragma: no cover
def func_with_reserved_names(
model_dump: str,
model_validate: int,
dict: list[str],

View file

@ -887,7 +887,7 @@ class TestServerResourceTemplates:
def get_data_mismatched(org: str, repo_2: str) -> str: # pragma: no cover
return f"Data for {org}"
"""Test that a resource with no parameters works as a regular resource""" # pragma: no cover
"""Test that a resource with no parameters works as a regular resource"""
mcp = FastMCP()
@mcp.resource("resource://static")

View file

@ -182,7 +182,7 @@ class TestAddTools:
class TestCallTools:
@pytest.mark.anyio
async def test_call_tool(self):
def sum(a: int, b: int) -> int: # pragma: no cover
def sum(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@ -193,7 +193,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_async_tool(self):
async def double(n: int) -> int: # pragma: no cover
async def double(n: int) -> int:
"""Double a number."""
return n * 2
@ -260,7 +260,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_tool_with_list_int_input(self):
def sum_vals(vals: list[int]) -> int: # pragma: no cover
def sum_vals(vals: list[int]) -> int:
return sum(vals)
manager = ToolManager()
@ -273,7 +273,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_tool_with_list_str_or_str_input(self):
def concat_strs(vals: list[str] | str) -> str: # pragma: no cover
def concat_strs(vals: list[str] | str) -> str:
return vals if isinstance(vals, str) else "".join(vals)
manager = ToolManager()
@ -297,7 +297,7 @@ class TestCallTools:
shrimp: list[Shrimp]
x: None
def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]: # pragma: no cover
def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]:
return [x.name for x in tank.shrimp]
manager = ToolManager()
@ -375,7 +375,7 @@ class TestContextHandling:
async def test_context_injection_async(self):
"""Test that context is properly injected in async tools."""
async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str: # pragma: no cover
async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str:
assert isinstance(ctx, Context)
return str(x)
@ -467,7 +467,7 @@ class TestStructuredOutput:
name: str
age: int
def get_user(user_id: int) -> UserOutput: # pragma: no cover
def get_user(user_id: int) -> UserOutput:
"""Get user by ID."""
return UserOutput(name="John", age=30)
@ -481,7 +481,7 @@ class TestStructuredOutput:
async def test_tool_with_primitive_output(self):
"""Test tool with primitive return type."""
def double_number(n: int) -> int: # pragma: no cover
def double_number(n: int) -> int:
"""Double a number."""
return 10
@ -502,7 +502,7 @@ class TestStructuredOutput:
expected_output = {"name": "Alice", "age": 25}
def get_user_dict(user_id: int) -> UserDict: # pragma: no cover
def get_user_dict(user_id: int) -> UserDict:
"""Get user as dict."""
return UserDict(name="Alice", age=25)
@ -522,7 +522,7 @@ class TestStructuredOutput:
expected_output = {"name": "Bob", "age": 40}
def get_person() -> Person: # pragma: no cover
def get_person() -> Person:
"""Get a person."""
return Person("Bob", 40)
@ -539,7 +539,7 @@ class TestStructuredOutput:
expected_list = [1, 2, 3, 4, 5]
expected_output = {"result": expected_list}
def get_numbers() -> list[int]: # pragma: no cover
def get_numbers() -> list[int]:
"""Get a list of numbers."""
return expected_list
@ -590,7 +590,7 @@ class TestStructuredOutput:
async def test_tool_with_dict_str_any_output(self):
"""Test tool with dict[str, Any] return type."""
def get_config() -> dict[str, Any]: # pragma: no cover
def get_config() -> dict[str, Any]:
"""Get configuration"""
return {"debug": True, "port": 8080, "features": ["auth", "logging"]}
@ -615,7 +615,7 @@ class TestStructuredOutput:
async def test_tool_with_dict_str_typed_output(self):
"""Test tool with dict[str, T] return type for specific T."""
def get_scores() -> dict[str, int]: # pragma: no cover
def get_scores() -> dict[str, int]:
"""Get player scores"""
return {"alice": 100, "bob": 85, "charlie": 92}
@ -879,7 +879,7 @@ class TestRemoveTools:
async def test_call_removed_tool_raises_error(self):
"""Test that calling a removed tool raises ToolError."""
def greet(name: str) -> str: # pragma: no cover
def greet(name: str) -> str:
"""Greet someone."""
return f"Hello, {name}!"

Some files were not shown because too many files have changed in this diff Show more