Chameleon: Implement display_HotPlugAtSuspend test
This change implements the display_HotPlugAtSuspend test which remotely
emulates external display hot-plug during suspend using Chameleon board.
The configs it tests are:
(plugged_before_suspend, plugged_after_suspend, plugged_before_resume):
(True, True, True)
(True, False, False)
(True, False, True)
(False, True, True)
(False, True, False)
It verifies DUT behavior response to these configs.
BUG=chromium:343790
TEST=manaul
$ test_that --fast --args "chameleon_host=$CHAMELEON_IP" $DUT_IP \
e:display_HotPlugAtSuspend.*
Change-Id: I1b8515c7cebbb56e8a2290d6014947aabc046794
Reviewed-on: https://chromium-review.googlesource.com/186347
Reviewed-by: Wai-Hong Tam <waihong@chromium.org>
Commit-Queue: Wai-Hong Tam <waihong@chromium.org>
Tested-by: Wai-Hong Tam <waihong@chromium.org>
diff --git a/server/cros/chameleon/display_client.py b/server/cros/chameleon/display_client.py
index 27ee84b..e36f95d 100644
--- a/server/cros/chameleon/display_client.py
+++ b/server/cros/chameleon/display_client.py
@@ -122,10 +122,22 @@
return self._display_xmlrpc_client.set_mirrored(is_mirrored)
- def suspend_resume(self):
- """Suspends the DUT for 10 seconds."""
+ def suspend_resume(self, suspend_time=10):
+ """Suspends the DUT for a given time in second.
+
+ @param suspend_time: Suspend time in second, default: 10s.
+ """
# TODO(waihong): Use other general API instead of this RPC.
- return self._display_xmlrpc_client.suspend_resume()
+ return self._display_xmlrpc_client.suspend_resume(suspend_time)
+
+
+ def suspend_resume_bg(self, suspend_time=10):
+ """Suspends the DUT for a given time in second in the background.
+
+ @param suspend_time: Suspend time in second, default: 10s.
+ """
+ # TODO(waihong): Use other general API instead of this RPC.
+ return self._display_xmlrpc_client.suspend_resume_bg(suspend_time)
def reconnect_output_and_wait(self):
diff --git a/server/site_tests/display_HotPlugAtSuspend/control.extended b/server/site_tests/display_HotPlugAtSuspend/control.extended
new file mode 100644
index 0000000..7090bfd
--- /dev/null
+++ b/server/site_tests/display_HotPlugAtSuspend/control.extended
@@ -0,0 +1,28 @@
+# 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.
+
+from autotest_lib.server import utils
+
+AUTHOR = "chromeos-chameleon"
+NAME = "display_HotPlugAtSuspend.extended"
+PURPOSE = "Remotely controlled display hot-plug and suspend test."
+CRITERIA = "This test will fail if DUT doesn't see the display after resume."
+SUITE = "chameleon"
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "display"
+TEST_TYPE = "server"
+
+DOC = """
+This test remotely emulates external display hot-plug and suspend/resume.
+"""
+
+args_dict = utils.args_to_dict(args)
+chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
+
+def run(machine):
+ host = hosts.create_host(machine, chameleon_args=chameleon_args)
+ job.run_test("display_HotPlugAtSuspend", host=host, tag="extended")
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/display_HotPlugAtSuspend/control.mirrored b/server/site_tests/display_HotPlugAtSuspend/control.mirrored
new file mode 100644
index 0000000..d42ded3
--- /dev/null
+++ b/server/site_tests/display_HotPlugAtSuspend/control.mirrored
@@ -0,0 +1,29 @@
+# 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.
+
+from autotest_lib.server import utils
+
+AUTHOR = "chromeos-chameleon"
+NAME = "display_HotPlugAtSuspend.mirrored"
+PURPOSE = "Remotely controlled display hot-plug and suspend test."
+CRITERIA = "This test will fail if DUT doesn't see the display after resume."
+SUITE = "chameleon"
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "display"
+TEST_TYPE = "server"
+
+DOC = """
+This test remotely emulates external display hot-plug and suspend/resume.
+"""
+
+args_dict = utils.args_to_dict(args)
+chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
+
+def run(machine):
+ host = hosts.create_host(machine, chameleon_args=chameleon_args)
+ job.run_test("display_HotPlugAtSuspend", host=host, test_mirrored=True,
+ tag="mirrored")
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/display_HotPlugAtSuspend/display_HotPlugAtSuspend.py b/server/site_tests/display_HotPlugAtSuspend/display_HotPlugAtSuspend.py
new file mode 100644
index 0000000..0ee6083
--- /dev/null
+++ b/server/site_tests/display_HotPlugAtSuspend/display_HotPlugAtSuspend.py
@@ -0,0 +1,139 @@
+# 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.
+
+"""This is a display hot-plug and suspend test using the Chameleon board."""
+
+import logging
+import time
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.server.cros.chameleon import chameleon_test
+
+
+class display_HotPlugAtSuspend(chameleon_test.ChameleonTest):
+ """Display hot-plug and suspend test.
+
+ This test talks to a Chameleon board and a DUT to set up, run, and verify
+ DUT behavior response to different configuration of hot-plug during
+ suspend/resume.
+ """
+ version = 1
+ PLUG_CONFIGS = [
+ # (plugged_before_suspend, plugged_after_suspend, plugged_before_resume)
+ (True, True, True),
+ (True, False, False),
+ (True, False, True),
+ (False, True, True),
+ (False, True, False),
+ ]
+ # Duration of suspend, in second.
+ SUSPEND_DURATION = 15
+ # Time for the transition of suspend.
+ SUSPEND_TRANSITION_TIME = 2
+ # Time margin to do plug/unplug before resume.
+ TIME_MARGIN_BEFORE_RESUME = 5
+ # Allow a range of pixel value difference.
+ PIXEL_DIFF_VALUE_MARGIN = 5
+ # A range of pixel number which a cursor covers. We accept this number
+ # of pixels not matched in the case of a cursor showed.
+ CURSOR_PIXEL_NUMBER = 100
+ # Time to wait the calibration image stable, like waiting the info
+ # window "DisplayTestExtension triggered full screen" disappeared.
+ CALIBRATION_IMAGE_SETUP_TIME = 10
+
+
+ def cleanup(self):
+ # Make the connector plugged at the end.
+ self.chameleon_port.plug()
+ super(display_HotPlugAtSuspend, self).cleanup()
+
+
+ def run_once(self, host, test_mirrored=False):
+ width, height = self.chameleon_port.get_resolution()
+ logging.info('See the display on Chameleon: port %d (%s) %dx%d',
+ self.chameleon_port.get_connector_id(),
+ self.chameleon_port.get_connector_type(),
+ width, height)
+ # Keep the original connector name, for later comparison.
+ expected_connector = self.display_client.get_connector_name()
+ logging.info('See the display on DUT: %s', expected_connector)
+
+ logging.info('Set mirrored: %s', test_mirrored)
+ self.display_client.set_mirrored(test_mirrored)
+
+ errors = []
+ for (plugged_before_suspend, plugged_after_suspend,
+ plugged_before_resume) in self.PLUG_CONFIGS:
+ logging.info('TESTING THE CASE: %s > suspend > %s > %s > resume',
+ 'plug' if plugged_before_suspend else 'unplug',
+ 'plug' if plugged_after_suspend else 'unplug',
+ 'plug' if plugged_before_resume else 'unplug')
+ boot_id = host.get_boot_id()
+ if plugged_before_suspend:
+ self.chameleon_port.plug()
+ else:
+ self.chameleon_port.unplug()
+
+ logging.info('Going to suspend, for %d seconds...',
+ self.SUSPEND_DURATION)
+ time_before_suspend = time.time()
+ self.display_client.suspend_resume_bg(self.SUSPEND_DURATION)
+
+ # Confirm DUT suspended.
+ logging.info('- Wait for sleep...')
+ time.sleep(self.SUSPEND_TRANSITION_TIME)
+ host.test_wait_for_sleep()
+ if plugged_after_suspend:
+ self.chameleon_port.plug()
+ else:
+ self.chameleon_port.unplug()
+
+ current_time = time.time()
+ sleep_time = (self.SUSPEND_DURATION -
+ (current_time - time_before_suspend) -
+ self.TIME_MARGIN_BEFORE_RESUME)
+ logging.info('- Sleep for %.2f seconds...', sleep_time)
+ time.sleep(sleep_time)
+ if plugged_before_resume:
+ self.chameleon_port.plug()
+ else:
+ self.chameleon_port.unplug()
+ time.sleep(self.TIME_MARGIN_BEFORE_RESUME)
+
+ logging.info('- Wait for resume...')
+ host.test_wait_for_resume(boot_id)
+
+ logging.info('Resumed back')
+ current_connector = self.display_client.get_connector_name()
+ # Check the DUT behavior: see the external display?
+ if plugged_before_resume:
+ if not current_connector:
+ raise error.TestFail('Failed to see the external display')
+ elif current_connector != expected_connector:
+ raise error.TestFail(
+ 'See a different display: %s != %s' %
+ (current_connector, expected_connector))
+
+ logging.info('Waiting the calibration image stable.')
+ self.display_client.load_calibration_image((width, height))
+ self.display_client.move_cursor_to_bottom_right()
+ time.sleep(self.CALIBRATION_IMAGE_SETUP_TIME)
+
+ error_message = self.check_screen_with_chameleon(
+ 'SCREEN-%dx%d-%c-S-%c-P-R' % (
+ width, height,
+ 'P' if plugged_before_suspend else 'U',
+ 'P' if plugged_after_suspend else 'U'),
+ self.PIXEL_DIFF_VALUE_MARGIN,
+ self.CURSOR_PIXEL_NUMBER)
+ if error_message:
+ errors.append(error_message)
+ else:
+ if current_connector:
+ raise error.TestFail(
+ 'See a not-expected external display: %s' %
+ current_connector)
+
+ if errors:
+ raise error.TestFail('; '.join(errors))