Merge lp:~veebers/autopilot/1.4_fixing_backend_being_none into lp:autopilot

Proposed by Christopher Lee
Status: Merged
Approved by: Christopher Lee
Approved revision: 351
Merged at revision: 350
Proposed branch: lp:~veebers/autopilot/1.4_fixing_backend_being_none
Merge into: lp:autopilot
Prerequisite: lp:~veebers/autopilot/generic_proxy_objects_id
Diff against target: 340 lines (+78/-61)
7 files modified
autopilot/introspection/__init__.py (+10/-10)
autopilot/introspection/dbus.py (+16/-37)
autopilot/introspection/qt.py (+1/-1)
autopilot/tests/unit/test_introspection_features.py (+19/-10)
autopilot/tests/unit/test_matchers.py (+1/-1)
autopilot/tests/unit/test_types.py (+3/-2)
docs/porting/porting.rst (+28/-0)
To merge this branch: bzr merge lp:~veebers/autopilot/1.4_fixing_backend_being_none
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Thomi Richards (community) Approve
Review via email: mp+190022@code.launchpad.net

This proposal supersedes a proposal from 2013-10-07.

Commit message

Fixes issue where a classes _Backend can be None causes uncaught exceptions.

Description of the change

Fixes issue where a classes _Backend can be None causes uncaught exceptions.
The backend is now stored as an instance variable.

Forward port of: https://code.launchpad.net/~veebers/autopilot/fixing_backend_being_none/+merge/188762

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote : Posted in a previous version of this proposal

LGTM

review: Approve
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

LGTM.

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

FAILED: Continuous integration, rev:348
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~veebers/autopilot/1.4_fixing_backend_being_none/+merge/190022/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/autopilot-ci/264/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-saucy-amd64-ci/192
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-saucy-armhf-ci/192
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-saucy-i386-ci/36

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/autopilot-ci/264/rebuild

review: Needs Fixing (continuous-integration)
349. By Christopher Lee

Added porting notes.

350. By Christopher Lee

Fixed the documentation

351. By Christopher Lee

