Servo AutoTest framework and pwr_button test.

BUG=chromium-os:16588
TEST=manually tested with servo and partner device

Change-Id: I1a89b60203c511ff716ba16cdea986dcfbe53659
Reviewed-on: http://gerrit.chromium.org/gerrit/3098
Reviewed-by: <craigdh@google.com>
Tested-by: <craigdh@google.com>
Reviewed-by: Nirnimesh <nirnimesh@chromium.org>
diff --git a/server/cros/__init__.py b/server/cros/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/server/cros/__init__.py
diff --git a/server/cros/servo.py b/server/cros/servo.py
new file mode 100755
index 0000000..1bfeec0
--- /dev/null
+++ b/server/cros/servo.py
@@ -0,0 +1,233 @@
+# Copyright (c) 2011 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.
+#
+# Expects to be run in an environment with sudo and no interactive password
+# prompt, such as within the Chromium OS development chroot.
+
+
+import time
+import xmlrpclib
+import subprocess
+
+
+class Servo:
+    """Manages control of a Servo board.
+
+    Servo is a board developed by hardware group to aide in the debug and
+    control of various partner devices. Servo's features include the simulation
+    of pressing the power button, closing the lid, and pressing Ctrl-d. This
+    class manages setting up and communicating with a servo demon (servod)
+    process. It provides both high-level functions for common servo tasks and
+    low-level functions for directly setting and reading gpios.
+    """
+
+    _server = None
+    _servod = None
+
+
+    def __init__(self, servo_port, xml_config='servo.xml', cold_reset=False):
+        """Sets up the servo communication infrastructure.
+
+        Args:
+          servo_port: Port the servod process should listen on.
+          xml_config: Configuration XML file for servod.
+          cold_reset: If True, cold reset device and boot during init,
+                      otherwise perform init with device running.
+        """
+        # launch servod
+        self._launch_servod(servo_port, xml_config)
+
+        # connect to servod
+        assert servo_port
+
+        self._connect_servod(servo_port)
+        if cold_reset:
+            self._init_seq_cold_reset_devmode()
+        else:
+            self._init_seq()
+
+
+
+    def power_long_press(self):
+        """Simulate a long (8 sec) power button press."""
+        self.power_key(8)
+
+
+    def power_normal_press(self):
+        """Simulate a normal (1 sec) power button press."""
+        self.power_key(1)
+
+
+    def power_short_press(self):
+        """Simulate a short (0.1 sec) power button press."""
+        self.power_key(0.1)
+
+
+    def power_key(self, secs=1):
+        """Simulate a power button press.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set('pwr_button', 'press')
+        time.sleep(secs)
+        self.set('pwr_button', 'release')
+
+
+    def lid_open(self):
+        """Simulate opening the lid."""
+        self.set('lid_open', 'yes')
+
+
+    def lid_close(self):
+        """Simulate closing the lid."""
+        self.set('lid_open', 'no')
+
+
+    def ctrl_d(self, secs=0.5):
+        """Simulate Ctrl-d simultaneous button presses.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m1', 'r2_c2')
+        self.set_nocheck('kbd_m2', 'r1_c1')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def enter_key(self, secs=0.5):
+        """Simulate Enter key button press.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m1', 'r3_c2')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def refresh_key(self, secs=0.5):
+        """Simulate Refresh key (F3) button press.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m2', 'r2_c1')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def imaginary_key(self, secs=0.5):
+        """Simulate imaginary key button press.
+
+        Maps to a key that doesn't physically exist.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m2', 'r3_c1')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def boot_devmode(self):
+        """Boot a dev-mode device that is powered off."""
+        self.set('pwr_button', 'release')
+        time.sleep(1)
+        self.power_normal_press()
+        time.sleep(8)
+        self.ctrl_d()
+        time.sleep(15)
+
+    def _init_seq_cold_reset_devmode(self):
+        """Cold reset, init device, and boot in dev-mode."""
+        self._cold_reset()
+        self._init_seq()
+        self.set('dev_mode', 'on')
+        self.boot_devmode()
+
+
+    def get(self, gpio_name):
+        """Get the value of a gpio from Servod."""
+        assert gpio_name
+        return self._server.get(gpio_name)
+
+
+    def set(self, gpio_name, gpio_value):
+        """Set and check the value of a gpio using Servod."""
+        assert gpio_name and gpio_value
+        self._server.set(gpio_name, gpio_value)
+        assert gpio_value == self.get(gpio_name)
+
+
+    def set_nocheck(self, gpio_name, gpio_value):
+        """Set the value of a gpio using Servod."""
+        assert gpio_name and gpio_value
+        self._server.set(gpio_name, gpio_value)
+
+
+    def __del__(self):
+        """Kill the Servod process."""
+        assert self._servod
+        # kill servod one way or another
+        try:
+            # won't work without superuser privileges
+            self._servod.terminate()
+        except:
+            # should work without superuser privileges
+            assert subprocess.call(['sudo', 'kill', str(self._servod.pid)])
+
+
+    def _launch_servod(self, servo_port, xml_config='servo.xml'):
+        """Launch the servod process.
+
+        Args:
+          servo_port: Port to start servod listening on.
+          xml_config: XML configuration file for servod.
+        """
+        self._servod = subprocess.Popen(['sudo', 'servod', '-c',
+                                         str(xml_config),
+                                         '--host=localhost',
+                                         '--port=' + str(servo_port)],
+                                        0, None, None, None, subprocess.PIPE)
+        # wait for servod to initialize
+        timeout = 10
+        while ("Listening" not in self._servod.stderr.readline() and
+               self._servod.returncode is None and timeout > 0):
+            time.sleep(1)
+            timeout -= 1
+        assert self._servod.returncode is None and timeout
+
+
+    def _init_seq(self):
+        """Initiate starting state for servo."""
+        self.set('tx_dir', 'input')
+        self.set('servo_dut_tx', 'off')
+        self.set('lid_open', 'yes')
+        self.set('rec_mode', 'off')
+
+
+    def _cold_reset(self):
+        """Perform a cold reset of the EC.
+
+        Has the side effect of shutting off the device.
+        """
+        self.set('cold_reset', 'on')
+        time.sleep(2)
+        self.set('cold_reset', 'off')
+
+
+    def _connect_servod(self, servo_port=''):
+        """Connect to the Servod process with XMLRPC.
+
+        Args:
+          servo_port: Port the Servod process is listening on.
+        """
+        remote = 'http://localhost:%s' % servo_port
+        self._server = xmlrpclib.ServerProxy(remote)
diff --git a/server/cros/servotest.py b/server/cros/servotest.py
new file mode 100755
index 0000000..8597cbf
--- /dev/null
+++ b/server/cros/servotest.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2011 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 subprocess
+
+from autotest_lib.server import test, autotest
+import autotest_lib.server.cros.servo
+
+class ServoTest(test.test):
+    """AutoTest test class that creates and destroys a servo object.
+
+    Servo-based server side AutoTests can inherit from this object.
+    """
+    version = 1
+    servo = None
+    _ip = None
+
+
+    def initialize(self, host, servo_port, xml_config='servo.xml'):
+        """Create a Servo object."""
+        self.servo = autotest_lib.server.cros.servo.Servo(servo_port,
+                                                          xml_config)
+        self._ip = host.ip
+
+
+    def assert_ping(self):
+        """Ping to assert that the device is up."""
+        assert self.ping_test(self._ip)
+
+
+    def assert_pingfail(self):
+        """Ping to assert that the device is down."""
+        assert not self.ping_test(self._ip)
+
+
+    def ping_test(self, hostname, timeout=5):
+        """Verify whether a host responds to a ping.
+
+        Args:
+          hostname: Hostname to ping.
+          timeout: Time in seconds to wait for a response.
+        """
+        return subprocess.call(['ping', '-c', '1', '-W',
+                                str(timeout), hostname]) == 0
+
+
+    def cleanup(self):
+        """Delete the Servo object."""
+        del self.servo
diff --git a/server/site_tests/platform_LongPressPower/control b/server/site_tests/platform_LongPressPower/control
new file mode 100644
index 0000000..dc4393e
--- /dev/null
+++ b/server/site_tests/platform_LongPressPower/control
@@ -0,0 +1,32 @@
+# Copyright (c) 2011 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.
+#
+# Test expects to be run on a jailbroken device in developer mode.
+
+AUTHOR = "Chrome OS Team"
+NAME = "platform_LongPressPower"
+PURPOSE = "Servo based ChromeOS functional tests."
+CRITERIA = "This test will fail if ChromesOS does not respond to a ping."
+TIME = "LONG"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "platform"
+TEST_TYPE = "server"
+
+DOC = """
+This test uses servo to press and hold the device power button and uses ping
+to validate behavior.
+"""
+servo_port = 9999
+servo_xml_config = 'servo.xml'
+
+def run_longpresspower(machine):
+    global servo_port
+    global servo_xml_config
+    host = hosts.create_host(machine)
+    port = servo_port
+    servo_port += 1
+    job.run_test("platform_LongPressPower", host=host, servo_port=port,
+                 xml_config=servo_xml_config)
+
+parallel_simple(run_longpresspower, machines)
diff --git a/server/site_tests/platform_LongPressPower/platform_LongPressPower.py b/server/site_tests/platform_LongPressPower/platform_LongPressPower.py
new file mode 100755
index 0000000..85e0221
--- /dev/null
+++ b/server/site_tests/platform_LongPressPower/platform_LongPressPower.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2011 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 time
+
+import autotest_lib.server.cros.servotest
+
+class platform_LongPressPower(autotest_lib.server.cros.servotest.ServoTest):
+    """Uses servo pwr_button gpio to power the host off and back on.
+    """
+    version = 1
+
+    def run_once(self, host):
+        # ensure host starts in a good state
+        self.assert_ping()
+        # turn off device
+        self.servo.power_long_press()
+        # ensure host is now off
+        self.assert_pingfail()
+        # ensure host boots
+        self.servo.boot_devmode()
+        self.assert_ping()