zeitgeist team mailing list archive
-
zeitgeist team
-
Mailing list archive
-
Message #04714
[Branch ~zeitgeist/zeitgeist/bluebird] Rev 386: Fix duplicate signal/monitor connections and test suit improvements
Merge authors:
Siegfried Gevatter (rainct)
Related merge proposals:
https://code.launchpad.net/~rainct/zeitgeist/922620/+merge/91920
proposed by: Siegfried Gevatter (rainct)
------------------------------------------------------------
revno: 386 [merge]
fixes bugs: https://launchpad.net/bugs/910199 https://launchpad.net/bugs/922620
committer: Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
branch nick: bluebird.foo
timestamp: Tue 2012-02-07 23:58:24 +0100
message:
Fix duplicate signal/monitor connections and test suit improvements
- Fix monitors and signals being installed/connected to twice.
- Fix signals being connected to twice.
- Add test cases for signal and monitor re-connection.
- Give each test case a fresh private bus.
- Make it possible to run more than one test at once by randomizing DISPLAY.
modified:
python/client.py
test/dbus/blacklist-test.py
test/dbus/monitor-test.py
test/dbus/testutils.py
--
lp:zeitgeist
https://code.launchpad.net/~zeitgeist/zeitgeist/bluebird
Your team Zeitgeist Framework Team is subscribed to branch lp:zeitgeist.
To unsubscribe from this branch go to https://code.launchpad.net/~zeitgeist/zeitgeist/bluebird/+edit-subscription
=== modified file 'python/client.py'
--- python/client.py 2011-10-29 13:31:12 +0000
+++ python/client.py 2012-02-07 22:45:47 +0000
@@ -40,6 +40,19 @@
log = logging.getLogger("zeitgeist.client")
+# This is here so testutils.py can override it with a private bus connection.
+# Init needs to be lazy so tests will use the private D-Bus instance.
+global session_bus
+session_bus = None
+def get_bus():
+ global session_bus
+ if session_bus is None:
+ session_bus = dbus.SessionBus()
+ return session_bus
+def _set_bus(bus):
+ global session_bus
+ session_bus = bus
+
class _DBusInterface(object):
"""Wrapper around dbus.Interface adding convenience methods."""
@@ -47,7 +60,6 @@
# that here because otherwise all instances would share their state.
_disconnect_callbacks = None
_reconnect_callbacks = None
- _generic_callbacks = None
@staticmethod
def get_members(introspection_xml):
@@ -69,8 +81,9 @@
def reconnect(self):
if not self._reconnect_when_needed:
return
- self.__proxy = dbus.SessionBus().get_object(
- self.__iface.requested_bus_name, self.__object_path)
+ self.__proxy = get_bus().get_object(
+ self.__iface.requested_bus_name, self.__object_path,
+ follow_name_owner_changes=True)
self.__iface = dbus.Interface(self.__proxy, self.__interface_name)
self._load_introspection_data()
@@ -131,8 +144,7 @@
self.reconnect()
if signal not in self.__signals:
raise TypeError("Unknown signal name: %s" % signal)
- self._generic_callbacks.add((signal, callback))
- self.__proxy.connect_to_signal(
+ return self.__proxy.connect_to_signal(
signal,
callback,
dbus_interface=self.__interface_name,
@@ -167,29 +179,28 @@
self._reconnect_when_needed = reconnect
self._load_introspection_data()
+ self._first_connection = True
self._disconnect_callbacks = set()
self._reconnect_callbacks = set()
- self._generic_callbacks = set()
# Listen to (dis)connection notifications, for connect_exit and connect_join
def name_owner_changed(connection_name):
if connection_name == "":
- callbacks = self._disconnect_callbacks
self.__methods = self.__signals = None
+ for callback in self._disconnect_callbacks:
+ callback()
+ elif self._first_connection:
+ # python-dbus guarantees that it'll call NameOwnerChanged at startup
+ # (even if the service was already running). When that happens, we
+ # don't want to connect the signals a second time.
+ self._first_connection = False
else:
if not self._reconnect_when_needed:
return
self.reconnect()
- callbacks = self._reconnect_callbacks
- for signal, callback in self._generic_callbacks:
- try:
- self.connect(signal, callback)
- except TypeError:
- log.exception("Failed to reconnect to signal \"%s\" "
- "after engine disconnection." % signal)
- for callback in callbacks:
- callback()
- dbus.SessionBus().watch_name_owner(self.__iface.requested_bus_name,
+ for callback in self._reconnect_callbacks:
+ callback()
+ get_bus().watch_name_owner(self.__iface.requested_bus_name,
name_owner_changed)
class ZeitgeistDBusInterface(object):
@@ -233,7 +244,8 @@
if not name in cls.__shared_state["extension_interfaces"]:
interface_name = "org.gnome.zeitgeist.%s" % name
object_path = "/org/gnome/zeitgeist/%s" % path
- proxy = dbus.SessionBus().get_object(busname, object_path)
+ proxy = get_bus().get_object(busname, object_path,
+ follow_name_owner_changes=True)
iface = _DBusInterface(proxy, interface_name, object_path)
iface.BUS_NAME = busname
iface.INTERFACE_NAME = interface_name
@@ -244,8 +256,8 @@
def __init__(self, reconnect=True):
if not "dbus_interface" in self.__shared_state:
try:
- proxy = dbus.SessionBus().get_object(self.BUS_NAME,
- self.OBJECT_PATH)
+ proxy = get_bus().get_object(self.BUS_NAME,
+ self.OBJECT_PATH, follow_name_owner_changes=True)
except dbus.exceptions.DBusException, e:
if e.get_dbus_name() == "org.freedesktop.DBus.Error.ServiceUnknown":
raise RuntimeError(
@@ -292,7 +304,7 @@
self._path = monitor_path
self._insert_callback = insert_callback
self._delete_callback = delete_callback
- dbus.service.Object.__init__(self, dbus.SessionBus(), monitor_path)
+ dbus.service.Object.__init__(self, get_bus(), monitor_path)
def get_path (self): return self._path
path = property(get_path,
=== modified file 'test/dbus/blacklist-test.py'
--- test/dbus/blacklist-test.py 2011-09-07 20:48:27 +0000
+++ test/dbus/blacklist-test.py 2012-02-07 21:08:49 +0000
@@ -110,11 +110,13 @@
newAllTemplates = blacklist.GetTemplates()
self.assertEquals(len(newAllTemplates), 0)
- def testBlacklistSignals(self):
+ def testBlacklistSignals(self, mainloop=None, connect_signals=True):
self.blacklist = self._get_blacklist_iface()
- mainloop = self.create_mainloop()
+ if mainloop is None:
+ mainloop = self.create_mainloop()
template1 = Event.new_for_values(
+ timestamp=0,
interpretation=Interpretation.ACCESS_EVENT,
subject_uri="http://nothingtoseehere.gov")
@@ -124,7 +126,6 @@
@asyncTestMethod(mainloop)
def cb_added(template_id, event_template):
global hit
- print "*** cb_added with hit", hit
self.assertEquals(hit, 0)
hit = 1
self.assertEquals(template_id, "TestTemplate")
@@ -140,8 +141,9 @@
mainloop.quit()
# Connect to signals
- self.blacklist.connect('TemplateAdded', cb_added)
- self.blacklist.connect('TemplateRemoved', cb_removed)
+ if connect_signals:
+ self.blacklist.connect('TemplateAdded', cb_added)
+ self.blacklist.connect('TemplateRemoved', cb_removed)
def launch_tests():
self.blacklist.AddTemplate("TestTemplate", template1)
@@ -150,5 +152,16 @@
mainloop.run()
+ def testBlacklistSignalWithReconnection(self):
+ mainloop = self.create_mainloop()
+ self.testBlacklistSignals(mainloop)
+
+ # Restart the Zeitgeist daemon...
+ self.kill_daemon()
+ self.spawn_daemon()
+
+ # ... and try again without re-connecting to the signals
+ self.testBlacklistSignals(mainloop, connect_signals=False)
+
if __name__ == "__main__":
unittest.main()
=== modified file 'test/dbus/monitor-test.py'
--- test/dbus/monitor-test.py 2011-09-07 20:48:27 +0000
+++ test/dbus/monitor-test.py 2012-02-07 22:45:47 +0000
@@ -286,5 +286,34 @@
self.assertEquals(1, len(result))
self.assertEquals(1, result.pop())
+ def testMonitorReconnection(self):
+ result = []
+ mainloop = self.create_mainloop()
+ events = parse_events("test/data/three_events.js")
+
+ @asyncTestMethod(mainloop)
+ def notify_insert_handler(time_range, events):
+ result.extend(events)
+ mainloop.quit()
+
+ @asyncTestMethod(mainloop)
+ def notify_delete_handler(time_range, event_ids):
+ mainloop.quit()
+ self.fail("Unexpected delete notification")
+
+ self.client.install_monitor(TimeRange.always(), [],
+ notify_insert_handler, notify_delete_handler)
+
+ # Restart the Zeitgeist daemon to test automagic monitor re-connection
+ self.kill_daemon()
+ self.spawn_daemon()
+
+ # Insert events in idle loop to give the reconnection logic enough time
+ gobject.idle_add(lambda *args: self.client.insert_events(events))
+
+ mainloop.run()
+
+ self.assertEquals(3, len(result))
+
if __name__ == "__main__":
unittest.main()
=== modified file 'test/dbus/testutils.py'
--- test/dbus/testutils.py 2012-02-07 17:49:49 +0000
+++ test/dbus/testutils.py 2012-02-07 22:17:46 +0000
@@ -21,12 +21,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
+import dbus
import os
import time
import sys
import signal
import tempfile
import shutil
+import random
from subprocess import Popen, PIPE
# DBus setup
@@ -36,7 +38,8 @@
# Import local Zeitgeist modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
-from zeitgeist.client import ZeitgeistDBusInterface, ZeitgeistClient
+from zeitgeist.client import ZeitgeistDBusInterface, ZeitgeistClient, \
+ get_bus, _set_bus
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, \
TimeRange, NULL_EVENT
@@ -175,11 +178,18 @@
# hack to clear the state of the interface
ZeitgeistDBusInterface._ZeitgeistDBusInterface__shared_state = {}
+
+ # Replace the bus connection with a private one for each test case,
+ # so that they don't share signals or other state
+ _set_bus(dbus.SessionBus(private=True))
+ get_bus().set_exit_on_disconnect(False)
+
self.client = ZeitgeistClient()
def tearDown(self):
assert self.daemon is not None
assert self.client is not None
+ get_bus().close()
self.kill_daemon()
if 'ZEITGEIST_TESTS_KEEP_TMP' in os.environ:
print '\n\nAll temporary files have been preserved in %s\n' \
@@ -389,7 +399,9 @@
self.assertEqual(ev1, ev2)
class DBusPrivateMessageBus(object):
- DISPLAY = ":27"
+ # Choose a random number so it's possible to have more than
+ # one test running at once.
+ DISPLAY = ":%d" % random.randint(20, 100)
def _run(self):
os.environ.update({"DISPLAY": self.DISPLAY})