Create a servo based WiFi suspend resume test.

BUG=chromium:358924
TEST=Manual; ran it

Change-Id: I40b0f724fecc343ff8784994c2d87841b32c532e
Reviewed-on: https://chromium-review.googlesource.com/212956
Reviewed-by: Kris Rambish <krisr@chromium.org>
Commit-Queue: Kris Rambish <krisr@chromium.org>
Tested-by: Kris Rambish <krisr@chromium.org>
diff --git a/server/cros/network/wifi_test_context_manager.py b/server/cros/network/wifi_test_context_manager.py
index 391279f..2a17fda 100644
--- a/server/cros/network/wifi_test_context_manager.py
+++ b/server/cros/network/wifi_test_context_manager.py
@@ -16,6 +16,9 @@
 from autotest_lib.server.cros.network import attenuator_controller
 from autotest_lib.server.cros.network import wifi_client
 
+from collections import namedtuple
+
+ConnectTime = namedtuple('ConnectTime', 'state, time')
 
 class WiFiTestContextManager(object):
     """A context manager for state used in WiFi autotests.
@@ -287,6 +290,7 @@
         @param timeout_seconds int number of seconds to wait for
                 connection on the given frequency.
 
+        @returns a named tuple of (state, time)
         """
         POLLING_INTERVAL_SECONDS = 1.0
         start_time = time.time()
@@ -296,7 +300,7 @@
             ap_num = 0
         desired_subnet = self.router.get_wifi_ip_subnet(ap_num)
         while duration() < timeout_seconds:
-            success, state, _ = self.client.wait_for_service_states(
+            success, state, conn_time  = self.client.wait_for_service_states(
                     ssid, self.CONNECTED_STATES, timeout_seconds - duration())
             if not success:
                 time.sleep(POLLING_INTERVAL_SECONDS)
@@ -319,7 +323,7 @@
                 continue
 
             self.assert_ping_from_dut(ap_num=ap_num)
-            return
+            return ConnectTime(state, conn_time)
 
         freq_error_str = (' on frequency %d Mhz' % freq) if freq else ''
         raise error.TestFail(
diff --git a/server/site_tests/network_WiFi_SuspendStress/control.11a b/server/site_tests/network_WiFi_SuspendStress/control.11a
new file mode 100644
index 0000000..3f862e4
--- /dev/null
+++ b/server/site_tests/network_WiFi_SuspendStress/control.11a
@@ -0,0 +1,40 @@
+# Copyright 2014 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.
+
+from autotest_lib.server import utils
+
+AUTHOR = 'krisr, tienchang, bmahadev'
+NAME = 'network_WiFi_SuspendStress.11a'
+TIME = 'MEDIUM'
+TEST_TYPE = 'Server'
+DEPENDENCIES = 'servo, wificell'
+
+DOC = """
+This test uses servo to simulate lid close and open events and checks that the
+wifi adapter is brought back up and connects to a 802.11a network on channels
+48, 64.
+"""
+
+from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
+from autotest_lib.server.cros.network import hostap_config
+
+args_dict = utils.args_to_dict(args)
+servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
+
+def run(machine):
+    host = hosts.create_host(machine, servo_args=servo_args)
+    a_mode = hostap_config.HostapConfig.MODE_11A
+    configurations = [(hostap_config.HostapConfig(channel=48, mode=a_mode),
+                       xmlrpc_datatypes.AssociationParameters()),
+                      (hostap_config.HostapConfig(channel=64, mode=a_mode),
+                       xmlrpc_datatypes.AssociationParameters())]
+
+    job.run_test('network_WiFi_SuspendStress',
+                 host=host,
+                 tag=NAME.split('.')[1],
+                 suspends=5,
+                 raw_cmdline_args=args,
+                 additional_params=configurations)
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SuspendStress/network_WiFi_SuspendStress.py b/server/site_tests/network_WiFi_SuspendStress/network_WiFi_SuspendStress.py
new file mode 100644
index 0000000..c2df90b
--- /dev/null
+++ b/server/site_tests/network_WiFi_SuspendStress/network_WiFi_SuspendStress.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2014 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 json
+import logging
+import time
+from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
+from autotest_lib.server import autotest
+from autotest_lib.server.cros import stress
+from autotest_lib.server.cros.network import wifi_cell_test_base
+
+_DELAY = 10
+_CLIENT_TERMINATION_FILE_PATH = '/tmp/simple_login_exit'
+
+
+class network_WiFi_SuspendStress(wifi_cell_test_base.WiFiCellTestBase):
+    """Uses servo to repeatedly close & open lid while running BrowserTests."""
+    version = 1
+
+
+    def parse_additional_arguments(self, commandline_args, additional_params):
+        """Hook into super class to take control files parameters.
+
+        @param commandline_args dict of parsed parameters from the autotest.
+        @param additional_params list of tuple(HostapConfig,
+                                               AssociationParameters).
+        """
+        self._configurations = additional_params
+
+
+    def logged_in(self):
+        """Checks if the host has a logged in user.
+
+        @return True if a user is logged in on the device.
+
+        """
+        try:
+            out = self._host.run('cryptohome --action=status').stdout
+        except:
+            return False
+        try:
+            status = json.loads(out.strip())
+        except ValueError:
+            logging.info('Cryptohome did not return a value.')
+            return False
+
+        success = any((mount['mounted'] for mount in status['mounts']))
+        if success:
+            # Chrome needs a few moments to get ready, otherwise an immediate
+            # suspend will power down the system.
+            time.sleep(5)
+        return success
+
+
+    def stress_wifi_suspend(self):
+        """Perform the suspend stress."""
+        self._host.servo.lid_close()
+        self._host.wait_down(timeout=_DELAY)
+        self._host.servo.lid_open()
+        self._host.wait_up(timeout=_DELAY)
+        state_info = self.context.wait_for_connection(
+            self.context.router.get_ssid())
+        self._timings.append(state_info.time)
+
+
+    def exit_client(self):
+        """End the client side test."""
+        self._host.run('touch %s' % _CLIENT_TERMINATION_FILE_PATH)
+
+
+    def run_once(self, suspends=5):
+        self._host = self.context.client.host
+        for router_conf, client_conf in self._configurations:
+            self.context.configure(router_conf)
+            assoc_params = xmlrpc_datatypes.AssociationParameters(
+                ssid=self.context.router.get_ssid())
+            self.context.assert_connect_wifi(assoc_params)
+
+            self._timings = list()
+
+            autotest_client = autotest.Autotest(self._host)
+            stressor = stress.CountedStressor(self.stress_wifi_suspend,
+                                              on_exit=self.exit_client)
+            stressor.start(suspends, start_condition=self.logged_in)
+            autotest_client.run_test('desktopui_SimpleLogin')
+            stressor.wait()
+
+            perf_dict = {'fastest': max(self._timings),
+                         'slowest': min(self._timings),
+                         'average': (float(sum(self._timings)) /
+                                     len(self._timings))}
+            for key in perf_dict:
+                self.output_perf_value(description=key,
+                    value=perf_dict[key],
+                    units='seconds',
+                    higher_is_better=False,
+                    graph=router_conf.perf_loggable_description)
+
+
+    def cleanup(self):
+        """Cold reboot the device so the WiFi card is back in a good state."""
+        self._host.servo.get_power_state_controller().reset()