Get baseline 100% clean coverage (#1553)

This commit is contained in:
Max Isbey 2025-11-11 13:09:32 +00:00 • committed by GitHub
commit 89e9c43acf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 1032 additions and 1084 deletions

View file

@ -13,7 +13,7 @@ async def test_list_tools_returns_all_tools():
for i in range(num_tools):
@mcp.tool(name=f"tool_{i}")
def dummy_tool_func():
def dummy_tool_func(): # pragma: no cover
f"""Tool number {i}"""
return i

View file

@ -110,7 +110,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():
while not Path(cleanup_marker).exists(): # pragma: no cover
await anyio.sleep(0.1)
# Verify cleanup marker was created - this works now that stdio_client
@ -121,9 +121,9 @@ async def test_lifespan_cleanup_executed():
finally:
# Clean up files
for path in [server_script, startup_marker, cleanup_marker]:
try:
try: # pragma: no cover
Path(path).unlink()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
pass
@ -213,27 +213,27 @@ 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:
if hasattr(process, "returncode") and process.returncode is not None: # pragma: no cover
pytest.fail(f"Server process exited with code {process.returncode}")
assert Path(startup_marker).exists(), "Server startup marker not created"
# Close stdin to signal shutdown
if process.stdin:
if process.stdin: # pragma: no branch
await process.stdin.aclose()
# Wait for process to exit gracefully
try:
with anyio.fail_after(5.0): # Increased from 2.0 to 5.0
await process.wait()
except TimeoutError:
except TimeoutError: # pragma: 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():
while not Path(cleanup_marker).exists(): # pragma: no cover
await anyio.sleep(0.1)
# Verify the cleanup ran - stdin closure enables graceful shutdown
@ -243,7 +243,7 @@ async def test_stdin_close_triggers_cleanup():
finally:
# Clean up files
for path in [server_script, startup_marker, cleanup_marker]:
try:
try: # pragma: no cover
Path(path).unlink()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
pass

View file

@ -11,12 +11,12 @@ async def test_resource_templates():
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
def get_greeting(name: str) -> str: # pragma: no cover
"""Get a personalized greeting"""
return f"Hello, {name}!"
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
def get_user_profile(user_id: str) -> str: # pragma: no cover
"""Dynamic user data"""
return f"Profile data for user {user_id}"
@ -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")
greeting_template = next(t for t in templates if t.name == "get_greeting") # pragma: no cover
assert greeting_template.uriTemplate == "greeting://{name}"
assert greeting_template.description == "Get a personalized greeting"
profile_template = next(t for t in templates if t.name == "get_user_profile")
profile_template = next(t for t in templates if t.name == "get_user_profile") # pragma: no cover
assert profile_template.uriTemplate == "users://{user_id}/profile"
assert profile_template.description == "Dynamic user data"

View file

@ -23,25 +23,25 @@ async def test_icons_and_website_url():
# Create tool with icon
@mcp.tool(icons=[test_icon])
def test_tool(message: str) -> str:
def test_tool(message: str) -> str: # pragma: no cover
"""A test tool with an icon."""
return message
# Create resource with icon
@mcp.resource("test://resource", icons=[test_icon])
def test_resource() -> str:
def test_resource() -> str: # pragma: no cover
"""A test resource with an icon."""
return "test content"
# Create prompt with icon
@mcp.prompt("test_prompt", icons=[test_icon])
def test_prompt(text: str) -> str:
def test_prompt(text: str) -> str: # pragma: no cover
"""A test prompt with an icon."""
return text
# Create resource template with icon
@mcp.resource("test://weather/{city}", icons=[test_icon])
def test_resource_template(city: str) -> str:
def test_resource_template(city: str) -> str: # pragma: no cover
"""Get weather for a city."""
return f"Weather for {city}"
@ -104,7 +104,7 @@ async def test_multiple_icons():
# Create tool with multiple icons
@mcp.tool(icons=[icon1, icon2, icon3])
def multi_icon_tool() -> str:
def multi_icon_tool() -> str: # pragma: no cover
"""A tool with multiple icons."""
return "success"
@ -125,7 +125,7 @@ async def test_no_icons_or_website():
mcp = FastMCP("BasicServer")
@mcp.tool()
def basic_tool() -> str:
def basic_tool() -> str: # pragma: no cover
"""A basic tool without icons."""
return "success"

View file

@ -25,28 +25,28 @@ async def test_resource_template_edge_cases():
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile")
def get_user_profile(user_id: str, optional_param: str | None = None) -> str:
def get_user_profile(user_id: str, optional_param: str | None = None) -> str: # pragma: no cover
return f"Profile for user {user_id}"
# Test case 3: Template with mismatched parameters
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile")
def get_user_profile_mismatch(different_param: str) -> str:
def get_user_profile_mismatch(different_param: str) -> str: # pragma: no cover
return f"Profile for user {different_param}"
# Test case 4: Template with extra function parameters
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile")
def get_user_profile_extra(user_id: str, extra_param: str) -> str:
def get_user_profile_extra(user_id: str, extra_param: str) -> str: # pragma: no cover
return f"Profile for user {user_id}"
# Test case 5: Template with missing function parameters
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile/{section}")
def get_user_profile_missing(user_id: str) -> str:
def get_user_profile_missing(user_id: str) -> str: # pragma: no cover
return f"Profile for user {user_id}"
# Verify valid template works

View file

@ -88,7 +88,7 @@ async def test_lowlevel_resource_mime_type():
return [ReadResourceContents(content=base64_string, mime_type="image/png")]
elif str(uri) == "test://image_bytes":
return [ReadResourceContents(content=bytes(image_bytes), mime_type="image/png")]
raise Exception(f"Resource not found: {uri}")
raise Exception(f"Resource not found: {uri}") # pragma: no cover
# Test that resources are listed with correct mime type
async with client_session(server) as client:

View file

@ -8,13 +8,13 @@ from mcp.server.session import ServerSession
class Database: # Replace with your actual DB type
@classmethod
async def connect(cls):
async def connect(cls): # pragma: no cover
return cls()
async def disconnect(self):
async def disconnect(self): # pragma: no cover
pass
def query(self):
def query(self): # pragma: no cover
return "Hello, World!"
@ -28,7 +28,7 @@ class AppContext:
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: # pragma: no cover
"""Manage application lifecycle with type-safe context"""
# Initialize on startup
db = await Database.connect()
@ -45,7 +45,7 @@ mcp = FastMCP("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools
@mcp.tool()
def query_db(ctx: Context[ServerSession, AppContext]) -> str:
def query_db(ctx: Context[ServerSession, AppContext]) -> str: # pragma: no cover
"""Tool that uses initialized resources"""
db = ctx.request_context.lifespan_context.db
return db.query()

View file

@ -10,7 +10,7 @@ from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test") # pragma: no cover
@pytest.mark.anyio
async def test_windows_stdio_client_with_session():
"""

View file

@ -62,7 +62,7 @@ async def test_notification_validation_error(tmp_path: Path):
return [TextContent(type="text", text=f"slow {request_count}")]
elif name == "fast":
return [TextContent(type="text", text=f"fast {request_count}")]
return [TextContent(type="text", text=f"unknown {request_count}")]
return [TextContent(type="text", text=f"unknown {request_count}")] # pragma: no cover
async def server_handler(
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
@ -107,7 +107,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()
scope.cancel() # pragma: 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

@ -89,9 +89,9 @@ async def test_malformed_initialize_request_does_not_crash_server():
assert second_response.id == "test_id_2"
assert second_response.error.code == INVALID_PARAMS
except anyio.WouldBlock:
except anyio.WouldBlock: # pragma: no cover
pytest.fail("No response received - server likely crashed")
finally:
finally: # pragma: no cover
# Close all streams to ensure proper cleanup
await read_send_stream.aclose()
await write_send_stream.aclose()
@ -154,7 +154,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:
finally: # pragma: no cover
# Close all streams to ensure proper cleanup
await read_send_stream.aclose()
await write_send_stream.aclose()