Merge lp:~elopio/autopilot/no_uinput_side-effects2 into lp:autopilot
- no_uinput_side-effects2
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~elopio/autopilot/no_uinput_side-effects2 |
Merge into: | lp:autopilot |
Diff against target: |
1143 lines (+731/-202) 4 files modified
autopilot/input/_uinput.py (+258/-192) autopilot/tests/functional/test_input_stack.py (+6/-5) autopilot/tests/unit/test_input.py (+465/-5) debian/control (+2/-0) |
To merge this branch: | bzr merge lp:~elopio/autopilot/no_uinput_side-effects2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Autopilot Hackers | Pending | ||
Review via email:
|
This proposal supersedes a proposal from 2014-01-21.
This proposal has been superseded by a proposal from 2014-01-21.
Commit message
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:426
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:/
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:427
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:/
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:430
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:/
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:431
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:/
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:433
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:/
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
Preview Diff
1 | === modified file 'autopilot/input/_uinput.py' |
2 | --- autopilot/input/_uinput.py 2013-11-07 05:53:36 +0000 |
3 | +++ autopilot/input/_uinput.py 2014-01-21 22:20:18 +0000 |
4 | @@ -1,7 +1,7 @@ |
5 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
6 | # |
7 | # Autopilot Functional Test Tool |
8 | -# Copyright (C) 2012-2013 Canonical |
9 | +# Copyright (C) 2012, 2013, 2014 Canonical |
10 | # |
11 | # This program is free software: you can redistribute it and/or modify |
12 | # it under the terms of the GNU General Public License as published by |
13 | @@ -20,10 +20,10 @@ |
14 | |
15 | """UInput device drivers.""" |
16 | |
17 | +from autopilot import utilities |
18 | from autopilot.input import Keyboard as KeyboardBase |
19 | from autopilot.input import Touch as TouchBase |
20 | from autopilot.input._common import get_center_point |
21 | -from autopilot.utilities import sleep |
22 | import autopilot.platform |
23 | |
24 | import logging |
25 | @@ -33,11 +33,6 @@ |
26 | |
27 | logger = logging.getLogger(__name__) |
28 | |
29 | -PRESS = 1 |
30 | -RELEASE = 0 |
31 | - |
32 | -_PRESSED_KEYS = [] |
33 | - |
34 | |
35 | def _get_devnode_path(): |
36 | """Provide a fallback uinput node for devices which don't support udev""" |
37 | @@ -47,13 +42,76 @@ |
38 | return devnode |
39 | |
40 | |
41 | +class _UInputKeyboardDevice(object): |
42 | + """Wrapper for the UInput Keyboard to execute its primitives.""" |
43 | + |
44 | + def __init__(self, device_class=UInput): |
45 | + super(_UInputKeyboardDevice, self).__init__() |
46 | + self._device = device_class(devnode=_get_devnode_path()) |
47 | + self._pressed_keys_ecodes = [] |
48 | + |
49 | + def press(self, key): |
50 | + """Press one key button. |
51 | + |
52 | + It ignores case, so, for example, 'a' and 'A' are mapped to the same |
53 | + key. |
54 | + |
55 | + """ |
56 | + ecode = self._get_ecode_for_key(key) |
57 | + logger.debug('Pressing %s (%r).', key, ecode) |
58 | + self._emit_press_event(ecode) |
59 | + self._pressed_keys_ecodes.append(ecode) |
60 | + |
61 | + def _get_ecode_for_key(self, key): |
62 | + key_name = key if key.startswith('KEY_') else 'KEY_' + key |
63 | + key_name = key_name.upper() |
64 | + ecode = e.ecodes.get(key_name, None) |
65 | + if ecode is None: |
66 | + raise ValueError('Unknown key name: %s.' % key) |
67 | + return ecode |
68 | + |
69 | + def _emit_press_event(self, ecode): |
70 | + press_value = 1 |
71 | + self._emit(ecode, press_value) |
72 | + |
73 | + def _emit(self, ecode, value): |
74 | + self._device.write(e.EV_KEY, ecode, value) |
75 | + self._device.syn() |
76 | + |
77 | + def release(self, key): |
78 | + """Release one key button. |
79 | + |
80 | + It ignores case, so, for example, 'a' and 'A' are mapped to the same |
81 | + key. |
82 | + |
83 | + """ |
84 | + ecode = self._get_ecode_for_key(key) |
85 | + if ecode in self._pressed_keys_ecodes: |
86 | + logger.debug('Releasing %s (%r).', key, ecode) |
87 | + self._emit_release_event(ecode) |
88 | + self._pressed_keys_ecodes.remove(ecode) |
89 | + else: |
90 | + raise ValueError('Key %r not pressed.' % key) |
91 | + |
92 | + def _emit_release_event(self, ecode): |
93 | + release_value = 0 |
94 | + self._emit(ecode, release_value) |
95 | + |
96 | + def release_pressed_keys(self): |
97 | + """Release all the keys that are currently pressed.""" |
98 | + for ecode in self._pressed_keys_ecodes: |
99 | + self._emit_release_event(ecode) |
100 | + self._pressed_keys_ecodes = [] |
101 | + |
102 | + |
103 | class Keyboard(KeyboardBase): |
104 | |
105 | - _device = UInput(devnode=_get_devnode_path()) |
106 | + _device = None |
107 | |
108 | - def _emit(self, event, value): |
109 | - Keyboard._device.write(e.EV_KEY, event, value) |
110 | - Keyboard._device.syn() |
111 | + def __init__(self, device_class=_UInputKeyboardDevice): |
112 | + super(Keyboard, self).__init__() |
113 | + if Keyboard._device is None: |
114 | + Keyboard._device = device_class() |
115 | |
116 | def _sanitise_keys(self, keys): |
117 | if keys == '+': |
118 | @@ -76,11 +134,9 @@ |
119 | raise TypeError("'keys' argument must be a string.") |
120 | |
121 | for key in self._sanitise_keys(keys): |
122 | - for event in Keyboard._get_events_for_key(key): |
123 | - logger.debug("Pressing %s (%r)", key, event) |
124 | - _PRESSED_KEYS.append(event) |
125 | - self._emit(event, PRESS) |
126 | - sleep(delay) |
127 | + for key_button in self._get_key_buttons(key): |
128 | + self._device.press(key_button) |
129 | + utilities.sleep(delay) |
130 | |
131 | def release(self, keys, delay=0.1): |
132 | """Send key release events only. |
133 | @@ -99,12 +155,9 @@ |
134 | raise TypeError("'keys' argument must be a string.") |
135 | |
136 | for key in reversed(self._sanitise_keys(keys)): |
137 | - for event in Keyboard._get_events_for_key(key): |
138 | - logger.debug("Releasing %s (%r)", key, event) |
139 | - if event in _PRESSED_KEYS: |
140 | - _PRESSED_KEYS.remove(event) |
141 | - self._emit(event, RELEASE) |
142 | - sleep(delay) |
143 | + for key_button in reversed(self._get_key_buttons(key)): |
144 | + self._device.release(key_button) |
145 | + utilities.sleep(delay) |
146 | |
147 | def press_and_release(self, keys, delay=0.1): |
148 | """Press and release all items in 'keys'. |
149 | @@ -145,98 +198,33 @@ |
150 | any keys that were pressed and not released. |
151 | |
152 | """ |
153 | - global _PRESSED_KEYS |
154 | - if len(_PRESSED_KEYS) == 0: |
155 | - return |
156 | - |
157 | - def _release(event): |
158 | - Keyboard._device.write(e.EV_KEY, event, RELEASE) |
159 | - Keyboard._device.syn() |
160 | - for event in _PRESSED_KEYS: |
161 | - logger.warning("Releasing key %r as part of cleanup call.", event) |
162 | - _release(event) |
163 | - _PRESSED_KEYS = [] |
164 | - |
165 | - @staticmethod |
166 | - def _get_events_for_key(key): |
167 | - """Return a list of events required to generate 'key' as an input. |
168 | - |
169 | - Multiple keys will be returned when the key specified requires more |
170 | + cls._device.release_pressed_keys() |
171 | + |
172 | + def _get_key_buttons(self, key): |
173 | + """Return a list of the key buttons required to press. |
174 | + |
175 | + Multiple buttons will be returned when the key specified requires more |
176 | than one keypress to generate (for example, upper-case letters). |
177 | |
178 | """ |
179 | - events = [] |
180 | + key_buttons = [] |
181 | if key.isupper() or key in _SHIFTED_KEYS: |
182 | - events.append(e.KEY_LEFTSHIFT) |
183 | - keyname = _UINPUT_CODE_TRANSLATIONS.get(key.upper(), key) |
184 | - evt = getattr(e, 'KEY_' + keyname.upper(), None) |
185 | - if evt is None: |
186 | - raise ValueError("Unknown key name: '%s'" % key) |
187 | - events.append(evt) |
188 | - return events |
189 | - |
190 | - |
191 | -last_tracking_id = 0 |
192 | - |
193 | - |
194 | -def get_next_tracking_id(): |
195 | - global last_tracking_id |
196 | - last_tracking_id += 1 |
197 | - return last_tracking_id |
198 | - |
199 | - |
200 | + key_buttons.append('KEY_LEFTSHIFT') |
201 | + key_name = _UINPUT_CODE_TRANSLATIONS.get(key.upper(), key) |
202 | + key_buttons.append(key_name) |
203 | + return key_buttons |
204 | + |
205 | + |
206 | +@utilities.deprecated('Touch') |
207 | def create_touch_device(res_x=None, res_y=None): |
208 | """Create and return a UInput touch device. |
209 | |
210 | If res_x and res_y are not specified, they will be queried from the system. |
211 | |
212 | """ |
213 | - |
214 | - if res_x is None or res_y is None: |
215 | - from autopilot.display import Display |
216 | - display = Display.create() |
217 | - # TODO: This calculation needs to become part of the display module: |
218 | - l = r = t = b = 0 |
219 | - for screen in range(display.get_num_screens()): |
220 | - geometry = display.get_screen_geometry(screen) |
221 | - if geometry[0] < l: |
222 | - l = geometry[0] |
223 | - if geometry[1] < t: |
224 | - t = geometry[1] |
225 | - if geometry[0] + geometry[2] > r: |
226 | - r = geometry[0] + geometry[2] |
227 | - if geometry[1] + geometry[3] > b: |
228 | - b = geometry[1] + geometry[3] |
229 | - res_x = r - l |
230 | - res_y = b - t |
231 | - |
232 | - # android uses BTN_TOOL_FINGER, whereas desktop uses BTN_TOUCH. I have no |
233 | - # idea why... |
234 | - touch_tool = e.BTN_TOOL_FINGER |
235 | - if autopilot.platform.model() == 'Desktop': |
236 | - touch_tool = e.BTN_TOUCH |
237 | - |
238 | - cap_mt = { |
239 | - e.EV_ABS: [ |
240 | - (e.ABS_X, (0, res_x, 0, 0)), |
241 | - (e.ABS_Y, (0, res_y, 0, 0)), |
242 | - (e.ABS_PRESSURE, (0, 65535, 0, 0)), |
243 | - (e.ABS_MT_POSITION_X, (0, res_x, 0, 0)), |
244 | - (e.ABS_MT_POSITION_Y, (0, res_y, 0, 0)), |
245 | - (e.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)), |
246 | - (e.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)), |
247 | - (e.ABS_MT_PRESSURE, (0, 255, 0, 0)), |
248 | - (e.ABS_MT_SLOT, (0, 9, 0, 0)), |
249 | - ], |
250 | - e.EV_KEY: [ |
251 | - touch_tool, |
252 | - ] |
253 | - } |
254 | - |
255 | - return UInput(cap_mt, name='autopilot-finger', version=0x2, |
256 | - devnode=_get_devnode_path()) |
257 | - |
258 | -_touch_device = create_touch_device() |
259 | + return UInput(events=_get_touch_events(), name='autopilot-finger', |
260 | + version=0x2, devnode=_get_devnode_path()) |
261 | + |
262 | |
263 | # Multiouch notes: |
264 | # ---------------- |
265 | @@ -281,58 +269,177 @@ |
266 | # about this is that the SLOT refers to a finger number, and the TRACKING_ID |
267 | # identifies a unique touch for the duration of it's existance. |
268 | |
269 | -_touch_fingers_in_use = [] |
270 | - |
271 | - |
272 | -def _get_touch_finger(): |
273 | - """Claim a touch finger id for use. |
274 | - |
275 | - :raises: RuntimeError if no more fingers are available. |
276 | - |
277 | - """ |
278 | - global _touch_fingers_in_use |
279 | - |
280 | - for i in range(9): |
281 | - if i not in _touch_fingers_in_use: |
282 | - _touch_fingers_in_use.append(i) |
283 | - return i |
284 | - raise RuntimeError("All available fingers have been used already.") |
285 | - |
286 | - |
287 | -def _release_touch_finger(finger_num): |
288 | - """Relase a previously-claimed finger id. |
289 | - |
290 | - :raises: RuntimeError if the finger given was never claimed, or was already |
291 | - released. |
292 | - |
293 | - """ |
294 | - global _touch_fingers_in_use |
295 | - |
296 | - if finger_num not in _touch_fingers_in_use: |
297 | - raise RuntimeError( |
298 | - "Finger %d was never claimed, or has already been released." % |
299 | - (finger_num)) |
300 | - _touch_fingers_in_use.remove(finger_num) |
301 | - assert(finger_num not in _touch_fingers_in_use) |
302 | + |
303 | +def _get_touch_events(res_x, res_y): |
304 | + if res_x is None or res_y is None: |
305 | + res_x, res_y = _get_system_resolution() |
306 | + |
307 | + touch_tool = _get_touch_tool() |
308 | + |
309 | + events = { |
310 | + e.EV_ABS: [ |
311 | + (e.ABS_X, (0, res_x, 0, 0)), |
312 | + (e.ABS_Y, (0, res_y, 0, 0)), |
313 | + (e.ABS_PRESSURE, (0, 65535, 0, 0)), |
314 | + (e.ABS_MT_POSITION_X, (0, res_x, 0, 0)), |
315 | + (e.ABS_MT_POSITION_Y, (0, res_y, 0, 0)), |
316 | + (e.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)), |
317 | + (e.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)), |
318 | + (e.ABS_MT_PRESSURE, (0, 255, 0, 0)), |
319 | + (e.ABS_MT_SLOT, (0, 9, 0, 0)), |
320 | + ], |
321 | + e.EV_KEY: [ |
322 | + touch_tool, |
323 | + ] |
324 | + } |
325 | + return events |
326 | + |
327 | + |
328 | +def _get_system_resolution(): |
329 | + from autopilot.display import Display |
330 | + display = Display.create() |
331 | + # TODO: This calculation needs to become part of the display module: |
332 | + l = r = t = b = 0 |
333 | + for screen in range(display.get_num_screens()): |
334 | + geometry = display.get_screen_geometry(screen) |
335 | + if geometry[0] < l: |
336 | + l = geometry[0] |
337 | + if geometry[1] < t: |
338 | + t = geometry[1] |
339 | + if geometry[0] + geometry[2] > r: |
340 | + r = geometry[0] + geometry[2] |
341 | + if geometry[1] + geometry[3] > b: |
342 | + b = geometry[1] + geometry[3] |
343 | + res_x = r - l |
344 | + res_y = b - t |
345 | + return res_x, res_y |
346 | + |
347 | + |
348 | +def _get_touch_tool(): |
349 | + # android uses BTN_TOOL_FINGER, whereas desktop uses BTN_TOUCH. I have |
350 | + # no idea why... |
351 | + if autopilot.platform.model() == 'Desktop': |
352 | + touch_tool = e.BTN_TOUCH |
353 | + else: |
354 | + touch_tool = e.BTN_TOOL_FINGER |
355 | + return touch_tool |
356 | + |
357 | + |
358 | +class _UInputTouchDevice(object): |
359 | + """Wrapper for the UInput Touch to execute its primitives. |
360 | + |
361 | + If res_x and res_y are not specified, they will be queried from the system. |
362 | + |
363 | + """ |
364 | + |
365 | + _touch_fingers_in_use = [] |
366 | + _max_number_of_fingers = 9 |
367 | + _last_tracking_id = 0 |
368 | + |
369 | + def __init__(self, res_x=None, res_y=None, device_class=UInput): |
370 | + super(_UInputTouchDevice, self).__init__() |
371 | + self._device = device_class( |
372 | + events=_get_touch_events(res_x, res_y), name='autopilot-finger', |
373 | + version=0x2, devnode=_get_devnode_path()) |
374 | + self._touch_finger_slot = None |
375 | + |
376 | + @property |
377 | + def pressed(self): |
378 | + return self._touch_finger_slot is not None |
379 | + |
380 | + def finger_down(self, x, y): |
381 | + """Internal: moves finger "finger" down on the touchscreen. |
382 | + |
383 | + :param x: The finger will be moved to this x coordinate. |
384 | + :param y: The finger will be moved to this y coordinate. |
385 | + |
386 | + """ |
387 | + if self.pressed: |
388 | + raise RuntimeError("Cannot press finger: it's already pressed.") |
389 | + self._touch_finger_slot = self._get_free_touch_finger_slot() |
390 | + |
391 | + self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot) |
392 | + self._device.write( |
393 | + e.EV_ABS, e.ABS_MT_TRACKING_ID, self._get_next_tracking_id()) |
394 | + press_value = 1 |
395 | + self._device.write(e.EV_KEY, e.BTN_TOOL_FINGER, press_value) |
396 | + self._device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) |
397 | + self._device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) |
398 | + self._device.write(e.EV_ABS, e.ABS_MT_PRESSURE, 400) |
399 | + self._device.syn() |
400 | + |
401 | + def _get_free_touch_finger_slot(self): |
402 | + """Return the id of a free touch finger. |
403 | + |
404 | + :raises: RuntimeError if no more fingers are available. |
405 | + |
406 | + """ |
407 | + for i in range(_UInputTouchDevice._max_number_of_fingers): |
408 | + if i not in _UInputTouchDevice._touch_fingers_in_use: |
409 | + _UInputTouchDevice._touch_fingers_in_use.append(i) |
410 | + return i |
411 | + raise RuntimeError('All available fingers have been used already.') |
412 | + |
413 | + def _get_next_tracking_id(self): |
414 | + _UInputTouchDevice._last_tracking_id += 1 |
415 | + return _UInputTouchDevice._last_tracking_id |
416 | + |
417 | + def finger_move(self, x, y): |
418 | + """Internal: moves finger "finger" on the touchscreen to pos (x,y) |
419 | + |
420 | + NOTE: The finger has to be down for this to have any effect. |
421 | + |
422 | + """ |
423 | + if not self.pressed: |
424 | + raise RuntimeError('Attempting to move without finger being down.') |
425 | + else: |
426 | + self._device.write( |
427 | + e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot) |
428 | + self._device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) |
429 | + self._device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) |
430 | + self._device.syn() |
431 | + |
432 | + def finger_up(self): |
433 | + """Internal: moves finger "finger" up from the touchscreen""" |
434 | + if not self.pressed: |
435 | + raise RuntimeError("Cannot release finger: it's not pressed.") |
436 | + self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot) |
437 | + lift_tracking_id = -1 |
438 | + self._device.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, lift_tracking_id) |
439 | + release_value = 0 |
440 | + self._device.write(e.EV_KEY, e.BTN_TOOL_FINGER, release_value) |
441 | + self._device.syn() |
442 | + self._release_touch_finger() |
443 | + |
444 | + def _release_touch_finger(self): |
445 | + """Release the touch finger.""" |
446 | + if (self._touch_finger_slot not in |
447 | + _UInputTouchDevice._touch_fingers_in_use): |
448 | + raise RuntimeError( |
449 | + "Finger %d was never claimed, or has already been released." % |
450 | + self._touch_finger_slot) |
451 | + _UInputTouchDevice._touch_fingers_in_use.remove( |
452 | + self._touch_finger_slot) |
453 | + self._touch_finger_slot = None |
454 | |
455 | |
456 | class Touch(TouchBase): |
457 | """Low level interface to generate single finger touch events.""" |
458 | |
459 | - def __init__(self): |
460 | + def __init__(self, device_class=_UInputTouchDevice): |
461 | super(Touch, self).__init__() |
462 | - self._touch_finger = None |
463 | + Touch._device = device_class() |
464 | |
465 | @property |
466 | def pressed(self): |
467 | - return self._touch_finger is not None |
468 | + return self._device.pressed |
469 | |
470 | def tap(self, x, y): |
471 | """Click (or 'tap') at given x and y coordinates.""" |
472 | logger.debug("Tapping at: %d,%d", x, y) |
473 | - self._finger_down(x, y) |
474 | - sleep(0.1) |
475 | - self._finger_up() |
476 | + self._device.finger_down(x, y) |
477 | + utilities.sleep(0.1) |
478 | + self._device.finger_up() |
479 | |
480 | def tap_object(self, object): |
481 | """Click (or 'tap') a given object""" |
482 | @@ -344,12 +451,12 @@ |
483 | """Press and hold a given object or at the given coordinates |
484 | Call release() when the object has been pressed long enough""" |
485 | logger.debug("Pressing at: %d,%d", x, y) |
486 | - self._finger_down(x, y) |
487 | + self._device.finger_down(x, y) |
488 | |
489 | def release(self): |
490 | """Release a previously pressed finger""" |
491 | logger.debug("Releasing") |
492 | - self._finger_up() |
493 | + self._device.finger_up() |
494 | |
495 | def move(self, x, y): |
496 | """Moves the pointing "finger" to pos(x,y). |
497 | @@ -357,14 +464,12 @@ |
498 | NOTE: The finger has to be down for this to have any effect. |
499 | |
500 | """ |
501 | - if self._touch_finger is None: |
502 | - raise RuntimeError("Attempting to move without finger being down.") |
503 | - self._finger_move(x, y) |
504 | + self._device.finger_move(x, y) |
505 | |
506 | def drag(self, x1, y1, x2, y2): |
507 | """Perform a drag gesture from (x1,y1) to (x2,y2)""" |
508 | logger.debug("Dragging from %d,%d to %d,%d", x1, y1, x2, y2) |
509 | - self._finger_down(x1, y1) |
510 | + self._device.finger_down(x1, y1) |
511 | |
512 | # Let's drag in 100 steps for now... |
513 | dx = 1.0 * (x2 - x1) / 100 |
514 | @@ -372,52 +477,13 @@ |
515 | cur_x = x1 + dx |
516 | cur_y = y1 + dy |
517 | for i in range(0, 100): |
518 | - self._finger_move(int(cur_x), int(cur_y)) |
519 | - sleep(0.002) |
520 | + self._device.finger_move(int(cur_x), int(cur_y)) |
521 | + utilities.sleep(0.002) |
522 | cur_x += dx |
523 | cur_y += dy |
524 | # Make sure we actually end up at target |
525 | - self._finger_move(x2, y2) |
526 | - self._finger_up() |
527 | - |
528 | - def _finger_down(self, x, y): |
529 | - """Internal: moves finger "finger" down on the touchscreen. |
530 | - |
531 | - :param x: The finger will be moved to this x coordinate. |
532 | - :param y: The finger will be moved to this y coordinate. |
533 | - |
534 | - """ |
535 | - if self._touch_finger is not None: |
536 | - raise RuntimeError("Cannot press finger: it's already pressed.") |
537 | - self._touch_finger = _get_touch_finger() |
538 | - |
539 | - _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger) |
540 | - _touch_device.write( |
541 | - e.EV_ABS, e.ABS_MT_TRACKING_ID, get_next_tracking_id()) |
542 | - _touch_device.write(e.EV_KEY, e.BTN_TOOL_FINGER, 1) |
543 | - _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) |
544 | - _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) |
545 | - _touch_device.write(e.EV_ABS, e.ABS_MT_PRESSURE, 400) |
546 | - _touch_device.syn() |
547 | - |
548 | - def _finger_move(self, x, y): |
549 | - """Internal: moves finger "finger" on the touchscreen to pos (x,y) |
550 | - NOTE: The finger has to be down for this to have any effect.""" |
551 | - if self._touch_finger is not None: |
552 | - _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger) |
553 | - _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x)) |
554 | - _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y)) |
555 | - _touch_device.syn() |
556 | - |
557 | - def _finger_up(self): |
558 | - """Internal: moves finger "finger" up from the touchscreen""" |
559 | - if self._touch_finger is None: |
560 | - raise RuntimeError("Cannot release finger: it's not pressed.") |
561 | - _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger) |
562 | - _touch_device.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, -1) |
563 | - _touch_device.write(e.EV_KEY, e.BTN_TOOL_FINGER, 0) |
564 | - _touch_device.syn() |
565 | - self._touch_finger = _release_touch_finger(self._touch_finger) |
566 | + self._device.finger_move(x2, y2) |
567 | + self._device.finger_up() |
568 | |
569 | |
570 | # veebers: there should be a better way to handle this. |
571 | |
572 | === modified file 'autopilot/tests/functional/test_input_stack.py' |
573 | --- autopilot/tests/functional/test_input_stack.py 2013-12-16 00:20:40 +0000 |
574 | +++ autopilot/tests/functional/test_input_stack.py 2014-01-21 22:20:18 +0000 |
575 | @@ -1,7 +1,7 @@ |
576 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
577 | # |
578 | # Autopilot Functional Test Tool |
579 | -# Copyright (C) 2012-2013 Canonical |
580 | +# Copyright (C) 2012, 2013, 2014 Canonical |
581 | # |
582 | # This program is free software: you can redistribute it and/or modify |
583 | # it under the terms of the GNU General Public License as published by |
584 | @@ -150,8 +150,8 @@ |
585 | from autopilot.input._X11 import _PRESSED_KEYS |
586 | return _PRESSED_KEYS |
587 | elif self.backend == 'UInput': |
588 | - from autopilot.input._uinput import _PRESSED_KEYS |
589 | - return _PRESSED_KEYS |
590 | + from autopilot.input import _uinput |
591 | + return _uinput.Keyboard._device._pressed_keys_ecodes |
592 | else: |
593 | self.fail("Don't know how to get pressed keys list for backend " |
594 | + self.backend |
595 | @@ -551,8 +551,9 @@ |
596 | test_result = FakeTestCase("test_press_key").run() |
597 | |
598 | self.assertThat(test_result.wasSuccessful(), Equals(True)) |
599 | - from autopilot.input._uinput import _PRESSED_KEYS |
600 | - self.assertThat(_PRESSED_KEYS, Equals([])) |
601 | + from autopilot.input import _uinput |
602 | + self.assertThat( |
603 | + _uinput.Keyboard._device._pressed_keys_ecodes, Equals([])) |
604 | |
605 | @patch('autopilot.input._X11.fake_input', new=lambda *args: None, ) |
606 | def test_mouse_button_released(self): |
607 | |
608 | === modified file 'autopilot/tests/unit/test_input.py' |
609 | --- autopilot/tests/unit/test_input.py 2013-12-10 03:10:11 +0000 |
610 | +++ autopilot/tests/unit/test_input.py 2014-01-21 22:20:18 +0000 |
611 | @@ -1,7 +1,7 @@ |
612 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
613 | # |
614 | # Autopilot Functional Test Tool |
615 | -# Copyright (C) 2013 Canonical |
616 | +# Copyright (C) 2013, 2014 Canonical |
617 | # |
618 | # This program is free software: you can redistribute it and/or modify |
619 | # it under the terms of the GNU General Public License as published by |
620 | @@ -17,11 +17,15 @@ |
621 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
622 | # |
623 | |
624 | -from mock import patch |
625 | +import mock |
626 | +import testscenarios |
627 | +from evdev import ecodes, uinput |
628 | from testtools import TestCase |
629 | from testtools.matchers import raises |
630 | |
631 | +from autopilot.input import _uinput |
632 | from autopilot.input._common import get_center_point |
633 | +from autopilot import utilities |
634 | |
635 | |
636 | class Empty(object): |
637 | @@ -84,7 +88,7 @@ |
638 | raises(expected_exception) |
639 | ) |
640 | |
641 | - @patch('autopilot.input._common.logger') |
642 | + @mock.patch('autopilot.input._common.logger') |
643 | def test_get_center_point_logs_with_globalRect(self, mock_logger): |
644 | obj = make_fake_object(globalRect=True) |
645 | x, y = get_center_point(obj) |
646 | @@ -100,7 +104,7 @@ |
647 | self.assertEqual(123, x) |
648 | self.assertEqual(345, y) |
649 | |
650 | - @patch('autopilot.input._common.logger') |
651 | + @mock.patch('autopilot.input._common.logger') |
652 | def test_get_center_point_logs_with_center_points(self, mock_logger): |
653 | obj = make_fake_object(center=True) |
654 | x, y = get_center_point(obj) |
655 | @@ -116,7 +120,7 @@ |
656 | self.assertEqual(110, x) |
657 | self.assertEqual(120, y) |
658 | |
659 | - @patch('autopilot.input._common.logger') |
660 | + @mock.patch('autopilot.input._common.logger') |
661 | def test_get_center_point_logs_with_xywh(self, mock_logger): |
662 | obj = make_fake_object(xywh=True) |
663 | x, y = get_center_point(obj) |
664 | @@ -151,3 +155,459 @@ |
665 | |
666 | self.assertEqual(123, x) |
667 | self.assertEqual(345, y) |
668 | + |
669 | + |
670 | +class UInputKeyboardDeviceTestCase(TestCase): |
671 | + """Test the integration with evdev.UInput for the keyboard.""" |
672 | + |
673 | + _PRESS_VALUE = 1 |
674 | + _RELEASE_VALUE = 0 |
675 | + |
676 | + def setUp(self): |
677 | + super(UInputKeyboardDeviceTestCase, self).setUp() |
678 | + self.keyboard = _uinput._UInputKeyboardDevice(device_class=mock.Mock) |
679 | + self.keyboard._device.mock_add_spec(uinput.UInput, spec_set=True) |
680 | + |
681 | + def test_press_key_should_emit_write_and_syn(self): |
682 | + self.keyboard.press('KEY_A') |
683 | + self._assert_key_press_emitted_write_and_syn('KEY_A') |
684 | + |
685 | + def _assert_key_press_emitted_write_and_syn(self, key): |
686 | + self._assert_emitted_write_and_syn(key, self._PRESS_VALUE) |
687 | + |
688 | + def _assert_emitted_write_and_syn(self, key, value): |
689 | + key_ecode = ecodes.ecodes.get(key) |
690 | + expected_calls = [ |
691 | + mock.call.write(ecodes.EV_KEY, key_ecode, value), |
692 | + mock.call.syn() |
693 | + ] |
694 | + |
695 | + self.assertEqual(expected_calls, self.keyboard._device.mock_calls) |
696 | + |
697 | + def test_press_key_should_append_leading_string(self): |
698 | + self.keyboard.press('A') |
699 | + self._assert_key_press_emitted_write_and_syn('KEY_A') |
700 | + |
701 | + def test_press_key_should_ignore_case(self): |
702 | + self.keyboard.press('a') |
703 | + self._assert_key_press_emitted_write_and_syn('KEY_A') |
704 | + |
705 | + def test_press_unexisting_key_should_raise_error(self): |
706 | + error = self.assertRaises( |
707 | + ValueError, self.keyboard.press, 'unexisting') |
708 | + |
709 | + self.assertEqual('Unknown key name: unexisting.', str(error)) |
710 | + |
711 | + def test_release_not_pressed_key_should_raise_error(self): |
712 | + error = self.assertRaises( |
713 | + ValueError, self.keyboard.release, 'A') |
714 | + |
715 | + self.assertEqual("Key 'A' not pressed.", str(error)) |
716 | + |
717 | + def test_release_key_should_emit_write_and_syn(self): |
718 | + self._press_key_and_reset_mock('KEY_A') |
719 | + |
720 | + self.keyboard.release('KEY_A') |
721 | + self._assert_key_release_emitted_write_and_syn('KEY_A') |
722 | + |
723 | + def _press_key_and_reset_mock(self, key): |
724 | + self.keyboard.press(key) |
725 | + self.keyboard._device.reset_mock() |
726 | + |
727 | + def _assert_key_release_emitted_write_and_syn(self, key): |
728 | + self._assert_emitted_write_and_syn(key, self._RELEASE_VALUE) |
729 | + |
730 | + def test_release_key_should_append_leading_string(self): |
731 | + self._press_key_and_reset_mock('KEY_A') |
732 | + |
733 | + self.keyboard.release('A') |
734 | + self._assert_key_release_emitted_write_and_syn('KEY_A') |
735 | + |
736 | + def test_release_key_should_ignore_case(self): |
737 | + self._press_key_and_reset_mock('KEY_A') |
738 | + |
739 | + self.keyboard.release('a') |
740 | + self._assert_key_release_emitted_write_and_syn('KEY_A') |
741 | + |
742 | + def test_release_unexisting_key_should_raise_error(self): |
743 | + error = self.assertRaises( |
744 | + ValueError, self.keyboard.release, 'unexisting') |
745 | + |
746 | + self.assertEqual('Unknown key name: unexisting.', str(error)) |
747 | + |
748 | + def test_release_pressed_keys_without_pressed_keys_should_do_nothing(self): |
749 | + self.keyboard.release_pressed_keys() |
750 | + self.assertEqual([], self.keyboard._device.mock_calls) |
751 | + |
752 | + def test_release_pressed_keys_with_pressed_keys(self): |
753 | + expected_calls = [ |
754 | + mock.call.write( |
755 | + ecodes.EV_KEY, ecodes.ecodes.get('KEY_A'), |
756 | + self._RELEASE_VALUE), |
757 | + mock.call.syn(), |
758 | + mock.call.write( |
759 | + ecodes.EV_KEY, ecodes.ecodes.get('KEY_B'), |
760 | + self._RELEASE_VALUE), |
761 | + mock.call.syn() |
762 | + ] |
763 | + |
764 | + self._press_key_and_reset_mock('KEY_A') |
765 | + self._press_key_and_reset_mock('KEY_B') |
766 | + |
767 | + self.keyboard.release_pressed_keys() |
768 | + |
769 | + self.assertEqual(expected_calls, self.keyboard._device.mock_calls) |
770 | + |
771 | + def test_release_pressed_keys_already_released(self): |
772 | + expected_calls = [] |
773 | + self.keyboard.press('KEY_A') |
774 | + self.keyboard.release_pressed_keys() |
775 | + self.keyboard._device.reset_mock() |
776 | + |
777 | + self.keyboard.release_pressed_keys() |
778 | + self.assertEqual(expected_calls, self.keyboard._device.mock_calls) |
779 | + |
780 | + |
781 | +class UInputKeyboardTestCase(testscenarios.TestWithScenarios, TestCase): |
782 | + """Test UInput Keyboard helper for autopilot tests.""" |
783 | + |
784 | + scenarios = [ |
785 | + ('single key', dict(keys='a', expected_calls_args=['a'])), |
786 | + ('upper-case letter', dict( |
787 | + keys='A', expected_calls_args=['KEY_LEFTSHIFT', 'A'])), |
788 | + ('key combination', dict( |
789 | + keys='a+b', expected_calls_args=['a', 'b'])) |
790 | + ] |
791 | + |
792 | + def setUp(self): |
793 | + super(UInputKeyboardTestCase, self).setUp() |
794 | + # Return to the original device after the test. |
795 | + self.addCleanup(self._set_keyboard_device, _uinput.Keyboard._device) |
796 | + _uinput.Keyboard._device = None |
797 | + self.keyboard = _uinput.Keyboard(device_class=mock.Mock) |
798 | + self.keyboard._device.mock_add_spec( |
799 | + _uinput._UInputKeyboardDevice, spec_set=True) |
800 | + # Mock the sleeps so we don't have to spend time actually sleeping. |
801 | + self.addCleanup(utilities.sleep.disable_mock) |
802 | + utilities.sleep.enable_mock() |
803 | + |
804 | + self.keyboard._device.reset_mock() |
805 | + |
806 | + def _set_keyboard_device(self, device): |
807 | + _uinput.Keyboard._device = device |
808 | + |
809 | + def test_press(self): |
810 | + expected_calls = [ |
811 | + mock.call.press(arg) for arg in self.expected_calls_args] |
812 | + self.keyboard.press(self.keys) |
813 | + |
814 | + self.assertEqual(expected_calls, self.keyboard._device.mock_calls) |
815 | + |
816 | + def test_release(self): |
817 | + self.keyboard.press(self.keys) |
818 | + self.keyboard._device.reset_mock() |
819 | + |
820 | + expected_calls = [ |
821 | + mock.call.release(arg) for arg in |
822 | + reversed(self.expected_calls_args)] |
823 | + self.keyboard.release(self.keys) |
824 | + |
825 | + self.assertEqual( |
826 | + expected_calls, self.keyboard._device.mock_calls) |
827 | + |
828 | + def test_press_and_release(self): |
829 | + expected_press_calls = [ |
830 | + mock.call.press(arg) for arg in self.expected_calls_args] |
831 | + expected_release_calls = [ |
832 | + mock.call.release(arg) for arg in |
833 | + reversed(self.expected_calls_args)] |
834 | + |
835 | + self.keyboard.press_and_release(self.keys) |
836 | + |
837 | + self.assertEqual( |
838 | + expected_press_calls + expected_release_calls, |
839 | + self.keyboard._device.mock_calls) |
840 | + |
841 | + |
842 | +class UInputTouchDeviceTestCase(TestCase): |
843 | + """Test the integration with evdev.UInput for the touch device.""" |
844 | + |
845 | + def setUp(self): |
846 | + super(UInputTouchDeviceTestCase, self).setUp() |
847 | + self._number_of_slots = 9 |
848 | + |
849 | + # Return to the original fingers after the test. |
850 | + self.addCleanup( |
851 | + self._set_fingers_in_use, |
852 | + _uinput._UInputTouchDevice._touch_fingers_in_use, |
853 | + _uinput._UInputTouchDevice._last_tracking_id) |
854 | + |
855 | + # Always start the tests without fingers in use. |
856 | + _uinput._UInputTouchDevice._touch_fingers_in_use = [] |
857 | + _uinput._UInputTouchDevice._last_tracking_id = 0 |
858 | + |
859 | + def _set_fingers_in_use(self, touch_fingers_in_use, last_tracking_id): |
860 | + _uinput._UInputTouchDevice._touch_fingers_in_use = touch_fingers_in_use |
861 | + _uinput._UInputTouchDevice._last_tracking_id = last_tracking_id |
862 | + |
863 | + def test_finger_down_should_use_free_slot(self): |
864 | + for slot in range(self._number_of_slots): |
865 | + touch = self._get_touch_device() |
866 | + |
867 | + touch.finger_down(0, 0) |
868 | + |
869 | + self._assert_finger_down_emitted_write_and_syn( |
870 | + touch, slot=slot, tracking_id=mock.ANY, x=0, y=0) |
871 | + |
872 | + def _get_touch_device(self): |
873 | + dummy_x_resolution = 100 |
874 | + dummy_y_resolution = 100 |
875 | + touch = _uinput._UInputTouchDevice( |
876 | + res_x=dummy_x_resolution, res_y=dummy_y_resolution, |
877 | + device_class=mock.Mock) |
878 | + touch._device.mock_add_spec(uinput.UInput, spec_set=True) |
879 | + return touch |
880 | + |
881 | + def _assert_finger_down_emitted_write_and_syn( |
882 | + self, touch, slot, tracking_id, x, y): |
883 | + press_value = 1 |
884 | + expected_calls = [ |
885 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot), |
886 | + mock.call.write( |
887 | + ecodes.EV_ABS, ecodes.ABS_MT_TRACKING_ID, tracking_id), |
888 | + mock.call.write( |
889 | + ecodes.EV_KEY, ecodes.BTN_TOOL_FINGER, press_value), |
890 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_X, x), |
891 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_Y, y), |
892 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_PRESSURE, 400), |
893 | + mock.call.syn() |
894 | + ] |
895 | + self.assertEqual(expected_calls, touch._device.mock_calls) |
896 | + |
897 | + def test_finger_down_without_free_slots_should_raise_error(self): |
898 | + # Claim all the available slots. |
899 | + for slot in range(self._number_of_slots): |
900 | + touch = self._get_touch_device() |
901 | + touch.finger_down(0, 0) |
902 | + |
903 | + touch = self._get_touch_device() |
904 | + |
905 | + # Try to use one more. |
906 | + error = self.assertRaises(RuntimeError, touch.finger_down, 11, 11) |
907 | + self.assertEqual( |
908 | + 'All available fingers have been used already.', str(error)) |
909 | + |
910 | + def test_finger_down_should_use_unique_tracking_id(self): |
911 | + for number in range(self._number_of_slots): |
912 | + touch = self._get_touch_device() |
913 | + touch.finger_down(0, 0) |
914 | + |
915 | + self._assert_finger_down_emitted_write_and_syn( |
916 | + touch, slot=mock.ANY, tracking_id=number + 1, x=0, y=0) |
917 | + |
918 | + def test_finger_down_should_not_reuse_tracking_ids(self): |
919 | + # Claim and release all the available slots once. |
920 | + for number in range(self._number_of_slots): |
921 | + touch = self._get_touch_device() |
922 | + touch.finger_down(0, 0) |
923 | + touch.finger_up() |
924 | + |
925 | + touch = self._get_touch_device() |
926 | + |
927 | + touch.finger_down(12, 12) |
928 | + self._assert_finger_down_emitted_write_and_syn( |
929 | + touch, slot=mock.ANY, tracking_id=number + 2, x=12, y=12) |
930 | + |
931 | + def test_finger_down_with_finger_pressed_should_raise_error(self): |
932 | + touch = self._get_touch_device() |
933 | + touch.finger_down(0, 0) |
934 | + |
935 | + error = self.assertRaises(RuntimeError, touch.finger_down, 0, 0) |
936 | + self.assertEqual( |
937 | + "Cannot press finger: it's already pressed.", str(error)) |
938 | + |
939 | + def test_finger_move_without_finger_pressed_should_raise_error(self): |
940 | + touch = self._get_touch_device() |
941 | + |
942 | + error = self.assertRaises(RuntimeError, touch.finger_move, 10, 10) |
943 | + self.assertEqual( |
944 | + 'Attempting to move without finger being down.', str(error)) |
945 | + |
946 | + def test_finger_move_should_use_assigned_slot(self): |
947 | + for slot in range(self._number_of_slots): |
948 | + touch = self._get_touch_device() |
949 | + touch.finger_down(0, 0) |
950 | + touch._device.reset_mock() |
951 | + |
952 | + touch.finger_move(10, 10) |
953 | + |
954 | + self._assert_finger_move_emitted_write_and_syn( |
955 | + touch, slot=slot, x=10, y=10) |
956 | + |
957 | + def _assert_finger_move_emitted_write_and_syn(self, touch, slot, x, y): |
958 | + expected_calls = [ |
959 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot), |
960 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_X, x), |
961 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_Y, y), |
962 | + mock.call.syn() |
963 | + ] |
964 | + self.assertEqual(expected_calls, touch._device.mock_calls) |
965 | + |
966 | + def test_finger_move_should_reuse_assigned_slot(self): |
967 | + first_slot = 0 |
968 | + touch = self._get_touch_device() |
969 | + touch.finger_down(1, 1) |
970 | + touch._device.reset_mock() |
971 | + |
972 | + touch.finger_move(13, 13) |
973 | + self._assert_finger_move_emitted_write_and_syn( |
974 | + touch, slot=first_slot, x=13, y=13) |
975 | + touch._device.reset_mock() |
976 | + |
977 | + touch.finger_move(14, 14) |
978 | + self._assert_finger_move_emitted_write_and_syn( |
979 | + touch, slot=first_slot, x=14, y=14) |
980 | + |
981 | + def test_finger_up_without_finger_pressed_should_raise_error(self): |
982 | + touch = self._get_touch_device() |
983 | + |
984 | + error = self.assertRaises(RuntimeError, touch.finger_up) |
985 | + self.assertEqual( |
986 | + "Cannot release finger: it's not pressed.", str(error)) |
987 | + |
988 | + def test_finger_up_should_use_assigned_slot(self): |
989 | + fingers = [] |
990 | + for slot in range(self._number_of_slots): |
991 | + touch = self._get_touch_device() |
992 | + touch.finger_down(0, 0) |
993 | + touch._device.reset_mock() |
994 | + fingers.append(touch) |
995 | + |
996 | + for slot, touch in enumerate(fingers): |
997 | + touch.finger_up() |
998 | + |
999 | + self._assert_finger_up_emitted_write_and_syn(touch, slot=slot) |
1000 | + |
1001 | + def _assert_finger_up_emitted_write_and_syn(self, touch, slot): |
1002 | + lift_tracking_id = -1 |
1003 | + release_value = 0 |
1004 | + expected_calls = [ |
1005 | + mock.call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot), |
1006 | + mock.call.write( |
1007 | + ecodes.EV_ABS, ecodes.ABS_MT_TRACKING_ID, lift_tracking_id), |
1008 | + mock.call.write( |
1009 | + ecodes.EV_KEY, ecodes.BTN_TOOL_FINGER, release_value), |
1010 | + mock.call.syn() |
1011 | + ] |
1012 | + self.assertEqual(expected_calls, touch._device.mock_calls) |
1013 | + |
1014 | + def test_finger_up_should_release_slot(self): |
1015 | + fingers = [] |
1016 | + # Claim all the available slots. |
1017 | + for slot in range(self._number_of_slots): |
1018 | + touch = self._get_touch_device() |
1019 | + touch.finger_down(0, 0) |
1020 | + fingers.append(touch) |
1021 | + |
1022 | + slot_to_reuse = 3 |
1023 | + fingers[slot_to_reuse].finger_up() |
1024 | + |
1025 | + touch = self._get_touch_device() |
1026 | + |
1027 | + # Try to use one more. |
1028 | + touch.finger_down(15, 15) |
1029 | + self._assert_finger_down_emitted_write_and_syn( |
1030 | + touch, slot=slot_to_reuse, tracking_id=mock.ANY, x=15, y=15) |
1031 | + |
1032 | + def test_pressed_with_finger_down(self): |
1033 | + touch = self._get_touch_device() |
1034 | + touch.finger_down(0, 0) |
1035 | + |
1036 | + self.assertTrue(touch.pressed) |
1037 | + |
1038 | + def test_pressed_without_finger_down(self): |
1039 | + touch = self._get_touch_device() |
1040 | + self.assertFalse(touch.pressed) |
1041 | + |
1042 | + def test_pressed_after_finger_up(self): |
1043 | + touch = self._get_touch_device() |
1044 | + touch.finger_down(0, 0) |
1045 | + touch.finger_up() |
1046 | + |
1047 | + self.assertFalse(touch.pressed) |
1048 | + |
1049 | + def test_pressed_with_other_finger_down(self): |
1050 | + other_touch = self._get_touch_device() |
1051 | + other_touch.finger_down(0, 0) |
1052 | + |
1053 | + touch = self._get_touch_device() |
1054 | + self.assertFalse(touch.pressed) |
1055 | + |
1056 | + |
1057 | +class UInputTouchTestCase(TestCase): |
1058 | + """Test UInput Touch helper for autopilot tests.""" |
1059 | + |
1060 | + def setUp(self): |
1061 | + super(UInputTouchTestCase, self).setUp() |
1062 | + self.touch = _uinput.Touch(device_class=mock.Mock) |
1063 | + self.touch._device.mock_add_spec( |
1064 | + _uinput._UInputTouchDevice, spec_set=True) |
1065 | + # Mock the sleeps so we don't have to spend time actually sleeping. |
1066 | + self.addCleanup(utilities.sleep.disable_mock) |
1067 | + utilities.sleep.enable_mock() |
1068 | + |
1069 | + def test_tap(self): |
1070 | + expected_calls = [ |
1071 | + mock.call.finger_down(0, 0), |
1072 | + mock.call.finger_up() |
1073 | + ] |
1074 | + |
1075 | + self.touch.tap(0, 0) |
1076 | + self.assertEqual(expected_calls, self.touch._device.mock_calls) |
1077 | + |
1078 | + def test_tap_object(self): |
1079 | + object_ = type('Dummy', (object,), {'globalRect': (0, 0, 10, 10)}) |
1080 | + expected_calls = [ |
1081 | + mock.call.finger_down(5, 5), |
1082 | + mock.call.finger_up() |
1083 | + ] |
1084 | + |
1085 | + self.touch.tap_object(object_) |
1086 | + self.assertEqual(expected_calls, self.touch._device.mock_calls) |
1087 | + |
1088 | + def test_press(self): |
1089 | + expected_calls = [mock.call.finger_down(0, 0)] |
1090 | + |
1091 | + self.touch.press(0, 0) |
1092 | + self.assertEqual(expected_calls, self.touch._device.mock_calls) |
1093 | + |
1094 | + def test_release(self): |
1095 | + expected_calls = [mock.call.finger_up()] |
1096 | + |
1097 | + self.touch.release() |
1098 | + self.assertEqual(expected_calls, self.touch._device.mock_calls) |
1099 | + |
1100 | + def test_move(self): |
1101 | + expected_calls = [mock.call.finger_move(10, 10)] |
1102 | + |
1103 | + self.touch.move(10, 10) |
1104 | + self.assertEqual(expected_calls, self.touch._device.mock_calls) |
1105 | + |
1106 | + |
1107 | +class MultipleUInputTouchBackend(_uinput._UInputTouchDevice): |
1108 | + |
1109 | + def __init__(self, res_x=100, res_y=100, device_class=mock.Mock): |
1110 | + super(MultipleUInputTouchBackend, self).__init__( |
1111 | + res_x, res_y, device_class) |
1112 | + |
1113 | + |
1114 | +class MultipleUInputTouchTestCase(TestCase): |
1115 | + |
1116 | + def test_with_multiple_touch(self): |
1117 | + finger1 = _uinput.Touch(device_class=MultipleUInputTouchBackend) |
1118 | + finger2 = _uinput.Touch(device_class=MultipleUInputTouchBackend) |
1119 | + |
1120 | + finger1.press(0, 0) |
1121 | + self.addCleanup(finger1.release) |
1122 | + |
1123 | + self.assertFalse(finger2.pressed) |
1124 | |
1125 | === modified file 'debian/control' |
1126 | --- debian/control 2014-01-21 13:02:00 +0000 |
1127 | +++ debian/control 2014-01-21 22:20:18 +0000 |
1128 | @@ -16,6 +16,7 @@ |
1129 | python-dbus, |
1130 | python-debian, |
1131 | python-dev, |
1132 | + python-evdev, |
1133 | python-fixtures, |
1134 | python-gi, |
1135 | python-junitxml, |
1136 | @@ -30,6 +31,7 @@ |
1137 | python-xlib, |
1138 | python3-all-dev (>= 3.3), |
1139 | python3-dbus, |
1140 | + python3-evdev, |
1141 | python3-fixtures, |
1142 | python3-gi, |
1143 | python3-junitxml, |
FAILED: Continuous integration, rev:425 /code.launchpad .net/~elopio/ autopilot/ no_uinput_ side-effects2/ +merge/ 202467/ +edit-commit- message
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:/
http:// jenkins. qa.ubuntu. com/job/ autopilot- ci/419/ jenkins. qa.ubuntu. com/job/ autopilot- trusty- amd64-ci/ 145/console jenkins. qa.ubuntu. com/job/ autopilot- trusty- armhf-ci/ 145/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 2495/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2497/ console
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/autopilot- ci/419/ rebuild
http://