test: zero out the suite's warning noise

121 warnings -> 0. Two upstream deprecations get narrowly-scoped
filterwarnings entries (the mcp streamablehttp_client rename — adoption
deliberately rides the v2 migration since the new entry point's call
shape changes again there; the starlette httpx TestClient notice). The
one real RuntimeWarning is fixed at the source: tests that mock
asyncio.run_coroutine_threadsafe handed real coroutines to a stub that
never awaited them, GC-firing 'coroutine was never awaited' inside
whatever unrelated test ran later (the same cross-test bleed mechanism
as the CI closed-stream spew — per-test filterwarnings markers cannot
catch it, which is why two such markers existed and still leaked). A
shared _dispatch_stub now closes real coroutines before returning the
canned future; the obsolete markers are removed.
This commit is contained in:
Patrick Buckley
2026-06-11 20:30:40 -07:00
parent 1497c392e4
commit 3e5f2c3870
2 changed files with 38 additions and 10 deletions
+9
View File
@@ -94,6 +94,15 @@ include = [
[tool.pytest.ini_options]
testpaths = ["tests"]
markers = ["live: requires a running LLM backend"]
filterwarnings = [
# mcp v1 deprecates streamablehttp_client for an entry point whose call
# shape only settles in v2 — adoption rides the deliberate v2 migration
# (pin capped <2); silence exactly this message until then.
"ignore:Use `streamable_http_client` instead",
# starlette deprecates the httpx-backed TestClient; revisit at the next
# starlette floor bump.
"ignore:Using `httpx` with `starlette.testclient` is deprecated",
]
[tool.ruff]
target-version = "py311"
+29 -10
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
import concurrent.futures
import inspect
import json
import time
from contextlib import AsyncExitStack, suppress
@@ -26,6 +27,26 @@ from turnstone.core.tools import INTERACTIVE_TOOLS, TOOLS, merge_mcp_tools
# ---------------------------------------------------------------------------
def _dispatch_stub(mock_future: MagicMock) -> Any:
"""Stand-in for ``asyncio.run_coroutine_threadsafe`` in sync-bridge tests.
Closes the never-scheduled coroutine before handing back the canned
future — a mocked dispatch never awaits it, and an unawaited coroutine
GC-fires "coroutine ... was never awaited" inside whatever unrelated
test happens to be running when collection finally occurs (cross-test
bleed that per-test filterwarnings markers cannot catch).
"""
def _rct(coro: Any, _loop: Any) -> MagicMock:
# Only real coroutines need (or survive) closing — several tests
# dispatch a plain MagicMock return value through this seam.
if inspect.iscoroutine(coro):
coro.close()
return mock_future
return _rct
def _fake_mcp_tool(name: str = "search", description: str = "Search stuff") -> MagicMock:
"""Create a mock MCP tool object matching the SDK's Tool type."""
tool = MagicMock()
@@ -2228,7 +2249,7 @@ class TestFutureCancellation:
mock_future = MagicMock()
mock_future.result.side_effect = concurrent.futures.TimeoutError()
with (
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(TimeoutError, match="timed out"),
):
mgr.call_tool_sync("mcp__test__search", {"query": "x"}, timeout=1)
@@ -2239,7 +2260,7 @@ class TestFutureCancellation:
mock_future = MagicMock()
mock_future.result.side_effect = concurrent.futures.TimeoutError()
with (
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(TimeoutError, match="timed out"),
):
mgr.read_resource_sync("file:///a.txt", timeout=1)
@@ -2250,7 +2271,7 @@ class TestFutureCancellation:
mock_future = MagicMock()
mock_future.result.side_effect = concurrent.futures.TimeoutError()
with (
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(TimeoutError, match="timed out"),
):
mgr.get_prompt_sync("mcp__test__review", timeout=1)
@@ -2263,7 +2284,7 @@ class TestFutureCancellation:
mock_future.result.side_effect = concurrent.futures.TimeoutError()
with (
patch.object(mgr, "_refresh_all", return_value=MagicMock()),
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(TimeoutError, match="timed out"),
):
mgr.refresh_sync(timeout=1)
@@ -2378,8 +2399,6 @@ class TestCircuitBreaker:
assert "srv" not in mgr._circuit_open_until
assert "srv" not in mgr._circuit_trip_count
@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning")
@pytest.mark.filterwarnings("ignore:coroutine.*was never awaited:RuntimeWarning")
def test_call_tool_sync_records_failure_on_timeout(self):
mgr = MCPClientManager({"test": {"type": "stdio", "command": "echo"}})
mock_session = MagicMock()
@@ -2390,7 +2409,7 @@ class TestCircuitBreaker:
mock_future = MagicMock()
mock_future.result.side_effect = concurrent.futures.TimeoutError()
with (
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(TimeoutError),
):
mgr.call_tool_sync("mcp__test__ping", {}, timeout=1)
@@ -2410,7 +2429,7 @@ class TestCircuitBreaker:
mock_result.isError = False
mock_future = MagicMock()
mock_future.result.return_value = mock_result
with patch("asyncio.run_coroutine_threadsafe", return_value=mock_future):
with patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)):
mgr.call_tool_sync("mcp__test__ping", {}, timeout=5)
assert mgr._consecutive_failures.get("test") is None
@@ -2427,7 +2446,7 @@ class TestCircuitBreaker:
mock_future = MagicMock()
mock_future.result.side_effect = BrokenPipeError("dead")
with (
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(BrokenPipeError),
):
mgr.call_tool_sync("mcp__test__ping", {}, timeout=5)
@@ -2461,7 +2480,7 @@ class TestCircuitBreaker:
mock_future = MagicMock()
mock_future.result.side_effect = McpError(ErrorData(code=-32601, message="tool not found"))
with (
patch("asyncio.run_coroutine_threadsafe", return_value=mock_future),
patch("asyncio.run_coroutine_threadsafe", new=_dispatch_stub(mock_future)),
pytest.raises(McpError),
):
mgr.call_tool_sync("mcp__test__ping", {}, timeout=5)