[Telepathy-commits] [telepathy-salut/master] Copy basic test infrastructure from gabble with s/gabble/salut where applicable

Sjoerd Simons sjoerd.simons at collabora.co.uk
Mon Sep 1 05:57:53 PDT 2008


---
 configure.ac                                |   22 ++-
 tests/Makefile.am                           |    6 +-
 tests/twisted/Makefile.am                   |   33 ++
 tests/twisted/avahi/test-register.py        |    3 +
 tests/twisted/saluttest.py                  |  386 +++++++++++++++++++++
 tests/twisted/servicetest.py                |  478 +++++++++++++++++++++++++++
 tests/twisted/tools/Makefile.am             |   32 ++
 tests/twisted/tools/exec-with-log.sh.in     |   15 +
 tests/twisted/tools/salut.service.in        |    3 +
 tests/twisted/tools/tmp-session-bus.conf.in |   30 ++
 tests/twisted/tools/with-session-bus.sh     |   73 ++++
 11 files changed, 1078 insertions(+), 3 deletions(-)
 create mode 100644 tests/twisted/Makefile.am
 create mode 100644 tests/twisted/avahi/test-register.py
 create mode 100644 tests/twisted/saluttest.py
 create mode 100644 tests/twisted/servicetest.py
 create mode 100644 tests/twisted/tools/Makefile.am
 create mode 100644 tests/twisted/tools/exec-with-log.sh.in
 create mode 100644 tests/twisted/tools/salut.service.in
 create mode 100644 tests/twisted/tools/tmp-session-bus.conf.in
 create mode 100644 tests/twisted/tools/with-session-bus.sh

diff --git a/configure.ac b/configure.ac
index c04998a..41a86e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -78,6 +78,22 @@ if test -z "$PYTHON"; then
   AC_MSG_ERROR([Python is required to compile this package])
 fi
 
