Skip to content

Commit bb2b795

Browse files
committed
test: add blockbuster to detect blocking calls in asyncio tests
1 parent cb0af4a commit bb2b795

3 files changed

Lines changed: 77 additions & 3 deletions

File tree

poetry.lock

Lines changed: 29 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ cython = "^3.2.4"
9191
setuptools = ">=65.6.3,<83.0.0"
9292
pytest-timeout = "^2.1.0"
9393
pytest-codspeed = ">=5.0.2,<6.0"
94+
blockbuster = ">=1.5.5,<2.0.0"
9495

9596
[tool.poetry.group.docs.dependencies]
9697
sphinx = "^7.4.7 || ^8.1.3"

tests/conftest.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import threading
6-
from collections.abc import AsyncGenerator, Generator
6+
from collections.abc import AsyncGenerator, Generator, Iterator
77
from unittest.mock import patch
88

99
import pytest
@@ -15,6 +15,52 @@
1515
from zeroconf._services import info as service_info
1616
from zeroconf.asyncio import AsyncZeroconf
1717

18+
try:
19+
from blockbuster import BlockBuster, blockbuster_ctx
20+
except ImportError: # platforms without blockbuster (e.g. PyPy under QEMU)
21+
BlockBuster = None # type: ignore[assignment,misc]
22+
blockbuster_ctx = None # type: ignore[assignment]
23+
24+
_BENCHMARKS_DIR = "tests/benchmarks"
25+
26+
# Tests that perform sync IO inside the asyncio event loop and trip
27+
# blockbuster. Marked xfail so CI stays green; pop entries as they get
28+
# fixed so the underlying blocking call is gone for good. The single
29+
# entry below pins the deliberate sync-bootstrap path Zeroconf takes
30+
# when constructed with `use_asyncio=False` from inside a running loop
31+
# — that block on the loop-thread-ready event is the behavior the test
32+
# is documenting, so it stays xfail by design.
33+
_KNOWN_BLOCKING: frozenset[str] = frozenset(
34+
{
35+
"tests/test_core.py::Framework::test_use_asyncio_false_forces_thread_when_loop_running",
36+
}
37+
)
38+
39+
40+
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
41+
"""Mark known-blocking tests xfail so blockbuster doesn't fail the suite."""
42+
if blockbuster_ctx is None:
43+
return
44+
marker = pytest.mark.xfail(
45+
reason="blockbuster: blocking call in asyncio path",
46+
strict=False,
47+
)
48+
for item in items:
49+
if item.nodeid in _KNOWN_BLOCKING:
50+
item.add_marker(marker)
51+
52+
53+
@pytest.fixture(autouse=True)
54+
def blockbuster(
55+
request: pytest.FixtureRequest,
56+
) -> Iterator[BlockBuster | None]:
57+
"""Fail any test that performs a blocking call inside the asyncio loop."""
58+
if blockbuster_ctx is None or _BENCHMARKS_DIR in str(request.node.fspath):
59+
yield None
60+
return
61+
with blockbuster_ctx() as bb:
62+
yield bb
63+
1864

1965
@pytest.fixture(autouse=True)
2066
def verify_threads_ended():

0 commit comments

Comments
 (0)