|
6 | 6 |
|
7 | 7 | import logging |
8 | 8 | import socket |
9 | | -import threading |
10 | 9 | import time |
11 | 10 | import os |
12 | 11 | import unittest |
13 | 12 | from threading import Event |
14 | | -from typing import List |
15 | 13 |
|
16 | 14 | import pytest |
17 | 15 |
|
18 | 16 | import zeroconf as r |
19 | | -from zeroconf import DNSAddress, DNSPointer, DNSQuestion, const, current_time_millis |
| 17 | +from zeroconf import DNSPointer, DNSQuestion, const, current_time_millis |
20 | 18 | import zeroconf._services.browser as _services_browser |
21 | 19 | from zeroconf import Zeroconf |
22 | 20 | from zeroconf._services import ServiceStateChange |
23 | 21 | from zeroconf._services.browser import ServiceBrowser |
24 | 22 | from zeroconf._services.info import ServiceInfo |
25 | | -from zeroconf.aio import AsyncZeroconf |
26 | 23 |
|
27 | | -from . import has_working_ipv6, _clear_cache, _inject_response |
| 24 | +from .. import has_working_ipv6, _inject_response |
28 | 25 |
|
29 | 26 |
|
30 | 27 | log = logging.getLogger('zeroconf') |
@@ -237,6 +234,113 @@ def mock_incoming_msg(service_state_change: r.ServiceStateChange) -> r.DNSIncomi |
237 | 234 | zeroconf.close() |
238 | 235 |
|
239 | 236 |
|
| 237 | +class TestServiceBrowserMultipleTypes(unittest.TestCase): |
| 238 | + def test_update_record(self): |
| 239 | + |
| 240 | + service_names = ['name2._type2._tcp.local.', 'name._type._tcp.local.', 'name._type._udp.local'] |
| 241 | + service_types = ['_type2._tcp.local.', '_type._tcp.local.', '_type._udp.local.'] |
| 242 | + |
| 243 | + service_added_count = 0 |
| 244 | + service_removed_count = 0 |
| 245 | + service_add_event = Event() |
| 246 | + service_removed_event = Event() |
| 247 | + |
| 248 | + class MyServiceListener(r.ServiceListener): |
| 249 | + def add_service(self, zc, type_, name) -> None: |
| 250 | + nonlocal service_added_count |
| 251 | + service_added_count += 1 |
| 252 | + if service_added_count == 3: |
| 253 | + service_add_event.set() |
| 254 | + |
| 255 | + def remove_service(self, zc, type_, name) -> None: |
| 256 | + nonlocal service_removed_count |
| 257 | + service_removed_count += 1 |
| 258 | + if service_removed_count == 3: |
| 259 | + service_removed_event.set() |
| 260 | + |
| 261 | + def mock_incoming_msg( |
| 262 | + service_state_change: r.ServiceStateChange, service_type: str, service_name: str, ttl: int |
| 263 | + ) -> r.DNSIncoming: |
| 264 | + generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE) |
| 265 | + generated.add_answer_at_time( |
| 266 | + r.DNSPointer(service_type, const._TYPE_PTR, const._CLASS_IN, ttl, service_name), 0 |
| 267 | + ) |
| 268 | + return r.DNSIncoming(generated.packets()[0]) |
| 269 | + |
| 270 | + zeroconf = r.Zeroconf(interfaces=['127.0.0.1']) |
| 271 | + service_browser = r.ServiceBrowser(zeroconf, service_types, listener=MyServiceListener()) |
| 272 | + |
| 273 | + try: |
| 274 | + wait_time = 3 |
| 275 | + |
| 276 | + # all three services added |
| 277 | + _inject_response( |
| 278 | + zeroconf, |
| 279 | + mock_incoming_msg(r.ServiceStateChange.Added, service_types[0], service_names[0], 120), |
| 280 | + ) |
| 281 | + _inject_response( |
| 282 | + zeroconf, |
| 283 | + mock_incoming_msg(r.ServiceStateChange.Added, service_types[1], service_names[1], 120), |
| 284 | + ) |
| 285 | + zeroconf.wait(100) |
| 286 | + |
| 287 | + called_with_refresh_time_check = False |
| 288 | + |
| 289 | + def _mock_get_expiration_time(self, percent): |
| 290 | + nonlocal called_with_refresh_time_check |
| 291 | + if percent == const._EXPIRE_REFRESH_TIME_PERCENT: |
| 292 | + called_with_refresh_time_check = True |
| 293 | + return 0 |
| 294 | + return self.created + (percent * self.ttl * 10) |
| 295 | + |
| 296 | + # Set an expire time that will force a refresh |
| 297 | + with unittest.mock.patch("zeroconf.DNSRecord.get_expiration_time", new=_mock_get_expiration_time): |
| 298 | + _inject_response( |
| 299 | + zeroconf, |
| 300 | + mock_incoming_msg(r.ServiceStateChange.Added, service_types[0], service_names[0], 120), |
| 301 | + ) |
| 302 | + # Add the last record after updating the first one |
| 303 | + # to ensure the service_add_event only gets set |
| 304 | + # after the update |
| 305 | + _inject_response( |
| 306 | + zeroconf, |
| 307 | + mock_incoming_msg(r.ServiceStateChange.Added, service_types[2], service_names[2], 120), |
| 308 | + ) |
| 309 | + service_add_event.wait(wait_time) |
| 310 | + assert called_with_refresh_time_check is True |
| 311 | + assert service_added_count == 3 |
| 312 | + assert service_removed_count == 0 |
| 313 | + |
| 314 | + _inject_response( |
| 315 | + zeroconf, |
| 316 | + mock_incoming_msg(r.ServiceStateChange.Updated, service_types[0], service_names[0], 0), |
| 317 | + ) |
| 318 | + |
| 319 | + # all three services removed |
| 320 | + _inject_response( |
| 321 | + zeroconf, |
| 322 | + mock_incoming_msg(r.ServiceStateChange.Removed, service_types[0], service_names[0], 0), |
| 323 | + ) |
| 324 | + _inject_response( |
| 325 | + zeroconf, |
| 326 | + mock_incoming_msg(r.ServiceStateChange.Removed, service_types[1], service_names[1], 0), |
| 327 | + ) |
| 328 | + _inject_response( |
| 329 | + zeroconf, |
| 330 | + mock_incoming_msg(r.ServiceStateChange.Removed, service_types[2], service_names[2], 0), |
| 331 | + ) |
| 332 | + service_removed_event.wait(wait_time) |
| 333 | + assert service_added_count == 3 |
| 334 | + assert service_removed_count == 3 |
| 335 | + |
| 336 | + finally: |
| 337 | + assert len(zeroconf.listeners) == 1 |
| 338 | + service_browser.cancel() |
| 339 | + assert len(zeroconf.listeners) == 0 |
| 340 | + zeroconf.remove_all_service_listeners() |
| 341 | + zeroconf.close() |
| 342 | + |
| 343 | + |
240 | 344 | def test_backoff(): |
241 | 345 | got_query = Event() |
242 | 346 |
|
|
0 commit comments