blob: 0f119d50ef24ef43f8944b5dc981052e5d01d836 [file] [log] [blame]
# Copyright 2012 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests keyboard functionality.
Description
-----------
This test check basic keyboard functionality by asking operator to press each
keys on keyboard once at a time.
The layout of the keyboard is derived from vpd 'region' value, and can be
overwritten by argument ``layout``.
If ``allow_multi_keys`` is True, the operator can press multiple keys at once
to speed up the testing.
If ``sequential_press`` or ``strict_sequential_press`` is True, the operator
have to press each key in order from top-left to bottom-right. Additionally, if
``strict_sequential_press`` is True, the test would fail if the operator press
the wrong key.
A dict ``repeat_times`` can be specified to indicate number of times each key
have to be pressed before the key is marked as checked.
The test would fail after ``timeout_secs`` seconds.
Test Procedure
--------------
1. The test shows an image of the keyboard, and each key labeled with how many
times it need to be pressed.
2. Operator press each key the number of times needed, and keys on UI would be
marked as such.
3. The test pass when all keys have been pressed for the number of times
needed, or fail after ``timeout_secs`` seconds.
Dependency
----------
Depends on 'evdev' module to monitor key presses.
Examples
--------
To test keyboard functionality, add this into test list::
{
"pytest_name": "keyboard"
}
To test keyboard functionality, allow multiple keys to be pressed at once, and
have a timeout of 10 seconds, add this into test list::
{
"pytest_name": "keyboard",
"args": {
"allow_multi_keys": true,
"timeout_secs": 10
}
}
To test keyboard functionality, ask operator to press keys in order, skip
keycode [4, 5, 6], have keycode 3 be pressed 5 times, and other keys be pressed
2 times to pass, add this into test list::
{
"pytest_name": "keyboard",
"args": {
"sequential_press": true,
"skip_keycodes": [4, 5, 6],
"repeat_times": {
"3": 5,
"default": 2
}
}
}
To test keyboard functionality, ask operator to press keys in order (and fail
the test if wrong key is pressed), and set keyboard layout to ISO, add this
into test list::
{
"pytest_name": "keyboard",
"args": {
"strict_sequential_press": true,
"layout": "ISO"
}
}
To test keyboard functionality, ask operator to press [2,3] and [4,5] at the
same time, and have a timeout of 300 seconds, add this into test list::
{
"pytest_name": "keyboard",
"args": {
"key_combinations": [[2, 3], [4, 5]],
"timeout_secs": 300
}
}
"""
import array
import ast
import fcntl
import os
import re
import struct
import time
from typing import List
from cros.factory.test.l10n import regions
from cros.factory.test import session
from cros.factory.test import test_case
from cros.factory.test.utils import evdev_utils
from cros.factory.testlog import testlog
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import file_utils
from cros.factory.utils import process_utils
from cros.factory.external.py_lib import evdev
# Defined in "uapi/linux/input.h":
# struct input_keymap_entry {
# #define INPUT_KEYMAP_BY_INDEX (1 << 0)
# __u8 flags;
# __u8 len;
# __u16 index;
# __u32 keycode;
# __u8 scancode[32];
# };
_INPUT_KEYMAP_ENTRY = 'BBHI32B'
# Defined in "uapi/linux/input.h":
# #define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry)
# See more details in "uapi/asm-generic/ioctl.h"
_EVIOCGKEYCODE_V2 = ((2 << 30) | (struct.calcsize(_INPUT_KEYMAP_ENTRY) << 16) |
(ord('E') << 8) | 0x04)
# Check acpigen_ps2_keybd.c for the definition.
_DEFAULT_FN_KEYCODES_IN_FIRST_ROW = [59, 60, 61, 62, 63, 64, 65, 66, 67, 68]
_ESC_KEY_CODE = 1
_POWER_KEY_CODE = 116
_LAST_FN_KEYCODES = [115, 116, 142, 183]
def IsStraussKeyboard():
"""Checks if the device under test (DUT) uses a Strauss keyboard.
This function executes the `ectool inventory` command on the DUT and
parses the output to determine if the keyboard is a Strauss model.
Returns:
True if the DUT has a Strauss keyboard, False otherwise.
"""
output = process_utils.CheckOutput(['ectool', 'inventory'])
for line in output.splitlines():
# This is defined in ec_feature_names in ectool.cc
if 'Strauss support' in line:
return True
return False
class KeyboardTest(test_case.TestCase):
"""Tests if all the keys on a keyboard are functioning. The test checks for
keydown and keyup events for each key, following certain order if required,
and passes if both events of all keys are received.
Among the args are two related arguments:
- sequential_press: a keycode is simply ignored if the key is not pressed
in order
- strict_sequential_press: the test failed immediately if a key is skipped.
"""
related_components = (test_case.TestCategory.KEYBOARD, )
ARGS = [
Arg(
'allow_multi_keys', bool, 'Allow multiple keys pressed '
'simultaneously. (Less strictly checking '
'with shorter cycle time)', default=False),
Arg(
'multi_keys_delay', (int, float), 'When ``allow_multi_keys`` is '
'``False``, do not fail the test if the delay between the '
'consecutivepresses is more than ``multi_keys_delay`` seconds.',
default=0),
Arg(
'layout', str, 'Use specified layout other than derived from VPD. '
'If None, the layout from the VPD is used.', default=None),
Arg('timeout_secs', int, 'Timeout for the test.', default=30),
Arg(
'sequential_press', bool, 'Indicate whether keycodes need to be '
'pressed sequentially or not.', default=False),
Arg(
'strict_sequential_press', bool, 'Indicate whether keycodes need to '
'be pressed strictly sequentially or not.', default=False),
Arg('board', str,
'If presents, in filename, the board name is appended after layout.',
default=''),
Arg(
'device_filter', (int, str),
'If present, the input event ID or a substring of the input device '
'name specifying which keyboard to test.', default=None),
Arg(
'key_order', list,
'Specify the sequential order of the keys to press. Will use the '
'order in .layout files if it is not specified.', default=[]),
Arg(
'fn_keycodes', list, 'The keycodes in the first row, esc and '
'power/lock key are excluded.', default=None),
Arg(
'last_fn_keycode', int, 'The keycode of the last fn key in the first '
'row. Note that it should be a lock or a power key', default=142),
Arg('replacement_keymap', dict, 'Deprecated, please use fn_keycodes',
default={}),
Arg(
'has_power_key', bool, 'If True, the last key in the first row will '
'be a power key, otherwise it will be a lock key.', default=True),
Arg('skip_keycodes', list, 'Keycodes to skip', default=[]),
Arg('skip_power_key', bool, 'Deprecated, please use skip_keycodes.',
default=False),
Arg(
'detect_long_press', bool, 'Detect long press event. Usually for '
'detecting bluetooth keyboard disconnection.', default=False),
Arg(
'repeat_times', dict, 'A dict object {key_code: times} to specify '
'number of presses required for keys specified in key code, e.g. '
'``{"28": 3, "57": 5}``, then ENTER (28) shall be pressed 3 times '
'while SPACE (57) shall be pressed 5 times. If you want all keys to '
'be pressed twice, you can do: ``{"default": 2}``. '
'You can find keycode mappings in /usr/include/linux/input.h',
default={}),
Arg('has_numpad', bool, 'The keyboard has a number pad or not.',
default=False),
Arg('vivaldi_keyboard', bool, 'Get function keys map from sysfs.',
default=True),
Arg(
'key_combinations', list, 'The element in the list is a list of key '
'codes, and these keys should be held at the same time to pass the '
'test. It is recommended to test 8 keys at once.', default=[]),
]
def setUp(self):
# yapf: disable
self.assertFalse(self.args.skip_power_key, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'skip_power_key is deprecated, please use skip_keycodes.')
self.assertFalse(
# yapf: disable
self.args.replacement_keymap, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'replacement_keymap is deprecated, please use fn_keycodes.')
# yapf: disable
self.assertFalse(self.args.allow_multi_keys and self.args.sequential_press, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'Sequential press requires one key at a time.')
self.assertFalse(
# yapf: disable
self.args.allow_multi_keys and self.args.strict_sequential_press, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'Strict sequential press requires one key at a time.')
# yapf: disable
self.assertTrue(self.args.multi_keys_delay >= 0, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'multi_keys_delay should be a positive number.')
# yapf: disable
if self.args.allow_multi_keys and self.args.multi_keys_delay > 0: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
session.console.warning('multi_keys_delay is not effective when '
'allow_multi_keys is set to True.')
# yapf: disable
if (not self.args.strict_sequential_press and # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
not self.args.sequential_press and self.args.key_order): # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
session.console.warning('key_order is not effective if it is not '
'needed to press sequentially.')
# yapf: disable
if self.args.fn_keycodes and self.args.vivaldi_keyboard: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
session.console.warning('the fn_keycodes will be '
'overridden by vivaldi_keyboard.')
# yapf: disable
if self.args.key_combinations: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.assertFalse(
# yapf: disable
self.args.repeat_times, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
'repeat_times is not supported ' # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'with key_combinations.')
# yapf: disable
self.assertFalse(self.args.key_order, 'key_order is not supported with ' # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'key_combinations.')
self.assertFalse(
# yapf: disable
self.args.strict_sequential_press, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'strict_sequential_press is not supported with key_combinations.')
self.assertFalse(
# yapf: disable
self.args.sequential_press, 'sequential_press is not ' # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'supported with key_combinations.')
self.assertTrue(
# yapf: disable
self.args.allow_multi_keys or self.args.multi_keys_delay == 0, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'multi_keys should be allowed when using key_combinations.')
self.assertTrue(
# yapf: disable
all(comb for comb in self.args.key_combinations), # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'Combination should have at least 1 key.')
# Get the keyboard input device.
try:
self.keyboard_device = evdev_utils.FindDevice(
# yapf: disable
self.args.device_filter, evdev_utils.IsKeyboardDevice) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
except evdev_utils.MultipleDevicesFoundError:
session.console.info(
"Please set the test argument 'device_filter' to one of the name.")
raise
layout = self.GetKeyboardLayout()
first_row_keys = self.GetKeycodesInFirstRow()
main_keys = self.GetLayoutKeycodes(layout)
flatten_main_keys = [
key for keys_in_row in main_keys for key in keys_in_row
]
numpad_layout = ('strauss_numpad'
# yapf: disable
if self.args.layout == 'STRAUSS' else 'numpad') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
numpad_keys = []
# yapf: disable
if self.args.has_numpad: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
numpad_keys = self.GetLayoutKeycodes(numpad_layout)
else:
# yapf: disable
self.ui.HideElement('instruction-sequential-numpad') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
flatten_numpad_keys = [
key for keys_in_row in numpad_keys for key in keys_in_row
]
self.hold_keys = set()
self.last_press_time = 0
# yapf: disable
self.frontend_proxy = self.ui.InitJSTestObject( # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'KeyboardTest', layout, first_row_keys, main_keys, numpad_keys)
# yapf: disable
default_number_to_press = self.args.repeat_times.get('default', 1) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.need_press_keys = {}
# yapf: disable
keycodes_to_skip = set(self.args.skip_keycodes) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
for key in set(flatten_main_keys) | set(first_row_keys) | set(
flatten_numpad_keys):
if key in keycodes_to_skip:
self.frontend_proxy.Skip(key)
else:
# yapf: disable
self.need_press_keys[key] = self.args.repeat_times.get( # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
str(key), default_number_to_press)
self.next_index = 0
# yapf: disable
if self.args.sequential_press or self.args.strict_sequential_press: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.args.key_order: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.key_order_list = self.args.key_order # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
invalid_key_order = [
key for key in self.key_order_list if key in keycodes_to_skip
]
if invalid_key_order:
session.console.warning(
'%s is specified in both key_order and'
'skip_keycodes.', invalid_key_order)
else:
self.key_order_list = [
key for key in first_row_keys + flatten_main_keys +
flatten_numpad_keys if key not in keycodes_to_skip
]
else:
self.key_order_list = []
# yapf: disable
self.ui.HideElement('instruction-sequential') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.ui.HideElement('instruction-sequential-numpad') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.args.allow_multi_keys or self.args.multi_keys_delay == 0: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.ui.HideElement('instruction-single-key') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.args.key_combinations: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.frontend_proxy.Hint(self.args.key_combinations[0], True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.dispatcher = evdev_utils.InputDeviceDispatcher(
# yapf: disable
self.keyboard_device, self.event_loop.CatchException(self.HandleEvent)) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
testlog.UpdateParam('malfunction_key',
description='The keycode of malfunction keys')
def GetKeycodesInFirstRow(self) -> list:
# yapf: disable
if self.args.vivaldi_keyboard: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
fn_keycodes = self.GetVivaldiKeycodes()
# yapf: disable
elif self.args.fn_keycodes: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
fn_keycodes = self.args.fn_keycodes # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
else:
fn_keycodes = _DEFAULT_FN_KEYCODES_IN_FIRST_ROW
last_key = (
_POWER_KEY_CODE
# yapf: disable
if self.args.has_power_key else self.args.last_fn_keycode) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.assertIn(last_key, _LAST_FN_KEYCODES)
return [_ESC_KEY_CODE] + fn_keycodes + [last_key]
def _GetKeyboardMapping(self):
"""Gets the scancode to keycode mapping.
Repeatedly request EVIOCGKEYCODE_V2 ioctl on device node with flag
INPUT_KEYMAP_BY_INDEX to fetch scancode keycode translation table for an
input device. Sequetially increase index until it returns error.
"""
mapping = {}
buf = array.array('b', [0] * struct.calcsize(_INPUT_KEYMAP_ENTRY))
# NOTE:
# 1. index is a __u16.
# 2. INPUT_KEYMAP_BY_INDEX = (1 << 0)
# See more details in the definition of input_keymap_entry.
for i in range(1 << 16):
struct.pack_into(_INPUT_KEYMAP_ENTRY, buf, 0, 1, 0, i, 0, *([0] * 32))
try:
ret_no = fcntl.ioctl(self.keyboard_device, _EVIOCGKEYCODE_V2, buf)
if ret_no != 0:
session.console.warning(
'Failed to fetch keymap at index %d: return_code=%d', i, ret_no)
continue
except OSError:
break
keymap_entry = struct.unpack(_INPUT_KEYMAP_ENTRY, buf)
scancode_len = keymap_entry[1]
keycode = keymap_entry[3]
scancode = int.from_bytes(keymap_entry[4:4 + scancode_len],
byteorder='little')
# Mapping to KEY_RESERVED (0) means the scancode is not used.
if keycode != 0:
mapping[scancode] = keycode
return mapping
def GetVivaldiKeycodes(self):
"""Gets the keycodes which are modifiable by partners, ESC is excluded."""
match = re.search(r'\d+$', self.keyboard_device.path)
if not match:
raise RuntimeError('Failed to get keyboard device ID')
event_id = match.group(0)
file_content = file_utils.ReadFile(
f'/sys/class/input/event{event_id}/device/device/function_row_physmap')
scancode_to_keycode = self._GetKeyboardMapping()
return [
scancode_to_keycode[int(s, 16)]
for s in file_content.strip().split()
if int(s, 16) != 0
]
def GetKeyboardLayout(self):
"""Uses the given keyboard layout or auto-detect from VPD."""
# yapf: disable
board = f'_{self.args.board}' if self.args.board else '' # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.args.layout: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
return self.args.layout + board # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# Use the primary keyboard_layout for testing.
strauss = 'STRAUSS_' if IsStraussKeyboard() else ''
region = process_utils.CheckOutput(['vpd', '-g', 'region']).strip()
return strauss + regions.REGIONS[region].keyboard_mechanical_layout + board
def GetLayoutKeycodes(self, layout) -> List[List[int]]:
"""Return a 2-D array for rendering the keyboard of different layout.
Each element represents the keycodes in a row."""
# yapf: disable
layout_filename = os.path.join(self.ui.GetStaticDirectoryPath(), # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
layout + '.layout')
return ast.literal_eval(file_utils.ReadFile(layout_filename))
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
return
if event.value == 1:
self.OnKeydown(event.code)
elif event.value == 0:
self.OnKeyup(event.code)
# yapf: disable
elif self.args.detect_long_press and event.value == 2: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
fail_msg = f'Got events on keycode {event.code} pressed too long.'
session.console.error(fail_msg)
self.FailTask(fail_msg)
def OnKeydown(self, keycode):
"""Callback when got a keydown event from evdev."""
if keycode not in self.need_press_keys:
session.console.warning('Got key down event on an excluded keycode %d',
keycode)
return
# yapf: disable
if (not self.args.allow_multi_keys and self.hold_keys and # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
time.time() - self.last_press_time < self.args.multi_keys_delay): # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.FailTask(
f'Got key down event on keycode {keycode} but there are other key '
f'pressed: {next(iter(self.hold_keys))}.')
if keycode in self.hold_keys:
self.FailTask(f'Got 2 key down events on keycode {keycode} but didn\'t '
'get key up event.')
# yapf: disable
self.last_press_time = time.time() # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.hold_keys.add(keycode)
self.frontend_proxy.Hold(keycode, self.need_press_keys[keycode])
def OnKeyup(self, keycode):
"""Callback when got a keyup event from evdev."""
if keycode not in self.need_press_keys:
return
if keycode not in self.hold_keys:
self.FailTask(
f'Got key up event for keycode {keycode} but did not get key down '
'event.')
# yapf: disable
if self.args.key_combinations: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.next_index == len(self.args.key_combinations): # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
return
# yapf: disable
keys = self.args.key_combinations[self.next_index] # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
if self.hold_keys == set(keys):
self.next_index += 1
# yapf: disable
if self.next_index == len(self.args.key_combinations): # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.PassTask()
self.hold_keys.remove(keycode)
self.frontend_proxy.Click(keycode, 1) # Restore the color of the key.
if not self.hold_keys:
# yapf: disable
self.frontend_proxy.Hint(self.args.key_combinations[self.next_index], # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
True)
return
self.hold_keys.remove(keycode)
if (self.next_index < len(self.key_order_list) and
keycode in self.key_order_list):
next_key = self.key_order_list[self.next_index]
if keycode != next_key:
# yapf: disable
if self.args.strict_sequential_press: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.FailTask(f'Expect keycode {next_key} but get {keycode}.')
else:
self.frontend_proxy.Click(keycode, self.need_press_keys[keycode])
return
if self.need_press_keys[keycode] == 1:
self.next_index += 1
if self.need_press_keys[keycode] > 0:
self.need_press_keys[keycode] -= 1
self.frontend_proxy.Click(keycode, self.need_press_keys[keycode])
if max(self.need_press_keys.values()) == 0:
self.PassTask()
def FailTestTimeout(self):
"""Fail the test due to timeout, and log untested keys."""
# yapf: disable
if self.args.key_combinations: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
failed_keys = self.args.key_combinations[self.next_index:] # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
else:
failed_keys = [
key for key, num_left in self.need_press_keys.items() if num_left
]
for failed_key in failed_keys:
testlog.LogParam('malfunction_key', failed_key)
self.FailTask(f'Keyboard test timed out. Malfunction keys: {failed_keys}.')
def runTest(self):
self.keyboard_device.grab()
self.dispatcher.StartDaemon()
# yapf: disable
self.ui.StartCountdownTimer(self.args.timeout_secs, self.FailTestTimeout) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.WaitTaskEnd()
def tearDown(self):
"""Terminates the running process or we'll have trouble stopping the test.
"""
self.dispatcher.Close()
self.keyboard_device.ungrab()