# 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.
import logging
import time
from collections import namedtuple
from contextlib import contextmanager
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.chameleon import chameleon
ChameleonPorts = namedtuple('ChameleonPorts', 'connected failed')
class ChameleonPortFinder(object):
"""
Responsible for finding all ports connected to the chameleon board.
It does not verify if these ports are connected to DUT.
"""
def __init__(self, chameleon_board):
"""
@param chameleon_board: a ChameleonBoard object representing the
Chameleon board whose ports we are interested
in finding.
"""
self.chameleon_board = chameleon_board
self.connected = None
self.failed = None
def find_all_ports(self):
"""
@returns a named tuple ChameleonPorts() containing a list of connected
ports as the first element and failed ports as second element.
"""
self.connected = self.chameleon_board.get_all_ports()
self.failed = []
return ChameleonPorts(self.connected, self.failed)
def find_port(self, interface):
"""
@param interface: string, the interface. e.g: HDMI, DP, VGA
@returns a ChameleonPort object if port is found, else None.
"""
connected_ports = self.find_all_ports().connected
for port in connected_ports:
if port.get_connector_type().lower() == interface.lower():
return port
return None
def __str__(self):
ports_to_str = lambda ports: ', '.join(
'%s(%d)' % (p.get_connector_type(), p.get_connector_id())
for p in ports)
if self.connected is None:
text = 'No port information. Did you run find_all_ports()?'
elif self.connected == []:
text = 'No port detected on the Chameleon board.'
else:
text = ('Detected %d connected port(s): %s. \t'
% (len(self.connected), ports_to_str(self.connected)))
if self.failed:
text += ('DUT failed to detect Chameleon ports: %s'
% ports_to_str(self.failed))
return text
class ChameleonInputFinder(ChameleonPortFinder):
"""
Responsible for finding all input ports connected to the chameleon board.
"""
def find_all_ports(self):
"""
@returns a named tuple ChameleonPorts() containing a list of connected
input ports as the first element and failed ports as second
element.
"""
self.connected = self.chameleon_board.get_all_inputs()
self.failed = []
return ChameleonPorts(self.connected, self.failed)
class ChameleonOutputFinder(ChameleonPortFinder):
"""
Responsible for finding all output ports connected to the chameleon board.
"""
def find_all_ports(self):
"""
@returns a named tuple ChameleonPorts() containing a list of connected
output ports as the first element and failed ports as the
second element.
"""
self.connected = self.chameleon_board.get_all_outputs()
self.failed = []
return ChameleonPorts(self.connected, self.failed)
class ChameleonVideoInputFinder(ChameleonInputFinder):
"""
Responsible for finding all video inputs connected to the chameleon board.
It also verifies if these ports are connected to DUT.
"""
REPLUG_DELAY_SEC = 1
def __init__(self, chameleon_board, display_facade):
"""
@param chameleon_board: a ChameleonBoard object representing the
Chameleon board whose ports we are interested
in finding.
@param display_facade: a display facade object, to access the DUT
display functionality, either locally or
remotely.
"""
super(ChameleonVideoInputFinder, self).__init__(chameleon_board)
self.display_facade = display_facade
self._TIMEOUT_VIDEO_STABLE_PROBE = 10
def _yield_all_ports(self, failed_ports=None, raise_error=False):
"""
Yields all connected video ports and ensures every of them plugged.
@param failed_ports: A list to append the failed port or None.
@param raise_error: True to raise TestFail if no connected video port.
@yields every connected ChameleonVideoInput which is ensured plugged
before yielding.
@raises TestFail if raise_error is True and no connected video port.
"""
yielded = False
all_ports = super(ChameleonVideoInputFinder, self).find_all_ports()
# unplug all ports
for port in all_ports.connected:
if port.has_video_support():
chameleon.ChameleonVideoInput(port).unplug()
# This is the workaround for samus with hdmi connection.
self.display_facade.reset_connector_if_applicable(
port.get_connector_type())
for port in all_ports.connected:
# Skip the non-video port.
if not port.has_video_support():
continue
video_port = chameleon.ChameleonVideoInput(port)
connector_type = video_port.get_connector_type()
# Plug the port to make it visible.
video_port.plug()
try:
# DUT takes some time to respond. Wait until the video signal
# to stabilize and wait for the connector change.
video_stable = video_port.wait_video_input_stable(
self._TIMEOUT_VIDEO_STABLE_PROBE)
output = utils.wait_for_value_changed(
self.display_facade.get_external_connector_name,
old_value=False)
if not output:
logging.warn('Maybe flaky that no display detected. Retry.')
video_port.unplug()
time.sleep(self.REPLUG_DELAY_SEC)
video_port.plug()
video_stable = video_port.wait_video_input_stable(
self._TIMEOUT_VIDEO_STABLE_PROBE)
output = utils.wait_for_value_changed(
self.display_facade.get_external_connector_name,
old_value=False)
logging.info('CrOS detected external connector: %r', output)
if output:
yield video_port
yielded = True
else:
if failed_ports is not None:
failed_ports.append(video_port)
logging.error('CrOS failed to see any external display')
if not video_stable:
logging.warn('Chameleon timed out waiting CrOS video')
finally:
# Unplug the port not to interfere with other tests.
video_port.unplug()
if raise_error and not yielded:
raise error.TestFail('No connected video port found between CrOS '
'and Chameleon.')
def iterate_all_ports(self):
"""
Iterates all connected video ports and ensures every of them plugged.
It is used via a for statement, like the following:
finder = ChameleonVideoInputFinder(chameleon_board, display_facade)
for chameleon_port in finder.iterate_all_ports()
# chameleon_port is automatically plugged before this line.
do_some_test_on(chameleon_port)
# chameleon_port is automatically unplugged after this line.
@yields every connected ChameleonVideoInput which is ensured plugged
before yeilding.
@raises TestFail if no connected video port.
"""
return self._yield_all_ports(raise_error=True)
@contextmanager
def use_first_port(self):
"""
Use the first connected video port and ensures it plugged.
It is used via a with statement, like the following:
finder = ChameleonVideoInputFinder(chameleon_board, display_facade)
with finder.use_first_port() as chameleon_port:
# chameleon_port is automatically plugged before this line.
do_some_test_on(chameleon_port)
# chameleon_port is automatically unplugged after this line.
@yields the first connected ChameleonVideoInput which is ensured plugged
before yeilding.
@raises TestFail if no connected video port.
"""
for port in self._yield_all_ports(raise_error=True):
yield port
break
def find_all_ports(self):
"""
@returns a named tuple ChameleonPorts() containing a list of connected
video inputs as the first element and failed ports as second
element.
"""
dut_failed_ports = []
connected_ports = list(self._yield_all_ports(dut_failed_ports))
self.connected = connected_ports
self.failed = dut_failed_ports
return ChameleonPorts(connected_ports, dut_failed_ports)
class ChameleonAudioInputFinder(ChameleonInputFinder):
"""
Responsible for finding all audio inputs connected to the chameleon board.
It does not verify if these ports are connected to DUT.
"""
def find_all_ports(self):
"""
@returns a named tuple ChameleonPorts() containing a list of connected
audio inputs as the first element and failed ports as second
element.
"""
all_ports = super(ChameleonAudioInputFinder, self).find_all_ports()
self.connected = [chameleon.ChameleonAudioInput(port)
for port in all_ports.connected
if port.has_audio_support()]
self.failed = []
return ChameleonPorts(self.connected, self.failed)
class ChameleonAudioOutputFinder(ChameleonOutputFinder):
"""
Responsible for finding all audio outputs connected to the chameleon board.
It does not verify if these ports are connected to DUT.
"""
def find_all_ports(self):
"""
@returns a named tuple ChameleonPorts() containing a list of connected
audio outputs as the first element and failed ports as second
element.
"""
all_ports = super(ChameleonAudioOutputFinder, self).find_all_ports()
self.connected = [chameleon.ChameleonAudioOutput(port)
for port in all_ports.connected
if port.has_audio_support()]
self.failed = []
return ChameleonPorts(self.connected, self.failed)