ci: add strict-no-cover to detect unnecessary coverage pragmas (#1897)
This commit is contained in:
parent
3cfdea0e97
commit
a7ddfdae07
13 changed files with 355 additions and 358 deletions
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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}!"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue