Merge lp:~thomir/unity/autopilot-attribute-feature into lp:unity

Proposed by Thomi Richards on 2012-04-12
Status: Merged
Approved by: Thomi Richards on 2012-04-17
Approved revision: 2229
Merged at revision: 2287
Proposed branch: lp:~thomir/unity/autopilot-attribute-feature
Merge into: lp:unity
Diff against target: 257 lines (+77/-42)
5 files modified
tests/autopilot/autopilot/emulators/unity/__init__.py (+54/-3)
tests/autopilot/autopilot/emulators/unity/dash.py (+10/-28)
tests/autopilot/autopilot/emulators/unity/hud.py (+4/-0)
tests/autopilot/autopilot/tests/__init__.py (+3/-3)
tests/autopilot/autopilot/tests/test_dash.py (+6/-8)
To merge this branch: bzr merge lp:~thomir/unity/autopilot-attribute-feature
Reviewer Review Type Date Requested Status
Marco Trevisan (Treviño) Approve on 2012-04-13
Brandon Schaefer (community) 2012-04-12 Approve on 2012-04-12
Review via email: mp+101823@code.launchpad.net

Commit Message

Added the autopilot wait_for attribute feature.

Description of the Change

Problem:

We often write code in autopilkot emulators that looks something like this:

for i in range(11):
    if self.some_attribute != expected_value:
        sleep(1)
        self.refresh_state()
raise RuntimeError("Attribute 'some_attribute' not set to expected_value after 10 seconds")

...since duplicated code is bad, we'd like instead to be able to write this like so:

self.some_attribute.wait_for(expected_value)

... while still using 'self.some_attribute' as an ordinary variable.

Solution:

This branch does exactly that. It also converts the dash and hud emulators to use this new feature.

To post a comment you must log in.
Brandon Schaefer (brandontschaefer) wrote :

Nice clean up!

review: Approve
Marco Trevisan (Treviño) (3v1n0) wrote :

Cool!

review: Approve
Marco Trevisan (Treviño) (3v1n0) wrote :

Ah, just one thing: what about to increase the number of the checks and reducing the sleep time (reducing the sleep time to .25 or .5...)?
AP is already quite slow as it is now.

Thomi Richards (thomir) wrote :

> Ah, just one thing: what about to increase the number of the checks and
> reducing the sleep time (reducing the sleep time to .25 or .5...)?
> AP is already quite slow as it is now.

Hi Marco,

I'm happy to experiment with the timings, but right now I'd rather not hot the dbus interface too hard. Perhaps in the Q cycle we can look at making this more responsive.

Unity Merger (unity-merger) wrote :

No commit message specified.

Unity Merger (unity-merger) wrote :

