Skip to content
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
build/
*.pyc
*.pyo
Thumbs.db
.DS_Store
.project
.pydevproject
.settings
.idea
.vslick
.cache

7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ Here's an example:
If you want to customize that you need to specify ``interfaces`` argument when
constructing ``Zeroconf`` object (see the code for details).

If you don't know the name of the service you need to browse for, try:

.. code-block:: python

from zeroconf import ZeroconfServiceTypes
print('\n'.join(ZeroconfServiceTypes.find()))

See examples directory for more.

Changelog
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ universal = 1
show-source = 1
import-order-style=google
application-import-names=zeroconf
max-line-length=110
139 changes: 123 additions & 16 deletions test_zeroconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@
import logging
import socket
import struct
import time
import unittest
from threading import Event

from mock import Mock
from six import indexbytes
from six.moves import xrange

import zeroconf as r
from zeroconf import (
DNSHinfo,
DNSText,
Listener,
ServiceBrowser,
ServiceInfo,
ServiceStateChange,
Zeroconf,
ZeroconfServiceTypes,
)

log = logging.getLogger('zeroconf')
Expand Down Expand Up @@ -66,6 +67,19 @@ def test_match_question(self):
self.assertEqual(len(generated.questions), len(parsed.questions))
self.assertEqual(question, parsed.questions[0])

def test_dns_hinfo(self):
generated = r.DNSOutgoing(0)
generated.add_additional_answer(
DNSHinfo('irrelevant', r._TYPE_HINFO, 0, 0, 'cpu', 'os'))
parsed = r.DNSIncoming(generated.packet())
self.assertEqual(parsed.answers[0].cpu, u'cpu')
self.assertEqual(parsed.answers[0].os, u'os')

generated = r.DNSOutgoing(0)
generated.add_additional_answer(
DNSHinfo('irrelevant', r._TYPE_HINFO, 0, 0, 'cpu', 'x' * 257))
self.assertRaises(r.NamePartTooLongException, generated.packet)


class PacketForm(unittest.TestCase):

Expand Down Expand Up @@ -151,6 +165,111 @@ def test_launch_and_close(self):
rv.close()


class Exceptions(unittest.TestCase):

def test_bad_service_info_name(self):
browser = Zeroconf()
self.assertRaises(r.BadTypeInNameException,
browser.get_service_info, "type", "type_not")
browser.close()


class ServiceTypesQuery(unittest.TestCase):

def test_integration_with_listener(self):

type_ = "_test_service_type._tcp.local."
name = "xxxyyy"
registration_name = "%s.%s" % (name, type_)

zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1'])
desc = {'path': '/~paulsm/'}
info = ServiceInfo(
type_, registration_name,
socket.inet_aton("10.0.1.2"), 80, 0, 0,
desc, "ash-2.local.")
zeroconf_registrar.register_service(info)

try:
service_types = ZeroconfServiceTypes.find(timeout=0.5)
assert type_ in service_types
service_types = ZeroconfServiceTypes.find(
zc=zeroconf_registrar, timeout=0.5)
assert type_ in service_types

finally:
zeroconf_registrar.close()


class ListenerTest(unittest.TestCase):

def test_integration_with_listener_class(self):

service_added = Event()
service_removed = Event()

type_ = "_http._tcp.local."
name = "xxxyyy"
registration_name = "%s.%s" % (name, type_)

class MyListener(object):
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()

zeroconf_browser = Zeroconf()
zeroconf_browser.add_service_listener(type_, MyListener())

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()
desc = {'path': '/~paulsm/'}
desc.update(properties)
info = ServiceInfo(
type_, registration_name,
socket.inet_aton("10.0.1.2"), 80, 0, 0,
desc, "ash-2.local.")
zeroconf_registrar.register_service(info)

try:
service_added.wait(1)
assert service_added.is_set()

# short pause to allow multicast timers to expire
time.sleep(2)

# clear the answer cache to force query
for record in zeroconf_browser.cache.entries():
zeroconf_browser.cache.remove(record)

# get service info without answer cache
info = zeroconf_browser.get_service_info(type_, registration_name)

assert info.properties[b'prop_none'] is False
assert info.properties[b'prop_string'] == properties['prop_string']
assert info.properties[b'prop_float'] is False
assert info.properties[b'prop_blank'] == properties['prop_blank']
assert info.properties[b'prop_true'] is True
assert info.properties[b'prop_false'] is False

zeroconf_registrar.unregister_service(info)
service_removed.wait(1)
assert service_removed.is_set()
finally:
zeroconf_registrar.close()
zeroconf_browser.close()


def test_integration():
service_added = Event()
service_removed = Event()
Expand Down Expand Up @@ -179,26 +298,14 @@ def on_service_state_change(zeroconf, service_type, state_change, name):
try:
service_added.wait(1)
assert service_added.is_set()
zeroconf_registrar.unregister_service(info)
service_removed.wait(1)
assert service_removed.is_set()
# Don't remove service, allow close() to cleanup

finally:
zeroconf_registrar.close()
browser.cancel()
zeroconf_browser.close()


def test_listener_handles_closed_socket_situation_gracefully():
error = socket.error(socket.EBADF)
error.errno = socket.EBADF

zeroconf = Mock()
zeroconf.socket.recvfrom.side_effect = error

listener = Listener(zeroconf)
listener.handle_read(zeroconf.socket)


def test_dnstext_repr_works():
# There was an issue on Python 3 that prevented DNSText's repr
# from working when the text was longer than 10 bytes
Expand Down
Loading