Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import socket
import sys
import time
from collections.abc import Iterable
from collections.abc import Callable, Iterable
from functools import cache
from threading import Event
from typing import Any
from unittest import mock

Expand Down Expand Up @@ -127,6 +128,16 @@ def _wait_for_start(zc: Zeroconf) -> None:
asyncio.run_coroutine_threadsafe(zc.async_wait_for_start(), zc.loop).result()


def _wait_for(predicate: Callable[[], bool], timeout: float = 2.0) -> bool:
"""Poll `predicate` from a non-loop thread until true or `timeout` seconds pass."""
deadline = time.monotonic() + timeout
while not predicate():
if time.monotonic() >= deadline:
return False
time.sleep(0.01)
return True


@cache
def has_working_ipv6():
"""Return True if the system can bind an IPv6 address."""
Expand Down Expand Up @@ -180,6 +191,26 @@ def _backdate_cache(zc: Zeroconf, ms: int = 1100) -> None:
record.created -= ms


def _restamp_cache(zc: Zeroconf, created: float, ttl: int) -> None:
"""Re-add every cached record with a new `created` and `ttl` from the loop thread.

Unlike `_backdate_cache` this goes through `_async_set_created_ttl`, so
the expiration heap is updated and the reaper will actually expire the
records; the heap is only safe to touch from the event loop thread.
"""
assert zc.loop is not None
done = Event()

def _restamp() -> None:
for store in list(zc.cache.cache.values()):
for record in list(store.values()):
zc.cache._async_set_created_ttl(record, created, ttl)
done.set()

zc.loop.call_soon_threadsafe(_restamp)
assert done.wait(2)


def time_changed_millis(millis: float | None = None) -> None:
"""Call all scheduled events for a time."""
loop = asyncio.get_running_loop()
Expand Down
29 changes: 8 additions & 21 deletions tests/services/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
from .. import (
QuestionHistoryWithoutSuppression,
_inject_response,
_restamp_cache,
_wait_for,
_wait_for_start,
has_working_ipv6,
make_service_info,
Expand Down Expand Up @@ -1490,16 +1492,10 @@ def update_service(self, zc, type_, name) -> None: # type: ignore[no-untyped-de
),
)
# Force the ttl to be 1 second
now = current_time_millis()
for cache_record in list(zc.cache.cache.values()):
for record in cache_record.values():
zc.cache._async_set_created_ttl(record, now, 1)
_restamp_cache(zc, current_time_millis(), 1)

# Wait for the add callback to fire from the original inject_response.
for _ in range(30):
time.sleep(0.01)
if len(callbacks) == 1:
break
_wait_for(lambda: len(callbacks) >= 1)

info.port = 400
info._dns_service_cache = None # we are mutating the record so clear the cache
Expand All @@ -1509,10 +1505,7 @@ def update_service(self, zc, type_, name) -> None: # type: ignore[no-untyped-de
mock_incoming_msg([info.dns_service()]),
)

for _ in range(30):
time.sleep(0.01)
if len(callbacks) == 2:
break
_wait_for(lambda: len(callbacks) >= 2)

assert callbacks == [
("add", type_, registration_name),
Expand All @@ -1525,15 +1518,9 @@ def update_service(self, zc, type_, name) -> None: # type: ignore[no-untyped-de
# Going through `_async_set_created_ttl` updates the expiration
# heap; mutating `record.created` directly would leave the heap
# entry pointing at the original `when` so the reaper never wakes.
past = current_time_millis() - 2000
for cache_record in list(zc.cache.cache.values()):
for record in list(cache_record.values()):
zc.cache._async_set_created_ttl(record, past, 1)

for _ in range(30):
time.sleep(0.01)
if len(callbacks) == 3:
break
_restamp_cache(zc, current_time_millis() - 2000, 1)

_wait_for(lambda: len(callbacks) >= 3)

assert callbacks == [
("add", type_, registration_name),
Expand Down
Loading