+# Check for a python >= 2.4 with twisted to run python tests
+AC_MSG_CHECKING([for Python >= 2.4 with Twisted and XMPP protocol support])
+for TEST_PYTHON in python2.4 python2.5 python; do
+  if $TEST_PYTHON -c "from sys import version_info; raise SystemExit(version_info < (2, 4, 0, 'final', 0))" >/dev/null 2>&1; then
+    if $TEST_PYTHON -c "import twisted.words.xish.domish, twisted.words.protocols.jabber, twisted.internet.reactor" >/dev/null 2>&1; then
+      AC_MSG_RESULT([$TEST_PYTHON])
+      AM_CONDITIONAL([WANT_TWISTED_TESTS], true)
+      break
+    else
+      TEST_PYTHON=false
+    fi
+  fi
+done
+AC_SUBST(TEST_PYTHON)
+AM_CONDITIONAL([WANT_TWISTED_TESTS], test false != "$TEST_PYTHON")
+
 dnl olpc specific code switch
 AC_ARG_ENABLE(olpc,
   AC_HELP_STRING([--enable-olpc],[compile with olpc specific code]),
@@ -239,7 +255,9 @@ AC_OUTPUT( Makefile                       \
            m4/Makefile                    \
            data/Makefile                  \
            extensions/Makefile            \
-           tools/Makefile		  \
+           tools/Makefile                 \
            tests/Makefile                 \
-           tests/inputs/Makefile
+           tests/inputs/Makefile          \
+           tests/twisted/Makefile         \
+           tests/twisted/tools/Makefile   \
 )
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 061a758..66b3de9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -133,7 +133,11 @@ clean-local:
 	-rm -rf outputs
 	-rm -f sasl-test.db
 
-SUBDIRS = inputs
+if WANT_TWISTED_TESTS
+  CHECK_TWISTED = twisted
+endif
+
+SUBDIRS = inputs $(CHECK_TWISTED)
 
 $(check_SCRIPTS): always-run
 	chmod +x $(srcdir)/$@
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
new file mode 100644
index 0000000..2ffc068
--- /dev/null
+++ b/tests/twisted/Makefile.am
@@ -0,0 +1,33 @@
+TWISTED_TESTS = \
+	avahi/test-register.py
+
+TESTS =
+
+TESTS_ENVIRONMENT = \
+	PYTHONPATH=@abs_top_srcdir@/tests/twisted:@abs_top_builddir@/tests/twisted
+
+check-local: check-coding-style check-twisted
+
+check-twisted:
+	$(MAKE) -C tools
+	rm -f tools/core
+	sh $(srcdir)/tools/with-session-bus.sh --config-file=tools/tmp-session-bus.conf -- $(MAKE) check-TESTS \
+		TESTS="$(TWISTED_TESTS)" \
+		TESTS_ENVIRONMENT="$(TESTS_ENVIRONMENT) $(TEST_PYTHON)"
+	@if test -e tools/core; then\
+		echo "Core dump exists: tools/core";\
+		exit 1;\
+	fi
+
+EXTRA_DIST = \
+	$(TWISTED_TESTS) \
+	saluttest.py \
+	servicetest.py
+
+CLEANFILES = salut-[1-9]*.log *.pyc */*.pyc
+
+check_misc_sources = $(TESTS)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+
+SUBDIRS = tools
diff --git a/tests/twisted/avahi/test-register.py b/tests/twisted/avahi/test-register.py
new file mode 100644
index 0000000..a0d1577
--- /dev/null
+++ b/tests/twisted/avahi/test-register.py
@@ -0,0 +1,3 @@
+import sys
+
+sys.exit(0)
diff --git a/tests/twisted/saluttest.py b/tests/twisted/saluttest.py
new file mode 100644
index 0000000..05d0aaf
--- /dev/null
+++ b/tests/twisted/saluttest.py
@@ -0,0 +1,386 @@
+
+"""
+Infrastructure code for testing Gabble by pretending to be a Jabber server.
+"""
+
+import base64
+import os
+import sha
+import sys
+
+import servicetest
+import twisted
+from twisted.words.xish import domish, xpath
+from twisted.words.protocols.jabber.client import IQ
+from twisted.words.protocols.jabber import xmlstream
+from twisted.internet import reactor
+
+import dbus
+
+NS_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
+NS_XMPP_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
+
+def make_result_iq(stream, iq):
+    result = IQ(stream, "result")
+    result["id"] = iq["id"]
+    query = iq.firstChildElement()
+
+    if query:
+        result.addElement((query.uri, query.name))
+
+    return result
+
+def acknowledge_iq(stream, iq):
+    stream.send(make_result_iq(stream, iq))
+
+def sync_stream(q, stream):
+    """Used to ensure that Gabble has processed all stanzas sent to it."""
+
+    iq = IQ(stream, "get")
+    iq.addElement(('http://jabber.org/protocol/disco#info', 'query'))
+    stream.send(iq)
+    q.expect('stream-iq', query_ns='http://jabber.org/protocol/disco#info')
+
+class JabberAuthenticator(xmlstream.Authenticator):
+    "Trivial XML stream authenticator that accepts one username/digest pair."
+
+    def __init__(self, username, password):
+        self.username = username
+        self.password = password
+        xmlstream.Authenticator.__init__(self)
+
+    def streamStarted(self, root=None):
+        if root:
+            self.xmlstream.sid = root.getAttribute('id')
+
+        self.xmlstream.sendHeader()
+        self.xmlstream.addOnetimeObserver(
+            "/iq/query[@xmlns='jabber:iq:auth']", self.initialIq)
+
+    def initialIq(self, iq):
+        result = IQ(self.xmlstream, "result")
+        result["id"] = iq["id"]
+        query = result.addElement('query')
+        query["xmlns"] = "jabber:iq:auth"
+        query.addElement('username', content='test')
+        query.addElement('password')
+        query.addElement('digest')
+        query.addElement('resource')
+        self.xmlstream.addOnetimeObserver('/iq/query/username', self.secondIq)
+        self.xmlstream.send(result)
+
+    def secondIq(self, iq):
+        username = xpath.queryForNodes('/iq/query/username', iq)
+        assert map(str, username) == [self.username]
+
+        digest = xpath.queryForNodes('/iq/query/digest', iq)
+        expect = sha.sha(self.xmlstream.sid + self.password).hexdigest()
+        assert map(str, digest) == [expect]
+
+        resource = xpath.queryForNodes('/iq/query/resource', iq)
+        assert map(str, resource) == ['Resource']
+
+        result = IQ(self.xmlstream, "result")
+        result["id"] = iq["id"]
+        self.xmlstream.send(result)
+        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
+
+
+class XmppAuthenticator(xmlstream.Authenticator):
+    def __init__(self, username, password):
+        xmlstream.Authenticator.__init__(self)
+        self.username = username
+        self.password = password
+        self.authenticated = False
+
+    def streamStarted(self, root=None):
+        if root:
+            self.xmlstream.sid = root.getAttribute('id')
+
+        self.xmlstream.sendHeader()
+
+        if self.authenticated:
+            # Initiator authenticated itself, and has started a new stream.
+
+            features = domish.Element((xmlstream.NS_STREAMS, 'features'))
+            bind = features.addElement((NS_XMPP_BIND, 'bind'))
+            self.xmlstream.send(features)
+
+            self.xmlstream.addOnetimeObserver(
+                "/iq/bind[@xmlns='%s']" % NS_XMPP_BIND, self.bindIq)
+        else:
+            features = domish.Element((xmlstream.NS_STREAMS, 'features'))
+            mechanisms = features.addElement((NS_XMPP_SASL, 'mechanisms'))
+            mechanism = mechanisms.addElement('mechanism', content='PLAIN')
+            self.xmlstream.send(features)
+
+            self.xmlstream.addOnetimeObserver("/auth", self.auth)
+
+    def auth(self, auth):
+        assert (base64.b64decode(str(auth)) ==
+            '\x00%s\x00%s' % (self.username, self.password))
+
+        success = domish.Element((NS_XMPP_SASL, 'success'))
+        self.xmlstream.send(success)
+        self.xmlstream.reset()
+        self.authenticated = True
+
+    def bindIq(self, iq):
+        assert xpath.queryForString('/iq/bind/resource', iq) == 'Resource'
+
+        result = IQ(self.xmlstream, "result")
+        result["id"] = iq["id"]
+        bind = result.addElement((NS_XMPP_BIND, 'bind'))
+        jid = bind.addElement('jid', content='test at localhost/Resource')
+        self.xmlstream.send(result)
+
+        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
+
+def make_stream_event(type, stanza):
+    event = servicetest.Event(type, stanza=stanza)
+    event.to = stanza.getAttribute("to")
+    return event
+
+def make_iq_event(iq):
+    event = make_stream_event('stream-iq', iq)
+    event.iq_type = iq.getAttribute("type")
+    query = iq.firstChildElement()
+
+    if query:
+        event.query = query
+        event.query_ns = query.uri
+        event.query_name = query.name
+
+        if query.getAttribute("node"):
+            event.query_node = query.getAttribute("node")
+
+    return event
+
+def make_presence_event(stanza):
+    event = make_stream_event('stream-presence', stanza)
+    event.presence_type = stanza.getAttribute('type')
+    return event
+
+def make_message_event(stanza):
+    event = make_stream_event('stream-message', stanza)
+    event.message_type = stanza.getAttribute('type')
+    return event
+
+class BaseXmlStream(xmlstream.XmlStream):
+    initiating = False
+    namespace = 'jabber:client'
+
+    def __init__(self, event_func, authenticator):
+        xmlstream.XmlStream.__init__(self, authenticator)
+        self.event_func = event_func
+        self.addObserver('//iq', lambda x: event_func(
+            make_iq_event(x)))
+        self.addObserver('//message', lambda x: event_func(
+            make_message_event(x)))
+        self.addObserver('//presence', lambda x: event_func(
+            make_presence_event(x)))
+        self.addObserver('//event/stream/authd', self._cb_authd)
+
+    def _cb_authd(self, _):
+        # called when stream is authenticated
+        self.addObserver(
+            "/iq/query[@xmlns='http://jabber.org/protocol/disco#info']",
+            self._cb_disco_iq)
+        self.event_func(servicetest.Event('stream-authenticated'))
+
+    def _cb_disco_iq(self, iq):
+        if iq.getAttribute('to') == 'localhost':
+            # add PEP support
+            nodes = xpath.queryForNodes(
+                "/iq/query[@xmlns='http://jabber.org/protocol/disco#info']",
+                iq)
+            query = nodes[0]
+            identity = query.addElement('identity')
+            identity['category'] = 'pubsub'
+            identity['type'] = 'pep'
+
+            iq['type'] = 'result'
+            self.send(iq)
+
+class JabberXmlStream(BaseXmlStream):
+    version = (0, 9)
+
+class XmppXmlStream(BaseXmlStream):
+    version = (1, 0)
+
+def make_connection(bus, event_func, params=None):
+    default_params = {
+        'account': 'test at localhost/Resource',
+        'password': 'pass',
+        'resource': 'Resource',
+        'server': 'localhost',
+        'port': dbus.UInt32(4242),
+        }
+
+    if params:
+        default_params.update(params)
+
+    return servicetest.make_connection(bus, event_func, 'gabble', 'jabber',
+        default_params)
+
+def make_stream(event_func, authenticator=None, protocol=None, port=4242):
+    # set up Jabber server
+
+    if authenticator is None:
+        authenticator = JabberAuthenticator('test', 'pass')
+
+    if protocol is None:
+        protocol = JabberXmlStream
+
+    stream = protocol(event_func, authenticator)
+    factory = twisted.internet.protocol.Factory()
+    factory.protocol = lambda *args: stream
+    port = reactor.listenTCP(port, factory)
+    return (stream, port)
+
+def go(params=None, authenticator=None, protocol=None, start=None):
+    # hack to ease debugging
+    domish.Element.__repr__ = domish.Element.toXml
+
+    bus = dbus.SessionBus()
+    handler = servicetest.EventTest()
+    conn = make_connection(bus, handler.handle_event, params)
+    (stream, _) = make_stream(handler.handle_event, authenticator, protocol)
+    handler.data = {
+        'bus': bus,
+        'conn': conn,
+        'conn_iface': dbus.Interface(conn,
+            'org.freedesktop.Telepathy.Connection'),
+        'stream': stream}
+    handler.data['test'] = handler
+    handler.verbose = (os.environ.get('CHECK_TWISTED_VERBOSE', '') != '')
+    map(handler.expect, servicetest.load_event_handlers())
+
+    if '-v' in sys.argv:
+        handler.verbose = True
+
+    if start is None:
+        handler.data['conn'].Connect()
+    else:
+        start(handler.data)
+
+    reactor.run()
+
+def install_colourer():
+    def red(s):
+        return '\x1b[31m%s\x1b[0m' % s
+
+    def green(s):
+        return '\x1b[32m%s\x1b[0m' % s
+
+    patterns = {
+        'handled': green,
+        'not handled': red,
+        }
+
+    class Colourer:
+        def __init__(self, fh, patterns):
+            self.fh = fh
+            self.patterns = patterns
+
+        def write(self, s):
+            f = self.patterns.get(s, lambda x: x)
+            self.fh.write(f(s))
+
+    sys.stdout = Colourer(sys.stdout, patterns)
+    return sys.stdout
+
+
+def exec_test_deferred (fun, params, protocol=None, timeout=None):
+    # hack to ease debugging
+    domish.Element.__repr__ = domish.Element.toXml
+    colourer = None
+
+    if sys.stdout.isatty():
+        colourer = install_colourer()
+
+    queue = servicetest.IteratingEventQueue(timeout)
+    queue.verbose = (
+        os.environ.get('CHECK_TWISTED_VERBOSE', '') != ''
+        or '-v' in sys.argv)
+
+    bus = dbus.SessionBus()
+    conn = make_connection(bus, queue.append, params)
+    (stream, port) = make_stream(queue.append, protocol=protocol)
+
+    error = None
+
+    try:
+        fun(queue, bus, conn, stream)
+    except Exception, e:
+        import traceback
+        traceback.print_exc()
+        error = e
+
+    try:
+        if colourer:
+          sys.stdout = colourer.fh
+        d = port.stopListening()
+        if error is None:
+            d.addBoth((lambda *args: reactor.crash()))
+        else:
+            # please ignore the POSIX behind the curtain
+            d.addBoth((lambda *args: os._exit(1)))
+
+        conn.Disconnect()
+        # second call destroys object
+        conn.Disconnect()
+    except dbus.DBusException, e:
+        pass
+
+def exec_test(fun, params=None, protocol=None, timeout=None):
+  reactor.callWhenRunning (exec_test_deferred, fun, params, protocol, timeout)
+  reactor.run()
+
+# Useful routines for server-side vCard handling
+current_vcard = domish.Element(('vcard-temp', 'vCard'))
+
+def handle_get_vcard(event, data):
+    iq = event.stanza
+
+    if iq['type'] != 'get':
+        return False
+
+    if iq.uri != 'jabber:client':
+        return False
+
+    vcard = list(iq.elements())[0]
+
+    if vcard.name != 'vCard':
+        return False
+
+    # Send back current vCard
+    new_iq = IQ(data['stream'], 'result')
+    new_iq['id'] = iq['id']
+    new_iq.addChild(current_vcard)
+    data['stream'].send(new_iq)
+    return True
+
+def handle_set_vcard(event, data):
+    global current_vcard
+    iq = event.stanza
+
+    if iq['type'] != 'set':
+        return False
+
+    if iq.uri != 'jabber:client':
+        return False
+
+    vcard = list(iq.elements())[0]
+
+    if vcard.name != 'vCard':
+        return False
+
+    current_vcard = iq.firstChildElement()
+
+    new_iq = IQ(data['stream'], 'result')
+    new_iq['id'] = iq['id']
+    data['stream'].send(new_iq)
+    return True
+
+
diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py
new file mode 100644
index 0000000..a700630
--- /dev/null
+++ b/tests/twisted/servicetest.py
@@ -0,0 +1,478 @@
+
+"""
+Infrastructure code for testing connection managers.
+"""
+
+from twisted.internet import glib2reactor
+from twisted.internet.protocol import Protocol, Factory, ClientFactory
+glib2reactor.install()
+
+import pprint
+import traceback
+import unittest
+
+import dbus.glib
+
+from twisted.internet import reactor
+
+tp_name_prefix = 'org.freedesktop.Telepathy'
+tp_path_prefix = '/org/freedesktop/Telepathy'
+
+class TryNextHandler(Exception):
+    pass
+
+def lazy(func):
+    def handler(event, data):
+        if func(event, data):
+            return True
+        else:
+            raise TryNextHandler()
+    handler.__name__ = func.__name__
+    return handler
+
+def match(type, **kw):
+    def decorate(func):
+        def handler(event, data, *extra, **extra_kw):
+            if event.type != type:
+                return False
+
+            for key, value in kw.iteritems():
+                if not hasattr(event, key):
+                    return False
+
+                if getattr(event, key) != value:
+                    return False
+
+            return func(event, data, *extra, **extra_kw)
+
+        handler.__name__ = func.__name__
+        return handler
+
+    return decorate
+
+class Event:
+    def __init__(self, type, **kw):
+        self.__dict__.update(kw)
+        self.type = type
+
+def format_event(event):
+    ret = ['- type %s' % event.type]
+
+    for key in dir(event):
+        if key != 'type' and not key.startswith('_'):
+            ret.append('- %s: %s' % (
+                key, pprint.pformat(getattr(event, key))))
+
+            if key == 'error':
+                ret.append('%s' % getattr(event, key))
+
+    return ret
+
+class EventTest:
+    """Somewhat odd event dispatcher for asynchronous tests.
+
+    Callbacks are kept in a queue. Incoming events are passed to the first
+    callback. If the callback returns True, the callback is removed. If the
+    callback raises AssertionError, the test fails. If there are no more
+    callbacks, the test passes. The reactor is stopped when the test passes.
+    """
+
+    def __init__(self):
+        self.queue = []
+        self.data = {'test': self}
+        self.timeout_delayed_call = reactor.callLater(5, self.timeout_cb)
+        #self.verbose = True
+        self.verbose = False
+        # ugh
+        self.stopping = False
+
+    def timeout_cb(self):
+        print 'timed out waiting for events'
+        print self.queue[0]
+        self.fail()
+
+    def fail(self):
+        # ugh; better way to stop the reactor and exit(1)?
+        import os
+        os._exit(1)
+
+    def expect(self, f):
+        self.queue.append(f)
+
+    def log(self, s):
+        if self.verbose:
+            print s
+
+    def try_stop(self):
+        if self.stopping:
+            return True
+
+        if not self.queue:
+            self.log('no handlers left; stopping')
+            self.stopping = True
+            reactor.stop()
+            return True
+
+        return False
+
+    def call_handlers(self, event):
+        self.log('trying %r' % self.queue[0])
+        handler = self.queue.pop(0)
+
+        try:
+            ret = handler(event, self.data)
+            if not ret:
+                self.queue.insert(0, handler)
+        except TryNextHandler, e:
+            if self.queue:
+                ret = self.call_handlers(event)
+            else:
+                ret = False
+            self.queue.insert(0, handler)
+
+        return ret
+
+    def handle_event(self, event):
+        if self.try_stop():
+            return
+
+        self.log('got event:')
+        self.log('- type: %s' % event.type)
+        map(self.log, format_event(event))
+
+        try:
+            ret = self.call_handlers(event)
+        except SystemExit, e:
+            if e.code:
+                print "Unsuccessful exit:", e
+                self.fail()
+            else:
+                self.queue[:] = []
+                ret = True
+        except AssertionError, e:
+            print 'test failed:'
+            traceback.print_exc()
+            self.fail()
+        except (Exception, KeyboardInterrupt), e:
+            print 'error in handler:'
+            traceback.print_exc()
+            self.fail()
+
+        if ret not in (True, False):
+            print ("warning: %s() returned something other than True or False"
+                % self.queue[0].__name__)
+
+        if ret:
+            self.timeout_delayed_call.reset(5)
+            self.log('event handled')
+        else:
+            self.log('event not handled')
+
+        self.log('')
+        self.try_stop()
+
+class EventPattern:
+    def __init__(self, type, **properties):
+        self.type = type
+        self.properties = properties
+
+    def match(self, event):
+        if event.type != self.type:
+            return False
+
+        for key, value in self.properties.iteritems():
+            try:
+                if getattr(event, key) != value:
+                    return False
+            except AttributeError:
+                return False
+
+        return True
+
+class TimeoutError(Exception):
+    pass
+
+class BaseEventQueue:
+    """Abstract event queue base class.
+
+    Implement the wait() method to have something that works.
+    """
+
+    def __init__(self, timeout=None):
+        self.verbose = False
+
+        if timeout is None:
+            self.timeout = 5
+        else:
+            self.timeout = timeout
+
+    def log(self, s):
+        if self.verbose:
+            print s
+
+    def expect(self, type, **kw):
+        pattern = EventPattern(type, **kw)
+
+        while True:
+            event = self.wait()
+            self.log('got event:')
+            map(self.log, format_event(event))
+
+            if pattern.match(event):
+                self.log('handled')
+                self.log('')
+                return event
+
+            self.log('not handled')
+            self.log('')
+
+    def expect_many(self, *patterns):
+        ret = [None] * len(patterns)
+
+        while None in ret:
+            event = self.wait()
+            self.log('got event:')
+            map(self.log, format_event(event))
+
+            for i, pattern in enumerate(patterns):
+                if pattern.match(event):
+                    self.log('handled')
+                    self.log('')
+                    ret[i] = event
+                    break
+            else:
+                self.log('not handled')
+                self.log('')
+
+        return ret
+
+    def demand(self, type, **kw):
+        pattern = EventPattern(type, **kw)
+
+        event = self.wait()
+        self.log('got event:')
+        map(self.log, format_event(event))
+
+        if pattern.match(event):
+            self.log('handled')
+            self.log('')
+            return event
+
+        self.log('not handled')
+        raise RuntimeError('expected %r, got %r' % (pattern, event))
+
+class IteratingEventQueue(BaseEventQueue):
+    """Event queue that works by iterating the Twisted reactor."""
+
+    def __init__(self, timeout=None):
+        BaseEventQueue.__init__(self, timeout)
+        self.events = []
+
+    def wait(self):
+        stop = [False]
+
+        def later():
+            stop[0] = True
+
+        delayed_call = reactor.callLater(self.timeout, later)
+
+        while (not self.events) and (not stop[0]):
+            reactor.iterate(0.1)
+
+        if self.events:
+            delayed_call.cancel()
+            return self.events.pop(0)
+        else:
+            raise TimeoutError
+
+    def append(self, event):
+        self.events.append(event)
+
+    # compatibility
+    handle_event = append
+
+class TestEventQueue(BaseEventQueue):
+    def __init__(self, events):
+        BaseEventQueue.__init__(self)
+        self.events = events
+
+    def wait(self):
+        if self.events:
+            return self.events.pop(0)
+        else:
+            raise TimeoutError
+
+class EventQueueTest(unittest.TestCase):
+    def test_expect(self):
+        queue = TestEventQueue([Event('foo'), Event('bar')])
+        assert queue.expect('foo').type == 'foo'
+        assert queue.expect('bar').type == 'bar'
+
+    def test_expect_many(self):
+        queue = TestEventQueue([Event('foo'), Event('bar')])
+        bar, foo = queue.expect_many(
+            EventPattern('bar'),
+            EventPattern('foo'))
+        assert bar.type == 'bar'
+        assert foo.type == 'foo'
+
+    def test_timeout(self):
+        queue = TestEventQueue([])
+        self.assertRaises(TimeoutError, queue.expect, 'foo')
+
+    def test_demand(self):
+        queue = TestEventQueue([Event('foo'), Event('bar')])
+        foo = queue.demand('foo')
+        assert foo.type == 'foo'
+
+    def test_demand_fail(self):
+        queue = TestEventQueue([Event('foo'), Event('bar')])
+        self.assertRaises(RuntimeError, queue.demand, 'bar')
+
+def unwrap(x):
+    """Hack to unwrap D-Bus values, so that they're easier to read when
+    printed."""
+
+    if isinstance(x, list):
+        return map(unwrap, x)
+
+    if isinstance(x, tuple):
+        return tuple(map(unwrap, x))
+
+    if isinstance(x, dict):
+        return dict([(unwrap(k), unwrap(v)) for k, v in x.iteritems()])
+
+    for t in [unicode, str, long, int, float, bool]:
+        if isinstance(x, t):
+            return t(x)
+
+    return x
+
+def call_async(test, proxy, method, *args, **kw):
+    """Call a D-Bus method asynchronously and generate an event for the
+    resulting method return/error."""
+
+    def reply_func(*ret):
+        test.handle_event(Event('dbus-return', method=method,
+            value=unwrap(ret)))
+
+    def error_func(err):
+        test.handle_event(Event('dbus-error', method=method, error=err))
+
+    method_proxy = getattr(proxy, method)
+    kw.update({'reply_handler': reply_func, 'error_handler': error_func})
+    method_proxy(*args, **kw)
+
+def sync_dbus(bus, q, conn):
+    # Dummy D-Bus method call
+    call_async(q, conn, "InspectHandles", 1, [])
+
+    event = q.expect('dbus-return', method='InspectHandles')
+
+class ProxyWrapper:
+    def __init__(self, object, default, others):
+        self.object = object
+        self.default_interface = dbus.Interface(object, default)
+        self.interfaces = dict([
+            (name, dbus.Interface(object, iface))
+            for name, iface in others.iteritems()])
+
+    def __getattr__(self, name):
+        if name in self.interfaces:
+            return self.interfaces[name]
+
+        if name in self.object.__dict__:
+            return getattr(self.object, name)
+
+        return getattr(self.default_interface, name)
+
+def make_connection(bus, event_func, name, proto, params):
+    cm = bus.get_object(
+        tp_name_prefix + '.ConnectionManager.%s' % name,
+        tp_path_prefix + '/ConnectionManager/%s' % name)
+    cm_iface = dbus.Interface(cm, tp_name_prefix + '.ConnectionManager')
+
+    connection_name, connection_path = cm_iface.RequestConnection(
+        proto, params)
+    conn = bus.get_object(connection_name, connection_path)
+    conn = ProxyWrapper(conn, tp_name_prefix + '.Connection',
+        dict([
+            (name, tp_name_prefix + '.Connection.Interface.' + name)
+            for name in ['Aliasing', 'Avatars', 'Capabilities', 'Contacts',
+              'Presence']] +
+        [('Peer', 'org.freedesktop.DBus.Peer')]))
+
+    bus.add_signal_receiver(
+        lambda *args, **kw:
+            event_func(
+                Event('dbus-signal',
+                    path=unwrap(kw['path'])[len(tp_path_prefix):],
+                    signal=kw['member'], args=map(unwrap, args),
+                    interface=kw['interface'])),
+        None,       # signal name
+        None,       # interface
+        cm._named_service,
+        path_keyword='path',
+        member_keyword='member',
+        interface_keyword='interface',
+        byte_arrays=True
+        )
+
+    return conn
+
+def make_channel_proxy(conn, path, iface):
+    bus = dbus.SessionBus()
+    chan = bus.get_object(conn.object.bus_name, path)
+    chan = dbus.Interface(chan, tp_name_prefix + '.' + iface)
+    return chan
+
+def load_event_handlers():
+    path, _, _, _ = traceback.extract_stack()[0]
+    import compiler
+    import __main__
+    ast = compiler.parseFile(path)
+    return [
+        getattr(__main__, node.name)
+        for node in ast.node.asList()
+        if node.__class__ == compiler.ast.Function and
+            node.name.startswith('expect_')]
+
+class EventProtocol(Protocol):
+    def __init__(self, queue=None):
+        self.queue = queue
+
+    def dataReceived(self, data):
+        if self.queue is not None:
+            self.queue.handle_event(Event('socket-data', protocol=self,
+                data=data))
+
+    def sendData(self, data):
+        self.transport.write(data)
+
+class EventProtocolFactory(Factory):
+    def __init__(self, queue):
+        self.queue = queue
+
+    def buildProtocol(self, addr):
+        proto =  EventProtocol(self.queue)
+        self.queue.handle_event(Event('socket-connected', protocol=proto))
+        return proto
+
+class EventProtocolClientFactory(EventProtocolFactory, ClientFactory):
+    pass
+
+def watch_tube_signals(q, tube):
+    def got_signal_cb(*args, **kwargs):
+        q.handle_event(Event('tube-signal',
+            path=kwargs['path'],
+            signal=kwargs['member'],
+            args=map(unwrap, args),
+            tube=tube))
+
+    tube.add_signal_receiver(got_signal_cb,
+        path_keyword='path', member_keyword='member',
+        byte_arrays=True)
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/tests/twisted/tools/Makefile.am b/tests/twisted/tools/Makefile.am
new file mode 100644
index 0000000..d4459b4
--- /dev/null
+++ b/tests/twisted/tools/Makefile.am
@@ -0,0 +1,32 @@
+exec-with-log.sh: exec-with-log.sh.in
+	sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+	chmod +x $@
+
+%.conf: %.conf.in
+	sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+
+# We don't use the full filename for the .in because > 99 character filenames
+# in tarballs are non-portable (and automake 1.8 doesn't let us build
+# non-archaic tarballs)
+org.freedesktop.Telepathy.ConnectionManager.%.service: %.service.in
+	sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+
+# D-Bus service file for testing
+service_in_files = salut.service.in
+service_files = org.freedesktop.Telepathy.ConnectionManager.salut.service
+
+# D-Bus config file for testing
+conf_in_files = tmp-session-bus.conf.in
+conf_files = $(conf_in_files:.conf.in=.conf)
+
+BUILT_SOURCES = $(service_files) $(conf_files) exec-with-log.sh
+
+EXTRA_DIST = \
+	$(service_in_files) \
+	$(conf_in_files) \
+	exec-with-log.sh.in \
+	with-session-bus.sh
+
+CLEANFILES = \
+    $(BUILT_SOURCES) \
+    salut-testing.log
diff --git a/tests/twisted/tools/exec-with-log.sh.in b/tests/twisted/tools/exec-with-log.sh.in
new file mode 100644
index 0000000..9de3850
--- /dev/null
+++ b/tests/twisted/tools/exec-with-log.sh.in
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+cd "@abs_top_builddir@/tests/twisted/tools"
+
+export SALUT_DEBUG=all GIBBER_DEBUG=all
+ulimit -c unlimited
+exec > salut-testing.log 2>&1
+
+if test -n "$SALUT_TEST_VALGRIND"; then
+	export G_DEBUG=gc-friendly
+	export G_SLICE=always-malloc
+	SALUT_WRAPPER="valgrind --leak-check=full --num-callers=20"
+fi
+
+exec $SALUT_WRAPPER @abs_top_builddir@/src/telepathy-salut
diff --git a/tests/twisted/tools/salut.service.in b/tests/twisted/tools/salut.service.in
new file mode 100644
index 0000000..93adb71
--- /dev/null
+++ b/tests/twisted/tools/salut.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.ConnectionManager.salut
+Exec=@abs_top_builddir@/tests/twisted/tools/exec-with-log.sh
diff --git a/tests/twisted/tools/tmp-session-bus.conf.in b/tests/twisted/tools/tmp-session-bus.conf.in
new file mode 100644
index 0000000..fd670fd
--- /dev/null
+++ b/tests/twisted/tools/tmp-session-bus.conf.in
@@ -0,0 +1,30 @@
+<!-- This configuration file controls the per-user-login-session message bus.
+     Add a session-local.conf and edit that rather than changing this 
+     file directly. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+  <!-- Our well-known bus type, don't change this -->
+  <type>session</type>
+
+  <listen>unix:tmpdir=/tmp</listen>
+
+  <servicedir>@abs_top_builddir@/tests/twisted/tools</servicedir>
+
+  <policy context="default">
+    <!-- Allow everything to be sent -->
+    <allow send_destination="*"/>
+    <!-- Allow everything to be received -->
+    <allow eavesdrop="true"/>
+    <!-- Allow anyone to own anything -->
+    <allow own="*"/>
+  </policy>
+
+  <!-- This is included last so local configuration can override what's 
+       in this standard file -->
+  
+
+  
+
+</busconfig>
diff --git a/tests/twisted/tools/with-session-bus.sh b/tests/twisted/tools/with-session-bus.sh
new file mode 100644
index 0000000..26d9f24
--- /dev/null
+++ b/tests/twisted/tools/with-session-bus.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+# with-session-bus.sh - run a program with a temporary D-Bus session daemon
+#
+# The canonical location of this program is the telepathy-glib tools/
+# directory, please synchronize any changes with that copy.
+#
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+set -e
+
+me=with-session-bus
+
+dbus_daemon_args="--print-address=5 --print-pid=6 --fork"
+
+usage ()
+{
+  echo "usage: $me [options] -- program [program_options]" >&2
+  echo "Requires write access to the current directory." >&2
+  exit 2
+}
+
+while test "z$1" != "z--"; do
+  case "$1" in
+  --session)
+    dbus_daemon_args="$dbus_daemon_args --session"
+    shift
+    ;;
+  --config-file=*)
+    # FIXME: assumes config file doesn't contain any special characters
+    dbus_daemon_args="$dbus_daemon_args $1"
+    shift
+    ;;
+  *)
+    usage
+    ;;
+  esac
+done
+shift
+if test "z$1" = "z"; then usage; fi
+
+exec 5> $me-$$.address
+exec 6> $me-$$.pid
+
+cleanup ()
+{
+  pid=`head -n1 $me-$$.pid`
+  if test -n "$pid" ; then
+    echo "Killing temporary bus daemon: $pid" >&2
+    kill -INT "$pid"
+  fi
+  rm -f $me-$$.address
+  rm -f $me-$$.pid
+}
+
+trap cleanup INT HUP TERM
+dbus-daemon $dbus_daemon_args
+
+{ echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2
+{ echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2
+
+e=0
+DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`"
+export DBUS_SESSION_BUS_ADDRESS
+"$@" || e=$?
+
+trap - INT HUP TERM
+cleanup
+
+exit $e
-- 
1.5.6.3




More information about the Telepathy-commits mailing list