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

Proposed by Christopher Lee
Status: Superseded
Proposed branch: lp:~veebers/autopilot/1.4_fixing_backend_being_none
Merge into: lp:autopilot
Diff against target: 349 lines (+94/-63)
7 files modified
autopilot/introspection/__init__.py (+13/-10)
autopilot/introspection/dbus.py (+23/-38)
autopilot/introspection/qt.py (+1/-1)
autopilot/tests/functional/test_introspection_features.py (+34/-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)
To merge this branch: bzr merge lp:~veebers/autopilot/1.4_fixing_backend_being_none
Reviewer Review Type Date Requested Status
Thomi Richards (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+189524@code.launchpad.net

This proposal has been superseded by a proposal from 2013-10-09.

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.
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)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

LGTM

review: Approve
349. By Christopher Lee

Added porting notes.

350. By Christopher Lee

Fixed the documentation

351. By Christopher Lee

Merge trunk

Unmerged revisions

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-26 04:36:33 +0000
3+++ autopilot/introspection/__init__.py 2013-10-07 03:55:25 +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,13 +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- str(cls_name), proxy_bases, dict(_Backend=data_source)
19- )
20-
21- proxy = clsobj.get_root_instance()
22- return proxy
23+ # Merge the object hierarchy.
24+ clsobj = type(str("%sBase" % cls_name), proxy_bases, {})
25+
26+ proxy_class = type(str(cls_name), (clsobj,), {})
27+
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@@ -526,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-27 05:55:31 +0000
51+++ autopilot/introspection/dbus.py 2013-10-07 03:55:25 +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,17 +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- # override the _id attr from cls, since we don't want generated
172- # types to end up in the object registry.
173- class_type = type(str(name), (cls,), {'_id': None})
174- return class_type(state, path)
175+ # we want the object to inherit from the custom emulator base, not
176+ # the object class that is doing the selecting
177+ for base in type(self).__bases__:
178+ if issubclass(base, CustomEmulatorBase):
179+ base_class = base
180+ break
181+ else:
182+ base_class = type(self)
183+ class_type = type(str(name), (base_class,), {})
184+ return class_type(state, path, self._backend)
185
186 @contextmanager
187 def no_automatic_refreshing(self):
188
189=== modified file 'autopilot/introspection/qt.py'
190--- autopilot/introspection/qt.py 2013-09-18 23:14:02 +0000
191+++ autopilot/introspection/qt.py 2013-10-07 03:55:25 +0000
192@@ -100,7 +100,7 @@
193
194 """
195
196- return self._Backend.qt_introspection_iface
197+ return self._backend.qt_introspection_iface
198
199 @property
200 def slots(self):
201
202=== modified file 'autopilot/tests/functional/test_introspection_features.py'
203--- autopilot/tests/functional/test_introspection_features.py 2013-09-25 02:23:02 +0000
204+++ autopilot/tests/functional/test_introspection_features.py 2013-10-07 03:55:25 +0000
205@@ -24,7 +24,7 @@
206 import subprocess
207 import tempfile
208 from tempfile import mktemp
209-from testtools.matchers import Equals
210+from testtools.matchers import Equals, IsInstance, Not
211 from textwrap import dedent
212
213 from autopilot.matchers import Eventually
214@@ -86,6 +86,39 @@
215
216 self.assertThat(test_widget.visible, Eventually(Equals(True)))
217
218+ def test_selecting_generic_from_custom_is_not_inherited_from_custom(self):
219+ """Selecting a generic proxy object from a custom proxy object must not
220+ return an object derived of the custom object type.
221+
222+ """
223+ class MouseTestWidget(EmulatorBase):
224+ pass
225+
226+ app = self.start_mock_app(EmulatorBase)
227+ mouse_widget = app.select_single(MouseTestWidget)
228+
229+ child_label = mouse_widget.select_many("QLabel")[0]
230+
231+ self.assertThat(child_label, Not(IsInstance(MouseTestWidget)))
232+
233+ def test_selecting_custom_from_generic_is_not_inherited_from_generic(self):
234+ """Selecting a custom proxy object from a generic proxy object must
235+ return an object that is of the custom type.
236+
237+ """
238+ class MouseTestWidget(EmulatorBase):
239+ pass
240+
241+ app = self.start_mock_app(EmulatorBase)
242+ generic_window = app.select_single("QMainWindow")
243+
244+ mouse_widget = generic_window.select_single(MouseTestWidget)
245+
246+ self.assertThat(
247+ mouse_widget,
248+ Not(IsInstance(type(generic_window)))
249+ )
250+
251
252 class QMLCustomEmulatorTestCase(AutopilotTestCase):
253 """Test the introspection of a QML application with a custom emulator."""
254
255=== modified file 'autopilot/tests/unit/test_introspection_features.py'
256--- autopilot/tests/unit/test_introspection_features.py 2013-09-27 05:55:31 +0000
257+++ autopilot/tests/unit/test_introspection_features.py 2013-10-07 03:55:25 +0000
258@@ -18,7 +18,7 @@
259 #
260
261
262-from mock import patch
263+from mock import patch, Mock
264 from testtools import TestCase
265 from testtools.matchers import Equals, NotEquals
266 from testscenarios import TestWithScenarios
267@@ -120,7 +120,8 @@
268 def test_can_access_path_attribute(self):
269 fake_object = DBusIntrospectionObject(
270 dict(id=[0, 123], path=[0, '/some/path']),
271- '/'
272+ '/',
273+ Mock()
274 )
275 with fake_object.no_automatic_refreshing():
276 self.assertThat(fake_object.path, Equals('/some/path'))
277@@ -132,10 +133,14 @@
278 'large' is defined as more than 15.
279
280 """
281- with patch.object(DBusIntrospectionObject, '_Backend') as p:
282- p.introspection_iface.GetState.return_value = \
283- [('/path', {}) for i in range(16)]
284- DBusIntrospectionObject.get_state_by_path('some_query')
285+ fake_object = DBusIntrospectionObject(
286+ dict(id=[0, 123], path=[0, '/some/path']),
287+ '/',
288+ Mock()
289+ )
290+ fake_object._backend.introspection_iface.GetState.return_value = \
291+ [('/path', {}) for i in range(16)]
292+ fake_object.get_state_by_path('some_query')
293
294 mock_logger.warning.assert_called_once_with(
295 "Your query '%s' returned a lot of data (%d items). This "
296@@ -151,9 +156,13 @@
297 'small' is defined as 15 or fewer.
298
299 """
300- with patch.object(DBusIntrospectionObject, '_Backend') as p:
301- p.introspection_iface.GetState.return_value = \
302- [('/path', {}) for i in range(15)]
303- DBusIntrospectionObject.get_state_by_path('some_query')
304+ fake_object = DBusIntrospectionObject(
305+ dict(id=[0, 123], path=[0, '/some/path']),
306+ '/',
307+ Mock()
308+ )
309+ fake_object._backend.introspection_iface.GetState.return_value = \
310+ [('/path', {}) for i in range(15)]
311+ fake_object.get_state_by_path('some_query')
312
313 self.assertThat(mock_logger.warning.called, Equals(False))
314
315=== modified file 'autopilot/tests/unit/test_matchers.py'
316--- autopilot/tests/unit/test_matchers.py 2013-09-16 17:17:35 +0000
317+++ autopilot/tests/unit/test_matchers.py 2013-10-07 03:55:25 +0000
318@@ -66,7 +66,7 @@
319 """
320 class FakeObject(DBusIntrospectionObject):
321 def __init__(self, props):
322- super(FakeObject, self).__init__(props, "/FakeObject")
323+ super(FakeObject, self).__init__(props, "/FakeObject", None)
324 FakeObject._fake_props = props
325
326 @classmethod
327
328=== modified file 'autopilot/tests/unit/test_types.py'
329--- autopilot/tests/unit/test_types.py 2013-09-26 23:34:52 +0000
330+++ autopilot/tests/unit/test_types.py 2013-10-07 03:55:25 +0000
331@@ -20,7 +20,7 @@
332 from __future__ import absolute_import
333
334 from datetime import datetime, time
335-from mock import patch
336+from mock import patch, Mock
337 from testscenarios import TestWithScenarios
338 from testtools import TestCase
339 from testtools.matchers import Equals, IsInstance, NotEquals, raises
340@@ -636,7 +636,8 @@
341 """
342 DBusIntrospectionObject(
343 dict(foo=[0]),
344- '/some/dummy/path'
345+ '/some/dummy/path',
346+ Mock()
347 )
348 error_logger.assert_called_once_with(
349 "While constructing attribute '%s.%s': %s",

Subscribers

People subscribed via source and target branches