Chameleon: Enable Chameleon client-side support
This change introduces a ChameleonConnection abstraction which unifies the
Chameleon connection from both client-side and server-side. A client test
sets this connection up by given either a lab-DUT hostname or specified in
the command line arguments.
The example test case display_ClientChameleonConnection is added. It verifies
the basic hardware setup of Chameleon from a client-side test.
BUG=chromium:405143
TEST=Ran the old server test display_Resolution.mirrored passed.
TEST=Ran the new client test display_ClientChameleonConnection passed, using:
$ test_that --board peppy --args "chameleon_host=$CHAMELEON_IP" $DUT_IP \
display_ClientChameleonConnection
TEST=Disconnected the HDMI cable, ran the same test failed.
Change-Id: Iac4c247ddbf9f8dac462f9c1ecb8cda4df74f4a3
Reviewed-on: https://chromium-review.googlesource.com/213709
Tested-by: Wai-Hong Tam <waihong@chromium.org>
Reviewed-by: Hung-ying Tyan <tyanh@chromium.org>
Commit-Queue: Wai-Hong Tam <waihong@chromium.org>
diff --git a/client/cros/chameleon/chameleon.py b/client/cros/chameleon/chameleon.py
index f621411..91a5026 100644
--- a/client/cros/chameleon/chameleon.py
+++ b/client/cros/chameleon/chameleon.py
@@ -2,27 +2,85 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import httplib
+import socket
import time
import xmlrpclib
from PIL import Image
+from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.chameleon import edid
+CHAMELEON_PORT = 9992
+
+
+class ChameleonConnectionError(error.TestError):
+ """Indicates that connecting to Chameleon failed.
+
+ It is fatal to the test unless caught.
+ """
+ pass
+
+
+class ChameleonConnection(object):
+ """ChameleonConnection abstracts the network connection to the board.
+
+ ChameleonBoard and ChameleonPort use it for accessing Chameleon RPC.
+
+ """
+
+ def __init__(self, hostname, port=CHAMELEON_PORT):
+ """Constructs a ChameleonConnection.
+
+ @param hostname: Hostname the chameleond process is running.
+ @param port: Port number the chameleond process is listening on.
+
+ @raise ChameleonConnectionError if connection failed.
+ """
+ self.chameleond_proxy = ChameleonConnection._create_server_proxy(
+ hostname, port)
+
+
+ @staticmethod
+ def _create_server_proxy(hostname, port):
+ """Creates the chameleond server proxy.
+
+ @param hostname: Hostname the chameleond process is running.
+ @param port: Port number the chameleond process is listening on.
+
+ @return ServerProxy object to chameleond.
+
+ @raise ChameleonConnectionError if connection failed.
+ """
+ remote = 'http://%s:%s' % (hostname, port)
+ chameleond_proxy = xmlrpclib.ServerProxy(remote, allow_none=True)
+ # Call a RPC to test.
+ try:
+ chameleond_proxy.ProbeInputs()
+ except (socket.error,
+ xmlrpclib.ProtocolError,
+ httplib.BadStatusLine) as e:
+ raise ChameleonConnectionError(e)
+ return chameleond_proxy
+
+
class ChameleonBoard(object):
"""ChameleonBoard is an abstraction of a Chameleon board.
A Chameleond RPC proxy is passed to the construction such that it can
use this proxy to control the Chameleon board.
+
"""
- def __init__(self, chameleond_proxy):
+ def __init__(self, chameleon_connection):
"""Construct a ChameleonBoard.
- @param chameleond_proxy: Chameleond RPC proxy object.
+ @param chameleon_connection: ChameleonConnection object.
"""
- self._chameleond_proxy = chameleond_proxy
+ self._chameleond_proxy = chameleon_connection.chameleond_proxy
def reset(self):
@@ -201,3 +259,45 @@
# The return value of RPC is converted to a list. Convert it back to
# a tuple.
return tuple(self._chameleond_proxy.DetectResolution(self._input_id))
+
+
+def make_chameleon_hostname(dut_hostname):
+ """Given a DUT's hostname, returns the hostname of its Chameleon.
+
+ @param dut_hostname: Hostname of a DUT.
+
+ @return Hostname of the DUT's Chameleon.
+ """
+ host_parts = dut_hostname.split('.')
+ host_parts[0] = host_parts[0] + '-chameleon'
+ return '.'.join(host_parts)
+
+
+def create_chameleon_board(dut_hostname, args):
+ """Given either DUT's hostname or argments, creates a ChameleonBoard object.
+
+ If the DUT's hostname is in the lab zone, it connects to the Chameleon by
+ append the hostname with '-chameleon' suffix. If not, checks if the args
+ contains the key-value pair 'chameleon_host=IP'.
+
+ @param dut_hostname: Hostname of a DUT.
+ @param args: A string of arguments passed from the command line.
+
+ @return A ChameleonBoard object.
+
+ @raise ChameleonConnectionError if unknown hostname.
+ """
+ connection = None
+ hostname = make_chameleon_hostname(dut_hostname)
+ if utils.host_is_in_lab_zone(hostname):
+ connection = ChameleonConnection(hostname)
+ else:
+ args_dict = utils.args_to_dict(args)
+ hostname = args_dict.get('chameleon_host', None)
+ port = args_dict.get('chameleon_port', CHAMELEON_PORT)
+ if hostname:
+ connection = ChameleonConnection(hostname, port)
+ else:
+ raise ChameleonConnectionError('No chameleon_host is given in args')
+
+ return ChameleonBoard(connection)
diff --git a/client/site_tests/display_ClientChameleonConnection/control b/client/site_tests/display_ClientChameleonConnection/control
new file mode 100644
index 0000000..1df14b2
--- /dev/null
+++ b/client/site_tests/display_ClientChameleonConnection/control
@@ -0,0 +1,21 @@
+# 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.
+
+AUTHOR = 'chromeos-chameleon'
+NAME = 'display_ClientChameleonConnection'
+PURPOSE = 'Chameleon connection test from client-side.'
+CRITERIA = 'This test fails if DUT and Chameleon are not connected properly.'
+SUITE = 'chameleon_dp, chameleon_hdmi'
+TIME = 'SHORT'
+TEST_CATEGORY = 'Functional'
+TEST_CLASS = 'display'
+TEST_TYPE = 'client'
+DEPENDENCIES = 'chameleon'
+
+DOC = """
+This test checks the connection between DUT and Chameleon.
+"""
+
+host = next(iter(job.hosts))
+job.run_test('display_ClientChameleonConnection', host=host, args=args)
diff --git a/client/site_tests/display_ClientChameleonConnection/display_ClientChameleonConnection.py b/client/site_tests/display_ClientChameleonConnection/display_ClientChameleonConnection.py
new file mode 100755
index 0000000..02b6b66
--- /dev/null
+++ b/client/site_tests/display_ClientChameleonConnection/display_ClientChameleonConnection.py
@@ -0,0 +1,65 @@
+# 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.
+
+"""This is a client-side test to check the Chameleon connection."""
+
+import logging
+
+from autotest_lib.client.bin import test
+from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros.chameleon import chameleon
+
+
+class display_ClientChameleonConnection(test.test):
+ """Chameleon connection client test.
+
+ This test talks to a Chameleon board from DUT. Try to plug the Chameleon
+ ports and see if DUT detects them.
+ """
+ version = 1
+
+ _TIMEOUT_VIDEO_STABLE_PROBE = 10
+
+
+ def run_once(self, host, args):
+ self.chameleon = chameleon.create_chameleon_board(host.hostname, args)
+ self.chameleon.reset()
+
+ connected_ports = []
+ dut_failed_ports = []
+ for chameleon_port in self.chameleon.get_all_ports():
+ connector_type = chameleon_port.get_connector_type()
+ # Try to plug the port such that DUT can detect it.
+ chameleon_port.plug()
+ # DUT takes some time to respond. Wait until the video signal
+ # to stabilize.
+ chameleon_port.wait_video_input_stable(
+ self._TIMEOUT_VIDEO_STABLE_PROBE)
+
+ # Add the connected ports if they are detected by xrandr.
+ xrandr_output = utils.get_xrandr_output_state()
+ for output in xrandr_output.iterkeys():
+ if output.startswith(connector_type):
+ connected_ports.append(chameleon_port)
+ break
+ else:
+ dut_failed_ports.append(chameleon_port)
+
+ # Unplug the port afterward.
+ chameleon_port.unplug()
+
+ if connected_ports:
+ ports_to_str = lambda ports: ', '.join(
+ '%s(%d)' % (p.get_connector_type(), p.get_connector_id())
+ for p in ports)
+ logging.info('Detected %d connected ports: %s',
+ len(connected_ports), ports_to_str(connected_ports))
+ if dut_failed_ports:
+ message = 'DUT failed to detect Chameleon ports: %s' % (
+ ports_to_str(dut_failed_ports))
+ logging.error(message)
+ raise error.TestFail(message)
+ else:
+ raise error.TestFail('No port connected to Chameleon')
diff --git a/server/hosts/chameleon_host.py b/server/hosts/chameleon_host.py
index 99a5687..3d96209 100644
--- a/server/hosts/chameleon_host.py
+++ b/server/hosts/chameleon_host.py
@@ -6,26 +6,11 @@
"""This file provides core logic for connecting a Chameleon Daemon."""
-import xmlrpclib
-
from autotest_lib.client.bin import utils
from autotest_lib.client.cros.chameleon import chameleon
from autotest_lib.server.hosts import ssh_host
-def make_chameleon_hostname(dut_hostname):
- """Given a DUT's hostname, return the hostname of its Chameleon.
-
- @param dut_hostname: hostname of a DUT.
-
- @return hostname of the DUT's Chameleon.
-
- """
- host_parts = dut_hostname.split('.')
- host_parts[0] = host_parts[0] + '-chameleon'
- return '.'.join(host_parts)
-
-
class ChameleonHost(ssh_host.SSHHost):
"""Host class for a host that controls a Chameleon."""
@@ -51,8 +36,8 @@
super(ChameleonHost, self)._initialize(hostname=chameleon_host,
*args, **dargs)
self._is_in_lab = utils.host_is_in_lab_zone(self.hostname)
- remote = 'http://%s:%s' % (self.hostname, chameleon_port)
- self._chameleond_proxy = xmlrpclib.ServerProxy(remote, allow_none=True)
+ self._chameleon_connection = chameleon.ChameleonConnection(
+ self.hostname, chameleon_port)
def is_in_lab(self):
@@ -81,7 +66,7 @@
"""Create a ChameleonBoard object."""
# TODO(waihong): Add verify and repair logic which are required while
# deploying to Cros Lab.
- return chameleon.ChameleonBoard(self._chameleond_proxy)
+ return chameleon.ChameleonBoard(self._chameleon_connection)
def create_chameleon_host(dut, chameleon_args):
@@ -105,7 +90,7 @@
@returns: A ChameleonHost object or None.
"""
- hostname = make_chameleon_hostname(dut)
+ hostname = chameleon.make_chameleon_hostname(dut)
if utils.host_is_in_lab_zone(hostname):
return ChameleonHost(chameleon_host=hostname)
elif chameleon_args: