1# Copyright 2014 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import time 7from collections import namedtuple 8from contextlib import contextmanager 9 10from autotest_lib.client.bin import utils 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.cros.chameleon import chameleon 13 14ChameleonPorts = namedtuple('ChameleonPorts', 'connected failed') 15 16 17class ChameleonPortFinder(object): 18 """ 19 Responsible for finding all ports connected to the chameleon board. 20 21 It does not verify if these ports are connected to DUT. 22 23 """ 24 25 def __init__(self, chameleon_board): 26 """ 27 @param chameleon_board: a ChameleonBoard object representing the 28 Chameleon board whose ports we are interested 29 in finding. 30 31 """ 32 self.chameleon_board = chameleon_board 33 self.connected = None 34 self.failed = None 35 36 37 def find_all_ports(self): 38 """ 39 @returns a named tuple ChameleonPorts() containing a list of connected 40 ports as the first element and failed ports as second element. 41 42 """ 43 self.connected = self.chameleon_board.get_all_ports() 44 self.failed = [] 45 46 return ChameleonPorts(self.connected, self.failed) 47 48 49 def find_port(self, interface): 50 """ 51 @param interface: string, the interface. e.g: HDMI, DP, VGA 52 @returns a ChameleonPort object if port is found, else None. 53 54 """ 55 connected_ports = self.find_all_ports().connected 56 57 for port in connected_ports: 58 if port.get_connector_type().lower() == interface.lower(): 59 return port 60 61 return None 62 63 64 def __str__(self): 65 ports_to_str = lambda ports: ', '.join( 66 '%s(%d)' % (p.get_connector_type(), p.get_connector_id()) 67 for p in ports) 68 69 if self.connected is None: 70 text = 'No port information. Did you run find_all_ports()?' 71 elif self.connected == []: 72 text = 'No port detected on the Chameleon board.' 73 else: 74 text = ('Detected %d connected port(s): %s. \t' 75 % (len(self.connected), ports_to_str(self.connected))) 76 77 if self.failed: 78 text += ('DUT failed to detect Chameleon ports: %s' 79 % ports_to_str(self.failed)) 80 81 return text 82 83 84class ChameleonInputFinder(ChameleonPortFinder): 85 """ 86 Responsible for finding all input ports connected to the chameleon board. 87 88 """ 89 90 def find_all_ports(self): 91 """ 92 @returns a named tuple ChameleonPorts() containing a list of connected 93 input ports as the first element and failed ports as second 94 element. 95 96 """ 97 self.connected = self.chameleon_board.get_all_inputs() 98 self.failed = [] 99 100 return ChameleonPorts(self.connected, self.failed) 101 102 103class ChameleonOutputFinder(ChameleonPortFinder): 104 """ 105 Responsible for finding all output ports connected to the chameleon board. 106 107 """ 108 109 def find_all_ports(self): 110 """ 111 @returns a named tuple ChameleonPorts() containing a list of connected 112 output ports as the first element and failed ports as the 113 second element. 114 115 """ 116 self.connected = self.chameleon_board.get_all_outputs() 117 self.failed = [] 118 119 return ChameleonPorts(self.connected, self.failed) 120 121 122class ChameleonVideoInputFinder(ChameleonInputFinder): 123 """ 124 Responsible for finding all video inputs connected to the chameleon board. 125 126 It also verifies if these ports are connected to DUT. 127 128 """ 129 130 REPLUG_DELAY_SEC = 1 131 132 def __init__(self, chameleon_board, display_facade): 133 """ 134 @param chameleon_board: a ChameleonBoard object representing the 135 Chameleon board whose ports we are interested 136 in finding. 137 @param display_facade: a display facade object, to access the DUT 138 display functionality, either locally or 139 remotely. 140 141 """ 142 super(ChameleonVideoInputFinder, self).__init__(chameleon_board) 143 self.display_facade = display_facade 144 self._TIMEOUT_VIDEO_STABLE_PROBE = 10 145 146 147 def _yield_all_ports(self, failed_ports=None, raise_error=False): 148 """ 149 Yields all connected video ports and ensures every of them plugged. 150 151 @param failed_ports: A list to append the failed port or None. 152 @param raise_error: True to raise TestFail if no connected video port. 153 @yields every connected ChameleonVideoInput which is ensured plugged 154 before yielding. 155 156 @raises TestFail if raise_error is True and no connected video port. 157 158 """ 159 yielded = False 160 all_ports = super(ChameleonVideoInputFinder, self).find_all_ports() 161 162 # unplug all ports 163 for port in all_ports.connected: 164 if port.has_video_support(): 165 chameleon.ChameleonVideoInput(port).unplug() 166 # This is the workaround for samus with hdmi connection. 167 self.display_facade.reset_connector_if_applicable( 168 port.get_connector_type()) 169 170 for port in all_ports.connected: 171 # Skip the non-video port. 172 if not port.has_video_support(): 173 continue 174 175 video_port = chameleon.ChameleonVideoInput(port) 176 connector_type = video_port.get_connector_type() 177 # Plug the port to make it visible. 178 video_port.plug() 179 try: 180 # DUT takes some time to respond. Wait until the video signal 181 # to stabilize and wait for the connector change. 182 video_stable = video_port.wait_video_input_stable( 183 self._TIMEOUT_VIDEO_STABLE_PROBE) 184 output = utils.wait_for_value_changed( 185 self.display_facade.get_external_connector_name, 186 old_value=False) 187 188 if not output: 189 logging.warn('Maybe flaky that no display detected. Retry.') 190 video_port.unplug() 191 time.sleep(self.REPLUG_DELAY_SEC) 192 video_port.plug() 193 video_stable = video_port.wait_video_input_stable( 194 self._TIMEOUT_VIDEO_STABLE_PROBE) 195 output = utils.wait_for_value_changed( 196 self.display_facade.get_external_connector_name, 197 old_value=False) 198 199 logging.info('CrOS detected external connector: %r', output) 200 201 if output: 202 yield video_port 203 yielded = True 204 else: 205 if failed_ports is not None: 206 failed_ports.append(video_port) 207 logging.error('CrOS failed to see any external display') 208 if not video_stable: 209 logging.warn('Chameleon timed out waiting CrOS video') 210 finally: 211 # Unplug the port not to interfere with other tests. 212 video_port.unplug() 213 214 if raise_error and not yielded: 215 raise error.TestFail('No connected video port found between CrOS ' 216 'and Chameleon.') 217 218 219 def iterate_all_ports(self): 220 """ 221 Iterates all connected video ports and ensures every of them plugged. 222 223 It is used via a for statement, like the following: 224 225 finder = ChameleonVideoInputFinder(chameleon_board, display_facade) 226 for chameleon_port in finder.iterate_all_ports() 227 # chameleon_port is automatically plugged before this line. 228 do_some_test_on(chameleon_port) 229 # chameleon_port is automatically unplugged after this line. 230 231 @yields every connected ChameleonVideoInput which is ensured plugged 232 before yeilding. 233 234 @raises TestFail if no connected video port. 235 236 """ 237 return self._yield_all_ports(raise_error=True) 238 239 240 @contextmanager 241 def use_first_port(self): 242 """ 243 Use the first connected video port and ensures it plugged. 244 245 It is used via a with statement, like the following: 246 247 finder = ChameleonVideoInputFinder(chameleon_board, display_facade) 248 with finder.use_first_port() as chameleon_port: 249 # chameleon_port is automatically plugged before this line. 250 do_some_test_on(chameleon_port) 251 # chameleon_port is automatically unplugged after this line. 252 253 @yields the first connected ChameleonVideoInput which is ensured plugged 254 before yeilding. 255 256 @raises TestFail if no connected video port. 257 258 """ 259 for port in self._yield_all_ports(raise_error=True): 260 yield port 261 break 262 263 264 def find_all_ports(self): 265 """ 266 @returns a named tuple ChameleonPorts() containing a list of connected 267 video inputs as the first element and failed ports as second 268 element. 269 270 """ 271 dut_failed_ports = [] 272 connected_ports = list(self._yield_all_ports(dut_failed_ports)) 273 self.connected = connected_ports 274 self.failed = dut_failed_ports 275 276 return ChameleonPorts(connected_ports, dut_failed_ports) 277 278 279class ChameleonAudioInputFinder(ChameleonInputFinder): 280 """ 281 Responsible for finding all audio inputs connected to the chameleon board. 282 283 It does not verify if these ports are connected to DUT. 284 285 """ 286 287 def find_all_ports(self): 288 """ 289 @returns a named tuple ChameleonPorts() containing a list of connected 290 audio inputs as the first element and failed ports as second 291 element. 292 293 """ 294 all_ports = super(ChameleonAudioInputFinder, self).find_all_ports() 295 self.connected = [chameleon.ChameleonAudioInput(port) 296 for port in all_ports.connected 297 if port.has_audio_support()] 298 self.failed = [] 299 300 return ChameleonPorts(self.connected, self.failed) 301 302 303class ChameleonAudioOutputFinder(ChameleonOutputFinder): 304 """ 305 Responsible for finding all audio outputs connected to the chameleon board. 306 307 It does not verify if these ports are connected to DUT. 308 309 """ 310 311 def find_all_ports(self): 312 """ 313 @returns a named tuple ChameleonPorts() containing a list of connected 314 audio outputs as the first element and failed ports as second 315 element. 316 317 """ 318 all_ports = super(ChameleonAudioOutputFinder, self).find_all_ports() 319 self.connected = [chameleon.ChameleonAudioOutput(port) 320 for port in all_ports.connected 321 if port.has_audio_support()] 322 self.failed = [] 323 324 return ChameleonPorts(self.connected, self.failed) 325