Merge lp:~veebers/autopilot/fixing_backend_being_none into lp:autopilot/1.3
- fixing_backend_being_none
- Merge into 1.3
Status: | Merged |
---|---|
Approved by: | Ćukasz Zemczak |
Approved revision: | 343 |
Merged at revision: | 343 |
Proposed branch: | lp:~veebers/autopilot/fixing_backend_being_none |
Merge into: | lp:autopilot/1.3 |
Diff against target: |
271 lines (+47/-59) 5 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) |
To merge this branch: | bzr merge lp:~veebers/autopilot/fixing_backend_being_none |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Thomi Richards (community) | Approve | ||
Review via email: mp+188762@code.launchpad.net |
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.
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:343
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Andy Doan (doanac) wrote : | # |
I've run the smoke image testing suite on my mako at home with this branch. I'm not sure if this is a problem or not. When run under our CI logic, I get a unity8 failure:
18:29:58.118 INFO globals:50 - Starting test unity8.
18:29:58.119 WARNING testcase:118 - No tracing available - install the python-
18:29:58.155 WARNING testcase:155 - Process manager backend unavailable, application snapshot support disabled.
18:29:58.227 INFO __init__:156 - Launching process: ['/usr/
18:29:58.272 ERROR testresult:35 - ERROR: unity8.
18:29:58.274 ERROR testresult:35 - traceback: {{{
Traceback (most recent call last):
File "/home/
super(TestDispl
File "/home/
self.launch_
File "/home/
"-geometry", geometry, app_type='qt')
File "/home/
process = launch_
File "/home/
cwd=cwd
File "/home/
**kwargs
File "/usr/lib/
errread, errwrite)
File "/usr/lib/
raise child_exception
TypeError: execv() arg 2 must contain only strings
}}}
However, phablet-test-run is working. So i think its just something environmental at my place.
Andy Doan (doanac) wrote : | # |
I can reproduce the error now. It happens when running the test by itself. ie:
phablet-test-run -n -v unity8.
Thomi Richards (thomir-deactivatedaccount) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'autopilot/introspection/__init__.py' | |||
2 | --- autopilot/introspection/__init__.py 2013-09-26 05:31:16 +0000 | |||
3 | +++ autopilot/introspection/__init__.py 2013-10-02 04:15:28 +0000 | |||
4 | @@ -42,7 +42,6 @@ | |||
5 | 42 | AP_INTROSPECTION_IFACE, | 42 | AP_INTROSPECTION_IFACE, |
6 | 43 | ) | 43 | ) |
7 | 44 | from autopilot.introspection.dbus import ( | 44 | from autopilot.introspection.dbus import ( |
8 | 45 | _clear_backends_for_proxy_object, | ||
9 | 46 | CustomEmulatorBase, | 45 | CustomEmulatorBase, |
10 | 47 | DBusIntrospectionObject, | 46 | DBusIntrospectionObject, |
11 | 48 | get_classname_from_path, | 47 | get_classname_from_path, |
12 | @@ -474,16 +473,17 @@ | |||
13 | 474 | proxy_bases = proxy_bases + (emulator_base, ) | 473 | proxy_bases = proxy_bases + (emulator_base, ) |
14 | 475 | cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source) | 474 | cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source) |
15 | 476 | 475 | ||
21 | 477 | _clear_backends_for_proxy_object(emulator_base) | 476 | # Merge the object hierarchy. |
22 | 478 | clsobj = type( | 477 | clsobj = type(str("%sBase" % cls_name), proxy_bases, {}) |
18 | 479 | # Merge the object hierarchy. | ||
19 | 480 | str("%sBase" % cls_name), proxy_bases, dict(_Backend=data_source) | ||
20 | 481 | ) | ||
23 | 482 | 478 | ||
24 | 483 | proxy_class = type(str(cls_name), (clsobj,), {}) | 479 | proxy_class = type(str(cls_name), (clsobj,), {}) |
25 | 484 | 480 | ||
28 | 485 | proxy = proxy_class.get_root_instance() | 481 | try: |
29 | 486 | return proxy | 482 | dbus_tuple = data_source.introspection_iface.GetState("/")[0] |
30 | 483 | path, state = dbus_tuple | ||
31 | 484 | return proxy_class(state, path, data_source) | ||
32 | 485 | except IndexError: | ||
33 | 486 | raise RuntimeError("Unable to find root object of %r" % proxy_class) | ||
34 | 487 | 487 | ||
35 | 488 | 488 | ||
36 | 489 | def _get_proxy_object_base_classes(backend): | 489 | def _get_proxy_object_base_classes(backend): |
37 | @@ -518,8 +518,8 @@ | |||
38 | 518 | class ApplicationProxyObject(DBusIntrospectionObject): | 518 | class ApplicationProxyObject(DBusIntrospectionObject): |
39 | 519 | """A class that better supports query data from an application.""" | 519 | """A class that better supports query data from an application.""" |
40 | 520 | 520 | ||
43 | 521 | def __init__(self, state, path): | 521 | def __init__(self, state, path, backend): |
44 | 522 | super(ApplicationProxyObject, self).__init__(state, path) | 522 | super(ApplicationProxyObject, self).__init__(state, path, backend) |
45 | 523 | self._process = None | 523 | self._process = None |
46 | 524 | 524 | ||
47 | 525 | def set_process(self, process): | 525 | def set_process(self, process): |
48 | 526 | 526 | ||
49 | === modified file 'autopilot/introspection/dbus.py' | |||
50 | --- autopilot/introspection/dbus.py 2013-09-27 05:48:12 +0000 | |||
51 | +++ autopilot/introspection/dbus.py 2013-10-02 04:15:28 +0000 | |||
52 | @@ -111,20 +111,6 @@ | |||
53 | 111 | return class_object | 111 | return class_object |
54 | 112 | 112 | ||
55 | 113 | 113 | ||
56 | 114 | def _clear_backends_for_proxy_object(proxy_object): | ||
57 | 115 | """Iterate over the object registry and clear the dbus backend address set | ||
58 | 116 | on any class that has the same id as the specified proxy_object. | ||
59 | 117 | |||
60 | 118 | This is required so consecutive tests do not end up re-using the same dbus | ||
61 | 119 | backend address as a previous run, which will probably be incorrect. | ||
62 | 120 | |||
63 | 121 | """ | ||
64 | 122 | global _object_registry | ||
65 | 123 | for cls in _object_registry[proxy_object._id].itervalues(): | ||
66 | 124 | if type(proxy_object) is not cls: | ||
67 | 125 | cls._Backend = None | ||
68 | 126 | |||
69 | 127 | |||
70 | 128 | def translate_state_keys(state_dict): | 114 | def translate_state_keys(state_dict): |
71 | 129 | """Translates the *state_dict* passed in so the keys are usable as python | 115 | """Translates the *state_dict* passed in so the keys are usable as python |
72 | 130 | attributes.""" | 116 | attributes.""" |
73 | @@ -161,13 +147,12 @@ | |||
74 | 161 | 147 | ||
75 | 162 | __metaclass__ = IntrospectableObjectMetaclass | 148 | __metaclass__ = IntrospectableObjectMetaclass |
76 | 163 | 149 | ||
80 | 164 | _Backend = None | 150 | def __init__(self, state_dict, path, backend): |
78 | 165 | |||
79 | 166 | def __init__(self, state_dict, path): | ||
81 | 167 | self.__state = {} | 151 | self.__state = {} |
82 | 168 | self.__refresh_on_attribute = True | 152 | self.__refresh_on_attribute = True |
83 | 169 | self._set_properties(state_dict) | 153 | self._set_properties(state_dict) |
84 | 170 | self._path = path | 154 | self._path = path |
85 | 155 | self._backend = backend | ||
86 | 171 | 156 | ||
87 | 172 | def _set_properties(self, state_dict): | 157 | def _set_properties(self, state_dict): |
88 | 173 | """Creates and set attributes of *self* based on contents of | 158 | """Creates and set attributes of *self* based on contents of |
89 | @@ -462,8 +447,7 @@ | |||
90 | 462 | _, new_state = self.get_new_state() | 447 | _, new_state = self.get_new_state() |
91 | 463 | self._set_properties(new_state) | 448 | self._set_properties(new_state) |
92 | 464 | 449 | ||
95 | 465 | @classmethod | 450 | def get_all_instances(self): |
94 | 466 | def get_all_instances(cls): | ||
96 | 467 | """Get all instances of this class that exist within the Application | 451 | """Get all instances of this class that exist within the Application |
97 | 468 | state tree. | 452 | state tree. |
98 | 469 | 453 | ||
99 | @@ -481,23 +465,22 @@ | |||
100 | 481 | :return: List (possibly empty) of class instances. | 465 | :return: List (possibly empty) of class instances. |
101 | 482 | 466 | ||
102 | 483 | """ | 467 | """ |
106 | 484 | cls_name = cls.__name__ | 468 | cls_name = type(self).__name__ |
107 | 485 | instances = cls.get_state_by_path("//%s" % (cls_name)) | 469 | instances = self.get_state_by_path("//%s" % (cls_name)) |
108 | 486 | return [cls.make_introspection_object(i) for i in instances] | 470 | return [self.make_introspection_object(i) for i in instances] |
109 | 487 | 471 | ||
112 | 488 | @classmethod | 472 | def get_root_instance(self): |
111 | 489 | def get_root_instance(cls): | ||
113 | 490 | """Get the object at the root of this tree. | 473 | """Get the object at the root of this tree. |
114 | 491 | 474 | ||
115 | 492 | This will return an object that represents the root of the | 475 | This will return an object that represents the root of the |
116 | 493 | introspection tree. | 476 | introspection tree. |
117 | 494 | 477 | ||
118 | 495 | """ | 478 | """ |
120 | 496 | instances = cls.get_state_by_path("/") | 479 | instances = self.get_state_by_path("/") |
121 | 497 | if len(instances) != 1: | 480 | if len(instances) != 1: |
122 | 498 | logger.error("Could not retrieve root object.") | 481 | logger.error("Could not retrieve root object.") |
123 | 499 | return None | 482 | return None |
125 | 500 | return cls.make_introspection_object(instances[0]) | 483 | return self.make_introspection_object(instances[0]) |
126 | 501 | 484 | ||
127 | 502 | def __getattr__(self, name): | 485 | def __getattr__(self, name): |
128 | 503 | # avoid recursion if for some reason we have no state set (should never | 486 | # avoid recursion if for some reason we have no state set (should never |
129 | @@ -514,8 +497,7 @@ | |||
130 | 514 | "Class '%s' has no attribute '%s'." % | 497 | "Class '%s' has no attribute '%s'." % |
131 | 515 | (self.__class__.__name__, name)) | 498 | (self.__class__.__name__, name)) |
132 | 516 | 499 | ||
135 | 517 | @classmethod | 500 | def get_state_by_path(self, piece): |
134 | 518 | def get_state_by_path(cls, piece): | ||
136 | 519 | """Get state for a particular piece of the state tree. | 501 | """Get state for a particular piece of the state tree. |
137 | 520 | 502 | ||
138 | 521 | You should probably never need to call this directly. | 503 | You should probably never need to call this directly. |
139 | @@ -530,7 +512,7 @@ | |||
140 | 530 | "XPath query must be a string, not %r", type(piece)) | 512 | "XPath query must be a string, not %r", type(piece)) |
141 | 531 | 513 | ||
142 | 532 | with Timer("GetState %s" % piece): | 514 | with Timer("GetState %s" % piece): |
144 | 533 | data = cls._Backend.introspection_iface.GetState(piece) | 515 | data = self._backend.introspection_iface.GetState(piece) |
145 | 534 | if len(data) > 15: | 516 | if len(data) > 15: |
146 | 535 | logger.warning( | 517 | logger.warning( |
147 | 536 | "Your query '%s' returned a lot of data (%d items). This " | 518 | "Your query '%s' returned a lot of data (%d items). This " |
148 | @@ -562,8 +544,7 @@ | |||
149 | 562 | else: | 544 | else: |
150 | 563 | return self._path + "[id=%d]" % self.id | 545 | return self._path + "[id=%d]" % self.id |
151 | 564 | 546 | ||
154 | 565 | @classmethod | 547 | def make_introspection_object(self, dbus_tuple): |
153 | 566 | def make_introspection_object(cls, dbus_tuple): | ||
155 | 567 | """Make an introspection object given a DBus tuple of | 548 | """Make an introspection object given a DBus tuple of |
156 | 568 | (path, state_dict). | 549 | (path, state_dict). |
157 | 569 | 550 | ||
158 | @@ -572,23 +553,21 @@ | |||
159 | 572 | path, state = dbus_tuple | 553 | path, state = dbus_tuple |
160 | 573 | name = get_classname_from_path(path) | 554 | name = get_classname_from_path(path) |
161 | 574 | try: | 555 | try: |
165 | 575 | class_type = _object_registry[cls._id][name] | 556 | class_type = _object_registry[self._id][name] |
163 | 576 | if class_type._Backend is None: | ||
164 | 577 | class_type._Backend = cls._Backend | ||
166 | 578 | except KeyError: | 557 | except KeyError: |
167 | 579 | get_debug_logger().warning( | 558 | get_debug_logger().warning( |
168 | 580 | "Generating introspection instance for type '%s' based on " | 559 | "Generating introspection instance for type '%s' based on " |
169 | 581 | "generic class.", name) | 560 | "generic class.", name) |
170 | 582 | # we want the object to inherit from the custom emulator base, not | 561 | # we want the object to inherit from the custom emulator base, not |
171 | 583 | # the object class that is doing the selecting | 562 | # the object class that is doing the selecting |
173 | 584 | for base in cls.__bases__: | 563 | for base in type(self).__bases__: |
174 | 585 | if issubclass(base, CustomEmulatorBase): | 564 | if issubclass(base, CustomEmulatorBase): |
175 | 586 | base_class = base | 565 | base_class = base |
176 | 587 | break | 566 | break |
177 | 588 | else: | 567 | else: |
179 | 589 | base_class = cls | 568 | base_class = type(self) |
180 | 590 | class_type = type(str(name), (base_class,), {}) | 569 | class_type = type(str(name), (base_class,), {}) |
182 | 591 | return class_type(state, path) | 570 | return class_type(state, path, self._backend) |
183 | 592 | 571 | ||
184 | 593 | @contextmanager | 572 | @contextmanager |
185 | 594 | def no_automatic_refreshing(self): | 573 | def no_automatic_refreshing(self): |
186 | 595 | 574 | ||
187 | === modified file 'autopilot/introspection/qt.py' | |||
188 | --- autopilot/introspection/qt.py 2013-09-13 14:53:52 +0000 | |||
189 | +++ autopilot/introspection/qt.py 2013-10-02 04:15:28 +0000 | |||
190 | @@ -101,7 +101,7 @@ | |||
191 | 101 | 101 | ||
192 | 102 | """ | 102 | """ |
193 | 103 | 103 | ||
195 | 104 | return self._Backend.qt_introspection_iface | 104 | return self._backend.qt_introspection_iface |
196 | 105 | 105 | ||
197 | 106 | @property | 106 | @property |
198 | 107 | def slots(self): | 107 | def slots(self): |
199 | 108 | 108 | ||
200 | === modified file 'autopilot/tests/unit/test_introspection_features.py' | |||
201 | --- autopilot/tests/unit/test_introspection_features.py 2013-09-27 05:48:12 +0000 | |||
202 | +++ autopilot/tests/unit/test_introspection_features.py 2013-10-02 04:15:28 +0000 | |||
203 | @@ -18,7 +18,7 @@ | |||
204 | 18 | # | 18 | # |
205 | 19 | 19 | ||
206 | 20 | 20 | ||
208 | 21 | from mock import patch | 21 | from mock import patch, Mock |
209 | 22 | from testtools import TestCase | 22 | from testtools import TestCase |
210 | 23 | from testtools.matchers import Equals, NotEquals | 23 | from testtools.matchers import Equals, NotEquals |
211 | 24 | from testscenarios import TestWithScenarios | 24 | from testscenarios import TestWithScenarios |
212 | @@ -89,7 +89,8 @@ | |||
213 | 89 | def test_can_access_path_attribute(self): | 89 | def test_can_access_path_attribute(self): |
214 | 90 | fake_object = DBusIntrospectionObject( | 90 | fake_object = DBusIntrospectionObject( |
215 | 91 | dict(id=123, path='/some/path'), | 91 | dict(id=123, path='/some/path'), |
217 | 92 | '/' | 92 | '/', |
218 | 93 | None | ||
219 | 93 | ) | 94 | ) |
220 | 94 | with fake_object.no_automatic_refreshing(): | 95 | with fake_object.no_automatic_refreshing(): |
221 | 95 | self.assertThat(fake_object.path, Equals('/some/path')) | 96 | self.assertThat(fake_object.path, Equals('/some/path')) |
222 | @@ -101,10 +102,14 @@ | |||
223 | 101 | 'large' is defined as more than 15. | 102 | 'large' is defined as more than 15. |
224 | 102 | 103 | ||
225 | 103 | """ | 104 | """ |
230 | 104 | with patch.object(DBusIntrospectionObject, '_Backend') as p: | 105 | fake_object = DBusIntrospectionObject( |
231 | 105 | p.introspection_iface.GetState.return_value = \ | 106 | dict(id=123, path='/some/path'), |
232 | 106 | [('/path', {}) for i in range(16)] | 107 | '/', |
233 | 107 | DBusIntrospectionObject.get_state_by_path('some_query') | 108 | Mock() |
234 | 109 | ) | ||
235 | 110 | fake_object._backend.introspection_iface.GetState.return_value = \ | ||
236 | 111 | [('/path', {}) for i in range(16)] | ||
237 | 112 | fake_object.get_state_by_path('some_query') | ||
238 | 108 | 113 | ||
239 | 109 | mock_logger.warning.assert_called_once_with( | 114 | mock_logger.warning.assert_called_once_with( |
240 | 110 | "Your query '%s' returned a lot of data (%d items). This " | 115 | "Your query '%s' returned a lot of data (%d items). This " |
241 | @@ -120,9 +125,13 @@ | |||
242 | 120 | 'small' is defined as 15 or fewer. | 125 | 'small' is defined as 15 or fewer. |
243 | 121 | 126 | ||
244 | 122 | """ | 127 | """ |
249 | 123 | with patch.object(DBusIntrospectionObject, '_Backend') as p: | 128 | fake_object = DBusIntrospectionObject( |
250 | 124 | p.introspection_iface.GetState.return_value = \ | 129 | dict(id=123, path='/some/path'), |
251 | 125 | [('/path', {}) for i in range(15)] | 130 | '/', |
252 | 126 | DBusIntrospectionObject.get_state_by_path('some_query') | 131 | Mock() |
253 | 132 | ) | ||
254 | 133 | fake_object._backend.introspection_iface.GetState.return_value = \ | ||
255 | 134 | [('/path', {}) for i in range(15)] | ||
256 | 135 | fake_object.get_state_by_path('some_query') | ||
257 | 127 | 136 | ||
258 | 128 | self.assertThat(mock_logger.warning.called, Equals(False)) | 137 | self.assertThat(mock_logger.warning.called, Equals(False)) |
259 | 129 | 138 | ||
260 | === modified file 'autopilot/tests/unit/test_matchers.py' | |||
261 | --- autopilot/tests/unit/test_matchers.py 2013-08-07 03:18:32 +0000 | |||
262 | +++ autopilot/tests/unit/test_matchers.py 2013-10-02 04:15:28 +0000 | |||
263 | @@ -47,7 +47,7 @@ | |||
264 | 47 | """ | 47 | """ |
265 | 48 | class FakeObject(DBusIntrospectionObject): | 48 | class FakeObject(DBusIntrospectionObject): |
266 | 49 | def __init__(self, props): | 49 | def __init__(self, props): |
268 | 50 | super(FakeObject, self).__init__(props, "/FakeObject") | 50 | super(FakeObject, self).__init__(props, "/FakeObject", None) |
269 | 51 | FakeObject._fake_props = props | 51 | FakeObject._fake_props = props |
270 | 52 | 52 | ||
271 | 53 | @classmethod | 53 | @classmethod |
Looks good to me, except that changing class methods into regular (instance) methods is likely to break compatibility - probably not with Touch apps, but more liekly with unity7 or the desktop apps. Let's make sure this is not the case before merging this.