[Telepathy-commits] [telepathy-idle/master] Starting to add twisted test infrastructure

Jonathon Jongsma jjongsma at gnome.org
Fri Feb 13 17:43:15 PST 2009


The twisted test infrastructure was copied and modified from gabble.  I'm still
getting familiar with it and figuring out how things work, so it doesn't really
do much yet and there are even issues with the few barebones tests I've included
here.
---
 .gitignore                                  |   13 +-
 Makefile.am                                 |    5 +
 configure.ac                                |   20 +
 tests/Makefile.am                           |    1 +
 tests/twisted/Makefile.am                   |   37 ++
 tests/twisted/connect/connect-fail.py       |   16 +
 tests/twisted/connect/connect-success.py    |   24 ++
 tests/twisted/idletest.py                   |  169 +++++++++
 tests/twisted/servicetest.py                |  505 +++++++++++++++++++++++++++
 tests/twisted/tools/Makefile.am             |   33 ++
 tests/twisted/tools/exec-with-log.sh.in     |   27 ++
 tests/twisted/tools/idle.service.in         |    3 +
 tests/twisted/tools/tmp-session-bus.conf.in |   30 ++
 tests/twisted/tools/with-session-bus.sh     |   84 +++++
 14 files changed, 964 insertions(+), 3 deletions(-)
 create mode 100644 tests/twisted/Makefile.am
 create mode 100644 tests/twisted/connect/connect-fail.py
 create mode 100644 tests/twisted/connect/connect-success.py
 create mode 100644 tests/twisted/idletest.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/idle.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/.gitignore b/.gitignore
index 4c4b9a0..c236d3d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,13 @@ core
 cscope.out
 tags
 
