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

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}!"

View file

@ -102,7 +102,7 @@ async def test_dependent_completion_scenario():
db = context.arguments.get("database")
if db == "users_db":
return Completion(values=["users", "sessions", "permissions"], total=3, has_more=False)
elif db == "products_db": # pragma: no cover
elif db == "products_db":
return Completion(values=["products", "categories", "inventory"], total=3, has_more=False)
return Completion(values=[], total=0, has_more=False) # pragma: no cover
@ -153,7 +153,7 @@ async def test_completion_error_on_missing_context():
raise ValueError("Please select a database first to see available tables")
# Normal completion if context is provided
db = context.arguments.get("database")
if db == "test_db": # pragma: no cover
if db == "test_db":
return Completion(values=["users", "orders", "products"], total=3, has_more=False)
return Completion(values=[], total=0, has_more=False) # pragma: no cover

View file

@ -70,8 +70,7 @@ async def run_tool_test(
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)

View file

@ -71,8 +71,7 @@ async def run_tool_test(
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)

View file

@ -20,7 +20,7 @@ async def test_lowlevel_server_tool_annotations():
# Create a tool with annotations
@server.list_tools()
async def list_tools(): # pragma: no cover
async def list_tools():
return [
Tool(
name="echo",
@ -67,8 +67,7 @@ async def test_lowlevel_server_tool_annotations():
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)

View file

@ -391,8 +391,7 @@ async def test_create_message_tool_result_validation():
# Case 8: empty messages list - skips validation entirely
# Covers the `if messages:` branch (line 280->302)
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
with anyio.move_on_after(0.01): # pragma: no cover
with anyio.move_on_after(0.01): # pragma: no branch
await session.create_message(messages=[], max_tokens=100)