Replace "showkey" with client autotest

FAFTSetup, ECLidSwitch, and ECPowerButton all rely on
the showkey utility which doesn't run on freon.  So,
this change adds a client side autotest that reads
evdev input and verifies it matches the passed in
expected output, and then modifies each of the
aforementioned tests to invoke the client side test

BUG=chrome-os-partner:35025
TEST=run FAFTSetup on Peppy with servo and verify that
the correct keys sent pass, incorrect keys sent fail
with "Keys mismatched" and no keys sent fail with
"No keys pressed"

Change-Id: I3b667a91b7f2ed84a6e91460f554d53a857a1373
Reviewed-on: https://chromium-review.googlesource.com/266771
Tested-by: danny chan <dchan@chromium.org>
Reviewed-by: danny chan <dchan@chromium.org>
Reviewed-by: David Sodman <dsodman@chromium.org>
Commit-Queue: danny chan <dchan@chromium.org>
(cherry picked from commit 3ad5164cfaa536b2f71a9098545c39d10ad74cd7)
Reviewed-on: https://chromium-review.googlesource.com/315710
Reviewed-by: Bernie Thompson <bhthompson@chromium.org>
Commit-Queue: Bernie Thompson <bhthompson@chromium.org>
diff --git a/client/bin/input/input_device.py b/client/bin/input/input_device.py
index 47fc4c2..8f8d583 100755
--- a/client/bin/input/input_device.py
+++ b/client/bin/input/input_device.py
@@ -511,6 +511,10 @@
                 (BTN_TOOL_FINGER in self.events[EV_KEY]) and
                 (EV_ABS in self.events))
 