-tests/*
-!tests/Makefile.am
-!tests/*.[ch]
+tests/test-ctcp-kill-blingbling
+tests/test-ctcp-tokenize
+tests/test-text-encode-and-split
+tests/test-tokenize-leading-space
+tests/test-tokenize-leading-space.c
+
+tests/twisted/tools/exec-with-log.sh
+tests/twisted/tools/idle-testing.log
+tests/twisted/tools/org.freedesktop.Telepathy.ConnectionManager.idle.service
+tests/twisted/tools/tmp-session-bus.conf
diff --git a/Makefile.am b/Makefile.am
index 7c9188f..85b8107 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,3 +1,8 @@
 ACLOCAL_AMFLAGS = -I m4
 
 SUBDIRS = tools extensions src data m4 tests
+
+check-twisted : all
+	$(MAKE) -C tests/twisted check-twisted
+
+check-all: check check-twisted
diff --git a/configure.ac b/configure.ac
index ec834a3..057e6c5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -103,6 +103,24 @@ if test -z "$PYTHON"; then
   AC_MSG_ERROR([Python is required to compile this package])
 fi
 
+
+dnl check for a version of python that can run the twisted tests
+AC_MSG_CHECKING([for Python with Twisted and IRC protocol support])
+for TEST_PYTHON in python2.5 python2.6 python; do
+  if $TEST_PYTHON -c "from sys import version_info; import dbus, dbus.mainloop.glib; raise SystemExit(version_info < (2, 5, 0, 'final', 0))" >/dev/null 2>&1; then
+    if $TEST_PYTHON -c "import twisted.words.protocols.irc, twisted.internet.reactor" >/dev/null 2>&1; then
+      AM_CONDITIONAL([WANT_TWISTED_TESTS], true)
+      break
+    else
+      TEST_PYTHON=no
+    fi
+  fi
+done
+AC_MSG_RESULT([$TEST_PYTHON])
+AC_SUBST(TEST_PYTHON)
+AM_CONDITIONAL([WANT_TWISTED_TESTS], test xno != x$TEST_PYTHON)
+
+
 AS_AC_EXPAND(DATADIR, $datadir)
 DBUS_SERVICES_DIR="$DATADIR/dbus-1/services"
 AC_SUBST(DBUS_SERVICES_DIR)
@@ -114,5 +132,7 @@ AC_OUTPUT( Makefile \
            m4/Makefile \
            src/Makefile \
 					 tests/Makefile \
+						 tests/twisted/Makefile \
+							 tests/twisted/tools/Makefile \
 					 tools/Makefile \
 )
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 84e630c..5c1133d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -32,3 +32,4 @@ ALL_LIBS = \
 
 TESTS = $(check_PROGRAMS)
 
+SUBDIRS = twisted
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
new file mode 100644
index 0000000..502f89b
--- /dev/null
+++ b/tests/twisted/Makefile.am
@@ -0,0 +1,37 @@
+TWISTED_TESTS = \
+		connect/connect-fail.py \
+		connect/connect-success.py \
+		$(NULL)
+
+TESTS =
+
+TESTS_ENVIRONMENT = \
+	PYTHONPATH=@abs_top_srcdir@/tests/twisted:@abs_top_builddir@/tests/twisted
+
+if WANT_TWISTED_TESTS
+
+check-local: check-twisted
+
+check-twisted:
+	$(MAKE) -C tools
+	rm -f tools/core
+	rm -f tools/idle-testing.log
+	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
+
+endif
+
+EXTRA_DIST = \
+	     $(TWISTED_TESTS) \
+	     servicetest.py \
+	     idletest.py \
+	     $(NULL)
+
+CLEANFILES = idle-[1-9]*.log *.pyc */*.pyc
+
+SUBDIRS = tools
diff --git a/tests/twisted/connect/connect-fail.py b/tests/twisted/connect/connect-fail.py
new file mode 100644
index 0000000..e7c121f
--- /dev/null
+++ b/tests/twisted/connect/connect-fail.py
@@ -0,0 +1,16 @@
+
+"""
+Test Network error Handling
+"""
+
+import dbus
+from idletest import exec_test
+
+def test(q, bus, conn, stream):
+	conn.Connect()
+	q.expect('dbus-signal', signal='StatusChanged', args=[1,1])
+	q.expect('dbus-signal', signal='StatusChanged', args=[2,2])
+
+if __name__ == '__main__':
+    exec_test(test, {'port': dbus.UInt32(5600)})
+
diff --git a/tests/twisted/connect/connect-success.py b/tests/twisted/connect/connect-success.py
new file mode 100644
index 0000000..ddc50b2
--- /dev/null
+++ b/tests/twisted/connect/connect-success.py
@@ -0,0 +1,24 @@
+
+"""
+Test connecting to a server.
+"""
+
+from idletest import exec_test
+
+def test(q, bus, conn, stream):
+    conn.Connect()
+    q.expect('dbus-signal', signal='StatusChanged', args=[1, 1])
+#just hacked some stuff in to figure out what's going on
+    q.expect('irc-connected')
+    q.expect('irc-nick')
+    q.expect('irc-user')
+    #q.expect('dbus-signal', signal='PresenceUpdate',
+        #args=[{1L: (0L, {u'available': {}})}])
+    #q.expect('dbus-signal', signal='StatusChanged', args=[0, 1])
+    #conn.Disconnect()
+    #q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+    return True
+
+if __name__ == '__main__':
+    exec_test(test)
+
diff --git a/tests/twisted/idletest.py b/tests/twisted/idletest.py
new file mode 100644
index 0000000..5c43f6c
--- /dev/null
+++ b/tests/twisted/idletest.py
@@ -0,0 +1,169 @@
+
+"""
+Infrastructure code for testing Idle by pretending to be an IRC server.
+"""
+
+import os
+import sys
+import dbus
+import servicetest
+import twisted
+from twisted.words.protocols import irc
+from twisted.internet import reactor
+
+def make_irc_event(type, data):
+    event = servicetest.Event(type, data=data)
+    return event
+
+def make_connected_event():
+    event = make_irc_event('irc-connected', None)
+    return event
+
+def make_disconnected_event():
+    event = make_irc_event('irc-disconnected', None)
+    return event
+
+def make_privmsg_event(data):
+    event = make_irc_event('irc-privmsg', data)
+    return event
+
+def make_nick_event(data):
+    event = make_irc_event('irc-nick', data)
+    return event
+
+def make_user_event(data):
+    event = make_irc_event('irc-user', data)
+    return event
+
+class BaseIRCServer(irc.IRC):
+    def __init__(self, event_func):
+        self.event_func = event_func
+        self.authenticated = False;
+
+    def connectionMade(self):
+        print ("connection Made")
+        self.event_func(make_connected_event());
+
+    def connectionLost(self):
+        print ("connection Lost")
+        self.event_func(make_disconnected_event());
+
+    def dataReceived(self, data):
+        print ("data received: %s" % (data,));
+        (prefix, command, args) = irc.parsemsg(data);
+        if command == 'PRIVMSG':
+            self.event_func(make_privmsg_event(args));
+        elif command == 'USER':
+            self.event_func(make_user_event(args));
+        elif command == 'NICK':
+            self.event_func(make_nick_event(args[0]));
+
+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 start_server(event_func, protocol=None, port=6900):
+    # set up IRC server
+
+    if protocol is None:
+        protocol = BaseIRCServer
+
+    server = protocol(event_func)
+    factory = twisted.internet.protocol.Factory()
+    factory.protocol = lambda *args: server
+    port = reactor.listenTCP(port, factory)
+    return (server, port)
+
+def make_connection(bus, event_func, params=None):
+    default_params = {
+        'account': 'test',
+        'server': 'localhost',
+        'password': '',
+        'fullname': 'Test User',
+        'charset': 'UTF-8',
+        'quit-message': 'happy testing...',
+        'use-ssl': dbus.Boolean(False),
+        'port': dbus.UInt32(6900),
+        }
+
+    if params:
+        default_params.update(params)
+
+    return servicetest.make_connection(bus, event_func, 'idle', 'irc',
+        default_params)
+
+def exec_test_deferred (funs, params, protocol=None, timeout=None):
+    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)
+    (server, port) = start_server(queue.append, protocol=protocol)
+
+    error = None
+
+    try:
+        for f in funs:
+            conn = make_connection(bus, queue.append, params)
+            f(queue, bus, conn, server)
+            print "called f"
+    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()
+
+        if 'IDLE_TEST_REFDBG' in os.environ:
+            # we have to wait that Gabble timeouts so the process is properly
+            # exited and refdbg can generates its report
+            time.sleep(5.5)
+
+    except dbus.DBusException, e:
+        print "exception"
+        pass
+
+def exec_tests(funs, params=None, protocol=None, timeout=None):
+  reactor.callWhenRunning (exec_test_deferred, funs, params, protocol, timeout)
+  reactor.run()
+
+def exec_test(fun, params=None, protocol=None, timeout=None):
+  exec_tests([fun], params, protocol, timeout)
+
diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py
new file mode 100644
index 0000000..e63235e
--- /dev/null
+++ b/tests/twisted/servicetest.py
@@ -0,0 +1,505 @@
+
+"""
+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.predicate = lambda x: True
+        if 'predicate' in properties:
+            self.predicate = properties['predicate']
+            del properties['predicate']
+        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
+
+        if self.predicate(event):
+            return True
+
+        return False
+
+
+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
+        self.past_events = []
+
+        if timeout is None:
+            self.timeout = 5
+        else:
+            self.timeout = timeout
+
+    def log(self, s):
+        if self.verbose:
+            print s
+
+    def flush_past_events(self):
+        self.past_events = []
+
+    def expect_racy(self, type, **kw):
+        pattern = EventPattern(type, **kw)
+
+        for event in self.past_events:
+            if pattern.match(event):
+                self.log('past event handled')
+                map(self.log, format_event(event))
+                self.log('')
+                self.past_events.remove(event)
+                return event
+
+        return self.expect(type, **kw)
+
+    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.past_events.append(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.past_events.append(event)
+                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..bf76d55
--- /dev/null
+++ b/tests/twisted/tools/Makefile.am
@@ -0,0 +1,33 @@
+exec-with-log.sh: exec-with-log.sh.in
+	sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" \
+		-e "s|[@]abs_top_srcdir[@]|@abs_top_srcdir@|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 = idle.service.in
+service_files = org.freedesktop.Telepathy.ConnectionManager.idle.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) \
+    idle-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..3a343e5
--- /dev/null
+++ b/tests/twisted/tools/exec-with-log.sh.in
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+cd "@abs_top_builddir@/tests/twisted/tools"
+
+export IDLE_DEBUG=all LM_DEBUG=net
+ulimit -c unlimited
+exec >> idle-testing.log 2>&1
+
+if test -n "$IDLE_TEST_VALGRIND"; then
+        export G_DEBUG=${G_DEBUG:+"${G_DEBUG},"}gc-friendly
+        export G_SLICE=always-malloc
+        IDLE_WRAPPER="valgrind --leak-check=full --num-callers=20"
+        IDLE_WRAPPER="$IDLE_WRAPPER --show-reachable=yes"
+        IDLE_WRAPPER="$IDLE_WRAPPER --gen-suppressions=all"
+        IDLE_WRAPPER="$IDLE_WRAPPER --child-silent-after-fork=yes"
+        IDLE_WRAPPER="$IDLE_WRAPPER --suppressions=@abs_top_srcdir@/tests/twisted/tools/tp-glib.supp"
+elif test -n "$IDLE_TEST_REFDBG"; then
+        if test -z "$REFDBG_OPTIONS" ; then
+                export REFDBG_OPTIONS="btnum=10"
+        fi
+        if test -z "$IDLE_WRAPPER" ; then
+                IDLE_WRAPPER="refdbg"
+        fi
+fi
+
+export G_DEBUG=fatal-warnings" ${G_DEBUG}"
+exec @abs_top_builddir@/libtool --mode=execute $IDLE_WRAPPER @abs_top_builddir@/src/telepathy-idle
diff --git a/tests/twisted/tools/idle.service.in b/tests/twisted/tools/idle.service.in
new file mode 100644
index 0000000..467adef
--- /dev/null
+++ b/tests/twisted/tools/idle.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.ConnectionManager.idle
+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..84d8d65
--- /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="*" eavesdrop="true"/>
+    <!-- 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..519b9b1
--- /dev/null
+++ b/tests/twisted/tools/with-session-bus.sh
@@ -0,0 +1,84 @@
+#!/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-2008 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
+  echo "" >&2
+  echo "If \$WITH_SESSION_BUS_FORK_DBUS_MONITOR is set, fork dbus-monitor" >&2
+  echo "with the arguments in \$WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT." >&2
+  echo "The output of dbus-monitor is saved in $me-<pid>.dbus-monitor-logs" >&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
+
+if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then
+  echo -n "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2
+  dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT \
+        &> $me-$$.dbus-monitor-logs &
+fi
+
+"$@" || e=$?
+
+trap - INT HUP TERM
+cleanup
+
+exit $e
-- 
1.5.6.5




More information about the telepathy-commits mailing list