Merge lp:~veebers/autopilot/1.4_fixing_backend_being_none into lp:autopilot

Proposed by Christopher Lee on 2013-10-09
Status: Merged
Approved by: Christopher Lee on 2013-10-10
Approved revision: 351
Merged at revision: 350
Proposed branch: lp:~veebers/autopilot/1.4_fixing_backend_being_none
Merge into: lp:autopilot
Prerequisite: lp:~veebers/autopilot/generic_proxy_objects_id
Diff against target: 340 lines (+78/-61)
7 files modified
autopilot/introspection/__init__.py (+10/-10)
autopilot/introspection/dbus.py (+16/-37)
autopilot/introspection/qt.py (+1/-1)
autopilot/tests/unit/test_introspection_features.py (+19/-10)
autopilot/tests/unit/test_matchers.py (+1/-1)
autopilot/tests/unit/test_types.py (+3/-2)
docs/porting/porting.rst (+28/-0)
To merge this branch: bzr merge lp:~veebers/autopilot/1.4_fixing_backend_being_none
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration 2013-10-09 Approve on 2013-10-10
Thomi Richards (community) 2013-10-09 Approve on 2013-10-09
Review via email: mp+190022@code.launchpad.net

This proposal supersedes a proposal from 2013-10-07.

Commit message

Fixes issue where a classes _Backend can be None causes uncaught exceptions.

Description of the change

Fixes issue where a classes _Backend can be None causes uncaught exceptions.
The backend is now stored as an instance variable.

Forward port of: https://code.launchpad.net/~veebers/autopilot/fixing_backend_being_none/+merge/188762

To post a comment you must log in.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Thomi Richards (thomir) wrote : Posted in a previous version of this proposal

LGTM

review: Approve
Thomi Richards (thomir) wrote :

LGTM.

review: Approve
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:348
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~veebers/autopilot/1.4_fixing_backend_being_none/+merge/190022/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/autopilot-ci/264/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-saucy-amd64-ci/192
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-saucy-armhf-ci/192
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-saucy-i386-ci/36

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/autopilot-ci/264/rebuild

review: Needs Fixing (continuous-integration)
349. By Christopher Lee on 2013-10-09

Added porting notes.

350. By Christopher Lee on 2013-10-09

Fixed the documentation

351. By Christopher Lee on 2013-10-09

Merge trunk

PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
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-09-29 22:45:15 +0000
3+++ autopilot/introspection/__init__.py 2013-10-09 23:03:58 +0000
4@@ -43,7 +43,6 @@
5 AP_INTROSPECTION_IFACE,
6 )
7 from autopilot.introspection.dbus import (
8- _clear_backends_for_proxy_object,
9 CustomEmulatorBase,
10 DBusIntrospectionObject,
11 get_classname_from_path,
12@@ -485,16 +484,17 @@
13 proxy_bases = proxy_bases + (emulator_base, )
14 cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source)
15
16- _clear_backends_for_proxy_object(emulator_base)
17- clsobj = type(
18- # Merge the object hierarchy.
19- str("%sBase" % cls_name), proxy_bases, dict(_Backend=data_source)
20- )
21+ # Merge the object hierarchy.
22+ clsobj = type(str("%sBase" % cls_name), proxy_bases, {})
23
24 proxy_class = type(str(cls_name), (clsobj,), {})
25
26- proxy = proxy_class.get_root_instance()
27- return proxy
28+ try:
29+ dbus_tuple = data_source.introspection_iface.GetState("/")[0]
30+ path, state = dbus_tuple
31+ return proxy_class(state, path, data_source)
32+ except IndexError:
33+ raise RuntimeError("Unable to find root object of %r" % proxy_class)
34
35
36 def _get_proxy_object_base_classes(backend):
37@@ -529,8 +529,8 @@
38 class ApplicationProxyObject(DBusIntrospectionObject):
39 """A class that better supports query data from an application."""
40
41- def __init__(self, state, path):
42- super(ApplicationProxyObject, self).__init__(state, path)
43+ def __init__(self, state, path, backend):
44+ super(ApplicationProxyObject, self).__init__(state, path, backend)
45 self._process = None
46
47 def set_process(self, process):
48
49=== modified file 'autopilot/introspection/dbus.py'
50--- autopilot/introspection/dbus.py 2013-09-29 22:45:15 +0000
51+++ autopilot/introspection/dbus.py 2013-10-09 23:03:58 +0000
52@@ -126,20 +126,6 @@
53 return class_object
54
55
56-def _clear_backends_for_proxy_object(proxy_object):
57- """Iterate over the object registry and clear the dbus backend address set
58- on any class that has the same id as the specified proxy_object.
59-
60- This is required so consecutive tests do not end up re-using the same dbus
61- backend address as a previous run, which will probably be incorrect.
62-
63- """
64- global _object_registry
65- for cls in _object_registry[proxy_object._id].values():
66- if type(proxy_object) is not cls:
67- cls._Backend = None
68-
69-
70 def get_classname_from_path(object_path):
71 return object_path.split("/")[-1]
72
73@@ -174,14 +160,13 @@
74
75 """
76
77- _Backend = None
78-
79- def __init__(self, state_dict, path):
80+ def __init__(self, state_dict, path, backend):
81 self.__state = {}
82 self.__refresh_on_attribute = True
83 self._set_properties(state_dict)
84 self._path = path
85 self._poll_time = 10
86+ self._backend = backend
87
88 def _set_properties(self, state_dict):
89 """Creates and set attributes of *self* based on contents of
90@@ -484,8 +469,7 @@
91 _, new_state = self.get_new_state()
92 self._set_properties(new_state)
93
94- @classmethod
95- def get_all_instances(cls):
96+ def get_all_instances(self):
97 """Get all instances of this class that exist within the Application
98 state tree.
99
100@@ -503,23 +487,22 @@
101 :return: List (possibly empty) of class instances.
102
103 """
104- cls_name = cls.__name__
105- instances = cls.get_state_by_path("//%s" % (cls_name))
106- return [cls.make_introspection_object(i) for i in instances]
107+ cls_name = type(self).__name__
108+ instances = self.get_state_by_path("//%s" % (cls_name))
109+ return [self.make_introspection_object(i) for i in instances]
110
111- @classmethod
112- def get_root_instance(cls):
113+ def get_root_instance(self):
114 """Get the object at the root of this tree.
115
116 This will return an object that represents the root of the
117 introspection tree.
118
119 """
120- instances = cls.get_state_by_path("/")
121+ instances = self.get_state_by_path("/")
122 if len(instances) != 1:
123 logger.error("Could not retrieve root object.")
124 return None
125- return cls.make_introspection_object(instances[0])
126+ return self.make_introspection_object(instances[0])
127
128 def __getattr__(self, name):
129 # avoid recursion if for some reason we have no state set (should never
130@@ -536,8 +519,7 @@
131 "Class '%s' has no attribute '%s'." %
132 (self.__class__.__name__, name))
133
134- @classmethod
135- def get_state_by_path(cls, piece):
136+ def get_state_by_path(self, piece):
137 """Get state for a particular piece of the state tree.
138
139 You should probably never need to call this directly.
140@@ -552,7 +534,7 @@
141 "XPath query must be a string, not %r", type(piece))
142
143 with Timer("GetState %s" % piece):
144- data = cls._Backend.introspection_iface.GetState(piece)
145+ data = self._backend.introspection_iface.GetState(piece)
146 if len(data) > 15:
147 logger.warning(
148 "Your query '%s' returned a lot of data (%d items). This "
149@@ -584,8 +566,7 @@
150 else:
151 return self._path + "[id=%d]" % self.id
152
153- @classmethod
154- def make_introspection_object(cls, dbus_tuple):
155+ def make_introspection_object(self, dbus_tuple):
156 """Make an introspection object given a DBus tuple of
157 (path, state_dict).
158
159@@ -594,23 +575,21 @@
160 path, state = dbus_tuple
161 name = get_classname_from_path(path)
162 try:
163- class_type = _object_registry[cls._id][name]
164- if class_type._Backend is None:
165- class_type._Backend = cls._Backend
166+ class_type = _object_registry[self._id][name]
167 except KeyError:
168 get_debug_logger().warning(
169 "Generating introspection instance for type '%s' based on "
170 "generic class.", name)
171 # we want the object to inherit from the custom emulator base, not
172 # the object class that is doing the selecting
173- for base in cls.__bases__:
174+ for base in type(self).__bases__:
175 if issubclass(base, CustomEmulatorBase):
176 base_class = base
177 break
178 else:
179- base_class = cls
180+ base_class = type(self)
181 class_type = type(str(name), (base_class,), {})
182- return class_type(state, path)
183+ return class_type(state, path, self._backend)
184
185 @contextmanager
186 def no_automatic_refreshing(self):
187
188=== modified file 'autopilot/introspection/qt.py'
189--- autopilot/introspection/qt.py 2013-09-18 23:14:02 +0000
190+++ autopilot/introspection/qt.py 2013-10-09 23:03:58 +0000
191@@ -100,7 +100,7 @@
192
193 """
194
195- return self._Backend.qt_introspection_iface
196+ return self._backend.qt_introspection_iface
197
198 @property
199 def slots(self):
200
201=== modified file 'autopilot/tests/unit/test_introspection_features.py'
202--- autopilot/tests/unit/test_introspection_features.py 2013-09-27 05:55:31 +0000
203+++ autopilot/tests/unit/test_introspection_features.py 2013-10-09 23:03:58 +0000
204@@ -18,7 +18,7 @@
205 #
206
207
208-from mock import patch
209+from mock import patch, Mock
210 from testtools import TestCase
211 from testtools.matchers import Equals, NotEquals
212 from testscenarios import TestWithScenarios
213@@ -120,7 +120,8 @@
214 def test_can_access_path_attribute(self):
215 fake_object = DBusIntrospectionObject(
216 dict(id=[0, 123], path=[0, '/some/path']),
217- '/'
218+ '/',
219+ Mock()
220 )
221 with fake_object.no_automatic_refreshing():
222 self.assertThat(fake_object.path, Equals('/some/path'))
223@@ -132,10 +133,14 @@
224 'large' is defined as more than 15.
225
226 """
227- with patch.object(DBusIntrospectionObject, '_Backend') as p:
228- p.introspection_iface.GetState.return_value = \
229- [('/path', {}) for i in range(16)]
230- DBusIntrospectionObject.get_state_by_path('some_query')
231+ fake_object = DBusIntrospectionObject(
232+ dict(id=[0, 123], path=[0, '/some/path']),
233+ '/',
234+ Mock()
235+ )
236+ fake_object._backend.introspection_iface.GetState.return_value = \
237+ [('/path', {}) for i in range(16)]
238+ fake_object.get_state_by_path('some_query')
239
240 mock_logger.warning.assert_called_once_with(
241 "Your query '%s' returned a lot of data (%d items). This "
242@@ -151,9 +156,13 @@
243 'small' is defined as 15 or fewer.
244
245 """
246- with patch.object(DBusIntrospectionObject, '_Backend') as p:
247- p.introspection_iface.GetState.return_value = \
248- [('/path', {}) for i in range(15)]
249- DBusIntrospectionObject.get_state_by_path('some_query')
250+ fake_object = DBusIntrospectionObject(
251+ dict(id=[0, 123], path=[0, '/some/path']),
252+ '/',
253+ Mock()
254+ )
255+ fake_object._backend.introspection_iface.GetState.return_value = \
256+ [('/path', {}) for i in range(15)]
257+ fake_object.get_state_by_path('some_query')
258
259 self.assertThat(mock_logger.warning.called, Equals(False))
260
261=== modified file 'autopilot/tests/unit/test_matchers.py'
262--- autopilot/tests/unit/test_matchers.py 2013-09-16 17:17:35 +0000
263+++ autopilot/tests/unit/test_matchers.py 2013-10-09 23:03:58 +0000
264@@ -66,7 +66,7 @@
265 """
266 class FakeObject(DBusIntrospectionObject):
267 def __init__(self, props):
268- super(FakeObject, self).__init__(props, "/FakeObject")
269+ super(FakeObject, self).__init__(props, "/FakeObject", None)
270 FakeObject._fake_props = props
271
272 @classmethod
273
274=== modified file 'autopilot/tests/unit/test_types.py'
275--- autopilot/tests/unit/test_types.py 2013-09-26 23:34:52 +0000
276+++ autopilot/tests/unit/test_types.py 2013-10-09 23:03:58 +0000
277@@ -20,7 +20,7 @@
278 from __future__ import absolute_import
279
280 from datetime import datetime, time
281-from mock import patch
282+from mock import patch, Mock
283 from testscenarios import TestWithScenarios
284 from testtools import TestCase
285 from testtools.matchers import Equals, IsInstance, NotEquals, raises
286@@ -636,7 +636,8 @@
287 """
288 DBusIntrospectionObject(
289 dict(foo=[0]),
290- '/some/dummy/path'
291+ '/some/dummy/path',
292+ Mock()
293 )
294 error_logger.assert_called_once_with(
295 "While constructing attribute '%s.%s': %s",
296
297=== modified file 'docs/porting/porting.rst'
298--- docs/porting/porting.rst 2013-09-26 22:54:34 +0000
299+++ docs/porting/porting.rst 2013-10-09 23:03:58 +0000
300@@ -51,6 +51,30 @@
301
302 my_obj = self.app.wait_select_single("MyObject")
303
304+.. _dbus_backends:
305+
306+DBus backends and :class:`~autopilot.introspection.dbus.DBusIntrospectionObject` changes
307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
308+
309+Due to a change in how
310+:class:`~autopilot.introspection.dbus.DBusIntrospectionObject` objects store
311+their DBus backend a couple of classmethods have now become instance methods.
312+
313+These affected methods are:
314+
315+ * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_all_instances`
316+ * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_root_instance`
317+ * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_state_by_path`
318+
319+For example, if your old code is something along the lines of::
320+
321+ all_keys = KeyCustomEmulator.get_all_instances()
322+
323+You will instead need to have something like this instead::
324+
325+ all_keys = app_proxy.select_many(KeyCustomEmulator)
326+
327+
328 Python 3
329 ++++++++
330
331@@ -70,6 +94,10 @@
332 * A large code cleanup and reorganisation. In particular, lots of code that came from the Unity 3D codebase has been removed if it was deemed to not be useful to the majority of test authors. This code cleanup includes a flattening of the autopilot namespace. Previously, many useful classes lived under the ``autopilot.emulators`` namespace. These have now been moved into the ``autopilot`` namespace.
333
334
335+.. note:: There is an API breakage in autopilot 1.3. The changes outlined under
336+ the heading ":ref:`dbus_backends`" apply to version
337+ 1.3.1+13.10.20131003.1-0ubuntu1 and onwards .
338+
339 ``QtIntrospectionTestMixin`` and ``GtkIntrospectionTestMixin`` no longer exist
340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
341

Subscribers

People subscribed via source and target branches