Merge lp:~thomir-deactivatedaccount/autopilot/dbus-address-changes into lp:autopilot

Proposed by Thomi Richards
Status: Merged
Approved by: Christopher Lee
Approved revision: 185
Merged at revision: 180
Proposed branch: lp:~thomir-deactivatedaccount/autopilot/dbus-address-changes
Merge into: lp:autopilot
Diff against target: 427 lines (+205/-62)
5 files modified
autopilot/introspection/__init__.py (+24/-23)
autopilot/introspection/backends.py (+118/-0)
autopilot/introspection/dbus.py (+10/-34)
autopilot/introspection/qt.py (+1/-5)
autopilot/tests/test_backend.py (+52/-0)
To merge this branch: bzr merge lp:~thomir-deactivatedaccount/autopilot/dbus-address-changes
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Christopher Lee (community) Approve
Review via email: mp+161629@code.launchpad.net

Commit message

Changes to Dbus address backend code to make it easier to get a proxy object from a dbus address.

Description of the change

Several changes to support backing autopilot on to different dbus backend locations. It is now easier to get an autopilot proxy object from a dbus backend - this could even be on a separate machine.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
181. By Thomi Richards

Use the DBus address instance in place of service name and object path. Support for dbus introspection interface.

182. By Thomi Richards

Convert Qt interface.

183. By Thomi Richards

Fix missing import.

184. By Thomi Richards

Remove un-used optional parameter.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
185. By Thomi Richards

whitespace fix.

Revision history for this message
Christopher Lee (veebers) wrote :

