Merge lp:~canonical-platform-qa/autopilot/updating-docs-and-argument-usage into lp:autopilot

Proposed by Christopher Lee
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
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.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
588. By Christopher Lee

Merge pre-req fixes

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
589. By Christopher Lee

Pyflakes fix.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
590. By Christopher Lee

Merge pre-req. changes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
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.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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.

Subscribers

People subscribed via source and target branches