blob: bb5a9df8a51325e56f99421f8c4ee5aa5e6e8e0e [file] [log] [blame]
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A factory test to test the functionality of a mouse/trackpoint.
Description
-----------
A mouse/trackpoint test for moving and clicking.
Test Procedure
--------------
1. Move the mouse in all 4 directions. The corresponding grid will become green
once you move in that direction. The moving speed must be greater than
`move_threshold` for the test to detect the moving direction of the mouse.
2. Click the left, middle and right button of the mouse. The corresponding grid
will become green once you click the button.
If you don't pass the test in `timeout_secs` seconds, the test will fail.
Dependency
----------
- Based on Linux evdev.
Examples
--------
To test mouse/trackpoint with default parameters, add this in test list::
{
"pytest_name": "mouse"
}
If you want to change the time limit to 100 seconds::
{
"pytest_name": "mouse",
"args": {
"timeout_secs": 100
}
}
"""
import logging
import time
from cros.factory.test import test_case
from cros.factory.test.utils import evdev_utils
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import process_utils
from cros.factory.external.py_lib import evdev
class MouseTest(test_case.TestCase):
"""Tests the function of a mouse/trackpoint."""
related_components = tuple()
ARGS = [
Arg('device_filter', (int, str),
('Mouse input event id or evdev name. The test will probe for '
'event id if it is not given.'), default=None),
Arg('timeout_secs', int, 'Timeout for thte test.', default=20),
Arg('button_updown_secs', float,
'Max duration between button up and down.', default=0.6),
Arg('move_threshold', int, 'Speed threshold to detect the move direction',
default=3)
]
def setUp(self):
# yapf: disable
self.assertGreater(self.args.move_threshold, 0, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'move_threshold must be greater than 0.')
self.mouse_device = evdev_utils.FindDevice(
# yapf: disable
self.args.device_filter, evdev_utils.IsMouseDevice) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.mouse_device_name = self.mouse_device.name
self.frontend_proxy = None
self.dispatcher = None
self.down_keycode_time = {}
self.move_tested = {
'up': False,
'down': False,
'left': False,
'right': False}
self.click_tested = {
'left': False,
'middle': False,
'right': False
}
# Disable lid function since lid open|close will trigger button up event.
process_utils.CheckOutput(['ectool', 'forcelidopen', '1'])
def tearDown(self):
# yapf: disable
self.dispatcher.Close() # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.mouse_device.ungrab()
# Enable lid function.
process_utils.CheckOutput(['ectool', 'forcelidopen', '0'])
def HandleEvent(self, event):
"""Handler for evdev events."""
# yapf: disable
if event.type == evdev.ecodes.EV_KEY: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.OnClickButton(event.code, event.value)
# yapf: disable
elif event.type == evdev.ecodes.EV_REL: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.OnMoveDirection(event.code, event.value)
def OnClickButton(self, keycode, value):
button_map = {
# yapf: disable
evdev.ecodes.BTN_LEFT: 'left', # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
evdev.ecodes.BTN_MIDDLE: 'middle', # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
evdev.ecodes.BTN_RIGHT: 'right' # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
}
button = button_map[keycode]
if value == 1:
if self.down_keycode_time:
self.FailTask('More than one button clicked')
self.down_keycode_time[keycode] = time.time()
# yapf: disable
self.frontend_proxy.MarkClickButtonDown(button) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
else:
duration = time.time() - self.down_keycode_time[keycode]
# yapf: disable
if duration > self.args.button_updown_secs: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.FailTask(
# yapf: disable
f'The time between button up and down is {duration:f} second(s), ' # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
f'longer than {self.args.button_updown_secs:f} second(s).')
del self.down_keycode_time[keycode]
# yapf: disable
self.frontend_proxy.MarkClickButtonTested(button) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.click_tested[button] = True
self.CheckTestPassed()
def OnMoveDirection(self, keycode, value):
# yapf: disable
if abs(value) < self.args.move_threshold: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
return
# yapf: disable
if keycode == evdev.ecodes.REL_X: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
direction = 'right' if value > 0 else 'left'
# yapf: disable
elif keycode == evdev.ecodes.REL_Y: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
direction = 'down' if value > 0 else 'up'
else:
logging.warning('Unknown keycode: %d', keycode)
return
# yapf: disable
self.frontend_proxy.MarkMoveDirectionTested(direction) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.move_tested[direction] = True
self.CheckTestPassed()
def CheckTestPassed(self):
"""Check if all items have been tested."""
if all(self.click_tested.values()) and all(self.move_tested.values()):
self.PassTask()
def FailTestTimeout(self):
"""Fail the test due to timeout, and log untested functions."""
failed_move = [
direction for direction, tested in self.move_tested.items()
if not tested]
failed_click = [
button for button, tested in self.click_tested.items() if not tested
]
self.FailTask(
f'Test timed out. Malfunction move directions: {failed_move!r}. '
f'Malfunction buttons: {failed_click!r}')
def runTest(self):
self.mouse_device.grab()
# yapf: disable
self.ui.StartCountdownTimer(self.args.timeout_secs, self.FailTestTimeout) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.frontend_proxy = self.ui.InitJSTestObject('MouseTest') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.dispatcher = evdev_utils.InputDeviceDispatcher(
self.mouse_device,
# yapf: disable
self.event_loop.CatchException(self.HandleEvent)) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
logging.info('start monitor daemon thread')
self.dispatcher.StartDaemon()
self.WaitTaskEnd()