Merge lp:~veebers/autopilot/1.4_fixing_backend_being_none into lp:autopilot
- 1.4_fixing_backend_being_none
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Christopher Lee |
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Thomi Richards (community) | Approve | ||
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:/
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:348
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Thomi Richards (thomir-deactivatedaccount) wrote : Posted in a previous version of this proposal | # |
LGTM
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
LGTM.
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:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 349. By Christopher Lee
-
Added porting notes.
- 350. By Christopher Lee
-
Fixed the documentation
- 351. By Christopher Lee
-
Merge trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:350
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:351
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
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 | 43 | AP_INTROSPECTION_IFACE, | 43 | AP_INTROSPECTION_IFACE, |
6 | 44 | ) | 44 | ) |
7 | 45 | from autopilot.introspection.dbus import ( | 45 | from autopilot.introspection.dbus import ( |
8 | 46 | _clear_backends_for_proxy_object, | ||
9 | 47 | CustomEmulatorBase, | 46 | CustomEmulatorBase, |
10 | 48 | DBusIntrospectionObject, | 47 | DBusIntrospectionObject, |
11 | 49 | get_classname_from_path, | 48 | get_classname_from_path, |
12 | @@ -485,16 +484,17 @@ | |||
13 | 485 | proxy_bases = proxy_bases + (emulator_base, ) | 484 | proxy_bases = proxy_bases + (emulator_base, ) |
14 | 486 | cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source) | 485 | cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source) |
15 | 487 | 486 | ||
21 | 488 | _clear_backends_for_proxy_object(emulator_base) | 487 | # Merge the object hierarchy. |
22 | 489 | clsobj = type( | 488 | clsobj = type(str("%sBase" % cls_name), proxy_bases, {}) |
18 | 490 | # Merge the object hierarchy. | ||
19 | 491 | str("%sBase" % cls_name), proxy_bases, dict(_Backend=data_source) | ||
20 | 492 | ) | ||
23 | 493 | 489 | ||
24 | 494 | proxy_class = type(str(cls_name), (clsobj,), {}) | 490 | proxy_class = type(str(cls_name), (clsobj,), {}) |
25 | 495 | 491 | ||
28 | 496 | proxy = proxy_class.get_root_instance() | 492 | try: |
29 | 497 | return proxy | 493 | dbus_tuple = data_source.introspection_iface.GetState("/")[0] |
30 | 494 | path, state = dbus_tuple | ||
31 | 495 | return proxy_class(state, path, data_source) | ||
32 | 496 | except IndexError: | ||
33 | 497 | raise RuntimeError("Unable to find root object of %r" % proxy_class) | ||
34 | 498 | 498 | ||
35 | 499 | 499 | ||
36 | 500 | def _get_proxy_object_base_classes(backend): | 500 | def _get_proxy_object_base_classes(backend): |
37 | @@ -529,8 +529,8 @@ | |||
38 | 529 | class ApplicationProxyObject(DBusIntrospectionObject): | 529 | class ApplicationProxyObject(DBusIntrospectionObject): |
39 | 530 | """A class that better supports query data from an application.""" | 530 | """A class that better supports query data from an application.""" |
40 | 531 | 531 | ||
43 | 532 | def __init__(self, state, path): | 532 | def __init__(self, state, path, backend): |
44 | 533 | super(ApplicationProxyObject, self).__init__(state, path) | 533 | super(ApplicationProxyObject, self).__init__(state, path, backend) |
45 | 534 | self._process = None | 534 | self._process = None |
46 | 535 | 535 | ||
47 | 536 | def set_process(self, process): | 536 | def set_process(self, process): |
48 | 537 | 537 | ||
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 | 126 | return class_object | 126 | return class_object |
54 | 127 | 127 | ||
55 | 128 | 128 | ||
56 | 129 | def _clear_backends_for_proxy_object(proxy_object): | ||
57 | 130 | """Iterate over the object registry and clear the dbus backend address set | ||
58 | 131 | on any class that has the same id as the specified proxy_object. | ||
59 | 132 | |||
60 | 133 | This is required so consecutive tests do not end up re-using the same dbus | ||
61 | 134 | backend address as a previous run, which will probably be incorrect. | ||
62 | 135 | |||
63 | 136 | """ | ||
64 | 137 | global _object_registry | ||
65 | 138 | for cls in _object_registry[proxy_object._id].values(): | ||
66 | 139 | if type(proxy_object) is not cls: | ||
67 | 140 | cls._Backend = None | ||
68 | 141 | |||
69 | 142 | |||
70 | 143 | def get_classname_from_path(object_path): | 129 | def get_classname_from_path(object_path): |
71 | 144 | return object_path.split("/")[-1] | 130 | return object_path.split("/")[-1] |
72 | 145 | 131 | ||
73 | @@ -174,14 +160,13 @@ | |||
74 | 174 | 160 | ||
75 | 175 | """ | 161 | """ |
76 | 176 | 162 | ||
80 | 177 | _Backend = None | 163 | def __init__(self, state_dict, path, backend): |
78 | 178 | |||
79 | 179 | def __init__(self, state_dict, path): | ||
81 | 180 | self.__state = {} | 164 | self.__state = {} |
82 | 181 | self.__refresh_on_attribute = True | 165 | self.__refresh_on_attribute = True |
83 | 182 | self._set_properties(state_dict) | 166 | self._set_properties(state_dict) |
84 | 183 | self._path = path | 167 | self._path = path |
85 | 184 | self._poll_time = 10 | 168 | self._poll_time = 10 |
86 | 169 | self._backend = backend | ||
87 | 185 | 170 | ||
88 | 186 | def _set_properties(self, state_dict): | 171 | def _set_properties(self, state_dict): |
89 | 187 | """Creates and set attributes of *self* based on contents of | 172 | """Creates and set attributes of *self* based on contents of |
90 | @@ -484,8 +469,7 @@ | |||
91 | 484 | _, new_state = self.get_new_state() | 469 | _, new_state = self.get_new_state() |
92 | 485 | self._set_properties(new_state) | 470 | self._set_properties(new_state) |
93 | 486 | 471 | ||
96 | 487 | @classmethod | 472 | def get_all_instances(self): |
95 | 488 | def get_all_instances(cls): | ||
97 | 489 | """Get all instances of this class that exist within the Application | 473 | """Get all instances of this class that exist within the Application |
98 | 490 | state tree. | 474 | state tree. |
99 | 491 | 475 | ||
100 | @@ -503,23 +487,22 @@ | |||
101 | 503 | :return: List (possibly empty) of class instances. | 487 | :return: List (possibly empty) of class instances. |
102 | 504 | 488 | ||
103 | 505 | """ | 489 | """ |
107 | 506 | cls_name = cls.__name__ | 490 | cls_name = type(self).__name__ |
108 | 507 | instances = cls.get_state_by_path("//%s" % (cls_name)) | 491 | instances = self.get_state_by_path("//%s" % (cls_name)) |
109 | 508 | return [cls.make_introspection_object(i) for i in instances] | 492 | return [self.make_introspection_object(i) for i in instances] |
110 | 509 | 493 | ||
113 | 510 | @classmethod | 494 | def get_root_instance(self): |
112 | 511 | def get_root_instance(cls): | ||
114 | 512 | """Get the object at the root of this tree. | 495 | """Get the object at the root of this tree. |
115 | 513 | 496 | ||
116 | 514 | This will return an object that represents the root of the | 497 | This will return an object that represents the root of the |
117 | 515 | introspection tree. | 498 | introspection tree. |
118 | 516 | 499 | ||
119 | 517 | """ | 500 | """ |
121 | 518 | instances = cls.get_state_by_path("/") | 501 | instances = self.get_state_by_path("/") |
122 | 519 | if len(instances) != 1: | 502 | if len(instances) != 1: |
123 | 520 | logger.error("Could not retrieve root object.") | 503 | logger.error("Could not retrieve root object.") |
124 | 521 | return None | 504 | return None |
126 | 522 | return cls.make_introspection_object(instances[0]) | 505 | return self.make_introspection_object(instances[0]) |
127 | 523 | 506 | ||
128 | 524 | def __getattr__(self, name): | 507 | def __getattr__(self, name): |
129 | 525 | # avoid recursion if for some reason we have no state set (should never | 508 | # avoid recursion if for some reason we have no state set (should never |
130 | @@ -536,8 +519,7 @@ | |||
131 | 536 | "Class '%s' has no attribute '%s'." % | 519 | "Class '%s' has no attribute '%s'." % |
132 | 537 | (self.__class__.__name__, name)) | 520 | (self.__class__.__name__, name)) |
133 | 538 | 521 | ||
136 | 539 | @classmethod | 522 | def get_state_by_path(self, piece): |
135 | 540 | def get_state_by_path(cls, piece): | ||
137 | 541 | """Get state for a particular piece of the state tree. | 523 | """Get state for a particular piece of the state tree. |
138 | 542 | 524 | ||
139 | 543 | You should probably never need to call this directly. | 525 | You should probably never need to call this directly. |
140 | @@ -552,7 +534,7 @@ | |||
141 | 552 | "XPath query must be a string, not %r", type(piece)) | 534 | "XPath query must be a string, not %r", type(piece)) |
142 | 553 | 535 | ||
143 | 554 | with Timer("GetState %s" % piece): | 536 | with Timer("GetState %s" % piece): |
145 | 555 | data = cls._Backend.introspection_iface.GetState(piece) | 537 | data = self._backend.introspection_iface.GetState(piece) |
146 | 556 | if len(data) > 15: | 538 | if len(data) > 15: |
147 | 557 | logger.warning( | 539 | logger.warning( |
148 | 558 | "Your query '%s' returned a lot of data (%d items). This " | 540 | "Your query '%s' returned a lot of data (%d items). This " |
149 | @@ -584,8 +566,7 @@ | |||
150 | 584 | else: | 566 | else: |
151 | 585 | return self._path + "[id=%d]" % self.id | 567 | return self._path + "[id=%d]" % self.id |
152 | 586 | 568 | ||
155 | 587 | @classmethod | 569 | def make_introspection_object(self, dbus_tuple): |
154 | 588 | def make_introspection_object(cls, dbus_tuple): | ||
156 | 589 | """Make an introspection object given a DBus tuple of | 570 | """Make an introspection object given a DBus tuple of |
157 | 590 | (path, state_dict). | 571 | (path, state_dict). |
158 | 591 | 572 | ||
159 | @@ -594,23 +575,21 @@ | |||
160 | 594 | path, state = dbus_tuple | 575 | path, state = dbus_tuple |
161 | 595 | name = get_classname_from_path(path) | 576 | name = get_classname_from_path(path) |
162 | 596 | try: | 577 | try: |
166 | 597 | class_type = _object_registry[cls._id][name] | 578 | class_type = _object_registry[self._id][name] |
164 | 598 | if class_type._Backend is None: | ||
165 | 599 | class_type._Backend = cls._Backend | ||
167 | 600 | except KeyError: | 579 | except KeyError: |
168 | 601 | get_debug_logger().warning( | 580 | get_debug_logger().warning( |
169 | 602 | "Generating introspection instance for type '%s' based on " | 581 | "Generating introspection instance for type '%s' based on " |
170 | 603 | "generic class.", name) | 582 | "generic class.", name) |
171 | 604 | # we want the object to inherit from the custom emulator base, not | 583 | # we want the object to inherit from the custom emulator base, not |
172 | 605 | # the object class that is doing the selecting | 584 | # the object class that is doing the selecting |
174 | 606 | for base in cls.__bases__: | 585 | for base in type(self).__bases__: |
175 | 607 | if issubclass(base, CustomEmulatorBase): | 586 | if issubclass(base, CustomEmulatorBase): |
176 | 608 | base_class = base | 587 | base_class = base |
177 | 609 | break | 588 | break |
178 | 610 | else: | 589 | else: |
180 | 611 | base_class = cls | 590 | base_class = type(self) |
181 | 612 | class_type = type(str(name), (base_class,), {}) | 591 | class_type = type(str(name), (base_class,), {}) |
183 | 613 | return class_type(state, path) | 592 | return class_type(state, path, self._backend) |
184 | 614 | 593 | ||
185 | 615 | @contextmanager | 594 | @contextmanager |
186 | 616 | def no_automatic_refreshing(self): | 595 | def no_automatic_refreshing(self): |
187 | 617 | 596 | ||
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 | 100 | 100 | ||
193 | 101 | """ | 101 | """ |
194 | 102 | 102 | ||
196 | 103 | return self._Backend.qt_introspection_iface | 103 | return self._backend.qt_introspection_iface |
197 | 104 | 104 | ||
198 | 105 | @property | 105 | @property |
199 | 106 | def slots(self): | 106 | def slots(self): |
200 | 107 | 107 | ||
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 | 18 | # | 18 | # |
206 | 19 | 19 | ||
207 | 20 | 20 | ||
209 | 21 | from mock import patch | 21 | from mock import patch, Mock |
210 | 22 | from testtools import TestCase | 22 | from testtools import TestCase |
211 | 23 | from testtools.matchers import Equals, NotEquals | 23 | from testtools.matchers import Equals, NotEquals |
212 | 24 | from testscenarios import TestWithScenarios | 24 | from testscenarios import TestWithScenarios |
213 | @@ -120,7 +120,8 @@ | |||
214 | 120 | def test_can_access_path_attribute(self): | 120 | def test_can_access_path_attribute(self): |
215 | 121 | fake_object = DBusIntrospectionObject( | 121 | fake_object = DBusIntrospectionObject( |
216 | 122 | dict(id=[0, 123], path=[0, '/some/path']), | 122 | dict(id=[0, 123], path=[0, '/some/path']), |
218 | 123 | '/' | 123 | '/', |
219 | 124 | Mock() | ||
220 | 124 | ) | 125 | ) |
221 | 125 | with fake_object.no_automatic_refreshing(): | 126 | with fake_object.no_automatic_refreshing(): |
222 | 126 | self.assertThat(fake_object.path, Equals('/some/path')) | 127 | self.assertThat(fake_object.path, Equals('/some/path')) |
223 | @@ -132,10 +133,14 @@ | |||
224 | 132 | 'large' is defined as more than 15. | 133 | 'large' is defined as more than 15. |
225 | 133 | 134 | ||
226 | 134 | """ | 135 | """ |
231 | 135 | with patch.object(DBusIntrospectionObject, '_Backend') as p: | 136 | fake_object = DBusIntrospectionObject( |
232 | 136 | p.introspection_iface.GetState.return_value = \ | 137 | dict(id=[0, 123], path=[0, '/some/path']), |
233 | 137 | [('/path', {}) for i in range(16)] | 138 | '/', |
234 | 138 | DBusIntrospectionObject.get_state_by_path('some_query') | 139 | Mock() |
235 | 140 | ) | ||
236 | 141 | fake_object._backend.introspection_iface.GetState.return_value = \ | ||
237 | 142 | [('/path', {}) for i in range(16)] | ||
238 | 143 | fake_object.get_state_by_path('some_query') | ||
239 | 139 | 144 | ||
240 | 140 | mock_logger.warning.assert_called_once_with( | 145 | mock_logger.warning.assert_called_once_with( |
241 | 141 | "Your query '%s' returned a lot of data (%d items). This " | 146 | "Your query '%s' returned a lot of data (%d items). This " |
242 | @@ -151,9 +156,13 @@ | |||
243 | 151 | 'small' is defined as 15 or fewer. | 156 | 'small' is defined as 15 or fewer. |
244 | 152 | 157 | ||
245 | 153 | """ | 158 | """ |
250 | 154 | with patch.object(DBusIntrospectionObject, '_Backend') as p: | 159 | fake_object = DBusIntrospectionObject( |
251 | 155 | p.introspection_iface.GetState.return_value = \ | 160 | dict(id=[0, 123], path=[0, '/some/path']), |
252 | 156 | [('/path', {}) for i in range(15)] | 161 | '/', |
253 | 157 | DBusIntrospectionObject.get_state_by_path('some_query') | 162 | Mock() |
254 | 163 | ) | ||
255 | 164 | fake_object._backend.introspection_iface.GetState.return_value = \ | ||
256 | 165 | [('/path', {}) for i in range(15)] | ||
257 | 166 | fake_object.get_state_by_path('some_query') | ||
258 | 158 | 167 | ||
259 | 159 | self.assertThat(mock_logger.warning.called, Equals(False)) | 168 | self.assertThat(mock_logger.warning.called, Equals(False)) |
260 | 160 | 169 | ||
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 | 66 | """ | 66 | """ |
266 | 67 | class FakeObject(DBusIntrospectionObject): | 67 | class FakeObject(DBusIntrospectionObject): |
267 | 68 | def __init__(self, props): | 68 | def __init__(self, props): |
269 | 69 | super(FakeObject, self).__init__(props, "/FakeObject") | 69 | super(FakeObject, self).__init__(props, "/FakeObject", None) |
270 | 70 | FakeObject._fake_props = props | 70 | FakeObject._fake_props = props |
271 | 71 | 71 | ||
272 | 72 | @classmethod | 72 | @classmethod |
273 | 73 | 73 | ||
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 | 20 | from __future__ import absolute_import | 20 | from __future__ import absolute_import |
279 | 21 | 21 | ||
280 | 22 | from datetime import datetime, time | 22 | from datetime import datetime, time |
282 | 23 | from mock import patch | 23 | from mock import patch, Mock |
283 | 24 | from testscenarios import TestWithScenarios | 24 | from testscenarios import TestWithScenarios |
284 | 25 | from testtools import TestCase | 25 | from testtools import TestCase |
285 | 26 | from testtools.matchers import Equals, IsInstance, NotEquals, raises | 26 | from testtools.matchers import Equals, IsInstance, NotEquals, raises |
286 | @@ -636,7 +636,8 @@ | |||
287 | 636 | """ | 636 | """ |
288 | 637 | DBusIntrospectionObject( | 637 | DBusIntrospectionObject( |
289 | 638 | dict(foo=[0]), | 638 | dict(foo=[0]), |
291 | 639 | '/some/dummy/path' | 639 | '/some/dummy/path', |
292 | 640 | Mock() | ||
293 | 640 | ) | 641 | ) |
294 | 641 | error_logger.assert_called_once_with( | 642 | error_logger.assert_called_once_with( |
295 | 642 | "While constructing attribute '%s.%s': %s", | 643 | "While constructing attribute '%s.%s': %s", |
296 | 643 | 644 | ||
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 | 51 | 51 | ||
302 | 52 | my_obj = self.app.wait_select_single("MyObject") | 52 | my_obj = self.app.wait_select_single("MyObject") |
303 | 53 | 53 | ||
304 | 54 | .. _dbus_backends: | ||
305 | 55 | |||
306 | 56 | DBus backends and :class:`~autopilot.introspection.dbus.DBusIntrospectionObject` changes | ||
307 | 57 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
308 | 58 | |||
309 | 59 | Due to a change in how | ||
310 | 60 | :class:`~autopilot.introspection.dbus.DBusIntrospectionObject` objects store | ||
311 | 61 | their DBus backend a couple of classmethods have now become instance methods. | ||
312 | 62 | |||
313 | 63 | These affected methods are: | ||
314 | 64 | |||
315 | 65 | * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_all_instances` | ||
316 | 66 | * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_root_instance` | ||
317 | 67 | * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_state_by_path` | ||
318 | 68 | |||
319 | 69 | For example, if your old code is something along the lines of:: | ||
320 | 70 | |||
321 | 71 | all_keys = KeyCustomEmulator.get_all_instances() | ||
322 | 72 | |||
323 | 73 | You will instead need to have something like this instead:: | ||
324 | 74 | |||
325 | 75 | all_keys = app_proxy.select_many(KeyCustomEmulator) | ||
326 | 76 | |||
327 | 77 | |||
328 | 54 | Python 3 | 78 | Python 3 |
329 | 55 | ++++++++ | 79 | ++++++++ |
330 | 56 | 80 | ||
331 | @@ -70,6 +94,10 @@ | |||
332 | 70 | * 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. | 94 | * 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 | 71 | 95 | ||
334 | 72 | 96 | ||
335 | 97 | .. note:: There is an API breakage in autopilot 1.3. The changes outlined under | ||
336 | 98 | the heading ":ref:`dbus_backends`" apply to version | ||
337 | 99 | 1.3.1+13.10.20131003.1-0ubuntu1 and onwards . | ||
338 | 100 | |||
339 | 73 | ``QtIntrospectionTestMixin`` and ``GtkIntrospectionTestMixin`` no longer exist | 101 | ``QtIntrospectionTestMixin`` and ``GtkIntrospectionTestMixin`` no longer exist |
340 | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 102 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
341 | 75 | 103 |
PASSED: Continuous integration, rev:347 10.97.0. 26:8080/ job/autopilot- ci/262/ 10.97.0. 26:8080/ job/autopilot- saucy-amd64- ci/190 10.97.0. 26:8080/ job/autopilot- saucy-armhf- ci/190 10.97.0. 26:8080/ job/autopilot- saucy-i386- ci/34
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: 10.97.0. 26:8080/ job/autopilot- ci/262/ rebuild
http://