Skip to content

Commit 04831b7

Browse files
zzstoatzzclaude
andauthored
optimize test suite (#1893)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1f2d3f2 commit 04831b7

16 files changed

Lines changed: 88 additions & 40 deletions

.github/workflows/run-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
run: uv sync --frozen
4949

5050
- name: Run tests (excluding integration and client_process)
51-
run: uv run pytest --inline-snapshot=disable -v tests -m "not integration and not client_process"
51+
run: uv run pytest --inline-snapshot=disable -v tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal
5252

5353
- name: Run client process tests separately
5454
run: uv run pytest --inline-snapshot=disable -v tests -m "client_process" -x
@@ -74,7 +74,7 @@ jobs:
7474

7575
- name: Run integration tests
7676
# use longer per-test timeout than the default 3s
77-
run: uv run pytest -v tests -m "integration" --timeout=15
77+
run: uv run pytest -v tests -m "integration" --timeout=15 --numprocesses auto --maxprocesses 2 --dist worksteal
7878
env:
7979
FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }}
8080
FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }}

src/fastmcp/utilities/tests.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,20 @@ def run_server_in_process(
109109
proc.start()
110110

111111
# Wait for server to be running
112-
max_attempts = 10
112+
max_attempts = 30
113113
attempt = 0
114114
while attempt < max_attempts and proc.is_alive():
115115
try:
116116
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
117117
s.connect((host, port))
118118
break
119119
except ConnectionRefusedError:
120-
if attempt < 3:
121-
time.sleep(0.01)
122-
else:
120+
if attempt < 5:
121+
time.sleep(0.05)
122+
elif attempt < 15:
123123
time.sleep(0.1)
124+
else:
125+
time.sleep(0.2)
124126
attempt += 1
125127
else:
126128
raise RuntimeError(f"Server failed to start after {max_attempts} attempts")

tests/client/auth/test_oauth_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def run_server(host: str, port: int, **kwargs) -> None:
3939
fastmcp_server(f"http://{host}:{port}").run(host=host, port=port, **kwargs)
4040

4141

42-
@pytest.fixture(scope="module")
42+
@pytest.fixture
4343
def streamable_http_server() -> Generator[str, None, None]:
4444
with run_server_in_process(run_server, transport="http") as url:
4545
yield f"{url}/mcp"

tests/client/test_openapi_experimental.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,19 @@ def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None:
5555
app.run(host=host, port=port, **kwargs)
5656

5757

58-
@pytest.fixture(scope="module")
58+
@pytest.fixture
5959
def shttp_server() -> Generator[str, None, None]:
6060
with run_server_in_process(run_server, transport="http") as url:
6161
yield f"{url}/mcp"
6262

6363

64-
@pytest.fixture(scope="module")
64+
@pytest.fixture
6565
def sse_server() -> Generator[str, None, None]:
6666
with run_server_in_process(run_server, transport="sse") as url:
6767
yield f"{url}/sse"
6868

6969

70-
@pytest.fixture(scope="module")
70+
@pytest.fixture
7171
def proxy_server(shttp_server: str) -> Generator[str, None, None]:
7272
with run_server_in_process(
7373
run_proxy_server,

tests/client/test_openapi_legacy.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@ def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None:
5252
app.run(host=host, port=port, **kwargs)
5353

5454

55-
@pytest.fixture(scope="module")
55+
@pytest.fixture
5656
def shttp_server() -> Generator[str, None, None]:
5757
with run_server_in_process(run_server, transport="http") as url:
5858
yield f"{url}/mcp"
5959

6060

61-
@pytest.fixture(scope="module")
61+
@pytest.fixture
6262
def sse_server() -> Generator[str, None, None]:
6363
with run_server_in_process(run_server, transport="sse") as url:
6464
yield f"{url}/sse"
6565

6666

67-
@pytest.fixture(scope="module")
67+
@pytest.fixture
6868
def proxy_server(shttp_server: str) -> Generator[str, None, None]:
6969
with run_server_in_process(
7070
run_proxy_server,

tests/client/test_sse.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def run_server(host: str, port: int, **kwargs) -> None:
6767
fastmcp_server().run(host=host, port=port, **kwargs)
6868

6969

70-
@pytest.fixture(autouse=True, scope="module")
70+
@pytest.fixture(autouse=True)
7171
def sse_server() -> Generator[str, None, None]:
7272
with run_server_in_process(run_server, transport="sse") as url:
7373
yield f"{url}/sse"
@@ -161,6 +161,6 @@ async def test_timeout_client_timeout_does_not_override_tool_call_timeout_if_low
161161
"""
162162
async with Client(
163163
transport=SSETransport(sse_server),
164-
timeout=0.01,
164+
timeout=0.1,
165165
) as client:
166-
await client.call_tool("sleep", {"seconds": 0.1}, timeout=2)
166+
await client.call_tool("sleep", {"seconds": 0.01}, timeout=2)

tests/client/test_streamable_http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ async def test_timeout(self, streamable_http_server: str):
225225
with pytest.raises(McpError, match="Timed out"):
226226
async with Client(
227227
transport=StreamableHttpTransport(streamable_http_server),
228-
timeout=0.1,
228+
timeout=0.02,
229229
) as client:
230-
await client.call_tool("sleep", {"seconds": 0.2})
230+
await client.call_tool("sleep", {"seconds": 0.05})
231231

232232
async def test_timeout_tool_call(self, streamable_http_server: str):
233233
async with Client(

tests/conftest.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import socket
12
from collections.abc import Callable
23
from typing import Any
34

@@ -22,3 +23,37 @@ def import_rich_rule():
2223

2324
def get_fn_name(fn: Callable[..., Any]) -> str:
2425
return fn.__name__ # ty: ignore[unresolved-attribute]
26+
27+
28+
@pytest.fixture
29+
def worker_id(request):
30+
"""Get the xdist worker ID, or 'master' if not using xdist."""
31+
return getattr(request.config, "workerinput", {}).get("workerid", "master")
32+
33+
34+
@pytest.fixture
35+
def free_port():
36+
"""Get a free port for the test to use."""
37+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
38+
s.bind(("127.0.0.1", 0))
39+
s.listen(1)
40+
port = s.getsockname()[1]
41+
return port
42+
43+
44+
@pytest.fixture
45+
def free_port_factory(worker_id):
46+
"""Factory to get free ports that tracks used ports per test session."""
47+
used_ports = set()
48+
49+
def get_port():
50+
while True:
51+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
52+
s.bind(("127.0.0.1", 0))
53+
s.listen(1)
54+
port = s.getsockname()[1]
55+
if port not in used_ports:
56+
used_ports.add(port)
57+
return port
58+
59+
return get_port

tests/contrib/test_bulk_tool_caller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def no_return_tool_result_factory(arg1: str) -> CallToolRequestResult:
6767
)
6868

6969

70-
@pytest.fixture(scope="module")
70+
@pytest.fixture
7171
def live_server_with_tool() -> FastMCP:
7272
"""Fixture to create a FastMCP server instance with the echo_tool registered."""
7373
server = FastMCP()

tests/server/auth/providers/test_descope.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def add(a: int, b: int) -> int:
134134
mcp.run(host=host, port=port, transport="http")
135135

136136

137-
@pytest.fixture(scope="module")
137+
@pytest.fixture
138138
def mcp_server_url() -> Generator[str]:
139139
with run_server_in_process(run_mcp_server) as url:
140140
yield f"{url}/mcp"

0 commit comments

Comments
 (0)