No commit message specified.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/autopilot/autopilot/emulators/unity/__init__.py'
2--- tests/autopilot/autopilot/emulators/unity/__init__.py 2012-03-13 20:35:42 +0000
3+++ tests/autopilot/autopilot/emulators/unity/__init__.py 2012-04-12 22:27:24 +0000
4@@ -9,6 +9,7 @@
5
6 from dbus import Interface
7 import logging
8+from time import sleep
9
10 from autopilot.emulators.dbus_handler import session_bus
11
12@@ -28,8 +29,9 @@
13 class IntrospectableObjectMetaclass(type):
14 """Metaclass to insert appropriate classes into the object registry."""
15
16- def __new__(self, classname, bases, classdict):
17- class_object = type.__new__(self, classname, bases, classdict)
18+ def __new__(cls, classname, bases, classdict):
19+ """Add class name to type registry."""
20+ class_object = type.__new__(cls, classname, bases, classdict)
21 _object_registry[classname] = class_object
22 return class_object
23
24@@ -134,7 +136,56 @@
25 # don't store id in state dictionary -make it a proper instance attribute
26 if key == 'id':
27 self.id = value
28- self.__state[key.replace('-', '_')] = value
29+ key = key.replace('-', '_')
30+ self.__state[key] = self._make_attribute(key, value)
31+
32+ def _make_attribute(self, name, value):
33+ """Make an attribute for 'value', patched with the wait_for function."""
34+
35+ def wait_for(self, expected_value):
36+ """Wait up to 10 seconds for our value to change to 'expected_value'.
37+
38+ This works by refreshing the value using repeated dbus calls.
39+
40+ Raises RuntimeError if the attribute was not equal to the expected value
41+ after 10 seconds.
42+
43+ """
44+ # It's guaranteed that our value is up to date, since __getattr__ calls
45+ # refresh_state. This if statement stops us waiting if the value is
46+ # already what we expect:
47+ if self == expected_value:
48+ return
49+
50+ for i in range(11):
51+ new_state = get_state_by_name_and_id(
52+ self.parent.__class__.__name__,
53+ self.parent.id)
54+ if new_state[self.name] == expected_value:
55+ self.parent.set_properties(new_state)
56+ return
57+ sleep(1)
58+
59+ raise RuntimeError("Error: %s.%s was not equal to %r after 10 seconds."
60+ % (self.parent.__class__.__name__,
61+ self.name,
62+ expected_value))
63+
64+ # This looks like magic, but it's really not. We're creating a new type
65+ # on the fly that derives from the type of 'value' with a couple of
66+ # extra attributes: wait_for is the wait_for method above. 'parent' and
67+ # 'name' are needed by the wait_for method.
68+ #
69+ # We can't use traditional meta-classes here, since the type we're
70+ # deriving from is only known at call time, not at parse time (we could
71+ # override __call__ in the meta class, but that doesn't buy us anything
72+ # extra).
73+ #
74+ # A better way to do this would be with functools.partial, which I tried
75+ # initially, but doesn't work well with bound methods.
76+ t = type(value)
77+ attrs = {'wait_for': wait_for, 'parent':self, 'name':name}
78+ return type(t.__name__, (t,), attrs)(value)
79
80 def _get_child_tuples_by_type(self, desired_type):
81 """Get a list of (name,dict) pairs from children of the specified type.
82
83=== modified file 'tests/autopilot/autopilot/emulators/unity/dash.py'
84--- tests/autopilot/autopilot/emulators/unity/dash.py 2012-04-10 16:47:01 +0000
85+++ tests/autopilot/autopilot/emulators/unity/dash.py 2012-04-12 22:27:24 +0000
86@@ -7,8 +7,6 @@
87 # by the Free Software Foundation.
88 #
89
90-from time import sleep
91-
92 from autopilot.emulators.unity import (
93 get_state_by_path,
94 make_introspection_object,
95@@ -43,9 +41,10 @@
96 """
97 Reveals the dash if it's currently hidden, hides it otherwise.
98 """
99+ old_state = self.visible
100 logger.debug("Toggling dash visibility with Super key.")
101 self.keybinding("dash/reveal", 0.1)
102- sleep(1)
103+ self.visible.wait_for(not old_state)
104
105 def ensure_visible(self, clear_search=True):
106 """
107@@ -53,7 +52,7 @@
108 """
109 if not self.visible:
110 self.toggle_reveal()
111- self._wait_for_visibility(expect_visible=True)
112+ self.visible.wait_for(True)
113 if clear_search:
114 self.clear_search()
115
116@@ -63,16 +62,7 @@
117 """
118 if self.visible:
119 self.toggle_reveal()
120- self._wait_for_visibility(expect_visible=False)
121-
122- def _wait_for_visibility(self, expect_visible):
123- for i in range(11):
124- if self.visible != expect_visible:
125- sleep(1)
126- else:
127- return
128- raise RuntimeError("Dash not %s after waiting for 10 seconds." %
129- ("Visible" if expect_visible else "Hidden"))
130+ self.visible.wait_for(False)
131
132 @property
133 def visible(self):
134@@ -260,7 +250,8 @@
135 return filter_label
136 return None
137
138- def is_expanded(self):
139+ @property
140+ def expanded(self):
141 """Return True if the filterbar on this lens is expanded, False otherwise.
142 """
143 searchbar = self._get_searchbar()
144@@ -268,34 +259,25 @@
145
146 def ensure_expanded(self):
147 """Expand the filter bar, if it's not already."""
148- if not self.is_expanded():
149+ if not self.expanded:
150 searchbar = self._get_searchbar()
151 tx = searchbar.filter_label_x + (searchbar.filter_label_width / 2)
152 ty = searchbar.filter_label_y + (searchbar.filter_label_height / 2)
153 m = Mouse()
154 m.move(tx, ty)
155 m.click()
156- self._wait_for_expansion(True)
157+ self.expanded.wait_for(True)
158
159 def ensure_collapsed(self):
160 """Collapse the filter bar, if it's not already."""
161- if self.is_expanded():
162+ if self.expanded:
163 searchbar = self._get_searchbar()
164 tx = searchbar.filter_label_x + (searchbar.filter_label_width / 2)
165 ty = searchbar.filter_label_y + (searchbar.filter_label_height / 2)
166 m = Mouse()
167 m.move(tx, ty)
168 m.click()
169- self._wait_for_expansion(False)
170-
171- def _wait_for_expansion(self, expect_expanded):
172- for i in range(11):
173- if self.is_expanded() != expect_expanded:
174- sleep(1)
175- else:
176- return
177- raise RuntimeError("Filters not %s after waiting for 10 seconds." %
178- ("expanded" if expect_expanded else "collapsed"))
179+ self.expanded.wait_for(False)
180
181 def _get_searchbar(self):
182 """Get the searchbar.
183
184=== modified file 'tests/autopilot/autopilot/emulators/unity/hud.py'
185--- tests/autopilot/autopilot/emulators/unity/hud.py 2012-04-03 11:48:10 +0000
186+++ tests/autopilot/autopilot/emulators/unity/hud.py 2012-04-12 22:27:24 +0000
187@@ -26,15 +26,19 @@
188 """Hides the hud if it's not already hidden."""
189 if self.visible:
190 self.toggle_reveal()
191+ self.visible.wait_for(False)
192
193 def ensure_visible(self):
194 """Shows the hud if it's not already showing."""
195 if not self.visible:
196 self.toggle_reveal()
197+ self.visible.wait_for(True)
198
199 def toggle_reveal(self, tap_delay=0.1):
200 """Tap the 'Alt' key to toggle the hud visibility."""
201+ old_state = self.visible
202 self.keybinding("hud/reveal", tap_delay)
203+ self.visible.wait_for(not old_state)
204
205 def get_embedded_icon(self):
206 """Returns the HUD view embedded icon or None if is not shown."""
207
208=== modified file 'tests/autopilot/autopilot/tests/__init__.py'
209--- tests/autopilot/autopilot/tests/__init__.py 2012-04-11 06:32:50 +0000
210+++ tests/autopilot/autopilot/tests/__init__.py 2012-04-12 22:27:24 +0000
211@@ -292,10 +292,10 @@
212 keyboard layout bits are very unweildy. This seems like the best
213 solution, even a little bit brutish.
214 """
215- cmd = ['gsettings', command, schema] + args
216+ cmd = ['gsettings', command, schema] + list(args)
217 # strip to remove the trailing \n.
218- ret = check_output(cmd, shell=True).strip()
219- time.sleep(1)
220+ ret = check_output(cmd).strip()
221+ time.sleep(5)
222 reset_display()
223 return ret
224
225
226=== modified file 'tests/autopilot/autopilot/tests/test_dash.py'
227--- tests/autopilot/autopilot/tests/test_dash.py 2012-04-11 09:32:41 +0000
228+++ tests/autopilot/autopilot/tests/test_dash.py 2012-04-12 22:27:24 +0000
229@@ -17,6 +17,7 @@
230 class DashTestCase(AutopilotTestCase):
231 def setUp(self):
232 super(DashTestCase, self).setUp()
233+ self.set_unity_log_level("unity", "DEBUG")
234 self.set_unity_log_level("unity.shell", "DEBUG")
235 self.set_unity_log_level("unity.launcher", "DEBUG")
236 self.dash.ensure_hidden()
237@@ -76,15 +77,12 @@
238
239 class DashMultiKeyTests(DashSearchInputTests):
240 def setUp(self):
241- def set_multi_key():
242- """Binds Multi_key to caps lock"""
243- old_value = "\"%s\"" % self.call_gsettings_cmd('get', 'org.gnome.libgnomekbd.keyboard', '"options"')
244- self.addCleanup(self.call_gsettings_cmd, 'set', 'org.gnome.libgnomekbd.keyboard', '"options"', old_value)
245- self.call_gsettings_cmd('set', 'org.gnome.libgnomekbd.keyboard', '"options"', "\"['Compose key\tcompose:caps']\"")
246-
247 # set the multi key first so that we're not getting a new _DISPLAY while keys are held down.
248- set_multi_key()
249- super(DashMultiKeyTests, self).setUp()
250+ old_value = self.call_gsettings_cmd('get', 'org.gnome.libgnomekbd.keyboard', 'options')
251+ self.addCleanup(self.call_gsettings_cmd, 'set', 'org.gnome.libgnomekbd.keyboard', 'options', old_value)
252+ self.call_gsettings_cmd('set', 'org.gnome.libgnomekbd.keyboard', 'options', "['Compose key\tcompose:caps']")
253+
254+ super(DashMultiKeyTests, self).setUp()
255
256 def test_multi_key(self):
257 """Pressing 'Multi_key' must not add any characters to the search."""