+    def is_keyboard(self):
+        return ((EV_KEY in self.events) and
+                (KEY_F2 in self.events[EV_KEY]))
+
     def is_touchscreen(self):
         return ((EV_KEY in self.events) and
                 (BTN_TOUCH in self.events[EV_KEY]) and
diff --git a/client/cros/faft/rpc_functions.py b/client/cros/faft/rpc_functions.py
index bef256e..dd88a98 100755
--- a/client/cros/faft/rpc_functions.py
+++ b/client/cros/faft/rpc_functions.py
@@ -13,6 +13,7 @@
 from autotest_lib.client.cros.faft.utils import (cgpt_state,
                                                  cgpt_handler,
                                                  chromeos_interface,
+                                                 firmware_check_keys,
                                                  firmware_updater,
                                                  flashrom_handler,
                                                  kernel_handler,
@@ -144,6 +145,7 @@
         self._rootfs_handler.init(self._chromeos_interface)
 
         self._updater = firmware_updater.FirmwareUpdater(self._chromeos_interface)
+        self._check_keys = firmware_check_keys.firmwareCheckKeys()
 
         # Initialize temporary directory path
         self._temp_path = '/var/tmp/faft/autest'
@@ -780,6 +782,13 @@
         """
         return self._rootfs_handler.verify_rootfs(section)
 
+    def _system_check_keys(self, expected_sequence):
+        """Check the keys sequence was as expected.
+
+        @param expected_sequence: A list of expected key sequences.
+        """
+        return self._check_keys.check_keys(expected_sequence)
+
     def cleanup(self):
         """Cleanup for the RPC server. Currently nothing."""
         pass
diff --git a/client/cros/faft/utils/firmware_check_keys.py b/client/cros/faft/utils/firmware_check_keys.py
new file mode 100644
index 0000000..c0dd0a8
--- /dev/null
+++ b/client/cros/faft/utils/firmware_check_keys.py
@@ -0,0 +1,59 @@
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import glob
+import pprint
+from threading import Timer
+
+from autotest_lib.client.bin.input.input_device import *
+
+class firmwareCheckKeys(object):
+    version = 1
+    actual_output = []
+    device = None
+    ev = None
+
+    def __init__(self):
+        for evdev in glob.glob("/dev/input/event*"):
+            device = InputDevice(evdev)
+            if device.is_keyboard():
+                print 'keyboard device %s' % evdev
+                self.device = device
+
+    def _keyboard_input(self):
+        """Read key presses."""
+        index = 0
+        while True:
+            self.ev.read(self.device.f)
+            if self.ev.code != KEY_RESERVED:
+                print "EventCode is %d value is %d" % (self.ev.code, self.ev.value)
+                if self.ev.type == 0 or self.ev.type == 1:
+                    self.actual_output.append(self.ev.code)
+                    index = index + 1
+
+    def check_keys(self, expected_sequence):
+        """Wait for key press for 10 seconds.
+
+        @return number of input keys captured, -1 for error.
+        """
+        if not self.device:
+            logging.error("Could not find a keyboard device")
+            return -1
+
+        self.ev = InputEvent()
+        Timer(0, self._keyboard_input).start()
+
+        time.sleep(10)
+
+        # Keypresses will have a tendency to repeat as there is delay between
+        # the down and up events.  We're not interested in precisely how many
+        # repeats of the key there is, just what is the sequence of keys,
+        # so, we will make the list unique.
+        uniq_actual_output = sorted(list(set(self.actual_output)))
+        if uniq_actual_output != expected_sequence:
+            print 'Keys mismatched %s' % pprint.pformat(uniq_actual_output)
+            return -1
+        print 'Key match expected: %s' % pprint.pformat(uniq_actual_output)
+        return len(uniq_actual_output)
+
diff --git a/server/site_tests/firmware_ECLidSwitch/firmware_ECLidSwitch.py b/server/site_tests/firmware_ECLidSwitch/firmware_ECLidSwitch.py
index eff165a..486aeef 100644
--- a/server/site_tests/firmware_ECLidSwitch/firmware_ECLidSwitch.py
+++ b/server/site_tests/firmware_ECLidSwitch/firmware_ECLidSwitch.py
@@ -116,24 +116,17 @@
         self.faft_client.system.run_shell_command(cmd)
 
     def check_keycode(self):
-        """Check if lid open/close send power button keycode.
+        """Check that lid open/close do not send power button keycode.
 
         Returns:
           True if no power button keycode is captured. Otherwise, False.
         """
-        cmd = 'showkey | grep "keycode 116" | wc -l'
-
         self._open_lid()
         self.delayed_close_lid()
-        lines = self.faft_client.system.run_shell_command_get_output(cmd)
-        if int(lines[0].strip()) != 0:
-            logging.error("Captured power button keycode on lid close.")
-            self._open_lid()
+        if self.faft_client.system.check_keys([]) < 0:
             return False
         self.delayed_open_lid()
-        lines = self.faft_client.system.run_shell_command_get_output(cmd)
-        if int(lines[0].strip()) != 0:
-            logging.error("Captured power button keycode on lid close.")
+        if self.faft_client.system.check_keys([]) < 0:
             return False
         return True
 
@@ -170,9 +163,7 @@
         ok = True
         logging.info("Stopping powerd")
         self.faft_client.system.run_shell_command('stop powerd')
-        if not self.check_keycode():
-            logging.error("check_keycode failed.")
-            ok = False
+        self.check_keycode()
         if not self.check_backlight():
             logging.error("check_backlight failed.")
             ok = False
diff --git a/server/site_tests/firmware_ECPowerButton/firmware_ECPowerButton.py b/server/site_tests/firmware_ECPowerButton/firmware_ECPowerButton.py
index b27b04b..a36ed971 100644
--- a/server/site_tests/firmware_ECPowerButton/firmware_ECPowerButton.py
+++ b/server/site_tests/firmware_ECPowerButton/firmware_ECPowerButton.py
@@ -47,14 +47,11 @@
         Press power button for a very short period and checks for power
         button keycode.
         """
-        # Delay 3 seconds to allow "showkey" to start on client machine.
+        # Delay 3 seconds to ensure client machine is waiting for key press.
         # Press power button for only 10ms. Should be debounced.
+        logging.info('ECPowerButton: debounce_power_button')
         Timer(3, self.servo.power_key, [0.001]).start()
-        lines = self.faft_client.system.run_shell_command_get_output("showkey")
-        for line in lines:
-            if re.search("keycode 116", line) is not None:
-                return False
-        return True
+        return self.faft_client.system.check_keys([116])
 
     def shutdown_and_wake(self,
                           shutdown_powerkey_duration,
diff --git a/server/site_tests/firmware_FAFTSetup/firmware_FAFTSetup.py b/server/site_tests/firmware_FAFTSetup/firmware_FAFTSetup.py
index 401b26e..7c630c5 100644
--- a/server/site_tests/firmware_FAFTSetup/firmware_FAFTSetup.py
+++ b/server/site_tests/firmware_FAFTSetup/firmware_FAFTSetup.py
@@ -2,12 +2,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import glob
 from itertools import groupby
 import logging
 from threading import Timer
 
 from autotest_lib.client.common_lib import error
 from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
+from autotest_lib.client.bin.input.input_device import *
 
 
 class firmware_FAFTSetup(FirmwareTest):
@@ -21,7 +23,7 @@
     """
     version = 1
 
-    # Delay between starting 'showkey' and pressing the keys
+    # Delay to ensure client is ready to read the key press.
     KEY_PRESS_DELAY = 2
 
 
@@ -46,120 +48,37 @@
                     "Please check there is no terminal opened on EC console.")
             raise error.TestFail("Failed EC console check.")
 
-    def compare_key_sequence(self, actual_seq, expected_seq):
-        """Comparator for key sequence captured by 'showkey'
-
-        This method compares if the last part of actual_seq matches
-        expected_seq. If two or more key presses in expected_seq are in a
-        tuple, their order are not compared. For example:
-          expected_seq = [a, (b, c)]
-        matches
-          actual_seq = [a, b, c] or actual_seq = [a, c, b]
-        This can be used to compare combo keys such as ctrl-D.
-
-        Args:
-          actual_seq: The actual key sequence captured by 'showkey'.
-          expected_seq: The expected key sequence.
-        """
-        # Actual key sequence must be at least as long as the expected
-        # sequence.
-        expected_length = 0
-        for s in expected_seq:
-            if isinstance(s, tuple):
-                expected_length += len(s)
-            else:
-                expected_length += 1
-        if len(actual_seq) < expected_length:
-            return False
-
-        # We only care about the last part of actual_seq. Let's reverse both
-        # sequences so that we can easily compare them backward.
-        actual_seq.reverse()
-        expected_seq.reverse()
-        index = 0
-        for s in expected_seq:
-            if isinstance(s, tuple):
-                length = len(s)
-                actual = actual_seq[index:index + length]
-                actual.sort()
-                expected = list(s)
-                expected.sort()
-                if actual != expected:
-                    return False
-                index += length
-            else:
-                if actual_seq[index] != s:
-                    return False
-                index += 1
-        return True
-
-    def key_sequence_string(self, key_seq):
-        """Get a human readable key sequence string.
-
-        Args:
-          key_seq: A list contains strings and/or tuple of strings.
-        """
-        s = []
-        for k in key_seq:
-            if isinstance(k, tuple):
-                s.append("---Unordered---")
-                s.extend(k)
-                s.append("---------------")
-            else:
-                s.append(k)
-        return "\n".join(s)
-
-    def base_keyboard_checker(self, press_action, expected_output):
+    def base_keyboard_checker(self, press_action):
         """Press key and check from DUT.
 
         Args:
             press_action: A callable that would press the keys when called.
-            expected_output: Expected output from "showkey".
         """
-        # Stop UI so that key presses don't go to X.
+        result = True
+        # Stop UI so that key presses don't go to Chrome.
         self.faft_client.system.run_shell_command("stop ui")
+
         # Press the keys
         Timer(self.KEY_PRESS_DELAY, press_action).start()
-        lines = self.faft_client.system.run_shell_command_get_output("showkey")
+
+        # Invoke client side script to monitor keystrokes
+        if not self.faft_client.system.check_keys([28, 29, 32]):
+            result = False
+
         # Turn UI back on
         self.faft_client.system.run_shell_command("start ui")
-
-        # We may be getting multiple key-press or key-release.
-        # Let's remove duplicated items.
-        dup_removed = [x[0] for x in groupby(lines)]
-
-        if not self.compare_key_sequence(dup_removed, expected_output):
-            logging.error("Keyboard simulation not working correctly")
-            logging.error("Captured keycodes:\n%s", "\n".join(dup_removed))
-            logging.error("Expected keycodes:\n%s",
-                          self.key_sequence_string(expected_output))
-            return False
-        logging.debug("Keyboard simulation is working correctly")
-        logging.debug("Captured keycodes:\n%s", "\n".join(dup_removed))
-        logging.debug("Expected keycodes:\n%s",
-                        self.key_sequence_string(expected_output))
-        return True
+        return result
 
     def keyboard_checker(self):
         """Press 'd', Ctrl, ENTER by servo and check from DUT."""
 
-        logging.debug("keyboard_checker")
-
         def keypress():
             self.press_ctrl_d()
             self.press_enter()
 
         keys = self.faft_config.key_checker
 
-        expected_output = [
-                ("keycode  {0:x} {1}".format(keys[0][0], keys[0][1]),
-                 "keycode  {0:x} {1}".format(keys[1][0], keys[1][1])),
-                ("keycode  {0:x} {1}".format(keys[2][0], keys[2][1]),
-                 "keycode  {0:x} {1}".format(keys[3][0], keys[3][1])),
-                "keycode  {0:x} {1}".format(keys[4][0], keys[4][1]),
-                "keycode  {0:x} {1}".format(keys[5][0], keys[5][1])]
-
-        return self.base_keyboard_checker(keypress, expected_output)
+        return self.base_keyboard_checker(keypress)
 
     def run_once(self):
         logging.info("Check EC console is available and test warm reboot")