blob: 7de31c04213973c516b464e503065561741b847b [file] [log] [blame] [edit]
# Copyright 2014 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests button functionality.
Description
-----------
This test verifies if a button is working properly by checking if its state is
changed per given instruction.
You can specify the button in different ways using the ``button_key_name``
argument:
=================== ============================================================
Key Name Description
=================== ============================================================
``gpio:[-]NUM`` A GPIO button. ``NUM`` indicates GPIO number, and ``+/-``
indicates polarity (minus for active low, otherwise active
high).
``crossystem:NAME`` A ``crossystem`` value (1 or 0) that can be retrieved by
NAME.
``ectool:NAME`` A value for ``ectool gpioget`` to fetch.
``KEYNAME`` An ``evdev`` key name that can be read from ``/dev/input``.
Try to find the right name by running ``evtest``.
=================== ============================================================
Test Procedure
--------------
When started, the test will prompt operator to press and release given button N
times, and fail if not finished in given timeout.
Dependency
----------
Depends on the driver of specified button source: GPIO, ``crossystem``,
``ectool``, or ``evdev`` (which also needs ``/dev/input`` and ``evtest``).
Examples
--------
To test the recovery button 1 time in 30 seconds, add this in test list::
{
"pytest_name": "button",
"args": {
"button_key_name": "crossystem:recoverysw_cur"
}
}
To test volume down button (using ``evdev``) 3 times in 10 seconds::
{
"pytest_name": "button",
"args": {
"timeout_secs": 10,
"button_key_name": "KEY_VOLUMEDOWN",
"repeat_times": 3
}
}
"""
import logging
import time
from cros.factory.device import device_utils
from cros.factory.test import event_log # TODO(chuntsen): Deprecate event log.
from cros.factory.test.fixture import bft_fixture
from cros.factory.test.i18n import _
from cros.factory.test.i18n import arg_utils as i18n_arg_utils
from cros.factory.test import test_case
from cros.factory.test.utils import button_utils
from cros.factory.testlog import testlog
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import sync_utils
_DEFAULT_TIMEOUT = 30
class ButtonTest(test_case.TestCase):
"""Button factory test."""
related_components = (test_case.TestCategory.HARDWARE_BUTTON, )
ARGS = [
Arg('timeout_secs', int, 'Timeout value for the test.',
default=_DEFAULT_TIMEOUT),
Arg('button_key_name', str, 'Button key name.'),
Arg('device_filter', (int, str),
'Event ID or name for evdev. None for auto probe.',
default=None),
Arg('repeat_times', int, 'Number of press/release cycles to test',
default=1),
Arg('bft_fixture', dict, bft_fixture.TEST_ARG_HELP,
default=None),
Arg('bft_button_name', str, 'Button name for BFT fixture',
default=None),
i18n_arg_utils.I18nArg('button_name', 'The name of the button.')
]
def setUp(self):
self.dut = device_utils.CreateDUTInterface()
# yapf: disable
self.ui.ToggleTemplateClass('font-large', True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.button = button_utils.Button(self.dut, self.args.button_key_name, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.args.device_filter) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# Timestamps of starting, pressing, and releasing
# [started, pressed, released, pressed, released, pressed, ...]
self._action_timestamps = [time.time()]
# yapf: disable
if self.args.bft_fixture: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self._fixture = bft_fixture.CreateBFTFixture(**self.args.bft_fixture) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
else:
self._fixture = None
# Group checker for Testlog.
self.group_checker = testlog.GroupParam(
'button_wait', ['time_to_press', 'time_to_release'])
def tearDown(self):
timestamps = self._action_timestamps + [float('inf')]
for release_index in range(2, len(timestamps), 2):
time_to_press = (timestamps[release_index - 1] -
timestamps[release_index - 2])
time_to_release = (timestamps[release_index] -
timestamps[release_index - 1])
event_log.Log('button_wait_sec',
time_to_press_sec=time_to_press,
time_to_release_sec=time_to_release)
with self.group_checker:
testlog.LogParam('time_to_press', time_to_press)
testlog.LogParam('time_to_release', time_to_release)
if self._fixture:
try:
# yapf: disable
self._fixture.SimulateButtonRelease(self.args.bft_button_name) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
except Exception:
logging.warning('failed to release button', exc_info=True)
try:
self._fixture.Disconnect()
except Exception:
logging.warning('disconnection failure', exc_info=True)
def _PollForCondition(self, poll_method, condition_name):
elapsed_time = time.time() - self._action_timestamps[0]
sync_utils.PollForCondition(
poll_method=poll_method,
# yapf: disable
timeout_secs=self.args.timeout_secs - elapsed_time, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
condition_name=condition_name)
self._action_timestamps.append(time.time())
def runTest(self):
# yapf: disable
self.ui.StartFailingCountdownTimer(self.args.timeout_secs) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
for done in range(self.args.repeat_times): # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.args.repeat_times == 1: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
label = _('Press the {name} button', name=self.args.button_name) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
else:
label = _(
'Press the {name} button ({count}/{total})',
# yapf: disable
name=self.args.button_name, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
count=done,
# yapf: disable
total=self.args.repeat_times) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.ui.SetState(label) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
if self._fixture:
# yapf: disable
self._fixture.SimulateButtonPress(self.args.bft_button_name, 0) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self._PollForCondition(self.button.IsPressed, 'WaitForPress')
# yapf: disable
self.ui.SetState(_('Release the button')) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
if self._fixture:
# yapf: disable
self._fixture.SimulateButtonRelease(self.args.bft_button_name) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self._PollForCondition(lambda: not self.button.IsPressed(),
'WaitForRelease')