Awesome, looks good.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopilot/introspection/__init__.py'
2--- autopilot/introspection/__init__.py 2013-04-26 22:41:19 +0000
3+++ autopilot/introspection/__init__.py 2013-04-30 20:33:25 +0000
4@@ -24,6 +24,7 @@
5 mechanism, and probably isn't useful to most test authors.
6
7 """
8+from __future__ import absolute_import
9
10 import dbus
11 import logging
12@@ -32,20 +33,19 @@
13 import os
14
15
16+from autopilot.introspection.backends import DBusAddress
17 from autopilot.introspection.constants import (
18 AUTOPILOT_PATH,
19 QT_AUTOPILOT_IFACE,
20 AP_INTROSPECTION_IFACE,
21- DBUS_INTROSPECTION_IFACE,
22 )
23 from autopilot.introspection.dbus import (
24 clear_object_registry,
25 DBusIntrospectionObject,
26- object_passes_filters,
27- get_session_bus,
28 get_classname_from_path,
29 )
30-from autopilot.utilities import get_debug_logger, addCleanup
31+from autopilot.dbus_handler import get_session_bus
32+from autopilot.utilities import get_debug_logger
33
34
35 logger = logging.getLogger(__name__)
36@@ -166,11 +166,6 @@
37 bus_object = session_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
38 bus_iface = dbus.Interface(bus_object, 'org.freedesktop.DBus')
39
40- # clear the object registry, since it's specific to the dbus service, and we
41- # have just started a new service. We don't want the old types hanging around
42- # in the registry. We need a better method for this however.
43- clear_object_registry()
44-
45 logger.info("Looking for autopilot interface for PID %d (and children)", pid)
46 # We give the process 10 seconds grace time to get the dbus interface up...
47 for i in range(10):
48@@ -214,27 +209,37 @@
49 return result
50
51
52-def make_proxy_object_from_service_name(service_name, obj_path):
53+def make_proxy_object_from_service_name(service_name, obj_path, dbus_addr=None):
54 """Returns a root proxy object given a DBus service name."""
55 # parameters can sometimes be dbus.String instances, sometimes QString instances.
56 # it's easier to convert them here than at the calling sites.
57 service_name = str(service_name)
58 obj_path = str(obj_path)
59
60- proxy_bases = get_proxy_object_base_clases(service_name, obj_path)
61- cls_name, cls_state = get_proxy_object_class_name_and_state(service_name, obj_path)
62+ if dbus_addr is not None:
63+ be = DBusAddress.CustomBus(dbus_addr, service_name, obj_path)
64+ else:
65+ be = DBusAddress.SessionBus(service_name, obj_path)
66+
67+
68+ proxy_bases = get_proxy_object_base_clases(be)
69+ cls_name, cls_state = get_proxy_object_class_name_and_state(be)
70
71 clsobj = type(str(cls_name),
72 proxy_bases,
73- dict(DBUS_SERVICE=service_name,
74- DBUS_OBJECT=obj_path
75+ dict(_Backend = be
76 )
77 )
78+
79+ # clear the object registry, since it's specific to the dbus service, and we
80+ # have just started a new service. We don't want the old types hanging around
81+ # in the registry. We need a better method for this however.
82+ clear_object_registry(clsobj._Backend)
83 proxy = clsobj.get_root_instance()
84 return proxy
85
86
87-def get_proxy_object_base_clases(service_name, obj_path):
88+def get_proxy_object_base_clases(backend):
89 """Return tuple of the base classes to use when creating a proxy object
90 for the given service name & path.
91
92@@ -244,11 +249,9 @@
93
94 bases = [ApplicationProxyObject]
95
96- dbus_object = get_session_bus().get_object(service_name, obj_path)
97- introspection_iface = dbus.Interface(dbus_object, DBUS_INTROSPECTION_IFACE)
98- intro_xml = introspection_iface.Introspect()
99+ intro_xml = backend.dbus_introspection_iface.Introspect()
100 if AP_INTROSPECTION_IFACE not in intro_xml:
101- raise RuntimeError("Could not find Autopilot interface on service name '%s'" % service_name)
102+ raise RuntimeError("Could not find Autopilot interface on DBus backend '%s'" % backend)
103
104 if QT_AUTOPILOT_IFACE in intro_xml:
105 from autopilot.introspection.qt import QtObjectProxyMixin
106@@ -257,11 +260,9 @@
107 return tuple(bases)
108
109
110-def get_proxy_object_class_name_and_state(service_name, obj_path):
111+def get_proxy_object_class_name_and_state(backend):
112 """Return the class name and root state dictionary."""
113- dbus_object = get_session_bus().get_object(service_name, obj_path)
114- dbus_iface = dbus.Interface(dbus_object, AP_INTROSPECTION_IFACE)
115- object_path, object_state = dbus_iface.GetState("/")[0]
116+ object_path, object_state = backend.introspection_iface.GetState("/")[0]
117 return get_classname_from_path(object_path), object_state
118
119
120
121=== added file 'autopilot/introspection/backends.py'
122--- autopilot/introspection/backends.py 1970-01-01 00:00:00 +0000
123+++ autopilot/introspection/backends.py 2013-04-30 20:33:25 +0000
124@@ -0,0 +1,118 @@
125+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
126+#
127+# Autopilot Functional Test Tool
128+# Copyright (C) 2012-2013 Canonical
129+#
130+# This program is free software: you can redistribute it and/or modify
131+# it under the terms of the GNU General Public License as published by
132+# the Free Software Foundation, either version 3 of the License, or
133+# (at your option) any later version.
134+#
135+# This program is distributed in the hope that it will be useful,
136+# but WITHOUT ANY WARRANTY; without even the implied warranty of
137+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138+# GNU General Public License for more details.
139+#
140+# You should have received a copy of the GNU General Public License
141+# along with this program. If not, see <http://www.gnu.org/licenses/>.
142+#
143+
144+"Backend interface for autopilot."
145+from __future__ import absolute_import
146+
147+from dbus._dbus import BusConnection
148+import dbus
149+
150+from autopilot.introspection.constants import (
151+ AP_INTROSPECTION_IFACE,
152+ DBUS_INTROSPECTION_IFACE,
153+ QT_AUTOPILOT_IFACE,
154+ )
155+
156+
157+class DBusAddress(object):
158+
159+ "Store information about an Autopilot dbus backend, from keyword arguments."
160+
161+ @staticmethod
162+ def SessionBus(connection, object_path):
163+ """Construct a DBusAddress that backs on to the session bus."""
164+ return DBusAddress(dbus.SessionBus(), connection, object_path)
165+
166+ @staticmethod
167+ def SystemBus(connection, object_path):
168+ """Construct a DBusAddress that backs on to the system bus."""
169+ return DBusAddress(dbus.SystemBus(), connection, object_path)
170+
171+ @staticmethod
172+ def CustomBus(bus_address, connection, object_path):
173+ """Construct a DBusAddress that backs on to a custom bus.
174+
175+ :param bus_address: A string representing the address of the dbus bus to
176+ connect to.
177+
178+ """
179+ return DBusAddress(BusConnection(bus_address), connection, object_path)
180+
181+ def __init__(self, bus, connection, object_path):
182+ """Construct a DBusAddress instance.
183+
184+ :param bus: A valid DBus bus object.
185+ :param connection: A string connection name to look at, or None to search
186+ all dbus connections for objects that resemble an autopilot conection.
187+ :param object_path: The path to the object that provides the autopilot
188+ interface, or None to search for the object.
189+
190+ """
191+ # We cannot evaluate kwargs for accuracy now, since this class will be
192+ # created at module import time, at which point the bus backend probably
193+ # does not exist yet.
194+ self._bus = bus
195+ self._connection = connection
196+ self._object_path = object_path
197+
198+ @property
199+ def introspection_iface(self):
200+ if not isinstance(self._connection, basestring):
201+ raise TypeError("Service name must be a string.")
202+ if not isinstance(self._object_path, basestring):
203+ raise TypeError("Object name must be a string")
204+
205+ _debug_proxy_obj = self._bus.get_object(self._connection, self._object_path)
206+ return dbus.Interface(_debug_proxy_obj, AP_INTROSPECTION_IFACE)
207+
208+ @property
209+ def dbus_introspection_iface(self):
210+ dbus_object = self._bus.get_object(self._connection, self._object_path)
211+ return dbus.Interface(dbus_object, DBUS_INTROSPECTION_IFACE)
212+
213+ @property
214+ def qt_introspection_iface(self):
215+ _debug_proxy_obj = self._bus.get_object(self._connection, self._object_path)
216+ return dbus.Interface(_debug_proxy_obj, QT_AUTOPILOT_IFACE)
217+
218+ def __hash__(self):
219+ return hash((self._bus, self._connection, self._object_path))
220+
221+ def __eq__(self, other):
222+ return self._bus == other._bus and \
223+ self._connection == other._connection and \
224+ self._object_path == other._object_path
225+
226+ def __ne__(self, other):
227+ return self._object_path != other._object_path or \
228+ self._connection != other._connection or \
229+ self._bus != other._bus
230+
231+ def __str__(self):
232+ return repr(self)
233+
234+ def __repr__(self):
235+ if self._bus_type == dbus.Bus.TYPE_SESSION:
236+ name = "session"
237+ elif self._bus_type == dbus.Bus.TYPE_SYSTEM:
238+ name = "system"
239+ else:
240+ name = "custom"
241+ return "<%s bus %s %s>" % (name, self._connection, self._object_path)
242+
243
244=== modified file 'autopilot/introspection/dbus.py'
245--- autopilot/introspection/dbus.py 2013-04-30 00:04:23 +0000
246+++ autopilot/introspection/dbus.py 2013-04-30 20:33:25 +0000
247@@ -34,7 +34,6 @@
248 from time import sleep
249 from textwrap import dedent
250
251-from autopilot.dbus_handler import get_session_bus
252 from autopilot.introspection.constants import AP_INTROSPECTION_IFACE
253 from autopilot.utilities import Timer
254
255@@ -58,16 +57,15 @@
256 def __new__(cls, classname, bases, classdict):
257 """Add class name to type registry."""
258 class_object = type.__new__(cls, classname, bases, classdict)
259- _object_registry[classname] = class_object
260+ # The DBusIntrospectionObject class always has Backend == None, since it's
261+ # not introspectable itself. We need to compensate for this...
262+ if class_object._Backend is not None:
263+ _object_registry[class_object._Backend] = {classname:class_object}
264 return class_object
265
266
267-def clear_object_registry():
268- """Clear the object registry.
269-
270- .. important:: DO NOT CALL THIS UNLESS YOU REALLY KNOW WHAT YOU ARE DOING!
271- ... and even then, are you *sure*?
272- """
273+def clear_object_registry(target_backend):
274+ """Delete all class objects from the object registry for a single backend."""
275 global _object_registry
276
277 # NOTE: We used to do '_object_registry.clear()' here, but that causes issues
278@@ -80,31 +78,13 @@
279 # - Thomi Richards
280 to_delete = []
281 for k,v in _object_registry.iteritems():
282- if v.DBUS_SERVICE != "com.canonical.Unity":
283+ if k == target_backend:
284 to_delete.append(k)
285
286 for k in to_delete:
287 del _object_registry[k]
288
289
290-def get_introspection_iface(service_name, object_path):
291- """Get the autopilot introspection interface for the specified service name
292- and object path.
293-
294- :param string service_name:
295- :param string object_name:
296- :raises: **TypeError** on invalid parameter type
297-
298- """
299- if not isinstance(service_name, basestring):
300- raise TypeError("Service name must be a string.")
301- if not isinstance(object_path, basestring):
302- raise TypeError("Object name must be a string")
303-
304- _debug_proxy_obj = get_session_bus().get_object(service_name, object_path)
305- return Interface(_debug_proxy_obj, AP_INTROSPECTION_IFACE)
306-
307-
308 def translate_state_keys(state_dict):
309 """Translates the *state_dict* passed in so the keys are usable as python attributes."""
310 return {k.replace('-','_'):v for k,v in state_dict.iteritems() }
311@@ -137,8 +117,7 @@
312
313 __metaclass__ = IntrospectableObjectMetaclass
314
315- DBUS_SERVICE = None
316- DBUS_OBJECT = None
317+ _Backend = None
318
319 def __init__(self, state_dict, path):
320 self.__state = {}
321@@ -443,10 +422,7 @@
322 raise TypeError("XPath query must be a string, not %r", type(piece))
323
324 with Timer("GetState %s" % piece):
325- return get_introspection_iface(
326- cls.DBUS_SERVICE,
327- cls.DBUS_OBJECT
328- ).GetState(piece)
329+ return cls._Backend.introspection_iface.GetState(piece)
330
331 def get_new_state(self):
332 """Retrieve a new state dictionary for this class instance.
333@@ -474,7 +450,7 @@
334 path, state = dbus_tuple
335 name = get_classname_from_path(path)
336 try:
337- class_type = _object_registry[name]
338+ class_type = _object_registry[cls._Backend][name]
339 except KeyError:
340 logger.warning("Generating introspection instance for type '%s' based on generic class.", name)
341 class_type = type(str(name), (cls,), {})
342
343=== modified file 'autopilot/introspection/qt.py'
344--- autopilot/introspection/qt.py 2013-04-23 04:15:54 +0000
345+++ autopilot/introspection/qt.py 2013-04-30 20:33:25 +0000
346@@ -23,14 +23,11 @@
347
348 __all__ = ['QtApplicationLauncher']
349
350-import dbus
351 import functools
352
353 import logging
354
355 from autopilot.introspection import ApplicationLauncher
356-from autopilot.introspection.constants import QT_AUTOPILOT_IFACE
357-from autopilot.introspection.dbus import get_session_bus
358
359
360 logger = logging.getLogger(__name__)
361@@ -102,8 +99,7 @@
362
363 """
364
365- _debug_proxy_obj = get_session_bus().get_object(self.DBUS_SERVICE, self.DBUS_OBJECT)
366- return dbus.Interface(_debug_proxy_obj, QT_AUTOPILOT_IFACE)
367+ return self._Backend.qt_introspection_iface
368
369 @property
370 def slots(self):
371
372=== added file 'autopilot/tests/test_backend.py'
373--- autopilot/tests/test_backend.py 1970-01-01 00:00:00 +0000
374+++ autopilot/tests/test_backend.py 2013-04-30 20:33:25 +0000
375@@ -0,0 +1,52 @@
376+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
377+#
378+# Autopilot Functional Test Tool
379+# Copyright (C) 2012-2013 Canonical
380+#
381+# This program is free software: you can redistribute it and/or modify
382+# it under the terms of the GNU General Public License as published by
383+# the Free Software Foundation, either version 3 of the License, or
384+# (at your option) any later version.
385+#
386+# This program is distributed in the hope that it will be useful,
387+# but WITHOUT ANY WARRANTY; without even the implied warranty of
388+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
389+# GNU General Public License for more details.
390+#
391+# You should have received a copy of the GNU General Public License
392+# along with this program. If not, see <http://www.gnu.org/licenses/>.
393+#
394+
395+
396+from testtools import TestCase
397+from testtools.matchers import Equals, Not, NotEquals
398+from autopilot.introspection.backends import DBusAddress
399+
400+class DBusAddressTests(TestCase):
401+
402+ def test_can_construct(self):
403+ fake_bus = object()
404+ addr = DBusAddress(fake_bus, "conn", "path")
405+
406+ def test_can_store_address_in_dictionary(self):
407+ fake_bus = object()
408+ addr = DBusAddress(fake_bus, "conn", "path")
409+ dict(addr=object())
410+
411+ def test_equality_operator(self):
412+ fake_bus = object()
413+ addr1 = DBusAddress(fake_bus, "conn", "path")
414+
415+ self.assertThat(addr1, Equals(DBusAddress(fake_bus, "conn", "path")))
416+ self.assertThat(addr1, Not(Equals(DBusAddress(fake_bus, "conn", "new_path"))))
417+ self.assertThat(addr1, Not(Equals(DBusAddress(fake_bus, "conn2", "path"))))
418+ self.assertThat(addr1, Not(Equals(DBusAddress(object(), "conn", "path"))))
419+
420+ def test_inequality_operator(self):
421+ fake_bus = object()
422+ addr1 = DBusAddress(fake_bus, "conn", "path")
423+
424+ self.assertThat(addr1, Not(NotEquals(DBusAddress(fake_bus, "conn", "path"))))
425+ self.assertThat(addr1, NotEquals(DBusAddress(fake_bus, "conn", "new_path")))
426+ self.assertThat(addr1, NotEquals(DBusAddress(fake_bus, "conn2", "path")))
427+ self.assertThat(addr1, NotEquals(DBusAddress(object(), "conn", "path")))

Subscribers

People subscribed via source and target branches