Merge lp:~elopio/autopilot/no_uinput_side-effects into lp:autopilot

Proposed by Leo Arias
Status: Merged
Approved by: Thomi Richards
Approved revision: 455
Merged at revision: 439
Proposed branch: lp:~elopio/autopilot/no_uinput_side-effects
Merge into: lp:autopilot
Prerequisite: lp:~thomir-deactivatedaccount/autopilot/trunk-add-repr-compatibility
Diff against target: 1364 lines (+932/-213)
5 files modified
autopilot/input/_common.py (+12/-2)
autopilot/input/_uinput.py (+329/-203)
autopilot/tests/functional/test_input_stack.py (+6/-5)
autopilot/tests/unit/test_input.py (+583/-3)
debian/control (+2/-0)
To merge this branch: bzr merge lp:~elopio/autopilot/no_uinput_side-effects
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
VĂ­ctor R. Ruiz Pending
Autopilot Hackers Pending
Richard Huddie Pending
Christopher Lee Pending
Javier Collado Pending
Review via email: mp+207280@code.launchpad.net

This proposal supersedes a proposal from 2014-01-21.

Commit message

Refactor the _uinput module to avoid side-effects when we import it.

Description of the change

This is the first part of https://code.launchpad.net/~elopio/autopilot/no_uinput_side-effects2/+merge/202564
We need both to have all the tests passing, but together it's more than 1000 lines.
So, I suggest to review them separately, then merge them and confirm all the tests are passing on jenkins.
This one contains the refactors for the Keyboard, the other one for the Touch.

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: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Leo Arias (elopio) wrote : Posted in a previous version of this proposal

I split the branch in two, to make it easier to review.
Here are the Touch refactors:
https://code.launchpad.net/~elopio/autopilot/no_uinput_side-effects2/+merge/202414

Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Hi,

