8000 tests for issue 88 · yunstanford/python-sdk@888bdd3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 888bdd3

Browse files
committed
tests for issue 88
1 parent 960b923 commit 888bdd3

File tree

1 file changed

+100
-0
lines changed
< 8000 /div>

1 file changed

+100
-0
lines changed

tests/issues/test_88_random_error.py

Lines changed: 100 additions & 0 deletions
+
await server.run(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Test to reproduce issue #88: Random error thrown on response."""
2+
3+
from datetime import timedelta
4+
from pathlib import Path
5+
from typing import Sequence
6+
7+
import anyio
8+
import pytest
9+
10+from mcp.client.session import ClientSession
11+
from mcp.server.lowlevel import Server
12+
from mcp.shared.exceptions import McpError
13+
from mcp.types import (
14+
EmbeddedResource,
15+
ImageContent,
16+
TextContent,
17+
)
18+
19+
20+
@pytest.mark.anyio
21+
async def test_notification_validation_error(tmp_path: Path):
22+
"""Test that timeouts are handled gracefully and don't break the server.
23+
24+
This test verifies that when a client request times out:
25+
1. The server task stays alive
26+
2. The server can still handle new requests
27+
3. The client can make new requests
28+
4. No resources are leaked
29+
"""
30+
31+
server = Server(name="test")
32+
request_count = 0
33+
slow_request_complete = False
34+
35+
@server.call_tool()
36+
async def slow_tool(
37+
name: str, arg
38+
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
39+
nonlocal request_count, slow_request_complete
40+
request_count += 1
41+
42+
if name == "slow":
43+
# Long enough to ensure timeout
44+
await anyio.sleep(0.2)
45+
slow_request_complete = True
46+
return [TextContent(type="text", text=f"slow {request_count}")]
47+
elif name == "fast":
48+
# Fast enough to complete before timeout
49+
await anyio.sleep(0.01)
50+
return [TextContent(type="text", text=f"fast {request_count}")]
51+
return [TextContent(type="text", text=f"unknown {request_count}")]
52+
53+
async def server_handler(read_stream, write_stream):
54
55+
read_stream,
56+
write_stream,
57+
server.create_initialization_options(),
58+
raise_exceptions=True,
59+
)
60+
61+
async def client(read_stream, write_stream):
62+
# Use a timeout that's:
63+
# - Long enough for fast operations (>10ms)
64+
# - Short enough for slow operations (<200ms)
65+
# - Not too short to avoid flakiness
66+
async with ClientSession(
67+
read_stream, write_stream, read_timeout_seconds=timedelta(milliseconds=50)
68+
) as session:
69+
await session.initialize()
70+
71+
# First call should work (fast operation)
72+
result = await session.call_tool("fast")
73+
assert result.content == [TextContent(type="text", text="fast 1")]
74+
assert not slow_request_complete
75+
76+
# Second call should timeout (slow operation)
77+
with pytest.raises(McpError) as exc_info:
78+
await session.call_tool("slow")
79+
assert "Timed out while waiting" in str(exc_info.value)
80+
81+
# Wait for slow request to complete in the background
82+
await anyio.sleep(0.3)
83+
assert slow_request_complete
84+
85+
# Third call should work (fast operation),
86+
# proving server is still responsive
87+
result = await session.call_tool("fast")
88+
assert result.content == [TextContent(type="text", text="fast 3")]
89+
90+
# Run server and client in separate task groups to avoid cancellation
91+
server_writer, server_reader = anyio.create_memory_object_stream(1)
92+
client_writer, client_reader = anyio.create_memory_object_stream(1)
93+
94+
async with anyio.create_task_group() as tg:
95+
tg.start_soon(server_handler, server_reader, client_writer)
96+
# Wait for server to start and initialize
97+
await anyio.sleep(0.1)
98+
# Run client in a separate task to avoid cancellation
99+
async with anyio.create_task_group() as client_tg:
100+
client_tg.start_soon(client, client_reader, server_writer)

0 commit comments

Comments
 (0)
0