|
1 | 1 | import os |
2 | | -from collections.abc import Iterator |
| 2 | +from collections.abc import AsyncIterator, Iterator |
3 | 3 |
|
4 | 4 | import pytest |
5 | 5 |
|
|
18 | 18 | import mcp.shared._otel # noqa: E402 |
19 | 19 |
|
20 | 20 |
|
21 | | -@pytest.fixture |
22 | | -def anyio_backend(): |
| 21 | +@pytest.fixture(scope="session") |
| 22 | +def anyio_backend() -> str: |
23 | 23 | return "asyncio" |
24 | 24 |
|
25 | 25 |
|
| 26 | +@pytest.fixture(scope="module", autouse=True) |
| 27 | +async def _module_runner_lease(anyio_backend: str) -> AsyncIterator[None]: |
| 28 | + """Share one event loop across each module's tests instead of one per test. |
| 29 | +
|
| 30 | + anyio's pytest plugin tears its runner down whenever the last lease is |
| 31 | + released, so with only function-scoped async fixtures every async test |
| 32 | + creates and destroys its own event loop. On Windows each loop's self-pipe |
| 33 | + is an emulated loopback-TCP socketpair, and churning thousands of those per |
| 34 | + run can transiently exhaust kernel socket buffers — surfacing in CI as |
| 35 | + `OSError: [WinError 10055]` raised from `asyncio.new_event_loop()` before |
| 36 | + an arbitrary test's body even starts. Holding a module-scoped lease caps |
| 37 | + the churn at one loop per module per xdist worker. |
| 38 | +
|
| 39 | + Modules that parametrize `anyio_backend` or call `trio.run(...)` directly |
| 40 | + must shadow this fixture with a sync no-op: a module-scoped lease cannot |
| 41 | + depend on the function-scoped parameter (pytest raises ScopeMismatch at |
| 42 | + setup), and the lease's live asyncio loop lingers over direct trio runs, |
| 43 | + whose signal handling collides with the loop's wakeup fd on Windows. The |
| 44 | + lease also makes sniffio report asyncio to the module's sync tests, so a |
| 45 | + sync test must not call `anyio.run()` itself. |
| 46 | + """ |
| 47 | + yield |
| 48 | + |
| 49 | + |
26 | 50 | @pytest.fixture(name="capfire") |
27 | 51 | def _capfire_isolated(capfire: CaptureLogfire) -> Iterator[CaptureLogfire]: |
28 | 52 | """Override of logfire's `capfire` that scopes the MCP tracer to the test. |
|
0 commit comments