First off I'm unable to build the package as the tests don't pass (see
[1]). I'm not sure if this is due to the merge being split into two,
if it is can you please either make it so the MR stands on itis own
and can be built/tests run or re-propose with both merges in one (I
don't mind large MRs if a bunch of the lines are tests).

What's the purpose of changing 'patch' -> 'mock.patch'?
One (admittedly weak) reason to keep it as it was is that all the
tests use just 'patch' so it breaks from the current standard.
Another reason is that it add extraneous text around the test without
really adding to it. It doesn't help clarify what the test does or
expected result is etc.

Diff Line 58, 81: What's the rational behind using local method
variables to name these values as opposed to Class variables?

WRT the tests (diff lines 242-401)
Creating self.keyboard within setUp:
  Tests should be explicit so you should be able to look at a test and
  see what's happening (or at least intended). I think you need to be
  more specific with creating the keyboard (especially since that's
  what's being tested)

  I would suggest something like:

  def test_example(self):
      keyboard = self._create_UIKeyboard_with_mocked_backend()
      keyboard.press . . .

  or something similar.

Minor nit-pick: between diff lines 253 and 269 there is helper methods
mixed in between test methods.

[1] http://pastebin.ubuntu.com/6794531/

review: Needs Fixing
Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Oops, hit enter too soon, corrections below.

> (I
> don't mind large MRs if a bunch of the lines are tests).

Should read:
(I don't mind if the MR is a little larger, it should be as small as
possible but currently it looks like it's a little too small if it
doesn't include all the details for the tests too.)

> Minor nit-pick: between diff lines 253 and 269 there is helper methods
> mixed in between test methods.

Should read:
Diff lines 253 and 269 Helper methods:
  The helper methods should be public (shouldn't start with '_')

  They are also mixed in between test methods, they should be at the
  top of the class with the test methods following.

Revision history for this message
Leo Arias (elopio) wrote : Posted in a previous version of this proposal

> First off I'm unable to build the package as the tests don't pass (see
> [1]). I'm not sure if this is due to the merge being split into two,
> if it is can you please either make it so the MR stands on itis own
> and can be built/tests run or re-propose with both merges in one (I
> don't mind large MRs if a bunch of the lines are tests).

Yes, if you take a look at the other MP, you will be able to build the package as all the tests are passing there.
But I'm going to change some things based on your comments and merge them.

> What's the purpose of changing 'patch' -> 'mock.patch'?
> One (admittedly weak) reason to keep it as it was is that all the
> tests use just 'patch' so it breaks from the current standard.
> Another reason is that it add extraneous text around the test without
> really adding to it. It doesn't help clarify what the test does or
> expected result is etc.

I have some reasons to prefer this. It maintains the imports section cleaner, as I just have to import mock instead of all the classes I use from that module. And, when reading the code, it's easier to understand where the things come from. A good example is mock.call. Without the context that provides the module, it's confusing when you find in the code call(4), or call.write(4). We discussed about this some months ago too, and there are some also weak reasons to support this style, like the way import works on symbols instead of modules. In the end, I'm just used to this and I prefer it.
Your reason about keeping the current standard makes sense, so if you prefer it that way, I'll change it.

> Diff Line 58, 81: What's the rational behind using local method
> variables to name these values as opposed to Class variables?

I think that the definitions need to be as close to code it's using them as possible, and the scope for everything should be as small as possible. Here I'm just defining the variable to make it more readable, and it's being used only on the following line, so I prefer to keep the definition and the use next to each other.

> WRT the tests (diff lines 242-401)
> Creating self.keyboard within setUp:
> Tests should be explicit so you should be able to look at a test and
> see what's happening (or at least intended). I think you need to be
> more specific with creating the keyboard (especially since that's
> what's being tested)
>
> I would suggest something like:
>
> def test_example(self):
> keyboard = self._create_UIKeyboard_with_mocked_backend()
> keyboard.press . . .
>
> or something similar.

Yes, I'll change that.

Revision history for this message
Leo Arias (elopio) wrote : Posted in a previous version of this proposal

> Oops, hit enter too soon, corrections below.
>
> > (I
> > don't mind large MRs if a bunch of the lines are tests).
>
> Should read:
> (I don't mind if the MR is a little larger, it should be as small as
> possible but currently it looks like it's a little too small if it
> doesn't include all the details for the tests too.)

Ok, and you already did a first review to this branch, so I'm merging them.

> > Minor nit-pick: between diff lines 253 and 269 there is helper methods
> > mixed in between test methods.
>
> Should read:
> Diff lines 253 and 269 Helper methods:
> The helper methods should be public (shouldn't start with '_')

Why should they be public if they are not going to be used outside of this test class?

> They are also mixed in between test methods, they should be at the
> top of the class with the test methods following.

According to the clean code book, the code should be read from top to bottom. So when you are reading method_a, and it uses method_b, the definition of method_b should come right after the definition of method_a.
That's really useful when you are reading the code of a class for the first time, because what you will find first is the highest level method, followed by all the methods needed to implement it.
When you are done reading the details of that first high level method, you will find a second high level method followed by all its helpers.
If you don't want to read all the lower level details of the implementation, you can just read the high level public ones, because it's easy to notice which ones are their helpers, and the helpers hopefully have a clear and nice name.
Also, it keeps the vertical distance of code definitions as close as possible to their use. So if you are new to a class and you try to understand a method, you know you will find all its helpers close.
So, when I'm writing test classes, I try to make them readable from top to bottom, so you don't have to make many vertical jumps to understand the details of the tests and their helpers.

Having said aaaall of that, if on the tests classes you still prefer to have the helpers before the test methods, I can change it. I prefer it this way, but I can change it easily.

Thanks for the review :)

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
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
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Leo Arias (elopio) wrote : Posted in a previous version of this proposal
Download full text (6.8 KiB)

<veebers> elopio: to cover the comments: I wouldn't approve an MR that I couldn't build and run the tests for (even if the second half is just there) too much can go wrong.
<thomi> veebers: no only shouldn't you, it won't land if the tests don't pass
<elopio> veebers: last jenkins run was green. But now it's a 1000 MP :)
<thomi> (just thought I'd jump in there)
<veebers> elopio: I understand the import policy wrt to import mock vs from mock . . . But I think in this case it clutters the tests, which is really what this file is about. I feel test suites are special cases in some cases
<veebers> thomi: that's right, but the first thing I do is branch, build and run the tests
<veebers> so you normally wouldn't get a review either
<veebers> :-)
<elopio> veebers: ok, I'll change the imports.
<thomi> veebers: :)
<veebers> elopio: it does make a good point if you're calling "call.write(4)" for instance having mock.call.write is much more understandable
<elopio> veebers: yes, I don't agree with you here, but my policy is that if I can't convince my reviewer, I'll follow his suggestions and continue discussing later.
<elopio> we have a good chance to discuss over some beers next month :)
<veebers> elopio: aye sounds good
<elopio> veebers: pushed the imports change.
<elopio> oh, I did it too with the sleep and didn't notice.
<elopio> changing...
<veebers> elopio: ugh sorry, where was I
<veebers> elopio: the mixing of test and helper; Comes back to test suites being special cases and understanding the tests, being able to see all the tests that are there as well as the helper methods being secondary to anything
<elopio> veebers: ok, give me a couple of minutes for that.
<veebers> elopio: ack
<veebers> elopio: fyi, you can also use sleep as a context manager (i.e. "with sleep.mocked():") to move the mocking of sleep closer to the code that will use it
<elopio> veebers: pushed. I don't agree with you on the order of the methods, but I can live with it :)
<elopio> veebers: if I want all my tests to use the mocked sleep, I think it's cleaner to do it on the setup
<elopio> instead of using the context manager on every test.
<veebers> thomi: fyi, I think this issue is resolved now, but will create a vm/container to souble check
<veebers> double*
<veebers> elopio: heh sounds good :-)
<veebers> elopio: so I realise that your MR will break a couple of Unity8 tests, but that's my bad for checking input internals in a test :-\ So I'll sort the Unity8 tests
<veebers> is there a test if release() is called twice? Or if release is called when it's already released?
<veebers> (should call Runtime error I think from memory)
<veebers> Also, the public interface for Touch should state any exceptions it might raise (release, in this case -> RuntimeError)
<elopio> veebers: actually, the bug that caused your unity8 workaround is fixed.
<veebers> elopio: ah no, I'm talking about something else
<veebers> elopio: but also, awesome!
<veebers> I was wondering if this helped with that issue. Excellent
<veebers> I'm talking about code like this grossness: if self.touch._touch_finger is not None:
<elopio> veebers: this doesn't help that issue. They fixed it in mir.
<elopio> well, t...

Read more...

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote : Posted in a previous version of this proposal

Hi,

I'll jump in here :)

On Wed, Jan 22, 2014 at 4:05 PM, Leo Arias <email address hidden> wrote:

> > Should read:
> > Diff lines 253 and 269 Helper methods:
> > The helper methods should be public (shouldn't start with '_')
>
> Why should they be public if they are not going to be used outside of this
> test class?
>
>
They're assertion methods, and there's really no need to make them private.
I can easily imagine you might want to re-use them in the future. Making
them private has no benefits here, and has several drawbacks: they're less
visible, and it makes them look like methods that you don't want to be
reused. We're not exporting an API from test suites, so some slightly
different rules apply. If this were production code, I'd argue that you
make them private... but it's not, so I won't :)

> > They are also mixed in between test methods, they should be at the
> > top of the class with the test methods following.
>
> According to the clean code book, the code should be read from top to
> bottom. So when you are reading method_a, and it uses method_b, the
> definition of method_b should come right after the definition of method_a.
>

In production code, I totally agree with you. However, when reading a test
suite, it's helpful to be able to see all the helper functions together -
either at the top or the bottom of the class (I don't mind which). For
example, if I have a class like:

class FooTests(TestCase):

    def test_something(self):
        self.assert_widget_is_full(make_widget())

It's a pretty good bet that when I read this code next, one of the first
things I'll want to do is to satisfy myself that the
'assert_widget_is_full' method does what it says on the tin. Where do I go
to find that? You can't put it immeadiately below every test that uses
it.... so it makes more sense to either put it at the top of the class
(which means you'll scan it on your way to the test), or at the bottom of
the class.

> That's really useful when you are reading the code of a class for the
> first time, because what you will find first is the highest level method,
> followed by all the methods needed to implement it.
>

For production code, I entirely agree with you, but for tests, it's
unlikely that you'll be reading the first test case in the class. If you
start by reding the 7th test (for example), which way do you scroll to find
the assertion methods? :)

>
> Having said aaaall of that, if on the tests classes you still prefer to
> have the helpers before the test methods, I can change it. I prefer it this
> way, but I can change it easily.

Sorry to be so insistent, but please do - it'll make me happy :)

--
Thomi Richards
<email address hidden>

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
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Hi Leo, thanks for making those changes. I only have a couple more :-)

Most important is line: 302 will raise an exception as _get_touch_events isn't
passed anything (I presume res_x/res_y are the intended vars.)

line 213: It is possible (not likely, but possible) that cls._device will None,
please make a check here.
(The on_test_end stuff should be going away in the near future as it's a little
hacky, but is here to stay for now.)

Can you please add a test for this and make the fix? Also, if you could run
this over the Unity8 tests to ensure there are no other regressions (due to
there not being 100% coverage of this module).

Line 249: Can you expand on the deprecated message please? Something like "the
Touch class to instantiate a device object" or similar?
(Currently the deprecated method outputs this string: "This function is
deprecated. Please use '%(your_string_here)s' instead.")

lines 401 - 404: The docstring appears to contain details intended for the
__init__ method.

line 410: Minor: It seems inconstant to me to have this number stored as a
class variable where previously (i.e. press_value line 975) has been used
locally. I'm not really suggesting a fix, just noticing it.

line 547-548: raises details: get_center_point can also raise exceptions,
either add them to the docstring or easier, add a mention re: get_center_point
and it's docs.

574,586: the word 'touch' should probably be '"finger"' in the docstring
(":raises RuntimeError: if the touch is not pressed." )

review: Needs Fixing
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
Leo Arias (elopio) wrote : Posted in a previous version of this proposal

> Most important is line: 302 will raise an exception as _get_touch_events isn't
> passed anything (I presume res_x/res_y are the intended vars.)

Yes, my bad. I added a tests for get_touch_events.

> line 213: It is possible (not likely, but possible) that cls._device will
> None,
> please make a check here.
> (The on_test_end stuff should be going away in the near future as it's a
> little
> hacky, but is here to stay for now.)

Of course, added with tests.
>
> Can you please add a test for this and make the fix? Also, if you could run
> this over the Unity8 tests to ensure there are no other regressions (due to
> there not being 100% coverage of this module).

I'm trying to get unity8 tests to run with this branch now. I'll let you know the results.

> Line 249: Can you expand on the deprecated message please? Something like "the
> Touch class to instantiate a device object" or similar?
> (Currently the deprecated method outputs this string: "This function is
> deprecated. Please use '%(your_string_here)s' instead.")

Done.

> lines 401 - 404: The docstring appears to contain details intended for the
> __init__ method.

Right, sorry. Done.

> line 410: Minor: It seems inconstant to me to have this number stored as a
> class variable where previously (i.e. press_value line 975) has been used
> locally. I'm not really suggesting a fix, just noticing it.

I moved it closer to where it's being used.

> line 547-548: raises details: get_center_point can also raise exceptions,
> either add them to the docstring or easier, add a mention re: get_center_point
> and it's docs.

Done.

> 574,586: the word 'touch' should probably be '"finger"' in the docstring
> (":raises RuntimeError: if the touch is not pressed." )

Done.

Thanks for the suggestions.

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
Leo Arias (elopio) wrote : Posted in a previous version of this proposal

Here is the branch that updates unity:

https://code.launchpad.net/~elopio/unity8/no_uinput_side-effects/+merge/203876

I wanted to clean it more using only the public methods, but that needs this branch:
https://code.launchpad.net/~elopio/autopilot/fix1266601-Pointer-pressed-move/+merge/202206

Which in turn needs this refactor to land.
So for the moment it will be ugly, and when things start to get published, I'll clean it up.

Revision history for this message
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Hi Leo,

Awesome, looking good.

Only one minor thing to ask of you:
Please add a test for L275 to ensure the deprecated message is output.
(An example can be found within
test_using_pick_app_launcher_produces_deprecation_message here:
https://code.launchpad.net/~veebers/autopilot/fix_deprecate_pick_app_launcher/+merge/202784)

Once this is done I'll be more than happy to approve.

Thanks again.

review: Needs Fixing
Revision history for this message
Leo Arias (elopio) wrote : Posted in a previous version of this proposal

Done.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (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
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

Awesome, LGTM

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'autopilot/input/_common.py'
--- autopilot/input/_common.py 2013-12-10 03:10:11 +0000
+++ autopilot/input/_common.py 2014-02-19 18:32:31 +0000
@@ -26,8 +26,18 @@
2626
2727
28def get_center_point(object_proxy):28def get_center_point(object_proxy):
29 """Get the center point of an object, searching for several different ways29 """Get the center point of an object.
30 of determining exactly where the center is.30
31 It searches for several different ways of determining exactly where the
32 center is.
33
34 :raises ValueError: if `object_proxy` has the globalRect attribute but it
35 is not of the correct type.
36 :raises ValueError: if `object_proxy` doesn't have the globalRect
37 attribute, it has the x and y attributes instead, but they are not of
38 the correct type.
39 :raises ValueError: if `object_proxy` doesn't have any recognised position
40 attributes.
3141
32 """42 """
33 try:43 try:
3444
=== modified file 'autopilot/input/_uinput.py'
--- autopilot/input/_uinput.py 2013-11-07 05:53:36 +0000
+++ autopilot/input/_uinput.py 2014-02-19 18:32:31 +0000
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#2#
3# Autopilot Functional Test Tool3# Autopilot Functional Test Tool
4# Copyright (C) 2012-2013 Canonical4# Copyright (C) 2012, 2013, 2014 Canonical
5#5#
6# This program is free software: you can redistribute it and/or modify6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by7# it under the terms of the GNU General Public License as published by
@@ -17,27 +17,23 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#18#
1919
20
21"""UInput device drivers."""20"""UInput device drivers."""
2221
22import logging
23import os.path
24
25import six
26from evdev import UInput, ecodes as e
27
28import autopilot.platform
23from autopilot.input import Keyboard as KeyboardBase29from autopilot.input import Keyboard as KeyboardBase
24from autopilot.input import Touch as TouchBase30from autopilot.input import Touch as TouchBase
25from autopilot.input._common import get_center_point31from autopilot.input._common import get_center_point
26from autopilot.utilities import sleep32from autopilot.utilities import deprecated, sleep
27import autopilot.platform
2833
29import logging
30from evdev import UInput, ecodes as e
31import os.path
32import six
3334
34logger = logging.getLogger(__name__)35logger = logging.getLogger(__name__)
3536
36PRESS = 1
37RELEASE = 0
38
39_PRESSED_KEYS = []
40
4137
42def _get_devnode_path():38def _get_devnode_path():
43 """Provide a fallback uinput node for devices which don't support udev"""39 """Provide a fallback uinput node for devices which don't support udev"""
@@ -47,13 +43,78 @@
47 return devnode43 return devnode
4844
4945
46class _UInputKeyboardDevice(object):
47 """Wrapper for the UInput Keyboard to execute its primitives."""
48
49 def __init__(self, device_class=UInput):
50 super(_UInputKeyboardDevice, self).__init__()
51 self._device = device_class(devnode=_get_devnode_path())
52 self._pressed_keys_ecodes = []
53
54 def press(self, key):
55 """Press one key button.
56
57 It ignores case, so, for example, 'a' and 'A' are mapped to the same
58 key.
59
60 """
61 ecode = self._get_ecode_for_key(key)
62 logger.debug('Pressing %s (%r).', key, ecode)
63 self._emit_press_event(ecode)
64 self._pressed_keys_ecodes.append(ecode)
65
66 def _get_ecode_for_key(self, key):
67 key_name = key if key.startswith('KEY_') else 'KEY_' + key
68 key_name = key_name.upper()
69 ecode = e.ecodes.get(key_name, None)
70 if ecode is None:
71 raise ValueError('Unknown key name: %s.' % key)
72 return ecode
73
74 def _emit_press_event(self, ecode):
75 press_value = 1
76 self._emit(ecode, press_value)
77
78 def _emit(self, ecode, value):
79 self._device.write(e.EV_KEY, ecode, value)
80 self._device.syn()
81
82 def release(self, key):
83 """Release one key button.
84
85 It ignores case, so, for example, 'a' and 'A' are mapped to the same
86 key.
87
88 :raises ValueError: if ``key`` is not pressed.
89
90 """
91 ecode = self._get_ecode_for_key(key)
92 if ecode in self._pressed_keys_ecodes:
93 logger.debug('Releasing %s (%r).', key, ecode)
94 self._emit_release_event(ecode)
95 self._pressed_keys_ecodes.remove(ecode)
96 else:
97 raise ValueError('Key %r not pressed.' % key)
98
99 def _emit_release_event(self, ecode):
100 release_value = 0
101 self._emit(ecode, release_value)
102
103 def release_pressed_keys(self):
104 """Release all the keys that are currently pressed."""
105 for ecode in self._pressed_keys_ecodes:
106 self._emit_release_event(ecode)
107 self._pressed_keys_ecodes = []
108
109
50class Keyboard(KeyboardBase):110class Keyboard(KeyboardBase):
51111
52 _device = UInput(devnode=_get_devnode_path())112 _device = None
53113
54 def _emit(self, event, value):114 def __init__(self, device_class=_UInputKeyboardDevice):
55 Keyboard._device.write(e.EV_KEY, event, value)115 super(Keyboard, self).__init__()
56 Keyboard._device.syn()116 if Keyboard._device is None:
117 Keyboard._device = device_class()
57118
58 def _sanitise_keys(self, keys):119 def _sanitise_keys(self, keys):
59 if keys == '+':120 if keys == '+':
@@ -71,15 +132,15 @@
71132
72 presses the 'Alt' and 'F2' keys.133 presses the 'Alt' and 'F2' keys.
73134
135 :raises TypeError: if ``keys`` is not a string.
136
74 """137 """
75 if not isinstance(keys, six.string_types):138 if not isinstance(keys, six.string_types):
76 raise TypeError("'keys' argument must be a string.")139 raise TypeError("'keys' argument must be a string.")
77140
78 for key in self._sanitise_keys(keys):141 for key in self._sanitise_keys(keys):
79 for event in Keyboard._get_events_for_key(key):142 for key_button in self._get_key_buttons(key):
80 logger.debug("Pressing %s (%r)", key, event)143 self._device.press(key_button)
81 _PRESSED_KEYS.append(event)
82 self._emit(event, PRESS)
83 sleep(delay)144 sleep(delay)
84145
85 def release(self, keys, delay=0.1):146 def release(self, keys, delay=0.1):
@@ -94,16 +155,16 @@
94155
95 Keys are released in the reverse order in which they are specified.156 Keys are released in the reverse order in which they are specified.
96157
158 :raises TypeError: if ``keys`` is not a string.
159 :raises ValueError: if one of the keys to be released is not pressed.
160
97 """161 """
98 if not isinstance(keys, six.string_types):162 if not isinstance(keys, six.string_types):
99 raise TypeError("'keys' argument must be a string.")163 raise TypeError("'keys' argument must be a string.")
100164
101 for key in reversed(self._sanitise_keys(keys)):165 for key in reversed(self._sanitise_keys(keys)):
102 for event in Keyboard._get_events_for_key(key):166 for key_button in reversed(self._get_key_buttons(key)):
103 logger.debug("Releasing %s (%r)", key, event)167 self._device.release(key_button)
104 if event in _PRESSED_KEYS:
105 _PRESSED_KEYS.remove(event)
106 self._emit(event, RELEASE)
107 sleep(delay)168 sleep(delay)
108169
109 def press_and_release(self, keys, delay=0.1):170 def press_and_release(self, keys, delay=0.1):
@@ -118,6 +179,8 @@
118179
119 presses both the 'Alt' and 'F2' keys, and then releases both keys.180 presses both the 'Alt' and 'F2' keys, and then releases both keys.
120181
182 :raises TypeError: if ``keys`` is not a string.
183
121 """184 """
122 logger.debug("Pressing and Releasing: %s", keys)185 logger.debug("Pressing and Releasing: %s", keys)
123 self.press(keys, delay)186 self.press(keys, delay)
@@ -129,6 +192,8 @@
129 Only 'normal' keys can be typed with this method. Control characters192 Only 'normal' keys can be typed with this method. Control characters
130 (such as 'Alt' will be interpreted as an 'A', and 'l', and a 't').193 (such as 'Alt' will be interpreted as an 'A', and 'l', and a 't').
131194
195 :raises TypeError: if ``keys`` is not a string.
196
132 """197 """
133 if not isinstance(string, six.string_types):198 if not isinstance(string, six.string_types):
134 raise TypeError("'keys' argument must be a string.")199 raise TypeError("'keys' argument must be a string.")
@@ -145,98 +210,35 @@
145 any keys that were pressed and not released.210 any keys that were pressed and not released.
146211
147 """212 """
148 global _PRESSED_KEYS213 if cls._device is not None:
149 if len(_PRESSED_KEYS) == 0:214 cls._device.release_pressed_keys()
150 return215
151216 def _get_key_buttons(self, key):
152 def _release(event):217 """Return a list of the key buttons required to press.
153 Keyboard._device.write(e.EV_KEY, event, RELEASE)218
154 Keyboard._device.syn()219 Multiple buttons will be returned when the key specified requires more
155 for event in _PRESSED_KEYS:
156 logger.warning("Releasing key %r as part of cleanup call.", event)
157 _release(event)
158 _PRESSED_KEYS = []
159
160 @staticmethod
161 def _get_events_for_key(key):
162 """Return a list of events required to generate 'key' as an input.
163
164 Multiple keys will be returned when the key specified requires more
165 than one keypress to generate (for example, upper-case letters).220 than one keypress to generate (for example, upper-case letters).
166221
167 """222 """
168 events = []223 key_buttons = []
169 if key.isupper() or key in _SHIFTED_KEYS:224 if key.isupper() or key in _SHIFTED_KEYS:
170 events.append(e.KEY_LEFTSHIFT)225 key_buttons.append('KEY_LEFTSHIFT')
171 keyname = _UINPUT_CODE_TRANSLATIONS.get(key.upper(), key)226 key_name = _UINPUT_CODE_TRANSLATIONS.get(key.upper(), key)
172 evt = getattr(e, 'KEY_' + keyname.upper(), None)227 key_buttons.append(key_name)
173 if evt is None:228 return key_buttons
174 raise ValueError("Unknown key name: '%s'" % key)229
175 events.append(evt)230
176 return events231@deprecated('the Touch class to instantiate a device object')
177
178
179last_tracking_id = 0
180
181
182def get_next_tracking_id():
183 global last_tracking_id
184 last_tracking_id += 1
185 return last_tracking_id
186
187
188def create_touch_device(res_x=None, res_y=None):232def create_touch_device(res_x=None, res_y=None):
189 """Create and return a UInput touch device.233 """Create and return a UInput touch device.
190234
191 If res_x and res_y are not specified, they will be queried from the system.235 If res_x and res_y are not specified, they will be queried from the system.
192236
193 """237 """
194238 return UInput(events=_get_touch_events(res_x, res_y),
195 if res_x is None or res_y is None:239 name='autopilot-finger',
196 from autopilot.display import Display240 version=0x2, devnode=_get_devnode_path())
197 display = Display.create()241
198 # TODO: This calculation needs to become part of the display module:
199 l = r = t = b = 0
200 for screen in range(display.get_num_screens()):
201 geometry = display.get_screen_geometry(screen)
202 if geometry[0] < l:
203 l = geometry[0]
204 if geometry[1] < t:
205 t = geometry[1]
206 if geometry[0] + geometry[2] > r:
207 r = geometry[0] + geometry[2]
208 if geometry[1] + geometry[3] > b:
209 b = geometry[1] + geometry[3]
210 res_x = r - l
211 res_y = b - t
212
213 # android uses BTN_TOOL_FINGER, whereas desktop uses BTN_TOUCH. I have no
214 # idea why...
215 touch_tool = e.BTN_TOOL_FINGER
216 if autopilot.platform.model() == 'Desktop':
217 touch_tool = e.BTN_TOUCH
218
219 cap_mt = {
220 e.EV_ABS: [
221 (e.ABS_X, (0, res_x, 0, 0)),
222 (e.ABS_Y, (0, res_y, 0, 0)),
223 (e.ABS_PRESSURE, (0, 65535, 0, 0)),
224 (e.ABS_MT_POSITION_X, (0, res_x, 0, 0)),
225 (e.ABS_MT_POSITION_Y, (0, res_y, 0, 0)),
226 (e.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)),
227 (e.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)),
228 (e.ABS_MT_PRESSURE, (0, 255, 0, 0)),
229 (e.ABS_MT_SLOT, (0, 9, 0, 0)),
230 ],
231 e.EV_KEY: [
232 touch_tool,
233 ]
234 }
235
236 return UInput(cap_mt, name='autopilot-finger', version=0x2,
237 devnode=_get_devnode_path())
238
239_touch_device = create_touch_device()
240242
241# Multiouch notes:243# Multiouch notes:
242# ----------------244# ----------------
@@ -281,90 +283,253 @@
281# about this is that the SLOT refers to a finger number, and the TRACKING_ID283# about this is that the SLOT refers to a finger number, and the TRACKING_ID
282# identifies a unique touch for the duration of it's existance.284# identifies a unique touch for the duration of it's existance.
283285
284_touch_fingers_in_use = []286
285287def _get_touch_events(res_x=None, res_y=None):
286288 if res_x is None or res_y is None:
287def _get_touch_finger():289 res_x, res_y = _get_system_resolution()
288 """Claim a touch finger id for use.290
289291 touch_tool = _get_touch_tool()
290 :raises: RuntimeError if no more fingers are available.292
291293 events = {
292 """294 e.EV_ABS: [
293 global _touch_fingers_in_use295 (e.ABS_X, (0, res_x, 0, 0)),
294296 (e.ABS_Y, (0, res_y, 0, 0)),
295 for i in range(9):297 (e.ABS_PRESSURE, (0, 65535, 0, 0)),
296 if i not in _touch_fingers_in_use:298 (e.ABS_MT_POSITION_X, (0, res_x, 0, 0)),
297 _touch_fingers_in_use.append(i)299 (e.ABS_MT_POSITION_Y, (0, res_y, 0, 0)),
298 return i300 (e.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)),
299 raise RuntimeError("All available fingers have been used already.")301 (e.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)),
300302 (e.ABS_MT_PRESSURE, (0, 255, 0, 0)),
301303 (e.ABS_MT_SLOT, (0, 9, 0, 0)),
302def _release_touch_finger(finger_num):304 ],
303 """Relase a previously-claimed finger id.305 e.EV_KEY: [
304306 touch_tool,
305 :raises: RuntimeError if the finger given was never claimed, or was already307 ]
306 released.308 }
307309 return events
308 """310
309 global _touch_fingers_in_use311
310312def _get_system_resolution():
311 if finger_num not in _touch_fingers_in_use:313 from autopilot.display import Display
312 raise RuntimeError(314 display = Display.create()
313 "Finger %d was never claimed, or has already been released." %315 # TODO: This calculation needs to become part of the display module:
314 (finger_num))316 l = r = t = b = 0
315 _touch_fingers_in_use.remove(finger_num)317 for screen in range(display.get_num_screens()):
316 assert(finger_num not in _touch_fingers_in_use)318 geometry = display.get_screen_geometry(screen)
319 if geometry[0] < l:
320 l = geometry[0]
321 if geometry[1] < t:
322 t = geometry[1]
323 if geometry[0] + geometry[2] > r:
324 r = geometry[0] + geometry[2]
325 if geometry[1] + geometry[3] > b:
326 b = geometry[1] + geometry[3]
327 res_x = r - l
328 res_y = b - t
329 return res_x, res_y
330
331
332def _get_touch_tool():
333 # android uses BTN_TOOL_FINGER, whereas desktop uses BTN_TOUCH. I have
334 # no idea why...
335 if autopilot.platform.model() == 'Desktop':
336 touch_tool = e.BTN_TOUCH
337 else:
338 touch_tool = e.BTN_TOOL_FINGER
339 return touch_tool
340
341
342class _UInputTouchDevice(object):
343 """Wrapper for the UInput Touch to execute its primitives."""
344
345 _device = None
346 _touch_fingers_in_use = []
347 _last_tracking_id = 0
348
349 def __init__(self, res_x=None, res_y=None, device_class=UInput):
350 """Class constructor.
351
352 If res_x and res_y are not specified, they will be queried from the
353 system.
354
355 """
356 super(_UInputTouchDevice, self).__init__()
357 if _UInputTouchDevice._device is None:
358 _UInputTouchDevice._device = device_class(
359 events=_get_touch_events(res_x, res_y),
360 name='autopilot-finger',
361 version=0x2, devnode=_get_devnode_path())
362 self._touch_finger_slot = None
363
364 @property
365 def pressed(self):
366 return self._touch_finger_slot is not None
367
368 def finger_down(self, x, y):
369 """Internal: moves finger "finger" down on the touchscreen.
370
371 :param x: The finger will be moved to this x coordinate.
372 :param y: The finger will be moved to this y coordinate.
373
374 :raises RuntimeError: if the finger is already pressed.
375 :raises RuntimeError: if no more touch slots are available.
376
377 """
378 if self.pressed:
379 raise RuntimeError("Cannot press finger: it's already pressed.")
380 self._touch_finger_slot = self._get_free_touch_finger_slot()
381
382 self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot)
383 self._device.write(
384 e.EV_ABS, e.ABS_MT_TRACKING_ID, self._get_next_tracking_id())
385 press_value = 1
386 self._device.write(e.EV_KEY, e.BTN_TOOL_FINGER, press_value)
387 self._device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x))
388 self._device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y))
389 self._device.write(e.EV_ABS, e.ABS_MT_PRESSURE, 400)
390 self._device.syn()
391
392 def _get_free_touch_finger_slot(self):
393 """Return the id of a free touch finger.
394
395 :raises RuntimeError: if no more touch slots are available.
396
397 """
398 max_number_of_fingers = 9
399 for i in range(max_number_of_fingers):
400 if i not in _UInputTouchDevice._touch_fingers_in_use:
401 _UInputTouchDevice._touch_fingers_in_use.append(i)
402 return i
403 raise RuntimeError('All available fingers have been used already.')
404
405 def _get_next_tracking_id(self):
406 _UInputTouchDevice._last_tracking_id += 1
407 return _UInputTouchDevice._last_tracking_id
408
409 def finger_move(self, x, y):
410 """Internal: moves finger "finger" on the touchscreen to pos (x,y)
411
412 NOTE: The finger has to be down for this to have any effect.
413
414 :raises RuntimeError: if the finger is not pressed.
415
416 """
417 if not self.pressed:
418 raise RuntimeError('Attempting to move without finger being down.')
419 self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot)
420 self._device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x))
421 self._device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y))
422 self._device.syn()
423
424 def finger_up(self):
425 """Internal: moves finger "finger" up from the touchscreen
426
427 :raises RuntimeError: if the finger is not pressed.
428
429 """
430 if not self.pressed:
431 raise RuntimeError("Cannot release finger: it's not pressed.")
432 self._device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger_slot)
433 lift_tracking_id = -1
434 self._device.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, lift_tracking_id)
435 release_value = 0
436 self._device.write(e.EV_KEY, e.BTN_TOOL_FINGER, release_value)
437 self._device.syn()
438 self._release_touch_finger()
439
440 def _release_touch_finger(self):
441 """Release the touch finger.
442
443 :raises RuntimeError: if the finger was not claimed before or was
444 already released.
445
446 """
447 if (self._touch_finger_slot not in
448 _UInputTouchDevice._touch_fingers_in_use):
449 raise RuntimeError(
450 "Finger %d was never claimed, or has already been released." %
451 self._touch_finger_slot)
452 _UInputTouchDevice._touch_fingers_in_use.remove(
453 self._touch_finger_slot)
454 self._touch_finger_slot = None
317455
318456
319class Touch(TouchBase):457class Touch(TouchBase):
320 """Low level interface to generate single finger touch events."""458 """Low level interface to generate single finger touch events."""
321459
322 def __init__(self):460 def __init__(self, device_class=_UInputTouchDevice):
323 super(Touch, self).__init__()461 super(Touch, self).__init__()
324 self._touch_finger = None462 self._device = device_class()
325463
326 @property464 @property
327 def pressed(self):465 def pressed(self):
328 return self._touch_finger is not None466 return self._device.pressed
329467
330 def tap(self, x, y):468 def tap(self, x, y):
331 """Click (or 'tap') at given x and y coordinates."""469 """Click (or 'tap') at given x and y coordinates.
470
471 :raises RuntimeError: if the finger is already pressed.
472 :raises RuntimeError: if no more finger slots are available.
473
474 """
332 logger.debug("Tapping at: %d,%d", x, y)475 logger.debug("Tapping at: %d,%d", x, y)
333 self._finger_down(x, y)476 self._device.finger_down(x, y)
334 sleep(0.1)477 sleep(0.1)
335 self._finger_up()478 self._device.finger_up()
336479
337 def tap_object(self, object):480 def tap_object(self, object_):
338 """Click (or 'tap') a given object"""481 """Click (or 'tap') a given object.
482
483 :raises RuntimeError: if the finger is already pressed.
484 :raises RuntimeError: if no more finger slots are available.
485 :raises ValueError: if `object_` doesn't have any recognised position
486 attributes or if they are not of the correct type.
487
488 """
339 logger.debug("Tapping object: %r", object)489 logger.debug("Tapping object: %r", object)
340 x, y = get_center_point(object)490 x, y = get_center_point(object_)
341 self.tap(x, y)491 self.tap(x, y)
342492
343 def press(self, x, y):493 def press(self, x, y):
344 """Press and hold a given object or at the given coordinates494 """Press and hold a given object or at the given coordinates.
345 Call release() when the object has been pressed long enough"""495
496 Call release() when the object has been pressed long enough.
497
498 :raises RuntimeError: if the finger is already pressed.
499 :raises RuntimeError: if no more finger slots are available.
500
501 """
346 logger.debug("Pressing at: %d,%d", x, y)502 logger.debug("Pressing at: %d,%d", x, y)
347 self._finger_down(x, y)503 self._device.finger_down(x, y)
348504
349 def release(self):505 def release(self):
350 """Release a previously pressed finger"""506 """Release a previously pressed finger.
507
508 :raises RuntimeError: if the touch is not pressed.
509
510 """
351 logger.debug("Releasing")511 logger.debug("Releasing")
352 self._finger_up()512 self._device.finger_up()
353513
354 def move(self, x, y):514 def move(self, x, y):
355 """Moves the pointing "finger" to pos(x,y).515 """Moves the pointing "finger" to pos(x,y).
356516
357 NOTE: The finger has to be down for this to have any effect.517 NOTE: The finger has to be down for this to have any effect.
358518
519 :raises RuntimeError: if the finger is not pressed.
520
359 """521 """
360 if self._touch_finger is None:522 self._device.finger_move(x, y)
361 raise RuntimeError("Attempting to move without finger being down.")
362 self._finger_move(x, y)
363523
364 def drag(self, x1, y1, x2, y2):524 def drag(self, x1, y1, x2, y2):
365 """Perform a drag gesture from (x1,y1) to (x2,y2)"""525 """Perform a drag gesture from (x1,y1) to (x2,y2).
526
527 :raises RuntimeError: if the finger is already pressed.
528 :raises RuntimeError: if no more finger slots are available.
529
530 """
366 logger.debug("Dragging from %d,%d to %d,%d", x1, y1, x2, y2)531 logger.debug("Dragging from %d,%d to %d,%d", x1, y1, x2, y2)
367 self._finger_down(x1, y1)532 self._device.finger_down(x1, y1)
368533
369 # Let's drag in 100 steps for now...534 # Let's drag in 100 steps for now...
370 dx = 1.0 * (x2 - x1) / 100535 dx = 1.0 * (x2 - x1) / 100
@@ -372,52 +537,13 @@
372 cur_x = x1 + dx537 cur_x = x1 + dx
373 cur_y = y1 + dy538 cur_y = y1 + dy
374 for i in range(0, 100):539 for i in range(0, 100):
375 self._finger_move(int(cur_x), int(cur_y))540 self._device.finger_move(int(cur_x), int(cur_y))
376 sleep(0.002)541 sleep(0.002)
377 cur_x += dx542 cur_x += dx
378 cur_y += dy543 cur_y += dy
379 # Make sure we actually end up at target544 # Make sure we actually end up at target
380 self._finger_move(x2, y2)545 self._device.finger_move(x2, y2)
381 self._finger_up()546 self._device.finger_up()
382
383 def _finger_down(self, x, y):
384 """Internal: moves finger "finger" down on the touchscreen.
385
386 :param x: The finger will be moved to this x coordinate.
387 :param y: The finger will be moved to this y coordinate.
388
389 """
390 if self._touch_finger is not None:
391 raise RuntimeError("Cannot press finger: it's already pressed.")
392 self._touch_finger = _get_touch_finger()
393
394 _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger)
395 _touch_device.write(
396 e.EV_ABS, e.ABS_MT_TRACKING_ID, get_next_tracking_id())
397 _touch_device.write(e.EV_KEY, e.BTN_TOOL_FINGER, 1)
398 _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x))
399 _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y))
400 _touch_device.write(e.EV_ABS, e.ABS_MT_PRESSURE, 400)
401 _touch_device.syn()
402
403 def _finger_move(self, x, y):
404 """Internal: moves finger "finger" on the touchscreen to pos (x,y)
405 NOTE: The finger has to be down for this to have any effect."""
406 if self._touch_finger is not None:
407 _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger)
408 _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_X, int(x))
409 _touch_device.write(e.EV_ABS, e.ABS_MT_POSITION_Y, int(y))
410 _touch_device.syn()
411
412 def _finger_up(self):
413 """Internal: moves finger "finger" up from the touchscreen"""
414 if self._touch_finger is None:
415 raise RuntimeError("Cannot release finger: it's not pressed.")
416 _touch_device.write(e.EV_ABS, e.ABS_MT_SLOT, self._touch_finger)
417 _touch_device.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, -1)
418 _touch_device.write(e.EV_KEY, e.BTN_TOOL_FINGER, 0)
419 _touch_device.syn()
420 self._touch_finger = _release_touch_finger(self._touch_finger)
421547
422548
423# veebers: there should be a better way to handle this.549# veebers: there should be a better way to handle this.
424550
=== modified file 'autopilot/tests/functional/test_input_stack.py'
--- autopilot/tests/functional/test_input_stack.py 2013-12-16 00:20:40 +0000
+++ autopilot/tests/functional/test_input_stack.py 2014-02-19 18:32:31 +0000
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#2#
3# Autopilot Functional Test Tool3# Autopilot Functional Test Tool
4# Copyright (C) 2012-2013 Canonical4# Copyright (C) 2012, 2013, 2014 Canonical
5#5#
6# This program is free software: you can redistribute it and/or modify6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by7# it under the terms of the GNU General Public License as published by
@@ -150,8 +150,8 @@
150 from autopilot.input._X11 import _PRESSED_KEYS150 from autopilot.input._X11 import _PRESSED_KEYS
151 return _PRESSED_KEYS151 return _PRESSED_KEYS
152 elif self.backend == 'UInput':152 elif self.backend == 'UInput':
153 from autopilot.input._uinput import _PRESSED_KEYS153 from autopilot.input import _uinput
154 return _PRESSED_KEYS154 return _uinput.Keyboard._device._pressed_keys_ecodes
155 else:155 else:
156 self.fail("Don't know how to get pressed keys list for backend "156 self.fail("Don't know how to get pressed keys list for backend "
157 + self.backend157 + self.backend
@@ -551,8 +551,9 @@
551 test_result = FakeTestCase("test_press_key").run()551 test_result = FakeTestCase("test_press_key").run()
552552
553 self.assertThat(test_result.wasSuccessful(), Equals(True))553 self.assertThat(test_result.wasSuccessful(), Equals(True))
554 from autopilot.input._uinput import _PRESSED_KEYS554 from autopilot.input import _uinput
555 self.assertThat(_PRESSED_KEYS, Equals([]))555 self.assertThat(
556 _uinput.Keyboard._device._pressed_keys_ecodes, Equals([]))
556557
557 @patch('autopilot.input._X11.fake_input', new=lambda *args: None, )558 @patch('autopilot.input._X11.fake_input', new=lambda *args: None, )
558 def test_mouse_button_released(self):559 def test_mouse_button_released(self):
559560
=== modified file 'autopilot/tests/unit/test_input.py'
--- autopilot/tests/unit/test_input.py 2013-12-10 03:10:11 +0000
+++ autopilot/tests/unit/test_input.py 2014-02-19 18:32:31 +0000
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#2#
3# Autopilot Functional Test Tool3# Autopilot Functional Test Tool
4# Copyright (C) 2013 Canonical4# Copyright (C) 2013, 2014 Canonical
5#5#
6# This program is free software: you can redistribute it and/or modify6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by7# it under the terms of the GNU General Public License as published by
@@ -17,11 +17,16 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#18#
1919
20from mock import patch20import testscenarios
21from evdev import ecodes, uinput
22from mock import ANY, call, patch, Mock
23from six import StringIO
21from testtools import TestCase24from testtools import TestCase
22from testtools.matchers import raises25from testtools.matchers import Contains, raises
2326
27from autopilot.input import _uinput
24from autopilot.input._common import get_center_point28from autopilot.input._common import get_center_point
29from autopilot import utilities
2530
2631
27class Empty(object):32class Empty(object):
@@ -151,3 +156,578 @@
151156
152 self.assertEqual(123, x)157 self.assertEqual(123, x)
153 self.assertEqual(345, y)158 self.assertEqual(345, y)
159
160
161class UInputTestCase(TestCase):
162 """Tests for the global methods of the uinput module."""
163
164 def test_create_touch_device_must_print_deprecation_message(self):
165 with patch('sys.stderr', new=StringIO()) as stderr:
166 with patch('autopilot.input._uinput.UInput'):
167 _uinput.create_touch_device('dummy', 'dummy')
168 self.assertThat(
169 stderr.getvalue(),
170 Contains(
171 "This function is deprecated. Please use 'the Touch class to "
172 "instantiate a device object' instead."
173 )
174 )
175
176
177class UInputKeyboardDeviceTestCase(TestCase):
178 """Test the integration with evdev.UInput for the keyboard."""
179
180 _PRESS_VALUE = 1
181 _RELEASE_VALUE = 0
182
183 def get_keyboard_with_mocked_backend(self):
184 keyboard = _uinput._UInputKeyboardDevice(device_class=Mock)
185 keyboard._device.mock_add_spec(uinput.UInput, spec_set=True)
186 return keyboard
187
188 def assert_key_press_emitted_write_and_syn(self, keyboard, key):
189 self.assert_emitted_write_and_syn(keyboard, key, self._PRESS_VALUE)
190
191 def assert_key_release_emitted_write_and_syn(self, keyboard, key):
192 self.assert_emitted_write_and_syn(keyboard, key, self._RELEASE_VALUE)
193
194 def assert_emitted_write_and_syn(self, keyboard, key, value):
195 key_ecode = ecodes.ecodes.get(key)
196 expected_calls = [
197 call.write(ecodes.EV_KEY, key_ecode, value),
198 call.syn()
199 ]
200
201 self.assertEqual(expected_calls, keyboard._device.mock_calls)
202
203 def press_key_and_reset_mock(self, keyboard, key):
204 keyboard.press(key)
205 keyboard._device.reset_mock()
206
207 def test_press_key_must_emit_write_and_syn(self):
208 keyboard = self.get_keyboard_with_mocked_backend()
209 keyboard.press('KEY_A')
210 self.assert_key_press_emitted_write_and_syn(keyboard, 'KEY_A')
211
212 def test_press_key_must_append_leading_string(self):
213 keyboard = self.get_keyboard_with_mocked_backend()
214 keyboard.press('A')
215 self.assert_key_press_emitted_write_and_syn(keyboard, 'KEY_A')
216
217 def test_press_key_must_ignore_case(self):
218 keyboard = self.get_keyboard_with_mocked_backend()
219 keyboard.press('a')
220 self.assert_key_press_emitted_write_and_syn(keyboard, 'KEY_A')
221
222 def test_press_unexisting_key_must_raise_error(self):
223 keyboard = self.get_keyboard_with_mocked_backend()
224 error = self.assertRaises(
225 ValueError, keyboard.press, 'unexisting')
226
227 self.assertEqual('Unknown key name: unexisting.', str(error))
228
229 def test_release_not_pressed_key_must_raise_error(self):
230 keyboard = self.get_keyboard_with_mocked_backend()
231 error = self.assertRaises(
232 ValueError, keyboard.release, 'A')
233
234 self.assertEqual("Key 'A' not pressed.", str(error))
235
236 def test_release_key_must_emit_write_and_syn(self):
237 keyboard = self.get_keyboard_with_mocked_backend()
238 self.press_key_and_reset_mock(keyboard, 'KEY_A')
239
240 keyboard.release('KEY_A')
241 self.assert_key_release_emitted_write_and_syn(keyboard, 'KEY_A')
242
243 def test_release_key_must_append_leading_string(self):
244 keyboard = self.get_keyboard_with_mocked_backend()
245 self.press_key_and_reset_mock(keyboard, 'KEY_A')
246
247 keyboard.release('A')
248 self.assert_key_release_emitted_write_and_syn(keyboard, 'KEY_A')
249
250 def test_release_key_must_ignore_case(self):
251 keyboard = self.get_keyboard_with_mocked_backend()
252 self.press_key_and_reset_mock(keyboard, 'KEY_A')
253
254 keyboard.release('a')
255 self.assert_key_release_emitted_write_and_syn(keyboard, 'KEY_A')
256
257 def test_release_unexisting_key_must_raise_error(self):
258 keyboard = self.get_keyboard_with_mocked_backend()
259 error = self.assertRaises(
260 ValueError, keyboard.release, 'unexisting')
261
262 self.assertEqual('Unknown key name: unexisting.', str(error))
263
264 def test_release_pressed_keys_without_pressed_keys_must_do_nothing(self):
265 keyboard = self.get_keyboard_with_mocked_backend()
266 keyboard.release_pressed_keys()
267 self.assertEqual([], keyboard._device.mock_calls)
268
269 def test_release_pressed_keys_with_pressed_keys(self):
270 expected_calls = [
271 call.write(
272 ecodes.EV_KEY, ecodes.ecodes.get('KEY_A'),
273 self._RELEASE_VALUE),
274 call.syn(),
275 call.write(
276 ecodes.EV_KEY, ecodes.ecodes.get('KEY_B'),
277 self._RELEASE_VALUE),
278 call.syn()
279 ]
280
281 keyboard = self.get_keyboard_with_mocked_backend()
282 self.press_key_and_reset_mock(keyboard, 'KEY_A')
283 self.press_key_and_reset_mock(keyboard, 'KEY_B')
284
285 keyboard.release_pressed_keys()
286
287 self.assertEqual(expected_calls, keyboard._device.mock_calls)
288
289 def test_release_pressed_keys_already_released(self):
290 expected_calls = []
291 keyboard = self.get_keyboard_with_mocked_backend()
292 keyboard.press('KEY_A')
293 keyboard.release_pressed_keys()
294 keyboard._device.reset_mock()
295
296 keyboard.release_pressed_keys()
297 self.assertEqual(expected_calls, keyboard._device.mock_calls)
298
299
300class UInputKeyboardTestCase(testscenarios.TestWithScenarios, TestCase):
301 """Test UInput Keyboard helper for autopilot tests."""
302
303 scenarios = [
304 ('single key', dict(keys='a', expected_calls_args=['a'])),
305 ('upper-case letter', dict(
306 keys='A', expected_calls_args=['KEY_LEFTSHIFT', 'A'])),
307 ('key combination', dict(
308 keys='a+b', expected_calls_args=['a', 'b']))
309 ]
310
311 def setUp(self):
312 super(UInputKeyboardTestCase, self).setUp()
313 # Return to the original device after the test.
314 self.addCleanup(self.set_keyboard_device, _uinput.Keyboard._device)
315 # Mock the sleeps so we don't have to spend time actually sleeping.
316 self.addCleanup(utilities.sleep.disable_mock)
317 utilities.sleep.enable_mock()
318
319 def set_keyboard_device(self, device):
320 _uinput.Keyboard._device = device
321
322 def get_keyboard_with_mocked_backend(self):
323 _uinput.Keyboard._device = None
324 keyboard = _uinput.Keyboard(device_class=Mock)
325 keyboard._device.mock_add_spec(
326 _uinput._UInputKeyboardDevice, spec_set=True)
327 return keyboard
328
329 def test_press_must_put_press_device_keys(self):
330 expected_calls = [
331 call.press(arg) for arg in self.expected_calls_args]
332 keyboard = self.get_keyboard_with_mocked_backend()
333 keyboard.press(self.keys)
334
335 self.assertEqual(expected_calls, keyboard._device.mock_calls)
336
337 def test_release_must_release_device_keys(self):
338 keyboard = self.get_keyboard_with_mocked_backend()
339 keyboard.press(self.keys)
340 keyboard._device.reset_mock()
341
342 expected_calls = [
343 call.release(arg) for arg in
344 reversed(self.expected_calls_args)]
345 keyboard.release(self.keys)
346
347 self.assertEqual(
348 expected_calls, keyboard._device.mock_calls)
349
350 def test_press_and_release_must_press_device_keys(self):
351 expected_press_calls = [
352 call.press(arg) for arg in self.expected_calls_args]
353 ignored_calls = [
354 ANY for arg in self.expected_calls_args]
355
356 keyboard = self.get_keyboard_with_mocked_backend()
357 keyboard.press_and_release(self.keys)
358
359 self.assertEqual(
360 expected_press_calls + ignored_calls,
361 keyboard._device.mock_calls)
362
363 def test_press_and_release_must_release_device_keys_in_reverse_order(
364 self):
365 ignored_calls = [
366 ANY for arg in self.expected_calls_args]
367 expected_release_calls = [
368 call.release(arg) for arg in
369 reversed(self.expected_calls_args)]
370
371 keyboard = self.get_keyboard_with_mocked_backend()
372 keyboard.press_and_release(self.keys)
373
374 self.assertEqual(
375 ignored_calls + expected_release_calls,
376 keyboard._device.mock_calls)
377
378 def test_on_test_end_without_device_must_do_nothing(self):
379 _uinput.Keyboard._device = None
380 # This will fail if it calls anything from the device, as it's None.
381 _uinput.Keyboard.on_test_end(self)
382
383 def test_on_test_end_with_device_must_release_pressed_keys(self):
384 keyboard = self.get_keyboard_with_mocked_backend()
385 _uinput.Keyboard.on_test_end(self)
386 self.assertEqual(
387 [call.release_pressed_keys()], keyboard._device.mock_calls)
388
389
390class TouchEventsTestCase(TestCase):
391
392 def assert_expected_ev_abs(self, res_x, res_y, actual_ev_abs):
393 expected_ev_abs = [
394 (ecodes.ABS_X, (0, res_x, 0, 0)),
395 (ecodes.ABS_Y, (0, res_y, 0, 0)),
396 (ecodes.ABS_PRESSURE, (0, 65535, 0, 0)),
397 (ecodes.ABS_MT_POSITION_X, (0, res_x, 0, 0)),
398 (ecodes.ABS_MT_POSITION_Y, (0, res_y, 0, 0)),
399 (ecodes.ABS_MT_TOUCH_MAJOR, (0, 30, 0, 0)),
400 (ecodes.ABS_MT_TRACKING_ID, (0, 65535, 0, 0)),
401 (ecodes.ABS_MT_PRESSURE, (0, 255, 0, 0)),
402 (ecodes.ABS_MT_SLOT, (0, 9, 0, 0))
403 ]
404 self.assertEqual(expected_ev_abs, actual_ev_abs)
405
406 def test_get_touch_events_without_args_must_use_system_resolution(self):
407 with patch.object(
408 _uinput, '_get_system_resolution', spec_set=True,
409 autospec=True) as mock_system_resolution:
410 mock_system_resolution.return_value = (
411 'system_res_x', 'system_res_y')
412 events = _uinput._get_touch_events()
413
414 ev_abs = events.get(ecodes.EV_ABS)
415 self.assert_expected_ev_abs('system_res_x', 'system_res_y', ev_abs)
416
417 def test_get_touch_events_with_args_must_use_given_resulution(self):
418 events = _uinput._get_touch_events('given_res_x', 'given_res_y')
419 ev_abs = events.get(ecodes.EV_ABS)
420 self.assert_expected_ev_abs('given_res_x', 'given_res_y', ev_abs)
421
422
423class UInputTouchDeviceTestCase(TestCase):
424 """Test the integration with evdev.UInput for the touch device."""
425
426 def setUp(self):
427 super(UInputTouchDeviceTestCase, self).setUp()
428 self._number_of_slots = 9
429
430 # Return to the original device after the test.
431 self.addCleanup(
432 self.set_mouse_device,
433 _uinput._UInputTouchDevice._device,
434 _uinput._UInputTouchDevice._touch_fingers_in_use,
435 _uinput._UInputTouchDevice._last_tracking_id)
436
437 # Always start the tests without fingers in use.
438 _uinput._UInputTouchDevice._touch_fingers_in_use = []
439 _uinput._UInputTouchDevice._last_tracking_id = 0
440
441 def set_mouse_device(
442 self, device, touch_fingers_in_use, last_tracking_id):
443 _uinput._UInputTouchDevice._device = device
444 _uinput._UInputTouchDevice._touch_fingers_in_use = touch_fingers_in_use
445 _uinput._UInputTouchDevice._last_tracking_id = last_tracking_id
446
447 def get_touch_with_mocked_backend(self):
448 dummy_x_resolution = 100
449 dummy_y_resolution = 100
450
451 _uinput._UInputTouchDevice._device = None
452 touch = _uinput._UInputTouchDevice(
453 res_x=dummy_x_resolution, res_y=dummy_y_resolution,
454 device_class=Mock)
455 touch._device.mock_add_spec(uinput.UInput, spec_set=True)
456 return touch
457
458 def assert_finger_down_emitted_write_and_syn(
459 self, touch, slot, tracking_id, x, y):
460 press_value = 1
461 expected_calls = [
462 call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot),
463 call.write(
464 ecodes.EV_ABS, ecodes.ABS_MT_TRACKING_ID, tracking_id),
465 call.write(
466 ecodes.EV_KEY, ecodes.BTN_TOOL_FINGER, press_value),
467 call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_X, x),
468 call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_Y, y),
469 call.write(ecodes.EV_ABS, ecodes.ABS_MT_PRESSURE, 400),
470 call.syn()
471 ]
472 self.assertEqual(expected_calls, touch._device.mock_calls)
473
474 def assert_finger_move_emitted_write_and_syn(self, touch, slot, x, y):
475 expected_calls = [
476 call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot),
477 call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_X, x),
478 call.write(ecodes.EV_ABS, ecodes.ABS_MT_POSITION_Y, y),
479 call.syn()
480 ]
481 self.assertEqual(expected_calls, touch._device.mock_calls)
482
483 def assert_finger_up_emitted_write_and_syn(self, touch, slot):
484 lift_tracking_id = -1
485 release_value = 0
486 expected_calls = [
487 call.write(ecodes.EV_ABS, ecodes.ABS_MT_SLOT, slot),
488 call.write(
489 ecodes.EV_ABS, ecodes.ABS_MT_TRACKING_ID, lift_tracking_id),
490 call.write(
491 ecodes.EV_KEY, ecodes.BTN_TOOL_FINGER, release_value),
492 call.syn()
493 ]
494 self.assertEqual(expected_calls, touch._device.mock_calls)
495
496 def test_finger_down_must_use_free_slot(self):
497 for slot in range(self._number_of_slots):
498 touch = self.get_touch_with_mocked_backend()
499
500 touch.finger_down(0, 0)
501
502 self.assert_finger_down_emitted_write_and_syn(
503 touch, slot=slot, tracking_id=ANY, x=0, y=0)
504
505 def test_finger_down_without_free_slots_must_raise_error(self):
506 # Claim all the available slots.
507 for slot in range(self._number_of_slots):
508 touch = self.get_touch_with_mocked_backend()
509 touch.finger_down(0, 0)
510
511 touch = self.get_touch_with_mocked_backend()
512
513 # Try to use one more.
514 error = self.assertRaises(RuntimeError, touch.finger_down, 11, 11)
515 self.assertEqual(
516 'All available fingers have been used already.', str(error))
517
518 def test_finger_down_must_use_unique_tracking_id(self):
519 for number in range(self._number_of_slots):
520 touch = self.get_touch_with_mocked_backend()
521 touch.finger_down(0, 0)
522
523 self.assert_finger_down_emitted_write_and_syn(
524 touch, slot=ANY, tracking_id=number + 1, x=0, y=0)
525
526 def test_finger_down_must_not_reuse_tracking_ids(self):
527 # Claim and release all the available slots once.
528 for number in range(self._number_of_slots):
529 touch = self.get_touch_with_mocked_backend()
530 touch.finger_down(0, 0)
531 touch.finger_up()
532
533 touch = self.get_touch_with_mocked_backend()
534
535 touch.finger_down(12, 12)
536 self.assert_finger_down_emitted_write_and_syn(
537 touch, slot=ANY, tracking_id=number + 2, x=12, y=12)
538
539 def test_finger_down_with_finger_pressed_must_raise_error(self):
540 touch = self.get_touch_with_mocked_backend()
541 touch.finger_down(0, 0)
542
543 error = self.assertRaises(RuntimeError, touch.finger_down, 0, 0)
544 self.assertEqual(
545 "Cannot press finger: it's already pressed.", str(error))
546
547 def test_finger_move_without_finger_pressed_must_raise_error(self):
548 touch = self.get_touch_with_mocked_backend()
549
550 error = self.assertRaises(RuntimeError, touch.finger_move, 10, 10)
551 self.assertEqual(
552 'Attempting to move without finger being down.', str(error))
553
554 def test_finger_move_must_use_assigned_slot(self):
555 for slot in range(self._number_of_slots):
556 touch = self.get_touch_with_mocked_backend()
557 touch.finger_down(0, 0)
558 touch._device.reset_mock()
559
560 touch.finger_move(10, 10)
561
562 self.assert_finger_move_emitted_write_and_syn(
563 touch, slot=slot, x=10, y=10)
564
565 def test_finger_move_must_reuse_assigned_slot(self):
566 first_slot = 0
567 touch = self.get_touch_with_mocked_backend()
568 touch.finger_down(1, 1)
569 touch._device.reset_mock()
570
571 touch.finger_move(13, 13)
572 self.assert_finger_move_emitted_write_and_syn(
573 touch, slot=first_slot, x=13, y=13)
574 touch._device.reset_mock()
575
576 touch.finger_move(14, 14)
577 self.assert_finger_move_emitted_write_and_syn(
578 touch, slot=first_slot, x=14, y=14)
579
580 def test_finger_up_without_finger_pressed_must_raise_error(self):
581 touch = self.get_touch_with_mocked_backend()
582
583 error = self.assertRaises(RuntimeError, touch.finger_up)
584 self.assertEqual(
585 "Cannot release finger: it's not pressed.", str(error))
586
587 def test_finger_up_must_use_assigned_slot(self):
588 fingers = []
589 for slot in range(self._number_of_slots):
590 touch = self.get_touch_with_mocked_backend()
591 touch.finger_down(0, 0)
592 touch._device.reset_mock()
593 fingers.append(touch)
594
595 for slot, touch in enumerate(fingers):
596 touch.finger_up()
597
598 self.assert_finger_up_emitted_write_and_syn(touch, slot=slot)
599 touch._device.reset_mock()
600
601 def test_finger_up_must_release_slot(self):
602 fingers = []
603 # Claim all the available slots.
604 for slot in range(self._number_of_slots):
605 touch = self.get_touch_with_mocked_backend()
606 touch.finger_down(0, 0)
607 fingers.append(touch)
608
609 slot_to_reuse = 3
610 fingers[slot_to_reuse].finger_up()
611
612 touch = self.get_touch_with_mocked_backend()
613
614 # Try to use one more.
615 touch.finger_down(15, 15)
616 self.assert_finger_down_emitted_write_and_syn(
617 touch, slot=slot_to_reuse, tracking_id=ANY, x=15, y=15)
618
619 def test_device_with_finger_down_must_be_pressed(self):
620 touch = self.get_touch_with_mocked_backend()
621 touch.finger_down(0, 0)
622
623 self.assertTrue(touch.pressed)
624
625 def test_device_without_finger_down_must_not_be_pressed(self):
626 touch = self.get_touch_with_mocked_backend()
627 self.assertFalse(touch.pressed)
628
629 def test_device_after_finger_up_must_not_be_pressed(self):
630 touch = self.get_touch_with_mocked_backend()
631 touch.finger_down(0, 0)
632 touch.finger_up()
633
634 self.assertFalse(touch.pressed)
635
636 def test_press_other_device_must_not_press_all_of_them(self):
637 other_touch = self.get_touch_with_mocked_backend()
638 other_touch.finger_down(0, 0)
639
640 touch = self.get_touch_with_mocked_backend()
641 self.assertFalse(touch.pressed)
642
643
644class UInputTouchTestCase(TestCase):
645 """Test UInput Touch helper for autopilot tests."""
646
647 def setUp(self):
648 super(UInputTouchTestCase, self).setUp()
649 # Mock the sleeps so we don't have to spend time actually sleeping.
650 self.addCleanup(utilities.sleep.disable_mock)
651 utilities.sleep.enable_mock()
652
653 def get_touch_with_mocked_backend(self):
654 touch = _uinput.Touch(device_class=Mock)
655 touch._device.mock_add_spec(
656 _uinput._UInputTouchDevice, spec_set=True)
657 return touch
658
659 def test_tap_must_put_finger_down_and_then_up(self):
660 expected_calls = [
661 call.finger_down(0, 0),
662 call.finger_up()
663 ]
664
665 touch = self.get_touch_with_mocked_backend()
666 touch.tap(0, 0)
667 self.assertEqual(expected_calls, touch._device.mock_calls)
668
669 def test_tap_object_must_put_finger_down_and_then_up_on_the_center(self):
670 object_ = type('Dummy', (object,), {'globalRect': (0, 0, 10, 10)})
671 expected_calls = [
672 call.finger_down(5, 5),
673 call.finger_up()
674 ]
675
676 touch = self.get_touch_with_mocked_backend()
677 touch.tap_object(object_)
678 self.assertEqual(expected_calls, touch._device.mock_calls)
679
680 def test_press_must_put_finger_down(self):
681 expected_calls = [call.finger_down(0, 0)]
682
683 touch = self.get_touch_with_mocked_backend()
684 touch.press(0, 0)
685 self.assertEqual(expected_calls, touch._device.mock_calls)
686
687 def test_release_must_put_finger_up(self):
688 expected_calls = [call.finger_up()]
689
690 touch = self.get_touch_with_mocked_backend()
691 touch.release()
692 self.assertEqual(expected_calls, touch._device.mock_calls)
693
694 def test_move_must_move_finger(self):
695 expected_calls = [call.finger_move(10, 10)]
696
697 touch = self.get_touch_with_mocked_backend()
698 touch.move(10, 10)
699 self.assertEqual(expected_calls, touch._device.mock_calls)
700
701
702class MultipleUInputTouchBackend(_uinput._UInputTouchDevice):
703
704 def __init__(self, res_x=100, res_y=100, device_class=Mock):
705 super(MultipleUInputTouchBackend, self).__init__(
706 res_x, res_y, device_class)
707
708
709class MultipleUInputTouchTestCase(TestCase):
710
711 def setUp(self):
712 super(MultipleUInputTouchTestCase, self).setUp()
713 # Return to the original device after the test.
714 self.addCleanup(
715 self.set_mouse_device,
716 _uinput._UInputTouchDevice._device,
717 _uinput._UInputTouchDevice._touch_fingers_in_use,
718 _uinput._UInputTouchDevice._last_tracking_id)
719
720 def set_mouse_device(
721 self, device, touch_fingers_in_use, last_tracking_id):
722 _uinput._UInputTouchDevice._device = device
723 _uinput._UInputTouchDevice._touch_fingers_in_use = touch_fingers_in_use
724 _uinput._UInputTouchDevice._last_tracking_id = last_tracking_id
725
726 def test_press_other_device_must_not_press_all_of_them(self):
727 finger1 = _uinput.Touch(device_class=MultipleUInputTouchBackend)
728 finger2 = _uinput.Touch(device_class=MultipleUInputTouchBackend)
729
730 finger1.press(0, 0)
731 self.addCleanup(finger1.release)
732
733 self.assertFalse(finger2.pressed)
154734
=== modified file 'debian/control'
--- debian/control 2014-02-12 01:13:06 +0000
+++ debian/control 2014-02-19 18:32:31 +0000
@@ -16,6 +16,7 @@
16 python-dbus,16 python-dbus,
17 python-debian,17 python-debian,
18 python-dev,18 python-dev,
19 python-evdev,
19 python-fixtures,20 python-fixtures,
20 python-gi,21 python-gi,
21 python-junitxml,22 python-junitxml,
@@ -30,6 +31,7 @@
30 python-xlib,31 python-xlib,
31 python3-all-dev (>= 3.3),32 python3-all-dev (>= 3.3),
32 python3-dbus,33 python3-dbus,
34 python3-evdev,
33 python3-fixtures,35 python3-fixtures,
34 python3-gi,36 python3-gi,
35 python3-junitxml,37 python3-junitxml,

Subscribers

People subscribed via source and target branches