| # 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() |