Merge lp:~canonical-platform-qa/autopilot/updating-docs-and-argument-usage into lp:autopilot
- updating-docs-and-argument-usage
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~canonical-platform-qa/autopilot/updating-docs-and-argument-usage |
Merge into: | lp:autopilot |
Prerequisite: | lp:~veebers/autopilot/1.6-OR-removal_from-proxy-object |
Diff against target: |
642 lines (+196/-140) 9 files modified
autopilot/application/_launcher.py (+7/-6) autopilot/introspection/_search.py (+16/-43) autopilot/introspection/dbus.py (+14/-14) autopilot/testcase.py (+22/-6) autopilot/tests/functional/test_introspection_features.py (+4/-4) autopilot/tests/unit/test_application_launcher.py (+6/-6) autopilot/tests/unit/test_introspection.py (+1/-27) docs/porting/porting.rst (+95/-0) docs/tutorial/advanced_autopilot.rst (+31/-34) |
To merge this branch: | bzr merge lp:~canonical-platform-qa/autopilot/updating-docs-and-argument-usage |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
platform-qa-bot | continuous-integration | Approve | |
PS Jenkins bot | continuous-integration | Approve | |
prod-platform-qa | continuous-integration | Pending | |
Autopilot Hackers | Pending | ||
Review via email: mp+262562@code.launchpad.net |
Commit message
Documentation (including porting and narrative) update for the new 1.6 CPO behaviour.
Description of the change
Documentation (including porting and narrative) update for the new 1.6 CPO behaviour.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 588. By Christopher Lee
-
Merge pre-req fixes
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:588
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 589. By Christopher Lee
-
Pyflakes fix.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:587
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:589
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 590. By Christopher Lee
-
Merge pre-req. changes.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:590
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Max Brustkern (nuclearbob) wrote : | # |
I have a few comments, but most of it looks good.
- 591. By Christopher Lee
-
Merge pre-req. updates.
- 592. By Christopher Lee
-
Documentation improvement as per MP comments.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:592
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:592
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Unmerged revisions
- 592. By Christopher Lee
-
Documentation improvement as per MP comments.
- 591. By Christopher Lee
-
Merge pre-req. updates.
- 590. By Christopher Lee
-
Merge pre-req. changes.
- 589. By Christopher Lee
-
Pyflakes fix.
- 588. By Christopher Lee
-
Merge pre-req fixes
- 587. By Christopher Lee
-
Merge pre-req changes
- 586. By Christopher Lee
-
Cleanup of the porting doc.
- 585. By Christopher Lee
-
Cleanup use of sphinx generator use in code and tweak porting/tut details.
- 584. By Christopher Lee
-
Remove use of emulator_base incl. methods no longer relevant due to the removal of emulator_base
- 583. By Christopher Lee
-
Update usage (removal) of emulator_base
Preview Diff
1 | === modified file 'autopilot/application/_launcher.py' |
2 | --- autopilot/application/_launcher.py 2014-07-22 02:30:19 +0000 |
3 | +++ autopilot/application/_launcher.py 2015-07-21 03:40:37 +0000 |
4 | @@ -53,16 +53,17 @@ |
5 | introspection enabled. |
6 | |
7 | :keyword case_addDetail: addDetail method to use. |
8 | - :keyword proxy_base: custom proxy base class to use, defaults to None |
9 | + :keyword proxy_class: custom proxy class to use when creating the proxy |
10 | + object, defaults to None (and thus a generated default proxy). |
11 | :keyword dbus_bus: dbus bus to use, if set to something other than the |
12 | - default ('session') the environment will be patched |
13 | + default ('session') the environment will be patched. |
14 | |
15 | """ |
16 | |
17 | - def __init__(self, case_addDetail=None, emulator_base=None, |
18 | + def __init__(self, case_addDetail=None, proxy_class=None, |
19 | dbus_bus='session'): |
20 | super().__init__(case_addDetail) |
21 | - self.proxy_base = emulator_base |
22 | + self.proxy_class = proxy_class |
23 | self.dbus_bus = dbus_bus |
24 | |
25 | def setUp(self): |
26 | @@ -142,7 +143,7 @@ |
27 | pid = self._get_pid_for_launched_app(app_id) |
28 | proxy_object = get_proxy_object_for_existing_process( |
29 | dbus_bus=self.dbus_bus, |
30 | - emulator_base=self.proxy_base, |
31 | + cpo_class=self.proxy_class, |
32 | pid=pid |
33 | ) |
34 | return proxy_object |
35 | @@ -379,7 +380,7 @@ |
36 | app_path, capture_output, launch_dir, arguments) |
37 | proxy_object = get_proxy_object_for_existing_process( |
38 | dbus_bus=self.dbus_bus, |
39 | - emulator_base=self.proxy_base, |
40 | + cpo_class=self.proxy_class, |
41 | process=process, |
42 | pid=process.pid |
43 | ) |
44 | |
45 | === modified file 'autopilot/introspection/_search.py' |
46 | --- autopilot/introspection/_search.py 2015-07-21 03:40:37 +0000 |
47 | +++ autopilot/introspection/_search.py 2015-07-21 03:40:37 +0000 |
48 | @@ -32,7 +32,6 @@ |
49 | from autopilot.exceptions import ProcessSearchError |
50 | from autopilot.introspection import backends |
51 | from autopilot.introspection import constants |
52 | -from autopilot.introspection import dbus as ap_dbus |
53 | from autopilot.introspection import _proxy_objects as _po |
54 | from autopilot.introspection._xpathselect import get_classname_from_path |
55 | from autopilot.introspection.backends import WireProtocolVersionMismatch |
56 | @@ -52,21 +51,7 @@ |
57 | emulator_base, |
58 | dbus_bus='session' |
59 | ): |
60 | - """Return the autopilot proxy object for the given *process*. |
61 | - |
62 | - :raises RuntimeError: if no autopilot interface was found. |
63 | - |
64 | - """ |
65 | - pid = process.pid |
66 | - proxy_obj = get_proxy_object_for_existing_process( |
67 | - pid, |
68 | - process=process, |
69 | - emulator_base=emulator_base, |
70 | - dbus_bus=dbus_bus, |
71 | - ) |
72 | - proxy_obj.set_process(process) |
73 | - |
74 | - return proxy_obj |
75 | + raise NotImplementedError('This method is no longer available.') |
76 | |
77 | |
78 | def get_proxy_object_for_existing_process(**kwargs): |
79 | @@ -77,11 +62,12 @@ |
80 | application matching the search criteria (also supplied in kwargs, see |
81 | further down for explaination on what these can be.) |
82 | Returns a proxy object created using the supplied custom proxy object class |
83 | - **cpo_class** (which defaults to None). |
84 | + **cpo_class** (which defaults to None and thus returns a generated default |
85 | + proxy). |
86 | |
87 | - Note: for backwards compatibility this method will continue to accept |
88 | - emulator_base instead of cpo_class. Currently will issue a warning, in |
89 | - the future this will potentially be an error. |
90 | + .. note:: Prior to 1.6 emulator_base used to be a valid argument. Due to |
91 | + the changes in how CPOs are used and created (i.e. no more object |
92 | + registry) this argument is no longer supported. |
93 | |
94 | This function take kwargs arguments containing search parameter values to |
95 | use when searching for the target application. |
96 | @@ -111,7 +97,7 @@ |
97 | Defaults to 'session' |
98 | :param cpo_class: The custom proxy object class to use when creating the |
99 | resulting proxy object. |
100 | - Defaults to None resulting in a generated default object. |
101 | + Defaults to None resulting in a generated default proxy object. |
102 | |
103 | **Exceptions possibly thrown by this function:** |
104 | |
105 | @@ -159,15 +145,12 @@ |
106 | dbus_bus = _get_dbus_bus_from_string(kwargs.pop('dbus_bus', 'session')) |
107 | process = kwargs.pop('process', None) |
108 | cpo_class = kwargs.pop('cpo_class', None) |
109 | - emulator_base = kwargs.pop('emulator_base', None) |
110 | - if emulator_base is not None: |
111 | - logger.warning( |
112 | - 'The use of emulator_base is deprecated. Please use ' |
113 | - 'cpo_class instead.' |
114 | + if kwargs.pop('emulator_base', None) is not None: |
115 | + logger.error( |
116 | + 'The use of emulator_base is deprecated as of version 1.6. ' |
117 | + 'Perhaps cpo_class is what you want instead.' |
118 | ) |
119 | |
120 | - cpo_class = cpo_class if cpo_class else emulator_base |
121 | - |
122 | # Force default object_path |
123 | kwargs['object_path'] = kwargs.get('object_path', constants.AUTOPILOT_PATH) |
124 | # Special handling of pid. |
125 | @@ -448,7 +431,7 @@ |
126 | |
127 | |
128 | def _make_proxy_object_async( |
129 | - data_source, emulator_base, reply_handler, error_handler): |
130 | + data_source, proxy_class, reply_handler, error_handler): |
131 | """Make a proxy object for a dbus backend. |
132 | |
133 | Similar to :meth:`_make_proxy_object` except this method runs |
134 | @@ -471,20 +454,20 @@ |
135 | introspection_xml |
136 | ) |
137 | |
138 | - cpo_base = _po._get_applicable_class_name(emulator_base, path) |
139 | + cpo_base = _po._get_applicable_class_name(proxy_class, path) |
140 | |
141 | if not _po._is_cpo_class(cpo_base): |
142 | - proxy_class = _po._get_default_proxy_class( |
143 | + actual_proxy_class = _po._get_default_proxy_class( |
144 | cpo_base, |
145 | extension_classes |
146 | ) |
147 | else: |
148 | # It's actually a CPO class. |
149 | _po._apply_mixin_bases(cpo_base, extension_classes) |
150 | - proxy_class = cpo_base |
151 | + actual_proxy_class = cpo_base |
152 | |
153 | reply_handler( |
154 | - proxy_class(cls_state, path, backends.Backend(data_source)) |
155 | + actual_proxy_class(cls_state, path, backends.Backend(data_source)) |
156 | ) |
157 | |
158 | # Phase 2: We recieve the introspection string, and make an asynchronous |
159 | @@ -538,16 +521,6 @@ |
160 | subprocess.call(["kill", "%d" % self._process.pid]) |
161 | |
162 | |
163 | -def _extend_proxy_bases_with_emulator_base(proxy_bases, emulator_base): |
164 | - if emulator_base is None: |
165 | - emulator_base = type( |
166 | - 'DefaultEmulatorBase', |
167 | - (ap_dbus.CustomEmulatorBase,), |
168 | - {} |
169 | - ) |
170 | - return proxy_bases + (emulator_base, ) |
171 | - |
172 | - |
173 | def _get_dbus_address_object(connection_name, object_path, bus): |
174 | return backends.DBusAddress(bus, connection_name, object_path) |
175 | |
176 | |
177 | === modified file 'autopilot/introspection/dbus.py' |
178 | --- autopilot/introspection/dbus.py 2015-07-21 03:40:37 +0000 |
179 | +++ autopilot/introspection/dbus.py 2015-07-21 03:40:37 +0000 |
180 | @@ -180,8 +180,8 @@ |
181 | necessarily be immediate children), use :meth:`select_single` and |
182 | :meth:`select_many`. |
183 | |
184 | - Note. Any proxy objects return from this call will be generated |
185 | - defaults and will not use any Custom Proxy Object classes. |
186 | + .. note:: Any proxy objects return from this call will be generated |
187 | + defaults and will not use any Custom Proxy Object classes. |
188 | |
189 | """ |
190 | # Thomi: 2014-03-20: There used to be a call to 'self.refresh_state()' |
191 | @@ -196,8 +196,8 @@ |
192 | If this object has no parent (i.e.- it is the root of the introspection |
193 | tree). Then it returns itself. |
194 | |
195 | - Note. Any proxy object return from this call will be generated defaults |
196 | - and will not use any Custom Proxy Object classes. |
197 | + .. note:: Any proxy object return from this call will be generated |
198 | + defaults and will not use any Custom Proxy Object classes. |
199 | |
200 | """ |
201 | new_query = self._query.select_parent() |
202 | @@ -573,10 +573,10 @@ |
203 | |
204 | You can then define a Custom Proxy Object for this type like so:: |
205 | |
206 | - class RedRect(DBusIntrospectionObject): |
207 | - @classmethod |
208 | - def get_type_query_name(cls): |
209 | - return 'QQuickRectangle' |
210 | + class RedRect(DBusIntrospectionObject): |
211 | + @classmethod |
212 | + def get_type_query_name(cls): |
213 | + return 'QQuickRectangle' |
214 | |
215 | This is due to the qml engine storing 'RedRect' as a QQuickRectangle in |
216 | the UI tree and the xpathquery query needs a node type to query for. |
217 | @@ -597,12 +597,12 @@ |
218 | For instance a query can return a default generated proxy object and |
219 | then use this method to create a specific CPO from it. |
220 | |
221 | - Note:: |
222 | - There is no validation done on the acceptability of the passed |
223 | - proxy object to be instantiated as the calling class. |
224 | - (e.g. validate_dbus_object is not involved.) |
225 | - It is up to the caller to ensure that the passed proxy object is |
226 | - suitable. |
227 | + .. note:: There is no validation done on the acceptability of the |
228 | + passed proxy object to be instantiated as the calling class. |
229 | + (e.g. validate_dbus_object is not involved.) |
230 | + It is up to the caller to ensure that the passed proxy object is |
231 | + suitable or that any potential exceptions raised in the custom |
232 | + methods are caught accordingly. |
233 | |
234 | """ |
235 | _, state_dict = proxy_object._get_new_state() |
236 | |
237 | === modified file 'autopilot/testcase.py' |
238 | --- autopilot/testcase.py 2015-07-15 23:31:12 +0000 |
239 | +++ autopilot/testcase.py 2015-07-21 03:40:37 +0000 |
240 | @@ -267,8 +267,13 @@ |
241 | :keyword capture_output: If set to True (the default), the process |
242 | output will be captured and attached to the test as test detail. |
243 | |
244 | - :keyword emulator_base: If set, specifies the base class to be used for |
245 | - all emulators for this loaded application. |
246 | + Note. prior to 1.6 'emulator_base' used to be a valid argument. This is |
247 | + no longer the case as proxy classes work differently now. Proxy classes |
248 | + are created explicitly using the provided ``proxy_class``. If no class |
249 | + is passed than a generated default is used (a very basic proxy object). |
250 | + |
251 | + :keyword proxy_class: If set, specifies the custom proxy class to be |
252 | + created and returned for this application. |
253 | |
254 | :return: A proxy object that represents the application. Introspection |
255 | data is retrievable via this object. |
256 | @@ -307,8 +312,13 @@ |
257 | :param app_uris: Parameters used to launch the click package. This |
258 | parameter will be left empty if not used. |
259 | |
260 | - :keyword emulator_base: If set, specifies the base class to be used for |
261 | - all emulators for this loaded application. |
262 | + Note. prior to 1.6 'emulator_base' used to be a valid argument. This is |
263 | + no longer the case as proxy classes work differently now. Proxy classes |
264 | + are created explicitly using the provided ``proxy_class``. If no class |
265 | + is passed than a generated default is used (a very basic proxy object). |
266 | + |
267 | + :keyword proxy_class: If set, specifies the custom proxy class to be |
268 | + created and returned for this application. |
269 | |
270 | :raises RuntimeError: If the specified package_id cannot be found in |
271 | the click package manifest. |
272 | @@ -338,8 +348,14 @@ |
273 | app_proxy = self.launch_upstart_application("gallery-app") |
274 | |
275 | :param application_name: The name of the application to launch. |
276 | - :keyword emulator_base: If set, specifies the base class to be used for |
277 | - all emulators for this loaded application. |
278 | + |
279 | + Note. prior to 1.6 'emulator_base' used to be a valid argument. This is |
280 | + no longer the case as proxy classes work differently now. Proxy classes |
281 | + are created explicitly using the provided ``proxy_class``. If no class |
282 | + is passed than a generated default is used (a very basic proxy object). |
283 | + |
284 | + :keyword proxy_class: If set, specifies the custom proxy class to be |
285 | + created and returned for this application. |
286 | |
287 | :raises RuntimeError: If the specified application cannot be launched. |
288 | """ |
289 | |
290 | === modified file 'autopilot/tests/functional/test_introspection_features.py' |
291 | --- autopilot/tests/functional/test_introspection_features.py 2015-07-21 03:40:37 +0000 |
292 | +++ autopilot/tests/functional/test_introspection_features.py 2015-07-21 03:40:37 +0000 |
293 | @@ -60,7 +60,7 @@ |
294 | class IntrospectionFeatureTests(AutopilotTestCase): |
295 | """Test various features of the introspection code.""" |
296 | |
297 | - def start_mock_app(self, emulator_base): |
298 | + def start_mock_app(self, proxy_class): |
299 | window_spec_file = mktemp(suffix='.json') |
300 | window_spec = {"Contents": "MouseTest"} |
301 | json.dump( |
302 | @@ -73,7 +73,7 @@ |
303 | 'window-mocker', |
304 | window_spec_file, |
305 | app_type='qt', |
306 | - emulator_base=emulator_base, |
307 | + proxy_class=proxy_class, |
308 | ) |
309 | |
310 | def test_can_get_custom_proxy_for_app_root(self): |
311 | @@ -366,8 +366,8 @@ |
312 | return self.launch_test_application( |
313 | "/usr/lib/" + arch + "/qt5/bin/qmlscene", |
314 | qml_path, |
315 | - extra_args, |
316 | - emulator_base=EmulatorBase) |
317 | + extra_args |
318 | + ) |
319 | |
320 | def test_custom_emulator(self): |
321 | app = self.launch_test_qml() |
322 | |
323 | === modified file 'autopilot/tests/unit/test_application_launcher.py' |
324 | --- autopilot/tests/unit/test_application_launcher.py 2014-07-22 02:30:19 +0000 |
325 | +++ autopilot/tests/unit/test_application_launcher.py 2015-07-21 03:40:37 +0000 |
326 | @@ -74,21 +74,21 @@ |
327 | def test_init_uses_default_values(self): |
328 | launcher = ApplicationLauncher() |
329 | self.assertEqual(launcher.caseAddDetail, launcher.addDetail) |
330 | - self.assertEqual(launcher.proxy_base, None) |
331 | + self.assertEqual(launcher.proxy_class, None) |
332 | self.assertEqual(launcher.dbus_bus, 'session') |
333 | |
334 | def test_init_uses_passed_values(self): |
335 | case_addDetail = self.getUniqueString() |
336 | - emulator_base = self.getUniqueString() |
337 | + proxy_class = self.getUniqueString() |
338 | dbus_bus = self.getUniqueString() |
339 | |
340 | launcher = ApplicationLauncher( |
341 | case_addDetail=case_addDetail, |
342 | - emulator_base=emulator_base, |
343 | + proxy_class=proxy_class, |
344 | dbus_bus=dbus_bus, |
345 | ) |
346 | self.assertEqual(launcher.caseAddDetail, case_addDetail) |
347 | - self.assertEqual(launcher.proxy_base, emulator_base) |
348 | + self.assertEqual(launcher.proxy_class, proxy_class) |
349 | self.assertEqual(launcher.dbus_bus, dbus_bus) |
350 | |
351 | @patch('autopilot.application._launcher.fixtures.EnvironmentVariable') |
352 | @@ -203,7 +203,7 @@ |
353 | launcher.launch('') |
354 | gpofep.assert_called_once_with(process=lap.return_value, |
355 | pid=lap.return_value.pid, |
356 | - emulator_base=None, |
357 | + cpo_class=None, |
358 | dbus_bus='session') |
359 | |
360 | @patch('autopilot.application._launcher.' |
361 | @@ -688,7 +688,7 @@ |
362 | with patch.object(launcher, '_get_glib_loop'): |
363 | launcher.launch('') |
364 | gpofep.assert_called_once_with(pid=gp.return_value, |
365 | - emulator_base=None, |
366 | + cpo_class=None, |
367 | dbus_bus='session') |
368 | |
369 | @patch('autopilot.application._launcher.' |
370 | |
371 | === modified file 'autopilot/tests/unit/test_introspection.py' |
372 | --- autopilot/tests/unit/test_introspection.py 2015-07-21 03:40:37 +0000 |
373 | +++ autopilot/tests/unit/test_introspection.py 2015-07-21 03:40:37 +0000 |
374 | @@ -18,19 +18,14 @@ |
375 | # |
376 | |
377 | from dbus import String |
378 | -from unittest.mock import Mock |
379 | from testtools import TestCase |
380 | from testtools.matchers import ( |
381 | Equals, |
382 | IsInstance, |
383 | raises, |
384 | ) |
385 | -from autopilot.introspection import ( |
386 | - _proxy_objects as _po, |
387 | - _search as _s |
388 | -) |
389 | +from autopilot.introspection import _proxy_objects as _po |
390 | from autopilot.introspection.qt import QtObjectProxyMixin |
391 | -import autopilot.introspection as _i |
392 | |
393 | |
394 | class GetDetailsFromStateDataTests(TestCase): |
395 | @@ -141,24 +136,3 @@ |
396 | ), |
397 | Equals((QtObjectProxyMixin,)) |
398 | ) |
399 | - |
400 | - |
401 | -class ExtendProxyBasesWithEmulatorBaseTests(TestCase): |
402 | - |
403 | - def test_default_emulator_base_name(self): |
404 | - bases = _s._extend_proxy_bases_with_emulator_base(tuple(), None) |
405 | - self.assertThat(len(bases), Equals(1)) |
406 | - self.assertThat(bases[0].__name__, Equals("DefaultEmulatorBase")) |
407 | - self.assertThat(bases[0].__bases__[0], Equals(_i.CustomEmulatorBase)) |
408 | - |
409 | - def test_appends_custom_emulator_base(self): |
410 | - existing_bases = ('token',) |
411 | - custom_emulator_base = Mock() |
412 | - new_bases = _s._extend_proxy_bases_with_emulator_base( |
413 | - existing_bases, |
414 | - custom_emulator_base |
415 | - ) |
416 | - self.assertThat( |
417 | - new_bases, |
418 | - Equals(existing_bases + (custom_emulator_base,)) |
419 | - ) |
420 | |
421 | === modified file 'docs/porting/porting.rst' |
422 | --- docs/porting/porting.rst 2015-01-22 16:35:48 +0000 |
423 | +++ docs/porting/porting.rst 2015-07-21 03:40:37 +0000 |
424 | @@ -14,6 +14,101 @@ |
425 | |
426 | Autopilot versions earlier than 1.2 were not publicly announced, and were only used within Canonical. For that reason, this document assumes that version 1.2 is the lowest version of autopilot present `"in the wild"`. |
427 | |
428 | +Porting to Autopilot v1.6.x |
429 | +=========================== |
430 | + |
431 | +The 1.6 release contains a number of modifications that change the behaviour of |
432 | +:ref:`custom_proxy_classes` Custom Proxy Objects and how they are applied to |
433 | +results from the query methods (select_single, get_children etc. see |
434 | +:class:`~autopilot.introspection.ProxyBase` for further query method details). |
435 | + |
436 | +Custom Proxy Objects and Class |
437 | +++++++++++++++++++++++++++++++ |
438 | + |
439 | +In the past there was a private registry of CPO classes that was queried and an |
440 | +object instantiated from a class that existed there when creating a proxy |
441 | +object. The problem with this approach is that it relied on the python import |
442 | +mechanism to trigger when this registry was populated. This caused issues where |
443 | +the contents of the registry weren't deterministic and running a test in a |
444 | +different order could result in a different CPO class being used to create a |
445 | +proxy object (causing a test failure.) |
446 | + |
447 | +Now the use and creation of proxy objects using a Custom Proxy Object class is |
448 | +explicit. The CPO that you pass into the query method is the object that you |
449 | +get back (assuming the query succeeded). Passing a string (the default is '*') |
450 | +will result in a generated default which is an instantiated object of |
451 | +:class:`~autopilot.introspection.ProxyBase`. |
452 | + |
453 | +Default proxy objects |
454 | ++++++++++++++++++++++ |
455 | + |
456 | +In previous versions an application was launched with an ``emulator_base`` |
457 | +which ensured that any proxy objects created without a specific CPO were |
458 | +created with at least the ``emulator_base`` as a base class. This is no longer |
459 | +the case and and 'generated default' proxys created are created from |
460 | +:class:`~autopilot.introspection.ProxyBase`. |
461 | + |
462 | +So if you :meth:`~autopilot.introspection.ProxyBase.select_single` |
463 | +passing no type name or CPO (i.e. defaulting to '*') then you will get back a |
464 | +default generated proxy object with no extra methods, only those defined in |
465 | +:class:`~autopilot.introspection.ProxyBase`. |
466 | + |
467 | +It's the same if you select by passing a type name string into the method, even |
468 | +if you have defined a CPO of the same name and it is imported in this file, you |
469 | +will only get back a generated default (there is no more black magic, it's |
470 | +always explicit. It's either the CPO that is passed in or a generated default). |
471 | + |
472 | +Creating instances of Custom Proxy Classes |
473 | +++++++++++++++++++++++++++++++++++++++++++ |
474 | + |
475 | +If you need to get an instantiated CPO you need to either pass the class to the |
476 | +select method:: |
477 | + |
478 | + # This will return an object of MyCPOClass |
479 | + app.select_single(MyCPOClass, objectName='example') |
480 | + |
481 | +or you can create an object using the static/class method |
482 | +:meth:`~autopilot.introspection.ProxyBase.from_proxy_object`:: |
483 | + |
484 | + # proxy will be a default generated proxy (e.g. no special methods) whereas |
485 | + # cpo_proxy is an instance of MyCPOClass. |
486 | + proxy = app.select_single(objectName='example') |
487 | + cpo_proxy = MyCPOClass.from_proxy_object(proxy) |
488 | + |
489 | +Please see :meth:`~autopilot.introspection.ProxyBase.from_proxy_object` for |
490 | +further details. |
491 | + |
492 | +Removal of emulator_base |
493 | +++++++++++++++++++++++++ |
494 | + |
495 | +There is no longer a need to define your own base class to start using CPOs. |
496 | +When launching an application the ``emulator_base`` argument is no longer |
497 | +required or used (using it will log an error message). If you have a custom |
498 | +class you would like to use for the application proxy use ``proxy_class`` to |
499 | +instantiate a proxy object using that. |
500 | + |
501 | +Also note that because there is no longer an emulator_base or object |
502 | +registry. This means that the generated default proxy objects will not inherit |
503 | +from anything that you the author have defined, they will be just |
504 | +:class:`~autopilot.introspection.ProxyBase`. |
505 | + |
506 | +Clarification of generated vs. custom proxy in logs |
507 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
508 | + |
509 | +In the past it was possible that a test would fail due to an incorrect CPO |
510 | +being loaded into the registry and used. Even though there is no more registry |
511 | +there has been logging added to help determine if a helper is returning/using a |
512 | +generic or custom proxy. |
513 | + |
514 | +Check the logs for error messages, for attribute errors you will see either:: |
515 | + |
516 | + 'Generated Class <class> does not have attribute: . . .' |
517 | + |
518 | +if it is a generated default class, For custom proxy classes you will see:: |
519 | + |
520 | + 'Class <class> does not have attribute: . . .' |
521 | + |
522 | + |
523 | Porting to Autopilot v1.4.x |
524 | =========================== |
525 | |
526 | |
527 | === modified file 'docs/tutorial/advanced_autopilot.rst' |
528 | --- docs/tutorial/advanced_autopilot.rst 2015-05-07 21:46:35 +0000 |
529 | +++ docs/tutorial/advanced_autopilot.rst 2015-07-21 03:40:37 +0000 |
530 | @@ -521,22 +521,36 @@ |
531 | |
532 | However, sometimes you want to customize the class used to create these objects. The most common reason to want to do this is to provide methods that make it easier to inspect or interact with these objects. Autopilot allows test authors to provide their own custom classes, through a couple of simple steps: |
533 | |
534 | -1. First, you must define your own base class, to be used by all custom proxy objects in your test suite. This base class can be empty, but must derive from :class:`~autopilot.introspection.ProxyBase`. An example class might look like this:: |
535 | - |
536 | - from autopilot.introspection import ProxyBase |
537 | - |
538 | - |
539 | - class CustomProxyObjectBase(ProxyBase): |
540 | - """A base class for all custom proxy objects within this test suite.""" |
541 | - |
542 | +.. note:: Prior to autopilot version 1.6 you were required to define your own |
543 | + base class that would be the basis of all your subsequent custom |
544 | + proxy classes, this is no longer needed. |
545 | + |
546 | +.. note:: The previous behaviour was that an application was launched with an |
547 | + 'emulator_base' (i.e. your own base class) that was applied to any |
548 | + non-custom proxy objects created. This is no longer the case. The use |
549 | + of emualtor_base has been removed and all non-custom proxys returned |
550 | + are generic. |
551 | + |
552 | + |
553 | +1. You must define your class that derives from :class:`~autopilot.introspection.ProxyBase`. |
554 | For Ubuntu applications using Ubuntu UI Toolkit objects, you should derive your custom proxy object from UbuntuUIToolkitCustomProxyObjectBase. This base class is also derived from :class:`~autopilot.introspection.ProxyBase` and is used for all Ubuntu UI Toolkit custom proxy objects. So if you are introspecting objects from Ubuntu UI Toolkit then this is the base class to use. |
555 | |
556 | -2. Define the classes you want autopilot to use, instead of the default. The simplest method is to give the class the same name as the type you wish to override. For example, if you want to define your own custom class to be used every time autopilot generates an instance of a 'QLabel' object, the class definition would look like this:: |
557 | - |
558 | - class QLabel(CustomProxyObjectBase): |
559 | +.. note:: The Ubuntu UI Toolkit provides a large number of very useful custom proxy classes and helpers for the toolkit. It is recommended that you check there to see if there is a CPO suitable for your needs before writing your own. |
560 | + |
561 | +2. Define the classes you want autopilot to use, instead of the default. The simplest method is to give the class the same name as the type you wish to override. For example, if you want to define your own custom class to be used when selecting an instance of a 'QLabel' object, the class definition would look like this:: |
562 | + |
563 | + class QLabel(ProxyBase): |
564 | |
565 | # Add custom methods here... |
566 | |
567 | +As of version 1.6 it is possible to name your custom class something different to the underlying type name. To do this you need to override the get_type_query_name method to return a string to use in the generated query. For example this is a class that backs onto a QLabel type:: |
568 | + |
569 | + class MyCoolLabel(ProxyBase): |
570 | + def get_type_query_name(): |
571 | + # This will result in a query looking for QLabels but create a |
572 | + # MyCoolLabel object. |
573 | + return 'QLabel' |
574 | + |
575 | If you wish to implement more specific selection criteria, your class can override the validate_dbus_object method, which takes as arguments the dbus path and state. For example:: |
576 | |
577 | class SpecificQLabel(CustomProxyObjectBase): |
578 | @@ -545,7 +559,7 @@ |
579 | return (path.endswith('object_we_want') or |
580 | state['some_property'] == 'desired_value') |
581 | |
582 | -This method should return True if the object matches this custom proxy class, and False otherwise. If more than one custom proxy class matches an object, a :exc:`ValueError` will be raised at runtime. |
583 | +This method should return True if the object matches this custom proxy class, and False otherwise. |
584 | |
585 | An example using Ubuntu UI Toolkit which would be used to swipe up a PageWithBottomEdge object to reveal it's bottom edge menu could look like this:: |
586 | |
587 | @@ -559,29 +573,12 @@ |
588 | """Swipe up from the bottom edge of the Page |
589 | to reveal it's bottom edge menu.""" |
590 | |
591 | -3. Pass the custom proxy base class as an argument to the launch_test_application method on your test class. This base class should be the same base class that is used to write all of your custom proxy objects:: |
592 | - |
593 | - from autopilot.testcase import AutopilotTestCase |
594 | - |
595 | - class TestCase(AutopilotTestCase): |
596 | - |
597 | - def setUp(self): |
598 | - super().setUp() |
599 | - self.app = self.launch_test_application( |
600 | - '/path/to/the/application', |
601 | - emulator_base=CustomProxyObjectBase) |
602 | - |
603 | -For applications using objects from Ubuntu UI Toolkit, the emulator_base parameter should be:: |
604 | - |
605 | - emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase |
606 | - |
607 | - |
608 | -4. You can pass the custom proxy class to methods like :meth:`~autopilot.introspection.ProxyBase.select_single` instead of a string. So, for example, the following is a valid way of selecting the QLabel instances in an application:: |
609 | +3. You can pass the custom proxy class to methods like :meth:`~autopilot.introspection.ProxyBase.select_single` instead of a string. So, for example, the following is a valid way of selecting the QLabel instances in an application:: |
610 | |
611 | # Get all QLabels in the applicaton: |
612 | labels = self.app.select_many(QLabel) |
613 | |
614 | -If you are introspecting an application that already has a custom proxy base class defined, then this class can simply be imported and passed to the appropriate application launcher method. See :ref:`launching applications <launching_applications>` for more details on launching an application for introspection. This will allow you to call all of the public methods of the application's proxy base class directly in your test. |
615 | +When launching an application you can pass ``proxy_class`` to create an instance of that custom proxy object for the launched application. |
616 | |
617 | This example will run on desktop and uses the webbrowser application to navigate to a url using the base class go_to_url() method:: |
618 | |
619 | @@ -593,7 +590,7 @@ |
620 | def test_go_to_url(self): |
621 | app = self.launch_test_application( |
622 | 'webbrowser-app', |
623 | - emulator_base=browser.Webbrowser) |
624 | + cpo_class=browser.Webbrowser) |
625 | # main_window is a property of the Webbrowser class |
626 | app.main_window.go_to_url('http://www.ubuntu.com') |
627 | |
628 | @@ -641,12 +638,12 @@ |
629 | launcher = self.useFixture(ClickApplicationLauncher()) |
630 | app_proxy = launcher.launch('com.ubuntu.calculator') |
631 | |
632 | -Additional options can also be specified to set a custom addDetail method, a custom proxy base, or a custom dbus bus with which to patch the environment:: |
633 | +Additional options can also be specified to set a custom addDetail method, use a custom proxy class, or a custom dbus bus with which to patch the environment:: |
634 | |
635 | launcher = self.useFixture(NormalApplicationLauncher( |
636 | case_addDetail=self.addDetail, |
637 | dbus_bus='some_other_bus', |
638 | - proxy_base=my_proxy_class, |
639 | + proxy_class=my_proxy_class, |
640 | )) |
641 | |
642 | .. note:: You must pass the test case's 'addDetail' method to these application launch fixtures if you want application logs to be attached to the test result. This is due to the way fixtures are cleaned up, and is unavoidable. |
FAILED: Continuous integration, rev:587 jenkins. qa.ubuntu. com/job/ autopilot- ci/1090/ jenkins. qa.ubuntu. com/job/ autopilot- wily-amd64- ci/21/console jenkins. qa.ubuntu. com/job/ autopilot- wily-armhf- ci/20/console jenkins. qa.ubuntu. com/job/ autopilot- wily-i386- ci/21/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/autopilot- ci/1090/ rebuild
http://