Skip to content

Commit e240afd

Browse files
committed
test: speed up service-info request tests with quick_request_timing fixture
`AsyncServiceInfo.async_request` waits `_LISTENER_TIME` (200ms) plus 20-120ms of jitter before firing the first query. On real networks that staggering helps clients avoid synchronized bursts (RFC 6762 §5.2); on loopback it's pure overhead. Tests that only need to see which queries get sent paid the wait every time and had to pad their timeouts to ~500ms to accommodate it. New `quick_request_timing` fixture patches `_LISTENER_TIME=10` and the jitter interval to `(1, 5)`. With the fixture, callers can drop their timeouts and negative-wait windows to ~50-100ms. Speedups: - test_get_info_single: 1.01s → 0.11s - test_info_asking_default_is_asking_qm_questions_after_the_first_qu: 0.77s → 0.30s - test_asking_qu_questions: 0.52s → 0.05s - test_asking_qm_questions: similar, also picks up the fixture for symmetry with test_asking_qu_questions. No production change — both constants were already plain Python module attributes on `_services.info` (not declared in `info.pxd`), so the patches take effect under both the Cython and pure-Python builds.
1 parent 6db1f91 commit e240afd

3 files changed

Lines changed: 37 additions & 11 deletions

File tree

tests/conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from zeroconf import _core, const
1212
from zeroconf._handlers import query_handler
13+
from zeroconf._services import info as service_info
1314

1415

1516
@pytest.fixture(autouse=True)
@@ -59,3 +60,21 @@ def quick_timing() -> Generator[None]:
5960
patch.object(_core, "_UNREGISTER_TIME", 10),
6061
):
6162
yield
63+
64+
65+
@pytest.fixture
66+
def quick_request_timing() -> Generator[None]:
67+
"""Shorten the initial-query delay used by AsyncServiceInfo.async_request.
68+
69+
The 200ms `_LISTENER_TIME` and 20-120ms random jitter (RFC 6762
70+
§5.2) help spread queries from multiple clients on real networks.
71+
On loopback they're pure overhead — get_service_info-style tests
72+
wait ~250ms before the first query even fires. Opt in by adding
73+
`quick_request_timing` to a test's argument list, then drop the
74+
test's own timeouts (which had to accommodate that delay).
75+
"""
76+
with (
77+
patch.object(service_info, "_LISTENER_TIME", 10),
78+
patch.object(service_info, "_AVOID_SYNC_DELAY_RANDOM_INTERVAL", (1, 5)),
79+
):
80+
yield

tests/services/test_info.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ def get_service_info_helper(zc, type, name):
506506
zc.remove_all_service_listeners()
507507
zc.close()
508508

509+
@pytest.mark.usefixtures("quick_request_timing")
509510
def test_get_info_single(self):
510511
zc = r.Zeroconf(interfaces=["127.0.0.1"])
511512

@@ -551,6 +552,9 @@ def get_service_info_helper(zc, type, name):
551552
args=(zc, service_type, service_name),
552553
)
553554
helper_thread.start()
555+
# Positive wait — the first query fires within
556+
# `_LISTENER_TIME` + jitter (~15ms under
557+
# `quick_request_timing`, ~320ms without).
554558
wait_time = 1
555559

556560
# Expect query for SRV, TXT, A, AAAA
@@ -563,7 +567,10 @@ def get_service_info_helper(zc, type, name):
563567
assert r.DNSQuestion(service_name, const._TYPE_AAAA, const._CLASS_IN) in last_sent.questions
564568
assert service_info is None
565569

566-
# Expect no further queries
570+
# Expect no further queries — under `quick_request_timing`
571+
# the next query would have fired ~15ms after the previous
572+
# send, so 100ms is plenty of headroom for the negative
573+
# assertion.
567574
last_sent = None
568575
send_event.clear()
569576
_inject_response(
@@ -597,7 +604,7 @@ def get_service_info_helper(zc, type, name):
597604
]
598605
),
599606
)
600-
send_event.wait(wait_time)
607+
send_event.wait(0.1)
601608
assert last_sent is None
602609
assert service_info is not None
603610

@@ -980,7 +987,7 @@ def test_serviceinfo_accepts_bytes_or_string_dict():
980987
assert info_service.dns_text().text == b"\x0epath=/~paulsm/"
981988

982989

983-
def test_asking_qu_questions():
990+
def test_asking_qu_questions(quick_request_timing):
984991
"""Verify explicitly asking QU questions."""
985992
type_ = "_quservice._tcp.local."
986993
zeroconf = r.Zeroconf(interfaces=["127.0.0.1"])
@@ -999,12 +1006,12 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
9991006

10001007
# patch the zeroconf send
10011008
with patch.object(zeroconf, "async_send", send):
1002-
zeroconf.get_service_info(f"name.{type_}", type_, 500, question_type=r.DNSQuestionType.QU)
1009+
zeroconf.get_service_info(f"name.{type_}", type_, 50, question_type=r.DNSQuestionType.QU)
10031010
assert first_outgoing.questions[0].unicast is True # type: ignore[union-attr]
10041011
zeroconf.close()
10051012

10061013

1007-
def test_asking_qm_questions():
1014+
def test_asking_qm_questions(quick_request_timing):
10081015
"""Verify explicitly asking QM questions."""
10091016
type_ = "_quservice._tcp.local."
10101017
zeroconf = r.Zeroconf(interfaces=["127.0.0.1"])
@@ -1023,7 +1030,7 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
10231030

10241031
# patch the zeroconf send
10251032
with patch.object(zeroconf, "async_send", send):
1026-
zeroconf.get_service_info(f"name.{type_}", type_, 500, question_type=r.DNSQuestionType.QM)
1033+
zeroconf.get_service_info(f"name.{type_}", type_, 50, question_type=r.DNSQuestionType.QM)
10271034
assert first_outgoing.questions[0].unicast is False # type: ignore[union-attr]
10281035
zeroconf.close()
10291036

tests/test_asyncio.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT, v6_flow_scope=()):
11391139

11401140

11411141
@pytest.mark.asyncio
1142-
async def test_info_asking_default_is_asking_qm_questions_after_the_first_qu():
1142+
async def test_info_asking_default_is_asking_qm_questions_after_the_first_qu(quick_request_timing):
11431143
"""Verify the service info first question is QU and subsequent ones are QM questions."""
11441144
type_ = "_quservice._tcp.local."
11451145
aiozc = AsyncZeroconf(interfaces=["127.0.0.1"])
@@ -1182,11 +1182,11 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
11821182
# patch the zeroconf send
11831183
with patch.object(zeroconf_info, "async_send", send):
11841184
aiosinfo = AsyncServiceInfo(type_, registration_name)
1185-
# Patch _is_complete so we send multiple times. 500ms covers
1186-
# the QU query at 0ms plus the QM query at ~_LISTENER_TIME +
1187-
# max random delay (~320ms).
1185+
# Patch _is_complete so we send multiple times. Under
1186+
# `quick_request_timing` both the QU query at 0ms and the QM
1187+
# query at ~15ms land well inside 50ms.
11881188
with patch("zeroconf.asyncio.AsyncServiceInfo._is_complete", False):
1189-
await aiosinfo.async_request(aiozc.zeroconf, 500)
1189+
await aiosinfo.async_request(aiozc.zeroconf, 50)
11901190
try:
11911191
assert first_outgoing.questions[0].unicast is True # type: ignore[union-attr]
11921192
assert second_outgoing.questions[0].unicast is False # type: ignore[attr-defined]

0 commit comments

Comments
 (0)