Merge lp:~veebers/autopilot/remove_object_registry-CPO_creation_explicit into lp:autopilot

Proposed by Christopher Lee
Status: Superseded
Proposed branch: lp:~veebers/autopilot/remove_object_registry-CPO_creation_explicit
Merge into: lp:autopilot
Diff against target: 1472 lines (+568/-464)
13 files modified
autopilot/introspection/_object_registry.py (+0/-110)
autopilot/introspection/_proxy_objects.py (+211/-0)
autopilot/introspection/_search.py (+34/-151)
autopilot/introspection/backends.py (+11/-5)
autopilot/introspection/dbus.py (+77/-13)
autopilot/tests/functional/test_dbus_query.py (+6/-0)
autopilot/tests/functional/test_input_stack.py (+1/-1)
autopilot/tests/functional/test_introspection_features.py (+56/-40)
autopilot/tests/unit/test_introspection.py (+11/-8)
autopilot/tests/unit/test_introspection_backends.py (+9/-7)
autopilot/tests/unit/test_introspection_dbus.py (+68/-36)
autopilot/tests/unit/test_introspection_search.py (+0/-93)
autopilot/tests/unit/test_proxy_object.py (+84/-0)
To merge this branch: bzr merge lp:~veebers/autopilot/remove_object_registry-CPO_creation_explicit
Reviewer Review Type Date Requested Status
Autopilot Hackers Pending
Review via email: mp+261809@code.launchpad.net

This proposal has been superseded by a proposal from 2015-06-16.

Commit message

Remove the idea of (and actual) the object registry

Description of the change

Remove the idea of (and actual) the object registry. CPO usage will now be explicit and not magic.
The CPO passed to the select_* methods (and get_children...) will instantiate the passed in proxy object class.

To post a comment you must log in.
567. By Christopher Lee

Initial changes for async proxy creation (autopilot vis) not stable.

568. By Christopher Lee

Fix pyflake errors

569. By Christopher Lee

Merge and fix conflicts from push branch

570. By Christopher Lee

Another pyflake fix

571. By Christopher Lee

Remove question comment, has been answered

572. By Christopher Lee