Merge trunk

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'autopilot/introspection/__init__.py'
--- autopilot/introspection/__init__.py 2013-09-29 22:45:15 +0000
+++ autopilot/introspection/__init__.py 2013-10-09 23:03:58 +0000
@@ -43,7 +43,6 @@
43 AP_INTROSPECTION_IFACE,43 AP_INTROSPECTION_IFACE,
44)44)
45from autopilot.introspection.dbus import (45from autopilot.introspection.dbus import (
46 _clear_backends_for_proxy_object,
47 CustomEmulatorBase,46 CustomEmulatorBase,
48 DBusIntrospectionObject,47 DBusIntrospectionObject,
49 get_classname_from_path,48 get_classname_from_path,
@@ -485,16 +484,17 @@
485 proxy_bases = proxy_bases + (emulator_base, )484 proxy_bases = proxy_bases + (emulator_base, )
486 cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source)485 cls_name, cls_state = _get_proxy_object_class_name_and_state(data_source)
487486
488 _clear_backends_for_proxy_object(emulator_base)487 # Merge the object hierarchy.
489 clsobj = type(488 clsobj = type(str("%sBase" % cls_name), proxy_bases, {})
490 # Merge the object hierarchy.
491 str("%sBase" % cls_name), proxy_bases, dict(_Backend=data_source)
492 )
493489
494 proxy_class = type(str(cls_name), (clsobj,), {})490 proxy_class = type(str(cls_name), (clsobj,), {})
495491
496 proxy = proxy_class.get_root_instance()492 try:
497 return proxy493 dbus_tuple = data_source.introspection_iface.GetState("/")[0]
494 path, state = dbus_tuple
495 return proxy_class(state, path, data_source)
496 except IndexError:
497 raise RuntimeError("Unable to find root object of %r" % proxy_class)
498498
499499
500def _get_proxy_object_base_classes(backend):500def _get_proxy_object_base_classes(backend):
@@ -529,8 +529,8 @@
529class ApplicationProxyObject(DBusIntrospectionObject):529class ApplicationProxyObject(DBusIntrospectionObject):
530 """A class that better supports query data from an application."""530 """A class that better supports query data from an application."""
531531
532 def __init__(self, state, path):532 def __init__(self, state, path, backend):
533 super(ApplicationProxyObject, self).__init__(state, path)533 super(ApplicationProxyObject, self).__init__(state, path, backend)
534 self._process = None534 self._process = None
535535
536 def set_process(self, process):536 def set_process(self, process):
537537
=== modified file 'autopilot/introspection/dbus.py'
--- autopilot/introspection/dbus.py 2013-09-29 22:45:15 +0000
+++ autopilot/introspection/dbus.py 2013-10-09 23:03:58 +0000
@@ -126,20 +126,6 @@
126 return class_object126 return class_object
127127
128128
129def _clear_backends_for_proxy_object(proxy_object):
130 """Iterate over the object registry and clear the dbus backend address set
131 on any class that has the same id as the specified proxy_object.
132
133 This is required so consecutive tests do not end up re-using the same dbus
134 backend address as a previous run, which will probably be incorrect.
135
136 """
137 global _object_registry
138 for cls in _object_registry[proxy_object._id].values():
139 if type(proxy_object) is not cls:
140 cls._Backend = None
141
142
143def get_classname_from_path(object_path):129def get_classname_from_path(object_path):
144 return object_path.split("/")[-1]130 return object_path.split("/")[-1]
145131
@@ -174,14 +160,13 @@
174160
175 """161 """
176162
177 _Backend = None163 def __init__(self, state_dict, path, backend):
178
179 def __init__(self, state_dict, path):
180 self.__state = {}164 self.__state = {}
181 self.__refresh_on_attribute = True165 self.__refresh_on_attribute = True
182 self._set_properties(state_dict)166 self._set_properties(state_dict)
183 self._path = path167 self._path = path
184 self._poll_time = 10168 self._poll_time = 10
169 self._backend = backend
185170
186 def _set_properties(self, state_dict):171 def _set_properties(self, state_dict):
187 """Creates and set attributes of *self* based on contents of172 """Creates and set attributes of *self* based on contents of
@@ -484,8 +469,7 @@
484 _, new_state = self.get_new_state()469 _, new_state = self.get_new_state()
485 self._set_properties(new_state)470 self._set_properties(new_state)
486471
487 @classmethod472 def get_all_instances(self):
488 def get_all_instances(cls):
489 """Get all instances of this class that exist within the Application473 """Get all instances of this class that exist within the Application
490 state tree.474 state tree.
491475
@@ -503,23 +487,22 @@
503 :return: List (possibly empty) of class instances.487 :return: List (possibly empty) of class instances.
504488
505 """489 """
506 cls_name = cls.__name__490 cls_name = type(self).__name__
507 instances = cls.get_state_by_path("//%s" % (cls_name))491 instances = self.get_state_by_path("//%s" % (cls_name))
508 return [cls.make_introspection_object(i) for i in instances]492 return [self.make_introspection_object(i) for i in instances]
509493
510 @classmethod494 def get_root_instance(self):
511 def get_root_instance(cls):
512 """Get the object at the root of this tree.495 """Get the object at the root of this tree.
513496
514 This will return an object that represents the root of the497 This will return an object that represents the root of the
515 introspection tree.498 introspection tree.
516499
517 """500 """
518 instances = cls.get_state_by_path("/")501 instances = self.get_state_by_path("/")
519 if len(instances) != 1:502 if len(instances) != 1:
520 logger.error("Could not retrieve root object.")503 logger.error("Could not retrieve root object.")
521 return None504 return None
522 return cls.make_introspection_object(instances[0])505 return self.make_introspection_object(instances[0])
523506
524 def __getattr__(self, name):507 def __getattr__(self, name):
525 # avoid recursion if for some reason we have no state set (should never508 # avoid recursion if for some reason we have no state set (should never
@@ -536,8 +519,7 @@
536 "Class '%s' has no attribute '%s'." %519 "Class '%s' has no attribute '%s'." %
537 (self.__class__.__name__, name))520 (self.__class__.__name__, name))
538521
539 @classmethod522 def get_state_by_path(self, piece):
540 def get_state_by_path(cls, piece):
541 """Get state for a particular piece of the state tree.523 """Get state for a particular piece of the state tree.
542524
543 You should probably never need to call this directly.525 You should probably never need to call this directly.
@@ -552,7 +534,7 @@
552 "XPath query must be a string, not %r", type(piece))534 "XPath query must be a string, not %r", type(piece))
553535
554 with Timer("GetState %s" % piece):536 with Timer("GetState %s" % piece):
555 data = cls._Backend.introspection_iface.GetState(piece)537 data = self._backend.introspection_iface.GetState(piece)
556 if len(data) > 15:538 if len(data) > 15:
557 logger.warning(539 logger.warning(
558 "Your query '%s' returned a lot of data (%d items). This "540 "Your query '%s' returned a lot of data (%d items). This "
@@ -584,8 +566,7 @@
584 else:566 else:
585 return self._path + "[id=%d]" % self.id567 return self._path + "[id=%d]" % self.id
586568
587 @classmethod569 def make_introspection_object(self, dbus_tuple):
588 def make_introspection_object(cls, dbus_tuple):
589 """Make an introspection object given a DBus tuple of570 """Make an introspection object given a DBus tuple of
590 (path, state_dict).571 (path, state_dict).
591572
@@ -594,23 +575,21 @@
594 path, state = dbus_tuple575 path, state = dbus_tuple
595 name = get_classname_from_path(path)576 name = get_classname_from_path(path)
596 try:577 try:
597 class_type = _object_registry[cls._id][name]578 class_type = _object_registry[self._id][name]
598 if class_type._Backend is None:
599 class_type._Backend = cls._Backend
600 except KeyError:579 except KeyError:
601 get_debug_logger().warning(580 get_debug_logger().warning(
602 "Generating introspection instance for type '%s' based on "581 "Generating introspection instance for type '%s' based on "
603 "generic class.", name)582 "generic class.", name)
604 # we want the object to inherit from the custom emulator base, not583 # we want the object to inherit from the custom emulator base, not
605 # the object class that is doing the selecting584 # the object class that is doing the selecting
606 for base in cls.__bases__:585 for base in type(self).__bases__:
607 if issubclass(base, CustomEmulatorBase):586 if issubclass(base, CustomEmulatorBase):
608 base_class = base587 base_class = base
609 break588 break
610 else:589 else:
611 base_class = cls590 base_class = type(self)
612 class_type = type(str(name), (base_class,), {})591 class_type = type(str(name), (base_class,), {})
613 return class_type(state, path)592 return class_type(state, path, self._backend)
614593
615 @contextmanager594 @contextmanager
616 def no_automatic_refreshing(self):595 def no_automatic_refreshing(self):
617596
=== modified file 'autopilot/introspection/qt.py'
--- autopilot/introspection/qt.py 2013-09-18 23:14:02 +0000
+++ autopilot/introspection/qt.py 2013-10-09 23:03:58 +0000
@@ -100,7 +100,7 @@
100100
101 """101 """
102102
103 return self._Backend.qt_introspection_iface103 return self._backend.qt_introspection_iface
104104
105 @property105 @property
106 def slots(self):106 def slots(self):
107107
=== modified file 'autopilot/tests/unit/test_introspection_features.py'
--- autopilot/tests/unit/test_introspection_features.py 2013-09-27 05:55:31 +0000
+++ autopilot/tests/unit/test_introspection_features.py 2013-10-09 23:03:58 +0000
@@ -18,7 +18,7 @@
18#18#
1919
2020
21from mock import patch21from mock import patch, Mock
22from testtools import TestCase22from testtools import TestCase
23from testtools.matchers import Equals, NotEquals23from testtools.matchers import Equals, NotEquals
24from testscenarios import TestWithScenarios24from testscenarios import TestWithScenarios
@@ -120,7 +120,8 @@
120 def test_can_access_path_attribute(self):120 def test_can_access_path_attribute(self):
121 fake_object = DBusIntrospectionObject(121 fake_object = DBusIntrospectionObject(
122 dict(id=[0, 123], path=[0, '/some/path']),122 dict(id=[0, 123], path=[0, '/some/path']),
123 '/'123 '/',
124 Mock()
124 )125 )
125 with fake_object.no_automatic_refreshing():126 with fake_object.no_automatic_refreshing():
126 self.assertThat(fake_object.path, Equals('/some/path'))127 self.assertThat(fake_object.path, Equals('/some/path'))
@@ -132,10 +133,14 @@
132 'large' is defined as more than 15.133 'large' is defined as more than 15.
133134
134 """135 """
135 with patch.object(DBusIntrospectionObject, '_Backend') as p:136 fake_object = DBusIntrospectionObject(
136 p.introspection_iface.GetState.return_value = \137 dict(id=[0, 123], path=[0, '/some/path']),
137 [('/path', {}) for i in range(16)]138 '/',
138 DBusIntrospectionObject.get_state_by_path('some_query')139 Mock()
140 )
141 fake_object._backend.introspection_iface.GetState.return_value = \
142 [('/path', {}) for i in range(16)]
143 fake_object.get_state_by_path('some_query')
139144
140 mock_logger.warning.assert_called_once_with(145 mock_logger.warning.assert_called_once_with(
141 "Your query '%s' returned a lot of data (%d items). This "146 "Your query '%s' returned a lot of data (%d items). This "
@@ -151,9 +156,13 @@
151 'small' is defined as 15 or fewer.156 'small' is defined as 15 or fewer.
152157
153 """158 """
154 with patch.object(DBusIntrospectionObject, '_Backend') as p:159 fake_object = DBusIntrospectionObject(
155 p.introspection_iface.GetState.return_value = \160 dict(id=[0, 123], path=[0, '/some/path']),
156 [('/path', {}) for i in range(15)]161 '/',
157 DBusIntrospectionObject.get_state_by_path('some_query')162 Mock()
163 )
164 fake_object._backend.introspection_iface.GetState.return_value = \
165 [('/path', {}) for i in range(15)]
166 fake_object.get_state_by_path('some_query')
158167
159 self.assertThat(mock_logger.warning.called, Equals(False))168 self.assertThat(mock_logger.warning.called, Equals(False))
160169
=== modified file 'autopilot/tests/unit/test_matchers.py'
--- autopilot/tests/unit/test_matchers.py 2013-09-16 17:17:35 +0000
+++ autopilot/tests/unit/test_matchers.py 2013-10-09 23:03:58 +0000
@@ -66,7 +66,7 @@
66 """66 """
67 class FakeObject(DBusIntrospectionObject):67 class FakeObject(DBusIntrospectionObject):
68 def __init__(self, props):68 def __init__(self, props):
69 super(FakeObject, self).__init__(props, "/FakeObject")69 super(FakeObject, self).__init__(props, "/FakeObject", None)
70 FakeObject._fake_props = props70 FakeObject._fake_props = props
7171
72 @classmethod72 @classmethod
7373
=== modified file 'autopilot/tests/unit/test_types.py'
--- autopilot/tests/unit/test_types.py 2013-09-26 23:34:52 +0000
+++ autopilot/tests/unit/test_types.py 2013-10-09 23:03:58 +0000
@@ -20,7 +20,7 @@
20from __future__ import absolute_import20from __future__ import absolute_import
2121
22from datetime import datetime, time22from datetime import datetime, time
23from mock import patch23from mock import patch, Mock
24from testscenarios import TestWithScenarios24from testscenarios import TestWithScenarios
25from testtools import TestCase25from testtools import TestCase
26from testtools.matchers import Equals, IsInstance, NotEquals, raises26from testtools.matchers import Equals, IsInstance, NotEquals, raises
@@ -636,7 +636,8 @@
636 """636 """
637 DBusIntrospectionObject(637 DBusIntrospectionObject(
638 dict(foo=[0]),638 dict(foo=[0]),
639 '/some/dummy/path'639 '/some/dummy/path',
640 Mock()
640 )641 )
641 error_logger.assert_called_once_with(642 error_logger.assert_called_once_with(
642 "While constructing attribute '%s.%s': %s",643 "While constructing attribute '%s.%s': %s",
643644
=== modified file 'docs/porting/porting.rst'
--- docs/porting/porting.rst 2013-09-26 22:54:34 +0000
+++ docs/porting/porting.rst 2013-10-09 23:03:58 +0000
@@ -51,6 +51,30 @@
5151
52 my_obj = self.app.wait_select_single("MyObject")52 my_obj = self.app.wait_select_single("MyObject")
5353
54.. _dbus_backends:
55
56DBus backends and :class:`~autopilot.introspection.dbus.DBusIntrospectionObject` changes
57++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
58
59Due to a change in how
60:class:`~autopilot.introspection.dbus.DBusIntrospectionObject` objects store
61their DBus backend a couple of classmethods have now become instance methods.
62
63These affected methods are:
64
65 * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_all_instances`
66 * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_root_instance`
67 * :meth:`~autopilot.introspection.dbus.DBusIntrospectionObject.get_state_by_path`
68
69For example, if your old code is something along the lines of::
70
71 all_keys = KeyCustomEmulator.get_all_instances()
72
73You will instead need to have something like this instead::
74
75 all_keys = app_proxy.select_many(KeyCustomEmulator)
76
77
54Python 378Python 3
55++++++++79++++++++
5680
@@ -70,6 +94,10 @@
70* A large code cleanup and reorganisation. In particular, lots of code that came from the Unity 3D codebase has been removed if it was deemed to not be useful to the majority of test authors. This code cleanup includes a flattening of the autopilot namespace. Previously, many useful classes lived under the ``autopilot.emulators`` namespace. These have now been moved into the ``autopilot`` namespace.94* A large code cleanup and reorganisation. In particular, lots of code that came from the Unity 3D codebase has been removed if it was deemed to not be useful to the majority of test authors. This code cleanup includes a flattening of the autopilot namespace. Previously, many useful classes lived under the ``autopilot.emulators`` namespace. These have now been moved into the ``autopilot`` namespace.
7195
7296
97.. note:: There is an API breakage in autopilot 1.3. The changes outlined under
98 the heading ":ref:`dbus_backends`" apply to version
99 1.3.1+13.10.20131003.1-0ubuntu1 and onwards .
100
73``QtIntrospectionTestMixin`` and ``GtkIntrospectionTestMixin`` no longer exist101``QtIntrospectionTestMixin`` and ``GtkIntrospectionTestMixin`` no longer exist
74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
75103

Subscribers

People subscribed via source and target branches