async def test_async_service_registration() -> None: """Test registering services broadcasts the registration by default.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test1-srvc-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) calls = [] class MyListener(ServiceListener): def add_service(self, zeroconf: Zeroconf, type: str, name: str) -> None: calls.append(("add", type, name)) def remove_service(self, zeroconf: Zeroconf, type: str, name: str) -> None: calls.append(("remove", type, name)) def update_service(self, zeroconf: Zeroconf, type: str, name: str) -> None: calls.append(("update", type, name)) listener = MyListener() aiozc.zeroconf.add_service_listener(type_, listener) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) task = await aiozc.async_register_service(info) await task new_info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.3")], ) task = await aiozc.async_update_service(new_info) await task task = await aiozc.async_unregister_service(new_info) await task await aiozc.async_close() assert calls == [ ('add', type_, registration_name), ('update', type_, registration_name), ('remove', type_, registration_name), ]
def test_multiple_addresses(): type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ desc = {'path': '/~paulsm/'} address_parsed = "10.0.1.2" address = socket.inet_aton(address_parsed) # New kwarg way info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address, address]) assert info.addresses == [address, address] info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", parsed_addresses=[address_parsed, address_parsed], ) assert info.addresses == [address, address] if has_working_ipv6() and not os.environ.get('SKIP_IPV6'): address_v6_parsed = "2001:db8::1" address_v6 = socket.inet_pton(socket.AF_INET6, address_v6_parsed) infos = [ ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address, address_v6], ), ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", parsed_addresses=[address_parsed, address_v6_parsed], ), ] for info in infos: assert info.addresses == [address] assert info.addresses_by_version(r.IPVersion.All) == [address, address_v6] assert info.addresses_by_version(r.IPVersion.V4Only) == [address] assert info.addresses_by_version(r.IPVersion.V6Only) == [address_v6] assert info.parsed_addresses() == [address_parsed, address_v6_parsed] assert info.parsed_addresses(r.IPVersion.V4Only) == [address_parsed] assert info.parsed_addresses(r.IPVersion.V6Only) == [address_v6_parsed]
async def test_async_unregister_all_services() -> None: """Test unregistering all services.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test1-srvc-type._tcp.local." name = "xxxyyy" name2 = "abc" registration_name = "%s.%s" % (name, type_) registration_name2 = "%s.%s" % (name2, type_) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-1.local.", addresses=[socket.inet_aton("10.0.1.2")], ) info2 = ServiceInfo( type_, registration_name2, 80, 0, 0, desc, "ash-5.local.", addresses=[socket.inet_aton("10.0.1.5")], ) tasks = [] tasks.append(await aiozc.async_register_service(info)) tasks.append(await aiozc.async_register_service(info2)) await asyncio.gather(*tasks) tasks = [] tasks.append(aiozc.async_get_service_info(type_, registration_name)) tasks.append(aiozc.async_get_service_info(type_, registration_name2)) results = await asyncio.gather(*tasks) assert results[0] is not None assert results[1] is not None await aiozc.async_unregister_all_services() tasks = [] tasks.append(aiozc.async_get_service_info(type_, registration_name)) tasks.append(aiozc.async_get_service_info(type_, registration_name2)) results = await asyncio.gather(*tasks) assert results[0] is None assert results[1] is None # Verify we can call again await aiozc.async_unregister_all_services() await aiozc.async_close()
def test_get_name(self): """Verify the name accessor can strip the type.""" desc = {'path': '/~paulsm/'} service_name = 'name._type._tcp.local.' service_type = '_type._tcp.local.' service_server = 'ash-1.local.' service_address = socket.inet_aton("10.0.1.2") info = ServiceInfo( service_type, service_name, 22, 0, 0, desc, service_server, addresses=[service_address] ) assert info.get_name() == "name"
async def test_async_zeroconf_service_types(): type_ = "_test-srvc-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) zeroconf_registrar = AsyncZeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) task = await zeroconf_registrar.async_register_service(info) await task # Ensure we do not clear the cache until after the last broadcast is processed await asyncio.sleep(0.2) _clear_cache(zeroconf_registrar.zeroconf) try: service_types = await AsyncZeroconfServiceTypes.async_find( interfaces=['127.0.0.1'], timeout=0.5) assert type_ in service_types _clear_cache(zeroconf_registrar.zeroconf) service_types = await AsyncZeroconfServiceTypes.async_find( aiozc=zeroconf_registrar, timeout=0.5) assert type_ in service_types finally: await zeroconf_registrar.async_close()
async def test_async_wait_unblocks_on_update() -> None: """Test async_wait will unblock on update.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test-srvc4-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) task = await aiozc.async_register_service(info) # Should unblock due to update from the # registration now = current_time_millis() await aiozc.async_wait(50000) assert current_time_millis() - now < 3000 await task now = current_time_millis() await aiozc.async_wait(50) assert current_time_millis() - now < 1000 await aiozc.async_close()
async def test_async_service_registration_name_conflict() -> None: """Test registering services throws on name conflict.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test-srvc2-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) task = await aiozc.async_register_service(info) await task with pytest.raises(NonUniqueNameException): task = await aiozc.async_register_service(info) await task with pytest.raises(ServiceNameAlreadyRegistered): task = await aiozc.async_register_service(info, cooperating_responders=True) await task conflicting_info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-3.local.", addresses=[socket.inet_aton("10.0.1.3")], ) with pytest.raises(NonUniqueNameException): task = await aiozc.async_register_service(conflicting_info) await task await aiozc.async_close()
def test_filter_address_by_type_from_service_info(): """Verify dns_addresses can filter by ipversion.""" desc = {'path': '/~paulsm/'} type_ = "_homeassistant._tcp.local." name = "MyTestHome" registration_name = "%s.%s" % (name, type_) ipv4 = socket.inet_aton("10.0.1.2") ipv6 = socket.inet_pton(socket.AF_INET6, "2001:db8::1") info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[ipv4, ipv6]) def dns_addresses_to_addresses(dns_address: List[DNSAddress]): return [address.address for address in dns_address] assert dns_addresses_to_addresses(info.dns_addresses()) == [ipv4, ipv6] assert dns_addresses_to_addresses(info.dns_addresses(version=r.IPVersion.All)) == [ipv4, ipv6] assert dns_addresses_to_addresses(info.dns_addresses(version=r.IPVersion.V4Only)) == [ipv4] assert dns_addresses_to_addresses(info.dns_addresses(version=r.IPVersion.V6Only)) == [ipv6]
def test_changing_name_updates_serviceinfo_key(): """Verify a name change will adjust the underlying key value.""" type_ = "_homeassistant._tcp.local." name = "MyTestHome" info_service = ServiceInfo( type_, '%s.%s' % (name, type_), 80, 0, 0, {'path': '/~paulsm/'}, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) assert info_service.key == "mytesthome._homeassistant._tcp.local." info_service.name = "YourTestHome._homeassistant._tcp.local." assert info_service.key == "yourtesthome._homeassistant._tcp.local."
def test_legacy_record_update_listener(): """Test a RecordUpdateListener that does not implement update_records.""" # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) with pytest.raises(RuntimeError): r.RecordUpdateListener().update_record( zc, 0, r.DNSRecord('irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL) ) updates = [] class LegacyRecordUpdateListener(r.RecordUpdateListener): """A RecordUpdateListener that does not implement update_records.""" def update_record(self, zc: 'Zeroconf', now: float, record: r.DNSRecord) -> None: nonlocal updates updates.append(record) listener = LegacyRecordUpdateListener() zc.add_listener(listener, None) # dummy service callback def on_service_state_change(zeroconf, service_type, state_change, name): pass # start a browser type_ = "_homeassistant._tcp.local." name = "MyTestHome" browser = ServiceBrowser(zc, type_, [on_service_state_change]) info_service = ServiceInfo( type_, '%s.%s' % (name, type_), 80, 0, 0, {'path': '/~paulsm/'}, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) zc.register_service(info_service) zc.wait(1) browser.cancel() assert len(updates) assert len([isinstance(update, r.DNSPointer) and update.name == type_ for update in updates]) >= 1 zc.remove_listener(listener) # Removing a second time should not throw zc.remove_listener(listener) zc.close()
def test_service_browser_is_aware_of_port_changes(): """Test that the ServiceBrowser is aware of port changes.""" # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) # start a browser type_ = "_hap._tcp.local." registration_name = "xxxyyy.%s" % type_ callbacks = [] # dummy service callback def on_service_state_change(zeroconf, service_type, state_change, name): nonlocal callbacks if name == registration_name: callbacks.append((service_type, state_change, name)) browser = ServiceBrowser(zc, type_, [on_service_state_change]) desc = {'path': '/~paulsm/'} address_parsed = "10.0.1.2" address = socket.inet_aton(address_parsed) info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address]) def mock_incoming_msg(records) -> r.DNSIncoming: generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE) for record in records: generated.add_answer_at_time(record, 0) return r.DNSIncoming(generated.packets()[0]) _inject_response( zc, mock_incoming_msg([info.dns_pointer(), info.dns_service(), info.dns_text(), *info.dns_addresses()]), ) zc.wait(100) assert callbacks == [('_hap._tcp.local.', ServiceStateChange.Added, 'xxxyyy._hap._tcp.local.')] assert zc.get_service_info(type_, registration_name).port == 80 info.port = 400 _inject_response( zc, mock_incoming_msg([info.dns_service()]), ) zc.wait(100) assert callbacks == [ ('_hap._tcp.local.', ServiceStateChange.Added, 'xxxyyy._hap._tcp.local.'), ('_hap._tcp.local.', ServiceStateChange.Updated, 'xxxyyy._hap._tcp.local.'), ] assert zc.get_service_info(type_, registration_name).port == 400 browser.cancel() zc.close()
async def test_async_service_registration_name_does_not_match_type() -> None: """Test registering services throws when the name does not match the type.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test-srvc3-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) info.type = "_wrong._tcp.local." with pytest.raises(BadTypeInNameException): task = await aiozc.async_register_service(info) await task await aiozc.async_close()
async def test_async_context_manager() -> None: """Test using an async context manager.""" type_ = "_test10-sr-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) async with AsyncZeroconf(interfaces=['127.0.0.1']) as aiozc: info = ServiceInfo( type_, registration_name, 80, 0, 0, {'path': '/~paulsm/'}, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) task = await aiozc.async_register_service(info) await task aiosinfo = await aiozc.async_get_service_info(type_, registration_name) assert aiosinfo is not None
def test_service_info_rejects_expired_records(self): """Verify records that are expired are rejected.""" zc = r.Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} service_name = 'name._type._tcp.local.' service_type = '_type._tcp.local.' service_server = 'ash-1.local.' service_address = socket.inet_aton("10.0.1.2") ttl = 120 now = r.current_time_millis() info = ServiceInfo( service_type, service_name, 22, 0, 0, desc, service_server, addresses=[service_address] ) # Matching updates info.update_record( zc, now, r.DNSText( service_name, const._TYPE_TXT, const._CLASS_IN | const._CLASS_UNIQUE, ttl, b'\x04ff=0\x04ci=2\x04sf=0\x0bsh=6fLM5A==', ), ) assert info.properties[b"ci"] == b"2" # Expired record expired_record = r.DNSText( service_name, const._TYPE_TXT, const._CLASS_IN | const._CLASS_UNIQUE, ttl, b'\x04ff=0\x04ci=3\x04sf=0\x0bsh=6fLM5A==', ) expired_record.created = 1000 expired_record._expiration_time = 1000 info.update_record(zc, now, expired_record) assert info.properties[b"ci"] == b"2" zc.close()
def test_integration(): service_added = Event() service_removed = Event() unexpected_ttl = Event() got_query = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ def on_service_state_change(zeroconf, service_type, state_change, name): if name == registration_name: if state_change is ServiceStateChange.Added: service_added.set() elif state_change is ServiceStateChange.Removed: service_removed.set() zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) # we are going to patch the zeroconf send to check packet sizes old_send = zeroconf_browser.send time_offset = 0.0 def current_time_millis(): """Current system time in milliseconds""" return time.time() * 1000 + time_offset * 1000 expected_ttl = const._DNS_HOST_TTL nbr_answers = 0 def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT): """Sends an outgoing packet.""" pout = r.DNSIncoming(out.packets()[0]) nonlocal nbr_answers for answer in pout.answers: nbr_answers += 1 if not answer.ttl > expected_ttl / 2: unexpected_ttl.set() got_query.set() old_send(out, addr=addr, port=port) # patch the zeroconf send # patch the zeroconf current_time_millis # patch the backoff limit to ensure we always get one query every 1/4 of the DNS TTL with unittest.mock.patch.object(zeroconf_browser, "send", send), unittest.mock.patch.object( s, "current_time_millis", current_time_millis ), unittest.mock.patch.object(s, "_BROWSER_BACKOFF_LIMIT", int(expected_ttl / 4)): service_added = Event() service_removed = Event() browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")] ) zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() # Test that we receive queries containing answers only if the remaining TTL # is greater than half the original TTL sleep_count = 0 test_iterations = 50 while nbr_answers < test_iterations: # Increase simulated time shift by 1/4 of the TTL in seconds time_offset += expected_ttl / 4 zeroconf_browser.notify_all() sleep_count += 1 got_query.wait(0.1) got_query.clear() # Prevent the test running indefinitely in an error condition assert sleep_count < test_iterations * 4 assert not unexpected_ttl.is_set() # Don't remove service, allow close() to cleanup finally: zeroconf_registrar.close() service_removed.wait(1) assert service_removed.is_set() browser.cancel() zeroconf_browser.close()
def test_service_info_rejects_non_matching_updates(self): """Verify records with the wrong name are rejected.""" zc = r.Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} service_name = 'name._type._tcp.local.' service_type = '_type._tcp.local.' service_server = 'ash-1.local.' service_address = socket.inet_aton("10.0.1.2") ttl = 120 now = r.current_time_millis() info = ServiceInfo( service_type, service_name, 22, 0, 0, desc, service_server, addresses=[service_address] ) # Verify backwards compatiblity with calling with None info.update_record(zc, now, None) # Matching updates info.update_record( zc, now, r.DNSText( service_name, const._TYPE_TXT, const._CLASS_IN | const._CLASS_UNIQUE, ttl, b'\x04ff=0\x04ci=2\x04sf=0\x0bsh=6fLM5A==', ), ) assert info.properties[b"ci"] == b"2" info.update_record( zc, now, r.DNSService( service_name, const._TYPE_SRV, const._CLASS_IN | const._CLASS_UNIQUE, ttl, 0, 0, 80, 'ASH-2.local.', ), ) assert info.server_key == 'ash-2.local.' assert info.server == 'ASH-2.local.' new_address = socket.inet_aton("10.0.1.3") info.update_record( zc, now, r.DNSAddress( 'ASH-2.local.', const._TYPE_A, const._CLASS_IN | const._CLASS_UNIQUE, ttl, new_address, ), ) assert new_address in info.addresses # Non-matching updates info.update_record( zc, now, r.DNSText( "incorrect.name.", const._TYPE_TXT, const._CLASS_IN | const._CLASS_UNIQUE, ttl, b'\x04ff=0\x04ci=3\x04sf=0\x0bsh=6fLM5A==', ), ) assert info.properties[b"ci"] == b"2" info.update_record( zc, now, r.DNSService( "incorrect.name.", const._TYPE_SRV, const._CLASS_IN | const._CLASS_UNIQUE, ttl, 0, 0, 80, 'ASH-2.local.', ), ) assert info.server_key == 'ash-2.local.' assert info.server == 'ASH-2.local.' new_address = socket.inet_aton("10.0.1.4") info.update_record( zc, now, r.DNSAddress( "incorrect.name.", const._TYPE_A, const._CLASS_IN | const._CLASS_UNIQUE, ttl, new_address, ), ) assert new_address not in info.addresses zc.close()
def test_integration_with_listener_class(self): service_added = Event() service_removed = Event() service_updated = Event() service_updated2 = Event() subtype_name = "My special Subtype" type_ = "_http._tcp.local." subtype = subtype_name + "._sub." + type_ name = "UPPERxxxyyyæøå" registration_name = "%s.%s" % (name, subtype) class MyListener(r.ServiceListener): def add_service(self, zeroconf, type, name): zeroconf.get_service_info(type, name) service_added.set() def remove_service(self, zeroconf, type, name): service_removed.set() def update_service(self, zeroconf, type, name): service_updated2.set() class MySubListener(r.ServiceListener): def add_service(self, zeroconf, type, name): pass def remove_service(self, zeroconf, type, name): pass def update_service(self, zeroconf, type, name): service_updated.set() listener = MyListener() zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) zeroconf_browser.add_service_listener(subtype, listener) properties = dict( prop_none=None, prop_string=b'a_prop', prop_float=1.0, prop_blank=b'a blanked string', prop_true=1, prop_false=0, ) zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} # type: Dict desc.update(properties) addresses = [socket.inet_aton("10.0.1.2")] if has_working_ipv6() and not os.environ.get('SKIP_IPV6'): addresses.append(socket.inet_pton(socket.AF_INET6, "6001:db8::1")) addresses.append(socket.inet_pton(socket.AF_INET6, "2001:db8::1")) info_service = ServiceInfo( subtype, registration_name, port=80, properties=desc, server="ash-2.local.", addresses=addresses ) zeroconf_registrar.register_service(info_service) try: service_added.wait(1) assert service_added.is_set() # short pause to allow multicast timers to expire time.sleep(3) # clear the answer cache to force query _clear_cache(zeroconf_browser) cached_info = ServiceInfo(type_, registration_name) cached_info.load_from_cache(zeroconf_browser) assert cached_info.properties == {} # get service info without answer cache info = zeroconf_browser.get_service_info(type_, registration_name) assert info is not None assert info.properties[b'prop_none'] is None assert info.properties[b'prop_string'] == properties['prop_string'] assert info.properties[b'prop_float'] == b'1.0' assert info.properties[b'prop_blank'] == properties['prop_blank'] assert info.properties[b'prop_true'] == b'1' assert info.properties[b'prop_false'] == b'0' assert info.addresses == addresses[:1] # no V6 by default assert set(info.addresses_by_version(r.IPVersion.All)) == set(addresses) cached_info = ServiceInfo(type_, registration_name) cached_info.load_from_cache(zeroconf_browser) assert cached_info.properties is not None # Populate the cache zeroconf_browser.get_service_info(subtype, registration_name) # get service info with only the cache cached_info = ServiceInfo(subtype, registration_name) cached_info.load_from_cache(zeroconf_browser) assert cached_info.properties is not None assert cached_info.properties[b'prop_float'] == b'1.0' # get service info with only the cache with the lowercase name cached_info = ServiceInfo(subtype, registration_name.lower()) cached_info.load_from_cache(zeroconf_browser) # Ensure uppercase output is preserved assert cached_info.name == registration_name assert cached_info.key == registration_name.lower() assert cached_info.properties is not None assert cached_info.properties[b'prop_float'] == b'1.0' info = zeroconf_browser.get_service_info(subtype, registration_name) assert info is not None assert info.properties is not None assert info.properties[b'prop_none'] is None cached_info = ServiceInfo(subtype, registration_name.lower()) cached_info.load_from_cache(zeroconf_browser) assert cached_info.properties is not None assert cached_info.properties[b'prop_none'] is None # test TXT record update sublistener = MySubListener() zeroconf_browser.add_service_listener(registration_name, sublistener) properties['prop_blank'] = b'an updated string' desc.update(properties) info_service = ServiceInfo( subtype, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) zeroconf_registrar.update_service(info_service) service_updated.wait(1) assert service_updated.is_set() info = zeroconf_browser.get_service_info(type_, registration_name) assert info is not None assert info.properties[b'prop_blank'] == properties['prop_blank'] cached_info = ServiceInfo(subtype, registration_name) cached_info.load_from_cache(zeroconf_browser) assert cached_info.properties is not None assert cached_info.properties[b'prop_blank'] == properties['prop_blank'] zeroconf_registrar.unregister_service(info_service) service_removed.wait(1) assert service_removed.is_set() finally: zeroconf_registrar.close() zeroconf_browser.remove_service_listener(listener) zeroconf_browser.close()
async def test_service_info_async_request() -> None: """Test registering services broadcasts and query with AsyncServceInfo.async_request.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test1-srvc-type._tcp.local." name = "xxxyyy" name2 = "abc" registration_name = "%s.%s" % (name, type_) registration_name2 = "%s.%s" % (name2, type_) # Start a tasks BEFORE the registration that will keep trying # and see the registration a bit later get_service_info_task1 = asyncio.ensure_future( aiozc.async_get_service_info(type_, registration_name)) await asyncio.sleep(_LISTENER_TIME / 1000 / 2) get_service_info_task2 = asyncio.ensure_future( aiozc.async_get_service_info(type_, registration_name)) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-1.local.", addresses=[socket.inet_aton("10.0.1.2")], ) info2 = ServiceInfo( type_, registration_name2, 80, 0, 0, desc, "ash-5.local.", addresses=[socket.inet_aton("10.0.1.5")], ) tasks = [] tasks.append(await aiozc.async_register_service(info)) tasks.append(await aiozc.async_register_service(info2)) await asyncio.gather(*tasks) aiosinfo = await get_service_info_task1 assert aiosinfo is not None assert aiosinfo.addresses == [socket.inet_aton("10.0.1.2")] aiosinfo = await get_service_info_task2 assert aiosinfo is not None assert aiosinfo.addresses == [socket.inet_aton("10.0.1.2")] aiosinfo = await aiozc.async_get_service_info(type_, registration_name) assert aiosinfo is not None assert aiosinfo.addresses == [socket.inet_aton("10.0.1.2")] new_info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[ socket.inet_aton("10.0.1.3"), socket.inet_pton(socket.AF_INET6, "6001:db8::1") ], ) task = await aiozc.async_update_service(new_info) await task aiosinfo = await aiozc.async_get_service_info(type_, registration_name) assert aiosinfo is not None assert aiosinfo.addresses == [socket.inet_aton("10.0.1.3")] aiosinfos = await asyncio.gather( aiozc.async_get_service_info(type_, registration_name), aiozc.async_get_service_info(type_, registration_name2), ) assert aiosinfos[0] is not None assert aiosinfos[0].addresses == [socket.inet_aton("10.0.1.3")] assert aiosinfos[1] is not None assert aiosinfos[1].addresses == [socket.inet_aton("10.0.1.5")] aiosinfo = AsyncServiceInfo(type_, registration_name) _clear_cache(aiozc.zeroconf) # Generating the race condition is almost impossible # without patching since its a TOCTOU race with unittest.mock.patch("zeroconf.aio.AsyncServiceInfo._is_complete", False): await aiosinfo.async_request(aiozc, 3000) assert aiosinfo is not None assert aiosinfo.addresses == [socket.inet_aton("10.0.1.3")] task = await aiozc.async_unregister_service(new_info) await task aiosinfo = await aiozc.async_get_service_info(type_, registration_name) assert aiosinfo is None await aiozc.async_close()
async def test_async_service_browser() -> None: """Test AsyncServiceBrowser.""" aiozc = AsyncZeroconf(interfaces=['127.0.0.1']) type_ = "_test9-srvc-type._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) calls = [] with pytest.raises(NotImplementedError): AsyncServiceListener().add_service(aiozc, "_type", "name._type") with pytest.raises(NotImplementedError): AsyncServiceListener().remove_service(aiozc, "_type", "name._type") with pytest.raises(NotImplementedError): AsyncServiceListener().update_service(aiozc, "_type", "name._type") class MyListener(AsyncServiceListener): def add_service(self, aiozc: AsyncZeroconf, type: str, name: str) -> None: calls.append(("add", type, name)) def remove_service(self, aiozc: AsyncZeroconf, type: str, name: str) -> None: calls.append(("remove", type, name)) def update_service(self, aiozc: AsyncZeroconf, type: str, name: str) -> None: calls.append(("update", type, name)) listener = MyListener() await aiozc.async_add_service_listener(type_, listener) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) task = await aiozc.async_register_service(info) await task new_info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.3")], ) task = await aiozc.async_update_service(new_info) await task task = await aiozc.async_unregister_service(new_info) await task await aiozc.async_wait(1) await aiozc.async_close() assert calls == [ ('add', type_, registration_name), ('update', type_, registration_name), ('remove', type_, registration_name), ]