Skip to content

Commit 9ea76e8

Browse files
authored
Merge pull request #2550 from jlowin/deflake-integration-tests-2
Improve rate limit detection for integration tests
2 parents acf4db0 + 6faef9d commit 9ea76e8

File tree

3 files changed

+29
-6
lines changed

3 files changed

+29
-6
lines changed

.github/workflows/run-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ jobs:
9999
run: uv sync --upgrade
100100

101101
- name: Run integration tests
102-
# use longer per-test timeout than the default 3s
103-
run: uv run pytest tests -m "integration" --timeout=15 --numprocesses auto --maxprocesses 2 --dist worksteal
102+
# use longer per-test timeout for remote API calls (default is 5s)
103+
run: uv run pytest tests -m "integration" --timeout=30 --numprocesses auto --maxprocesses 2 --dist worksteal
104104
env:
105105
FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }}
106106
FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }}

src/fastmcp/client/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,11 @@ async def __aenter__(self):
374374
return await self._connect()
375375

376376
async def __aexit__(self, exc_type, exc_val, exc_tb):
377-
await self._disconnect()
377+
# Use a timeout to prevent hanging during cleanup if the connection is in a bad
378+
# state (e.g., rate-limited). The MCP SDK's transport may try to terminate the
379+
# session which can hang if the server is unresponsive.
380+
with anyio.move_on_after(5):
381+
await self._disconnect()
378382

379383
async def _connect(self):
380384
"""

tests/integration_tests/conftest.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
import pytest
44

55

6-
def _is_rate_limit_error(excinfo) -> bool:
7-
"""Check if an exception indicates a rate limit error from GitHub API."""
6+
def _is_rate_limit_error(excinfo, report=None) -> bool:
7+
"""Check if an exception indicates a rate limit error from GitHub API.
8+
9+
Args:
10+
excinfo: The exception info from pytest
11+
report: Optional test report for additional context (captured output, longrepr)
12+
"""
813
if excinfo is None:
914
return False
1015

@@ -28,6 +33,20 @@ def _is_rate_limit_error(excinfo) -> bool:
2833
if "429" in exc_str or "rate limit" in exc_str or "too many requests" in exc_str:
2934
return True
3035

36+
# Timeout exceptions may indicate rate limiting when the 429 causes asyncio
37+
# shutdown issues. Check if it's a timeout and look for 429 in the captured output.
38+
if "timeout" in exc_type.lower() or "timeout" in exc_str:
39+
# Check captured output for 429 indicators
40+
if report is not None:
41+
longrepr_str = str(report.longrepr).lower() if report.longrepr else ""
42+
if "429" in longrepr_str or "too many requests" in longrepr_str:
43+
return True
44+
45+
# Check captured stdout/stderr
46+
for section_name, content in getattr(report, "sections", []):
47+
if "429" in content or "too many requests" in content.lower():
48+
return True
49+
3150
return False
3251

3352

@@ -43,7 +62,7 @@ def pytest_runtest_makereport(item, call):
4362
and report.failed
4463
and not hasattr(report, "wasxfail")
4564
and item.module.__name__ == "tests.integration_tests.test_github_mcp_remote"
46-
and _is_rate_limit_error(call.excinfo)
65+
and _is_rate_limit_error(call.excinfo, report)
4766
):
4867
report.outcome = "skipped"
4968
report.longrepr = (

0 commit comments

Comments
 (0)