Test for error message (error message has been improved.

573. By Christopher Lee

Further tests and fixes for an issue found

574. By Christopher Lee

Pyflake fixes.

575. By Christopher Lee

Make vis work

576. By Christopher Lee

Cleanup for vis make_proxy

577. By Christopher Lee

Slight cleanup and removal of unneeded method

578. By Christopher Lee

Remove comment and clear up question from comment.

579. By Christopher Lee

Clean up docstring

580. By Christopher Lee

Fix tests due to updated code.

581. By Christopher Lee

Initial renaming from emulator_base to cpo_class

582. By Christopher Lee

Remove conversion notes

583. By Christopher Lee

Merge pre-req. changes

584. By Christopher Lee

Remove changes that accidently got in

585. By Christopher Lee

Remove un-used method

586. By Christopher Lee

Remove question comment and update docstring.

587. By Christopher Lee

Fix whitespace issue.

588. By Christopher Lee

Remove un-used test class.

589. By Christopher Lee

Merge pre-req. updates.

Unmerged revisions

589. By Christopher Lee

Merge pre-req. updates.

588. By Christopher Lee

Remove un-used test class.

587. By Christopher Lee

Fix whitespace issue.

586. By Christopher Lee

Remove question comment and update docstring.

585. By Christopher Lee

Remove un-used method

584. By Christopher Lee

Remove changes that accidently got in

583. By Christopher Lee

Merge pre-req. changes

582. By Christopher Lee

Remove conversion notes

581. By Christopher Lee

Initial renaming from emulator_base to cpo_class

580. By Christopher Lee

Fix tests due to updated code.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopilot/introspection/_object_registry.py'
2--- autopilot/introspection/_object_registry.py 2015-05-06 10:00:49 +0000
3+++ autopilot/introspection/_object_registry.py 2015-06-15 09:43:19 +0000
4@@ -42,10 +42,6 @@
5
6 """
7
8-from uuid import uuid4
9-
10-from autopilot.introspection._xpathselect import get_classname_from_path
11-from autopilot.utilities import get_debug_logger
12 from contextlib import contextmanager
13
14 _object_registry = {}
15@@ -62,92 +58,6 @@
16 return _proxy_extensions.get(id, ())
17
18
19-class IntrospectableObjectMetaclass(type):
20- """Metaclass to insert appropriate classes into the object registry."""
21-
22- def __new__(cls, classname, bases, classdict):
23- """Create a new proxy class, possibly adding it to the object registry.
24-
25- Test authors may derive a class from DBusIntrospectionObject or the
26- CustomEmulatorBase alias and use this as their 'emulator base'. This
27- class will be given a unique '_id' attribute. That attribute is the
28- first level index into the object registry. It's used so we can have
29- custom proxy classes for more than one process at the same time and
30- avoid clashes in the dictionary.
31- """
32- cls_id = None
33-
34- for base in bases:
35- if hasattr(base, '_id'):
36- cls_id = base._id
37- break
38- else:
39- # Ignore classes that are in the autopilot class heirarchy:
40- if classname not in (
41- 'ApplicationProxyObject',
42- 'CustomEmulatorBase',
43- 'DBusIntrospectionObject',
44- 'DBusIntrospectionObjectBase',
45- ):
46- # Add the '_id' attribute as a class attr:
47- cls_id = classdict['_id'] = uuid4()
48-
49- # use the bases passed to us, but extend it with whatever is stored in
50- # the proxy_extensions dictionary.
51- extensions = _get_proxy_bases_for_id(cls_id)
52- for extension in extensions:
53- if extension not in bases:
54- bases += (extension,)
55- # make the object. Nothing special here.
56- class_object = type.__new__(cls, classname, bases, classdict)
57-
58- if not classdict.get('__generated', False):
59- # If the newly made object has an id, add it to the object
60- # registry.
61- if getattr(class_object, '_id', None) is not None:
62- if class_object._id in _object_registry:
63- _object_registry[class_object._id][classname] = \
64- class_object
65- else:
66- _object_registry[class_object._id] = \
67- {classname: class_object}
68- # in all cases, return the class unchanged.
69- return class_object
70-
71-
72-DBusIntrospectionObjectBase = IntrospectableObjectMetaclass(
73- 'DBusIntrospectionObjectBase',
74- (object,),
75- {}
76-)
77-
78-
79-def _get_proxy_object_class(object_id, path, state):
80- """Return a custom proxy class, from the object registry or the default.
81-
82- This function first inspects the object registry using the object_id passed
83- in. The object_id will be unique to all custom proxy classes for the same
84- application.
85-
86- If that fails, we create a class on the fly based on the default class.
87-
88- :param object_id: The _id attribute of the class doing the lookup. This is
89- used to index into the object registry to retrieve the dict of proxy
90- classes to try.
91- :param path: dbus path
92- :param state: dbus state
93- :returns: appropriate custom proxy class
94- :raises ValueError: if more than one class in the dict matches
95-
96- """
97- class_type = _try_custom_proxy_classes(object_id, path, state)
98-
99- return class_type or _get_default_proxy_class(
100- object_id,
101- get_classname_from_path(path)
102- )
103-
104-
105 def _try_custom_proxy_classes(object_id, path, state):
106 """Identify which custom proxy class matches the dbus path and state.
107
108@@ -239,26 +149,6 @@
109 return order
110
111
112-def _get_default_proxy_class(id, name):
113- """Return a custom proxy object class of the default or a base class.
114-
115- We want the object to inherit from the class that is set as the emulator
116- base class, not the class that is doing the selecting. Using the passed id
117- we retrieve the relevant bases from the object registry.
118-
119- :param id: The object id (_id attribute) of the class doing the lookup.
120- :param name: name of new class
121- :returns: custom proxy object class
122-
123- """
124- get_debug_logger().warning(
125- "Generating introspection instance for type '%s' based on generic "
126- "class.", name)
127- if isinstance(name, bytes):
128- name = name.decode('utf-8')
129- return type(name, _get_proxy_bases_for_id(id), dict(__generated=True))
130-
131-
132 @contextmanager
133 def patch_registry(new_registry):
134 """A utility context manager that allows us to patch the object registry.
135
136=== added file 'autopilot/introspection/_proxy_objects.py'
137--- autopilot/introspection/_proxy_objects.py 1970-01-01 00:00:00 +0000
138+++ autopilot/introspection/_proxy_objects.py 2015-06-15 09:43:19 +0000
139@@ -0,0 +1,211 @@
140+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
141+#
142+# Autopilot Functional Test Tool
143+# Copyright (C) 2015 Canonical
144+#
145+# This program is free software: you can redistribute it and/or modify
146+# it under the terms of the GNU General Public License as published by
147+# the Free Software Foundation, either version 3 of the License, or
148+# (at your option) any later version.
149+#
150+# This program is distributed in the hope that it will be useful,
151+# but WITHOUT ANY WARRANTY; without even the implied warranty of
152+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
153+# GNU General Public License for more details.
154+#
155+# You should have received a copy of the GNU General Public License
156+# along with this program. If not, see <http://www.gnu.org/licenses/>.
157+#
158+
159+"""Functions for dealing with proxy classes and objects."""
160+
161+
162+from autopilot.introspection import (
163+ constants,
164+ dbus as ap_dbus
165+)
166+from autopilot.introspection._xpathselect import get_classname_from_path
167+from autopilot.utilities import get_debug_logger
168+
169+
170+def _get_introspection_xml_from_backend(
171+ backend, reply_handler=None, error_handler=None):
172+ """Get DBus Introspection xml from a backend.
173+
174+ :param backend: The backend object to query.
175+ :param reply_handler: If set, makes a dbus async call, and the result will
176+ be sent to reply_handler. This must be a callable object.
177+ :param error_handler: If set, this callable will recieve any errors, and
178+ the call will be made asyncronously.
179+ :returns: A string containing introspection xml, if called synchronously.
180+ :raises ValueError: if one, but not both of 'reply_handler' and
181+ 'error_handler' are set.
182+
183+ """
184+ if callable(reply_handler) and callable(error_handler):
185+ backend.dbus_introspection_iface.Introspect(
186+ reply_handler=reply_handler,
187+ error_handler=error_handler,
188+ )
189+ elif reply_handler or error_handler:
190+ raise ValueError(
191+ "Both 'reply_handler' and 'error_handler' must be set."
192+ )
193+ else:
194+ return backend.dbus_introspection_iface.Introspect()
195+
196+
197+def _get_proxy_bases_from_introspection_xml(introspection_xml):
198+ """Return tuple of the base classes to use when creating a proxy object.
199+
200+ Currently this works by looking for certain interface names in the XML. In
201+ the future we may want to parse the XML and perform more rigerous checks.
202+
203+ :param introspection_xml: An xml string that describes the exported object
204+ on the dbus backend. This determines which capabilities are present in
205+ the backend, and therefore which base classes should be used to create
206+ the proxy object.
207+
208+ :raises RuntimeError: if the autopilot interface cannot be found.
209+
210+ """
211+
212+ bases = []
213+
214+ if constants.AP_INTROSPECTION_IFACE not in introspection_xml:
215+ raise RuntimeError("Could not find Autopilot interface.")
216+
217+ if constants.QT_AUTOPILOT_IFACE in introspection_xml:
218+ from autopilot.introspection.qt import QtObjectProxyMixin
219+ bases.append(QtObjectProxyMixin)
220+
221+ return tuple(bases)
222+
223+
224+def _get_proxy_object_class_name_and_state(
225+ backend, reply_handler=None, error_handler=None):
226+ """Get details about this autopilot backend via a dbus GetState call.
227+
228+ :param reply_handler: A callable that must accept three positional
229+ arguments, which correspond to the return value of this function when
230+ called synchronously.
231+
232+ :param error_handler: A callable which will recieve any dbus errors, should
233+ they occur.
234+
235+ :raises ValueError: if one, but not both of reply_handler and error_handler
236+ are set.
237+
238+ :returns: A tuple containing the class name of the root of the
239+ introspection tree, the full path to the root of the introspection
240+ tree, and the state dictionary of the root node in the introspection
241+ tree.
242+
243+ """
244+ if callable(reply_handler) and callable(error_handler):
245+ # Async call:
246+ # Since we get an array of state, and we only care about the first one
247+ # we use a lambda to unpack it and get the details we want.
248+ backend.introspection_iface.GetState(
249+ "/",
250+ reply_handler=lambda r: reply_handler(
251+ *_get_details_from_state_data(r[0])
252+ ),
253+ error_handler=error_handler,
254+ )
255+ elif reply_handler or error_handler:
256+ raise ValueError(
257+ "Both 'reply_handler' and 'error_handler' must be set."
258+ )
259+ else:
260+ # Sync call
261+ state = backend.introspection_iface.GetState("/")[0]
262+ return _get_details_from_state_data(state)
263+
264+
265+def _get_details_from_state_data(state_data):
266+ """Get details from a state data array.
267+
268+ Returns class name, path, and state dictionary.
269+ """
270+ object_path, object_state = state_data
271+ return (
272+ get_classname_from_path(object_path),
273+ object_path.encode('utf-8'),
274+ object_state,
275+ )
276+
277+
278+def _get_default_proxy_class(name, bases):
279+ """Return a default custom proxy object class.
280+
281+ :param name: name of new class
282+ :param bases: Tuple of base classes to add to the base.
283+ :returns: custom proxy object class
284+
285+ """
286+ get_debug_logger().warning(
287+ "Generating introspection instance for type '%s' based on generic "
288+ "class.", name)
289+ if isinstance(name, bytes):
290+ name = name.decode('utf-8')
291+ return type(
292+ name,
293+ (ap_dbus.DBusIntrospectionObject,) + bases,
294+ dict(__generated=True)
295+ )
296+
297+
298+# vvveebers this probably shouldn't be 'private'.
299+def _get_proxy_object_class(cpo_or_string, path, dbus_backend):
300+ """Return the proxy class to use with the correct bases added
301+
302+ If passed a CPO returns that, if passed a string contructs a default class
303+ to use instead.
304+
305+ """
306+
307+ extension_classes = _get_extension_classes(dbus_backend)
308+
309+ if _is_cpo_class(cpo_or_string):
310+ _apply_mixin_bases(cpo_or_string, extension_classes)
311+ return cpo_or_string
312+ else:
313+ cpo_name = _get_applicable_class_name(cpo_or_string, path)
314+ return _get_default_proxy_class(cpo_name, extension_classes)
315+
316+
317+def _get_extension_classes(dbus_backend):
318+ """Return any extension classes that this object will need."""
319+
320+ intro_xml = _get_introspection_xml_from_backend(dbus_backend)
321+ try:
322+ return _get_proxy_bases_from_introspection_xml(intro_xml)
323+ except RuntimeError as e:
324+ e.args = (
325+ "Could not find Autopilot interface on dbus address '%s'."
326+ % dbus_backend,
327+ )
328+
329+
330+def _is_cpo_class(cpo_or_string):
331+ return cpo_or_string is not None and not isinstance(cpo_or_string, str)
332+
333+
334+def _get_applicable_class_name(cpo_name, path):
335+ """Return correct name for class.
336+
337+ It is possible that the cpo_name is '*' which is what was used in the
338+ query. Determine what the actual name should be.
339+
340+ """
341+ if cpo_name is None or cpo_name == '*':
342+ cls_name = get_classname_from_path(path)
343+ return cls_name
344+ else:
345+ return cpo_name
346+
347+
348+def _apply_mixin_bases(target_class, mixin_classes):
349+ to_add = tuple(set(mixin_classes).difference(target_class.__bases__))
350+ target_class.__bases__ += to_add
351
352=== modified file 'autopilot/introspection/_search.py'
353--- autopilot/introspection/_search.py 2015-05-06 10:03:43 +0000
354+++ autopilot/introspection/_search.py 2015-06-15 09:43:19 +0000
355@@ -33,7 +33,7 @@
356 from autopilot.introspection import backends
357 from autopilot.introspection import constants
358 from autopilot.introspection import dbus as ap_dbus
359-from autopilot.introspection import _object_registry
360+from autopilot.introspection import _proxy_objects as _po
361 from autopilot.introspection._xpathselect import get_classname_from_path
362 from autopilot.introspection.backends import WireProtocolVersionMismatch
363 from autopilot.introspection.utilities import (
364@@ -384,40 +384,22 @@
365 :param emulator_base: The emulator base object (or None), as provided by
366 the user.
367 """
368+
369+ # vvveebers emulator_base will become proxy_class (to be explicit as the
370+ # term emulator has long gone.).
371+
372 # make sure we always have an emulator base. Either use the one the user
373 # gave us, or make one:
374- emulator_base = emulator_base or _make_default_emulator_base()
375- _raise_if_base_class_not_actually_base(emulator_base)
376-
377- # Get the dbus introspection Xml for the backend.
378- intro_xml = _get_introspection_xml_from_backend(dbus_address)
379- try:
380- # Figure out if the backend has any extension methods, and return
381- # classes that understand how to use each of those extensions:
382- extension_classes = _get_proxy_bases_from_introspection_xml(intro_xml)
383-
384- # Register those base classes for everything that will derive from this
385- # emulator base class.
386- _object_registry.register_extension_classes_for_proxy_base(
387- emulator_base,
388- extension_classes,
389- )
390- except RuntimeError as e:
391- e.args = (
392- "Could not find Autopilot interface on dbus address '%s'."
393- % dbus_address,
394- )
395- raise e
396-
397- cls_name, path, cls_state = _get_proxy_object_class_name_and_state(
398+ # This will return a default class or the CPO with the correct stuff added.
399+
400+ cls_name, path, cls_state = _po._get_proxy_object_class_name_and_state(
401 dbus_address
402 )
403
404- proxy_class = _object_registry._get_proxy_object_class(
405- emulator_base._id,
406- path,
407- cls_state
408+ proxy_class = _po._get_proxy_object_class(
409+ emulator_base, path, dbus_address
410 )
411+
412 # For this object only, add the ApplicationProxy class, since it's the
413 # root of the tree. Ideally this would be nicer...
414 if ApplicationProxyObject not in proxy_class.__bases__:
415@@ -426,6 +408,7 @@
416
417
418 def _make_default_emulator_base():
419+ # vvveebers this will be removed (make_proxy_async needs updated.)
420 """Make a default base class for all proxy classes to derive from."""
421 return type("DefaultEmulatorBase", (ap_dbus.DBusIntrospectionObject,), {})
422
423@@ -471,6 +454,8 @@
424 reply_handler will be called with a single argument: The proxy object.
425
426 """
427+ # vvveebers emulator_base name will change to proxy_class.
428+
429 # Note: read this function backwards!
430 #
431 # Due to the callbacks, I need to define the end of the callback chain
432@@ -482,20 +467,26 @@
433 def build_proxy(introspection_xml, cls_name, path, cls_state):
434 # Figure out if the backend has any extension methods, and return
435 # classes that understand how to use each of those extensions:
436- extension_classes = _get_proxy_bases_from_introspection_xml(
437+ extension_classes = _po._get_proxy_bases_from_introspection_xml(
438 introspection_xml
439 )
440- # Register those base classes for everything that will derive from this
441- # emulator base class.
442- _object_registry.register_extension_classes_for_proxy_base(
443- emulator_base,
444- extension_classes,
445- )
446- proxy_class = _object_registry._get_proxy_object_class(
447- emulator_base._id,
448- path,
449- cls_state
450- )
451+
452+ # XXX ERROR!
453+ emulator_base = None
454+ if emulator_base is None:
455+ emulator_base = cls_name
456+
457+ if isinstance(emulator_base, str):
458+ cpo_name = emulator_base
459+ proxy_class = _po._get_default_proxy_class(
460+ cpo_name,
461+ extension_classes
462+ )
463+ else:
464+ # It's actually a CPO class.
465+ _po._apply_mixin_bases(emulator_base, extension_classes)
466+ proxy_class = emulator_base
467+
468 reply_handler(
469 proxy_class(cls_state, path, backends.Backend(data_source))
470 )
471@@ -503,7 +494,7 @@
472 # Phase 2: We recieve the introspection string, and make an asynchronous
473 # dbus call to get the state information for the root of this applicaiton.
474 def get_root_state(introspection_xml):
475- _get_proxy_object_class_name_and_state(
476+ _po._get_proxy_object_class_name_and_state(
477 data_source,
478 reply_handler=partial(build_proxy, introspection_xml),
479 error_handler=error_handler,
480@@ -513,121 +504,13 @@
481 # from the data_source provided for us.
482 emulator_base = emulator_base or _make_default_emulator_base()
483
484- _get_introspection_xml_from_backend(
485+ _po._get_introspection_xml_from_backend(
486 data_source,
487 reply_handler=get_root_state,
488 error_handler=error_handler
489 )
490
491
492-def _get_introspection_xml_from_backend(
493- backend, reply_handler=None, error_handler=None):
494- """Get DBus Introspection xml from a backend.
495-
496- :param backend: The backend object to query.
497- :param reply_handler: If set, makes a dbus async call, and the result will
498- be sent to reply_handler. This must be a callable object.
499- :param error_handler: If set, this callable will recieve any errors, and
500- the call will be made asyncronously.
501- :returns: A string containing introspection xml, if called synchronously.
502- :raises ValueError: if one, but not both of 'reply_handler' and
503- 'error_handler' are set.
504-
505- """
506- if callable(reply_handler) and callable(error_handler):
507- backend.dbus_introspection_iface.Introspect(
508- reply_handler=reply_handler,
509- error_handler=error_handler,
510- )
511- elif reply_handler or error_handler:
512- raise ValueError(
513- "Both 'reply_handler' and 'error_handler' must be set."
514- )
515- else:
516- return backend.dbus_introspection_iface.Introspect()
517-
518-
519-def _get_proxy_object_class_name_and_state(
520- backend, reply_handler=None, error_handler=None):
521- """Get details about this autopilot backend via a dbus GetState call.
522-
523- :param reply_handler: A callable that must accept three positional
524- arguments, which correspond to the return value of this function when
525- called synchronously.
526-
527- :param error_handler: A callable which will recieve any dbus errors, should
528- they occur.
529-
530- :raises ValueError: if one, but not both of reply_handler and error_handler
531- are set.
532-
533- :returns: A tuple containing the class name of the root of the
534- introspection tree, the full path to the root of the introspection
535- tree, and the state dictionary of the root node in the introspection
536- tree.
537-
538- """
539- if callable(reply_handler) and callable(error_handler):
540- # Async call:
541- # Since we get an array of state, and we only care about the first one
542- # we use a lambda to unpack it and get the details we want.
543- backend.introspection_iface.GetState(
544- "/",
545- reply_handler=lambda r: reply_handler(
546- *_get_details_from_state_data(r[0])
547- ),
548- error_handler=error_handler,
549- )
550- elif reply_handler or error_handler:
551- raise ValueError(
552- "Both 'reply_handler' and 'error_handler' must be set."
553- )
554- else:
555- # Sync call
556- state = backend.introspection_iface.GetState("/")[0]
557- return _get_details_from_state_data(state)
558-
559-
560-def _get_details_from_state_data(state_data):
561- """Get details from a state data array.
562-
563- Returns class name, path, and state dictionary.
564- """
565- object_path, object_state = state_data
566- return (
567- get_classname_from_path(object_path),
568- object_path.encode('utf-8'),
569- object_state,
570- )
571-
572-
573-def _get_proxy_bases_from_introspection_xml(introspection_xml):
574- """Return tuple of the base classes to use when creating a proxy object.
575-
576- Currently this works by looking for certain interface names in the XML. In
577- the future we may want to parse the XML and perform more rigerous checks.
578-
579- :param introspection_xml: An xml string that describes the exported object
580- on the dbus backend. This determines which capabilities are present in
581- the backend, and therefore which base classes should be used to create
582- the proxy object.
583-
584- :raises RuntimeError: if the autopilot interface cannot be found.
585-
586- """
587-
588- bases = []
589-
590- if constants.AP_INTROSPECTION_IFACE not in introspection_xml:
591- raise RuntimeError("Could not find Autopilot interface.")
592-
593- if constants.QT_AUTOPILOT_IFACE in introspection_xml:
594- from autopilot.introspection.qt import QtObjectProxyMixin
595- bases.append(QtObjectProxyMixin)
596-
597- return tuple(bases)
598-
599-
600 class ApplicationProxyObject(object):
601 """A class that better supports query data from an application."""
602
603
604=== modified file 'autopilot/introspection/backends.py'
605--- autopilot/introspection/backends.py 2015-06-09 15:35:08 +0000
606+++ autopilot/introspection/backends.py 2015-06-15 09:43:19 +0000
607@@ -51,7 +51,7 @@
608 _pid_is_running,
609 _get_bus_connections_pid,
610 )
611-from autopilot.introspection._object_registry import _get_proxy_object_class
612+from autopilot.introspection import _proxy_objects
613
614
615 _logger = logging.getLogger(__name__)
616@@ -245,14 +245,14 @@
617 )
618 return data
619
620- def execute_query_get_proxy_instances(self, query, id):
621+ def execute_query_get_proxy_instances(self, query, cpo_class):
622 """Execute 'query', returning proxy instances."""
623 data = self.execute_query_get_data(query)
624 objects = [
625 make_introspection_object(
626 t,
627 type(self)(self.ipc_address),
628- id,
629+ cpo_class,
630 )
631 for t in data
632 ]
633@@ -288,7 +288,7 @@
634 return self.fake_ipc_return_data
635
636
637-def make_introspection_object(dbus_tuple, backend, object_id):
638+def make_introspection_object(dbus_tuple, backend, cpo_class):
639 """Make an introspection object given a DBus tuple of
640 (path, state_dict).
641
642@@ -303,7 +303,13 @@
643 """
644 path, state = dbus_tuple
645 path = path.encode('utf-8')
646- class_object = _get_proxy_object_class(object_id, path, state)
647+ # import ipdb; ipdb.set_trace()
648+ # Path seems correct here.
649+ class_object = _proxy_objects._get_proxy_object_class(
650+ cpo_class,
651+ path,
652+ backend.ipc_address
653+ )
654 return class_object(state, path, backend)
655
656
657
658=== modified file 'autopilot/introspection/dbus.py'
659--- autopilot/introspection/dbus.py 2014-07-31 04:42:08 +0000
660+++ autopilot/introspection/dbus.py 2015-06-15 09:43:19 +0000
661@@ -30,9 +30,6 @@
662 import logging
663
664 from autopilot.exceptions import StateNotFoundError
665-from autopilot.introspection._object_registry import (
666- DBusIntrospectionObjectBase,
667-)
668 from autopilot.introspection.types import create_value_instance
669 from autopilot.introspection.utilities import translate_state_keys
670 from autopilot.introspection import _xpathselect as xpathselect
671@@ -42,7 +39,7 @@
672 _logger = logging.getLogger(__name__)
673
674
675-class DBusIntrospectionObject(DBusIntrospectionObjectBase):
676+class DBusIntrospectionObject(object):
677 """A class that supports transparent data retrieval from the application
678 under test.
679
680@@ -83,11 +80,15 @@
681 self.id
682 )
683
684- def _execute_query(self, query):
685+ def _execute_query(self, query, cpo_class=None):
686+ # Default to None because it's ok to create a default CPO oh or perhaps
687+ # default to a default dbus object?
688 """Execute query object 'query' and return the result."""
689+ # vvveebers this will need to be updated to take any CPO passed in.
690+ # as well as updating the backend code so that it can handle it.
691 return self._backend.execute_query_get_proxy_instances(
692 query,
693- getattr(self, '_id', None),
694+ cpo_class,
695 )
696
697 def _set_properties(self, state_dict):
698@@ -146,6 +147,9 @@
699 Tutorial Section :ref:`custom_proxy_classes`
700
701 """
702+ # vvveebers this will need updating (docs, and select_child).
703+ # Will def need to pass in the CPO class.
704+ # (seems that this will reside in execute_query.
705 new_query = self._query.select_child(
706 get_type_name(desired_type),
707 kwargs
708@@ -175,10 +179,13 @@
709 This returns a list of all children. To return only children of a
710 specific type, use :meth:`get_children_by_type`. To get objects
711 further down the introspection tree (i.e.- nodes that may not
712- necessarily be immeadiate children), use :meth:`select_single` and
713+ necessarily be immediate children), use :meth:`select_single` and
714 :meth:`select_many`.
715
716 """
717+ # vvveebers this will need a note stating that any objects returned
718+ # will be just defaults.
719+
720 # Thomi: 2014-03-20: There used to be a call to 'self.refresh_state()'
721 # here. That's not needed, since the only thing we use is the proxy
722 # path, which isn't affected by the current state.
723@@ -192,6 +199,8 @@
724 tree). Then it returns itself.
725
726 """
727+ # vvveebers This might also be an issue as there will be no record of
728+ # what CPO to use.
729 new_query = self._query.select_parent()
730 return self._execute_query(new_query)[0]
731
732@@ -237,7 +246,7 @@
733 type_name_str,
734 kwargs
735 )
736- instances = self._execute_query(new_query)
737+ instances = self._execute_query(new_query, type_name)
738 if len(instances) > 1:
739 raise ValueError("More than one item was returned for query")
740 if not instances:
741@@ -351,7 +360,7 @@
742 'any type'
743 if type_name_str == '*' else 'type ' + type_name_str, kwargs
744 )
745- return self._execute_query(new_query)
746+ return self._execute_query(new_query, type_name)
747
748 def refresh_state(self):
749 """Refreshes the object's state.
750@@ -385,6 +394,11 @@
751 :return: List (possibly empty) of class instances.
752
753 """
754+ # vvveebers this will need a revamp:
755+ # - getting cls_name like this will fail for CPOs with different
756+ # names.
757+ # - this would currently produce a default proxy as the CPO isn't
758+ # passed in: either pass self in (or similar) or perhaps 'from_cpo()'.
759 cls_name = type(self).__name__
760 return self._execute_query(
761 xpathselect.Query.whole_tree_search(cls_name)
762@@ -397,6 +411,10 @@
763 introspection tree.
764
765 """
766+ # vvveebers undecided if this should take a cpo argument so the
767+ # returned object can be instatiated instead of a default.
768+ # It appears that currently in use that the expectation is that it's a
769+ # default.
770 query = xpathselect.Query.pseudo_tree_root()
771 return self._execute_query(query)[0]
772
773@@ -410,10 +428,14 @@
774 if self.__refresh_on_attribute:
775 self.refresh_state()
776 return self.__state[name]
777+
778+ exception_msg = "{}Class '{}' has no attribute '{}'.".format(
779+ 'Generated ' if hasattr(self.__class__, '__generated') else '',
780+ self.__class__.__name__,
781+ name
782+ )
783 # attribute not found.
784- raise AttributeError(
785- "Class '%s' has no attribute '%s'." %
786- (self.__class__.__name__, name))
787+ raise AttributeError(exception_msg)
788
789 def _get_new_state(self):
790 """Retrieve a new state dictionary for this class instance.
791@@ -538,12 +560,47 @@
792 :returns: Whether this class is appropriate for the dbus object
793
794 """
795+
796+ # vvveebers this won't be needed with the newer design. Is there
797+ # perhaps a need for something along this line so CPOs are created for
798+ # the expected bits?
799+
800 state_name = xpathselect.get_classname_from_path(path)
801 if isinstance(state_name, str):
802 state_name = state_name.encode('utf-8')
803 class_name = cls.__name__.encode('utf-8')
804 return state_name == class_name
805
806+ @classmethod
807+ def get_type_query_name(cls):
808+ """Return the Type node name to use within the search query.
809+
810+ This allows for a Custom Proxy Object to be named differently to the
811+ underlying node type name.
812+
813+ For instance if you have a QML type defined in the file RedRect.qml::
814+
815+ import QtQuick 2.0
816+ Rectangle {
817+ color: red;
818+ }
819+
820+ You can then define a Custom Proxy Object for this type like so::
821+
822+ class RedRect(DBusIntrospectionObject):
823+ @classmethod
824+ def get_type_query_name(cls):
825+ return 'QQuickRectangle'
826+
827+ This is due to the qml engine storing 'RedRect' as a QQuickRectangle in
828+ the UI tree and the xpathquery query needs a node type to query for.
829+ By default the query will use the class name (in this case RedRect) but
830+ this will not match any node type in the tree.
831+
832+ """
833+
834+ return cls.__name__
835+
836
837 # TODO - can we add a deprecation warning around this somehow?
838 CustomEmulatorBase = DBusIntrospectionObject
839@@ -557,5 +614,12 @@
840
841 """
842 if not isinstance(maybe_string_or_class, str):
843- return maybe_string_or_class.__name__
844+ return _get_class_type_name(maybe_string_or_class)
845 return maybe_string_or_class
846+
847+
848+def _get_class_type_name(maybe_cpo_class):
849+ if hasattr(maybe_cpo_class, 'get_type_query_name'):
850+ return maybe_cpo_class.get_type_query_name()
851+ else:
852+ return maybe_cpo_class.__name__
853
854=== modified file 'autopilot/tests/functional/test_dbus_query.py'
855--- autopilot/tests/functional/test_dbus_query.py 2015-06-09 15:35:08 +0000
856+++ autopilot/tests/functional/test_dbus_query.py 2015-06-15 09:43:19 +0000
857@@ -217,6 +217,12 @@
858 self.assertThat(abs(end_time - start_time), GreaterThan(9))
859 self.assertThat(abs(end_time - start_time), LessThan(11))
860
861+ def test_selecting_without_classname_sets_proxy_classname(self):
862+ app = self.start_fully_featured_app()
863+ test_object = app.select_single(windowTitle='Default Window Title')
864+
865+ self.assertThat(test_object.__class__.__name__, Equals('QMainWindow'))
866+
867
868 @skipIf(platform.model() != "Desktop", "Only suitable on Desktop (WinMocker)")
869 class DbusCustomBusTests(AutopilotTestCase):
870
871=== modified file 'autopilot/tests/functional/test_input_stack.py'
872--- autopilot/tests/functional/test_input_stack.py 2015-05-08 04:32:51 +0000
873+++ autopilot/tests/functional/test_input_stack.py 2015-06-15 09:43:19 +0000
874@@ -379,7 +379,7 @@
875 screen_geometry = Display.create().get_screen_geometry(0)
876 target_x = screen_geometry[0] + 10
877 target_y = screen_geometry[1] + 10.6
878- self.device.move(target_x, target_y)
879+ device.move(target_x, target_y)
880 self.assertEqual(device.position(), (target_x, int(target_y)))
881
882 @patch('autopilot.platform.model', new=lambda *args: "Not Desktop", )
883
884=== modified file 'autopilot/tests/functional/test_introspection_features.py'
885--- autopilot/tests/functional/test_introspection_features.py 2015-04-30 03:34:13 +0000
886+++ autopilot/tests/functional/test_introspection_features.py 2015-06-15 09:43:19 +0000
887@@ -36,16 +36,15 @@
888 StartsWith,
889 )
890 from textwrap import dedent
891-from unittest.mock import patch
892 from io import StringIO
893
894 from autopilot import platform
895 from autopilot.matchers import Eventually
896 from autopilot.testcase import AutopilotTestCase
897+from autopilot.tests.functional import QmlScriptRunnerMixin
898 from autopilot.tests.functional.fixtures import TempDesktopFile
899 from autopilot.introspection import CustomEmulatorBase
900 from autopilot.introspection import _object_registry as object_registry
901-from autopilot.introspection import _search
902 from autopilot.introspection.qt import QtObjectProxyMixin
903 from autopilot.display import Display
904
905@@ -93,14 +92,16 @@
906 return path == b'/window-mocker'
907
908 # verify that the initial proxy object we get back is the correct type:
909- app = self.start_mock_app(EmulatorBase)
910+ app = self.start_mock_app(WindowMockerApp)
911 self.assertThat(type(app), Equals(WindowMockerApp))
912
913 # verify that we get the correct type from get_root_instance:
914- self.assertThat(
915- type(app.get_root_instance()),
916- Equals(WindowMockerApp)
917- )
918+ # vvveebers behaviour has changed, get_root_instance currently returns
919+ # a default cpo.
920+ # self.assertThat(
921+ # type(app.get_root_instance()),
922+ # Equals(WindowMockerApp)
923+ # )
924
925 def test_customised_proxy_classes_have_extension_classes(self):
926 class WindowMockerApp(EmulatorBase):
927@@ -112,21 +113,22 @@
928 self.assertThat(app.__class__.__bases__, Contains(QtObjectProxyMixin))
929
930 def test_customised_proxy_classes_have_multiple_extension_classes(self):
931- with object_registry.patch_registry({}):
932- class SecondEmulatorBase(CustomEmulatorBase):
933- pass
934-
935- class WindowMockerApp(EmulatorBase, SecondEmulatorBase):
936- @classmethod
937- def validate_dbus_object(cls, path, _state):
938- return path == b'/window-mocker'
939-
940- app = self.start_mock_app(EmulatorBase)
941- self.assertThat(app.__class__.__bases__, Contains(EmulatorBase))
942- self.assertThat(
943- app.__class__.__bases__,
944- Contains(SecondEmulatorBase)
945- )
946+ # vvveebers this test isn't really testing anything anymore as it's
947+ # handled by python (i.e. class inheritence)
948+ class SecondEmulatorBase(CustomEmulatorBase):
949+ pass
950+
951+ class WindowMockerApp(EmulatorBase, SecondEmulatorBase):
952+ @classmethod
953+ def validate_dbus_object(cls, path, _state):
954+ return path == b'/window-mocker'
955+
956+ app = self.start_mock_app(WindowMockerApp)
957+ self.assertThat(app.__class__.__bases__, Contains(EmulatorBase))
958+ self.assertThat(
959+ app.__class__.__bases__,
960+ Contains(SecondEmulatorBase)
961+ )
962
963 def test_handles_using_app_cpo_base_class(self):
964 # This test replicates an issue found in an application test suite
965@@ -139,30 +141,16 @@
966
967 self.start_mock_app(WindowMockerApp)
968
969- def test_warns_when_using_incorrect_cpo_base_class(self):
970- # Ensure the warning method is called when launching a proxy.
971- with object_registry.patch_registry({}):
972- class TestCPO(CustomEmulatorBase):
973- pass
974-
975- class WindowMockerApp(TestCPO):
976- @classmethod
977- def validate_dbus_object(cls, path, _state):
978- return path == b'/window-mocker'
979-
980- with patch.object(_search, 'logger') as p_logger:
981- self.start_mock_app(WindowMockerApp)
982- self.assertTrue(p_logger.warning.called)
983-
984- def test_can_select_custom_emulators_by_name(self):
985- """Must be able to select a custom emulator type by name."""
986+ def test_selecting_using_string_creates_default_cpo(self):
987+ """When not passing CPO must return generated default."""
988 class MouseTestWidget(EmulatorBase):
989 pass
990
991 app = self.start_mock_app(EmulatorBase)
992 test_widget = app.select_single('MouseTestWidget')
993
994- self.assertThat(type(test_widget), Equals(MouseTestWidget))
995+ self.assertThat(type(test_widget), Not(Equals(MouseTestWidget)))
996+ self.assertTrue(getattr(test_widget, '__generated'))
997
998 def test_can_select_custom_emulators_by_type(self):
999 """Must be able to select a custom emulator type by type."""
1000@@ -397,3 +385,31 @@
1001 [e[1] for e in result2.decorated.errors]
1002 )
1003 )
1004+
1005+
1006+class CustomCPOTest(AutopilotTestCase, QmlScriptRunnerMixin):
1007+
1008+ def launch_simple_qml_script(self):
1009+ simple_script = dedent("""
1010+ import QtQuick 2.0
1011+ Rectangle {
1012+ objectName: "ExampleRectangle"
1013+ }
1014+ """)
1015+ return self.start_qml_script(simple_script)
1016+
1017+ def test_cpo_can_be_named_different_to_underlying_type(self):
1018+ """A CPO with the correct name match method must be matched if the
1019+ class name is different to the Type name.
1020+
1021+ """
1022+ with object_registry.patch_registry({}):
1023+ class RandomNamedCPORectangle(CustomEmulatorBase):
1024+ @classmethod
1025+ def get_type_query_name(cls):
1026+ return 'QQuickRectangle'
1027+
1028+ app = self.launch_simple_qml_script()
1029+ rectangle = app.select_single(RandomNamedCPORectangle)
1030+
1031+ self.assertThat(rectangle.objectName, Equals('ExampleRectangle'))
1032
1033=== renamed file 'autopilot/tests/unit/test_introspection_object_registry.py' => 'autopilot/tests/unit/introspection_object_registry.py'
1034=== modified file 'autopilot/tests/unit/test_introspection.py'
1035--- autopilot/tests/unit/test_introspection.py 2014-07-31 04:42:08 +0000
1036+++ autopilot/tests/unit/test_introspection.py 2015-06-15 09:43:19 +0000
1037@@ -25,7 +25,10 @@
1038 IsInstance,
1039 raises,
1040 )
1041-import autopilot.introspection._search as _s
1042+from autopilot.introspection import (
1043+ _proxy_objects as _po,
1044+ _search as _s
1045+)
1046 from autopilot.introspection.qt import QtObjectProxyMixin
1047 import autopilot.introspection as _i
1048
1049@@ -35,21 +38,21 @@
1050 fake_state_data = (String('/some/path'), dict(foo=123))
1051
1052 def test_returns_classname(self):
1053- class_name, _, _ = _s._get_details_from_state_data(
1054+ class_name, _, _ = _po._get_details_from_state_data(
1055 self.fake_state_data
1056 )
1057 self.assertThat(class_name, Equals('path'))
1058
1059 def test_returns_path(self):
1060- _, path, _ = _s._get_details_from_state_data(self.fake_state_data)
1061+ _, path, _ = _po._get_details_from_state_data(self.fake_state_data)
1062 self.assertThat(path, Equals(b'/some/path'))
1063
1064 def test_returned_path_is_bytestring(self):
1065- _, path, _ = _s._get_details_from_state_data(self.fake_state_data)
1066+ _, path, _ = _po._get_details_from_state_data(self.fake_state_data)
1067 self.assertThat(path, IsInstance(type(b'')))
1068
1069 def test_returns_state_dict(self):
1070- _, _, state = _s._get_details_from_state_data(self.fake_state_data)
1071+ _, _, state = _po._get_details_from_state_data(self.fake_state_data)
1072 self.assertThat(state, Equals(dict(foo=123)))
1073
1074
1075@@ -119,13 +122,13 @@
1076
1077 def test_raises_RuntimeError_when_no_interface_is_found(self):
1078 self.assertThat(
1079- lambda: _s._get_proxy_bases_from_introspection_xml(""),
1080+ lambda: _po._get_proxy_bases_from_introspection_xml(""),
1081 raises(RuntimeError("Could not find Autopilot interface."))
1082 )
1083
1084 def test_returns_ApplicationProxyObject_claws_for_base_interface(self):
1085 self.assertThat(
1086- _s._get_proxy_bases_from_introspection_xml(
1087+ _po._get_proxy_bases_from_introspection_xml(
1088 self.fake_data_with_ap_interface
1089 ),
1090 Equals(())
1091@@ -133,7 +136,7 @@
1092
1093 def test_returns_both_base_and_qt_interface(self):
1094 self.assertThat(
1095- _s._get_proxy_bases_from_introspection_xml(
1096+ _po._get_proxy_bases_from_introspection_xml(
1097 self.fake_data_with_ap_and_qt_interfaces
1098 ),
1099 Equals((QtObjectProxyMixin,))
1100
1101=== modified file 'autopilot/tests/unit/test_introspection_backends.py'
1102--- autopilot/tests/unit/test_introspection_backends.py 2015-06-09 15:35:08 +0000
1103+++ autopilot/tests/unit/test_introspection_backends.py 2015-06-15 09:43:19 +0000
1104@@ -23,6 +23,7 @@
1105 from testtools.matchers import Equals, Not, NotEquals, IsInstance
1106
1107 from autopilot.introspection import (
1108+ _proxy_objects as _po,
1109 _xpathselect as xpathselect,
1110 backends,
1111 dbus,
1112@@ -325,21 +326,22 @@
1113 """Verify that a class has a validation method by default."""
1114 self.assertTrue(callable(self.DefaultSelector.validate_dbus_object))
1115
1116- @patch.object(backends, '_get_proxy_object_class')
1117+ @patch.object(_po, '_get_proxy_object_class')
1118 def test_make_introspection_object(self, gpoc):
1119 """Verify that make_introspection_object makes the right call."""
1120 gpoc.return_value = self.DefaultSelector
1121- fake_id = Mock()
1122+ fake_backend = Mock()
1123+ cpo_name = self.getUniqueString()
1124+
1125 new_fake = backends.make_introspection_object(
1126 (String('/Object'), {'id': [0, 42]}),
1127- None,
1128- fake_id,
1129+ fake_backend,
1130+ cpo_name
1131 )
1132 self.assertThat(new_fake, IsInstance(self.DefaultSelector))
1133 gpoc.assert_called_once_with(
1134- fake_id,
1135- b'/Object',
1136- {'id': [0, 42]}
1137+ cpo_name,
1138+ fake_backend.ipc_address
1139 )
1140
1141 def test_validate_dbus_object_matches_on_class_name(self):
1142
1143=== modified file 'autopilot/tests/unit/test_introspection_dbus.py'
1144--- autopilot/tests/unit/test_introspection_dbus.py 2014-07-22 02:30:19 +0000
1145+++ autopilot/tests/unit/test_introspection_dbus.py 2015-06-15 09:43:19 +0000
1146@@ -29,50 +29,19 @@
1147 from testtools.matchers import (
1148 Equals,
1149 Not,
1150- NotEquals,
1151 Raises,
1152 raises,
1153 )
1154
1155 from autopilot.exceptions import StateNotFoundError
1156-from autopilot.introspection import CustomEmulatorBase
1157-from autopilot.introspection import dbus
1158+from autopilot.introspection import (
1159+ _proxy_objects as _po,
1160+ CustomEmulatorBase,
1161+ dbus
1162+)
1163 from autopilot.utilities import sleep
1164
1165
1166-class IntrospectionFeatureTests(TestCase):
1167-
1168- def test_custom_emulator_base_does_not_have_id(self):
1169- self.assertThat(hasattr(CustomEmulatorBase, '_id'), Equals(False))
1170-
1171- def test_derived_emulator_bases_do_have_id(self):
1172- class MyEmulatorBase(CustomEmulatorBase):
1173- pass
1174- self.assertThat(hasattr(MyEmulatorBase, '_id'), Equals(True))
1175-
1176- def test_derived_children_have_same_id(self):
1177- class MyEmulatorBase(CustomEmulatorBase):
1178- pass
1179-
1180- class MyEmulator(MyEmulatorBase):
1181- pass
1182-
1183- class MyEmulator2(MyEmulatorBase):
1184- pass
1185-
1186- self.assertThat(MyEmulatorBase._id, Equals(MyEmulator._id))
1187- self.assertThat(MyEmulatorBase._id, Equals(MyEmulator2._id))
1188-
1189- def test_children_have_different_ids(self):
1190- class MyEmulatorBase(CustomEmulatorBase):
1191- pass
1192-
1193- class MyEmulatorBase2(CustomEmulatorBase):
1194- pass
1195-
1196- self.assertThat(MyEmulatorBase._id, NotEquals(MyEmulatorBase2._id))
1197-
1198-
1199 class DBusIntrospectionObjectTests(TestCase):
1200
1201 def test_can_access_path_attribute(self):
1202@@ -118,6 +87,49 @@
1203 ),
1204 )
1205
1206+ def test_base_class_provides_correct_query_name(self):
1207+ self.assertThat(
1208+ dbus.DBusIntrospectionObject.get_type_query_name(),
1209+ Equals('ProxyBase')
1210+ )
1211+
1212+ def test_inherited_uses_default_get_node_name(self):
1213+ class TestCPO(dbus.DBusIntrospectionObject):
1214+ pass
1215+
1216+ self.assertThat(
1217+ TestCPO.get_type_query_name(),
1218+ Equals('TestCPO')
1219+ )
1220+
1221+ def test_inherited_overwrites_node_name_is_correct(self):
1222+ class TestCPO(dbus.DBusIntrospectionObject):
1223+ @classmethod
1224+ def get_type_query_name(cls):
1225+ return "TestCPO"
1226+ self.assertThat(TestCPO.get_type_query_name(), Equals("TestCPO"))
1227+
1228+
1229+class ProxyObjectCreationTests(TestCase):
1230+ def test_default_class_mentioned_in_attr_error(self):
1231+ name = self.getUniqueString()
1232+ fake_class = _po._get_default_proxy_class(name, ())
1233+ fake_object = fake_class(
1234+ dict(id=[0, 123], path=[0, '/some/path']),
1235+ b'/root',
1236+ Mock()
1237+ )
1238+
1239+ expected_msg = "Generated Class '{}' has no attribute 'foo'.".format(
1240+ name
1241+ )
1242+
1243+ with fake_object.no_automatic_refreshing():
1244+ self.assertThat(
1245+ lambda: fake_object.foo(),
1246+ raises(AttributeError(expected_msg))
1247+ )
1248+
1249
1250 class ProxyObjectPrintTreeTests(TestCase):
1251
1252@@ -222,3 +234,23 @@
1253 class FooBarBaz(object):
1254 pass
1255 self.assertEqual("FooBarBaz", dbus.get_type_name(FooBarBaz))
1256+
1257+ def test_get_type_name_returns_classname(self):
1258+ class CustomCPO(dbus.DBusIntrospectionObject):
1259+ pass
1260+
1261+ type_name = dbus.get_type_name(CustomEmulatorBase)
1262+ self.assertThat(type_name, Equals('ProxyBase'))
1263+
1264+ def test_get_type_name_returns_custom_node_name(self):
1265+ class CustomCPO(dbus.DBusIntrospectionObject):
1266+ @classmethod
1267+ def get_type_query_name(cls):
1268+ return 'TestingCPO'
1269+ type_name = dbus.get_type_name(CustomCPO)
1270+ self.assertThat(type_name, Equals('TestingCPO'))
1271+
1272+ def test_get_type_name_returns_classname_of_non_proxybase_classes(self):
1273+ class Foo(object):
1274+ pass
1275+ self.assertEqual('Foo', dbus.get_type_name(Foo))
1276
1277=== modified file 'autopilot/tests/unit/test_introspection_search.py'
1278--- autopilot/tests/unit/test_introspection_search.py 2015-05-06 10:00:49 +0000
1279+++ autopilot/tests/unit/test_introspection_search.py 2015-06-15 09:43:19 +0000
1280@@ -35,7 +35,6 @@
1281 from autopilot.utilities import sleep
1282 from autopilot.introspection import _search as _s
1283
1284-from autopilot.introspection import CustomEmulatorBase
1285 from autopilot.introspection.constants import AUTOPILOT_PATH
1286
1287
1288@@ -732,95 +731,3 @@
1289 _s._find_matching_connections(bus, lambda *args: True)
1290
1291 dedupe.assert_called_once_with(["conn1"], bus)
1292-
1293-
1294-class ActualBaseClassTests(TestCase):
1295-
1296- def test_dont_raise_passed_base_when_is_only_base(self):
1297- class ActualBase(CustomEmulatorBase):
1298- pass
1299-
1300- try:
1301- _s._raise_if_base_class_not_actually_base(ActualBase)
1302- except ValueError:
1303- self.fail('Unexpected ValueError exception')
1304-
1305- def test_raises_if_passed_incorrect_base_class(self):
1306- class ActualBase(CustomEmulatorBase):
1307- pass
1308-
1309- class InheritedCPO(ActualBase):
1310- pass
1311-
1312- self.assertRaises(
1313- ValueError,
1314- _s._raise_if_base_class_not_actually_base,
1315- InheritedCPO
1316- )
1317-
1318- def test_raises_parent_with_simple_non_ap_multi_inheritance(self):
1319- """When mixing in non-customproxy classes must return the base."""
1320-
1321- class ActualBase(CustomEmulatorBase):
1322- pass
1323-
1324- class InheritedCPO(ActualBase):
1325- pass
1326-
1327- class TrickyOne(object):
1328- pass
1329-
1330- class FinalForm(InheritedCPO, TrickyOne):
1331- pass
1332-
1333- self.assertRaises(
1334- ValueError,
1335- _s._raise_if_base_class_not_actually_base,
1336- FinalForm
1337- )
1338-
1339- def test_raises_parent_with_non_ap_multi_inheritance(self):
1340-
1341- class ActualBase(CustomEmulatorBase):
1342- pass
1343-
1344- class InheritedCPO(ActualBase):
1345- pass
1346-
1347- class TrickyOne(object):
1348- pass
1349-
1350- class FinalForm(TrickyOne, InheritedCPO):
1351- pass
1352-
1353- self.assertRaises(
1354- ValueError,
1355- _s._raise_if_base_class_not_actually_base,
1356- FinalForm
1357- )
1358-
1359- def test_dont_raise_when_using_default_emulator_base(self):
1360- # _make_proxy_object potentially creates a default base.
1361- DefaultBase = _s._make_default_emulator_base()
1362- try:
1363- _s._raise_if_base_class_not_actually_base(DefaultBase)
1364- except ValueError:
1365- self.fail('Unexpected ValueError exception')
1366-
1367- def test_exception_message_contains_useful_information(self):
1368- class ActualBase(CustomEmulatorBase):
1369- pass
1370-
1371- class InheritedCPO(ActualBase):
1372- pass
1373-
1374- try:
1375- _s._raise_if_base_class_not_actually_base(InheritedCPO)
1376- except ValueError as err:
1377- self.assertEqual(
1378- str(err),
1379- _s.WRONG_CPO_CLASS_MSG.format(
1380- passed=InheritedCPO,
1381- actual=ActualBase
1382- )
1383- )
1384
1385=== added file 'autopilot/tests/unit/test_proxy_object.py'
1386--- autopilot/tests/unit/test_proxy_object.py 1970-01-01 00:00:00 +0000
1387+++ autopilot/tests/unit/test_proxy_object.py 2015-06-15 09:43:19 +0000
1388@@ -0,0 +1,84 @@
1389+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1390+#
1391+# Autopilot Functional Test Tool
1392+# Copyright (C) 2015 Canonical
1393+#
1394+# This program is free software: you can redistribute it and/or modify
1395+# it under the terms of the GNU General Public License as published by
1396+# the Free Software Foundation, either version 3 of the License, or
1397+# (at your option) any later version.
1398+#
1399+# This program is distributed in the hope that it will be useful,
1400+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1401+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1402+# GNU General Public License for more details.
1403+#
1404+# You should have received a copy of the GNU General Public License
1405+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1406+#
1407+
1408+from unittest.mock import (
1409+ Mock,
1410+ patch
1411+)
1412+from testtools import TestCase
1413+
1414+from autopilot.introspection import _proxy_objects as _po
1415+
1416+
1417+class GetProxyObjectClassesTests(TestCase):
1418+ pass
1419+
1420+
1421+class IsCpoClassTests(TestCase):
1422+
1423+ def test_returns_False_when_None(self):
1424+ self.assertFalse(_po._is_cpo_class(None))
1425+
1426+ def test_returns_False_when_is_string(self):
1427+ self.assertFalse(_po._is_cpo_class('foo'))
1428+
1429+ def test_returns_True_when_is_class(self):
1430+ class Foo(): pass
1431+ self.assertTrue(_po._is_cpo_class(Foo))
1432+
1433+class GetApplicableClassNameTests(TestCase):
1434+
1435+ @patch.object(_po, '_get_proxy_object_class_name_and_state')
1436+ def test_queries_dbus_name_unknown(self, po_cs):
1437+ fake_dbus_backend = Mock()
1438+ po_cs.return_value = (None, None, None)
1439+ _po._get_applicable_class_name(None, fake_dbus_backend)
1440+ po_cs.assert_called_once_with(fake_dbus_backend)
1441+
1442+ @patch.object(_po, '_get_proxy_object_class_name_and_state')
1443+ def test_returns_dbus_name_when_passed_None(self, po_cs):
1444+ name = self.getUniqueString()
1445+ po_cs.return_value = (name, None, None)
1446+ fake_dbus_backend = Mock()
1447+
1448+ self.assertEqual(
1449+ name,
1450+ _po._get_applicable_class_name(
1451+ None,
1452+ fake_dbus_backend
1453+ )
1454+ )
1455+
1456+ @patch.object(_po, '_get_proxy_object_class_name_and_state')
1457+ def test_returns_dbus_name_when_passed_star(self, po_cs):
1458+ name = self.getUniqueString()
1459+ po_cs.return_value = (name, None, None)
1460+ fake_dbus_backend = Mock()
1461+
1462+ self.assertEqual(
1463+ name,
1464+ _po._get_applicable_class_name(
1465+ '*',
1466+ fake_dbus_backend
1467+ )
1468+ )
1469+
1470+ def test_returns_name_of_string(self):
1471+ name = self.getUniqueString()
1472+ self.assertEqual(name, _po._get_applicable_class_name(name, None))

Subscribers

People subscribed via source and target branches