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 self.display_facade.reset_connector_if_applicable( 167 port.get_connector_type()) 168 169 for port in all_ports.connected: 170 # Skip the non-video port. 171 if not port.has_video_support(): 172 continue 173 174 video_port = chameleon.ChameleonVideoInput(port) 175 # Plug the port to make it visible. 176 video_port.plug() 177 try: 178 # DUT takes some time to respond. Wait until the video signal 179 # to stabilize and wait for the connector change. 180 video_stable = video_port.wait_video_input_stable( 181 self._TIMEOUT_VIDEO_STABLE_PROBE) 182 output = utils.wait_for_value_changed( 183 self.display_facade.get_external_connector_name, 184 old_value=False) 185 186 if not output: 187 logging.warn('Maybe flaky that no display detected. Retry.') 188 video_port.unplug() 189 time.sleep(self.REPLUG_DELAY_SEC) 190 video_port.plug() 191 video_stable = video_port.wait_video_input_stable( 192 self._TIMEOUT_VIDEO_STABLE_PROBE) 193 output = utils.wait_for_value_changed( 194 self.display_facade.get_external_connector_name, 195 old_value=False) 196 197 logging.info('CrOS detected external connector: %r', output) 198 199 if output: 200 yield video_port 201 yielded = True 202 else: 203 if failed_ports is not None: 204 failed_ports.append(video_port) 205 logging.error('CrOS failed to see any external display') 206 if not video_stable: 207 logging.warn('Chameleon timed out waiting CrOS video') 208 finally: 209 # Unplug the port not to interfere with other tests. 210 video_port.unplug() 211 212 if raise_error and not yielded: 213 raise error.TestFail('No connected video port found between CrOS ' 214 'and Chameleon.') 215 216 217 def iterate_all_ports(self): 218 """ 219 Iterates all connected video ports and ensures every of them plugged. 220 221 It is used via a for statement, like the following: 222 223 finder = ChameleonVideoInputFinder(chameleon_board, display_facade) 224 for chameleon_port in finder.iterate_all_ports() 225 # chameleon_port is automatically plugged before this line. 226 do_some_test_on(chameleon_port) 227 # chameleon_port is automatically unplugged after this line. 228 229 @yields every connected ChameleonVideoInput which is ensured plugged 230 before yeilding. 231 232 @raises TestFail if no connected video port. 233 234 """ 235 return self._yield_all_ports(raise_error=True) 236 237 238 @contextmanager 239 def use_first_port(self): 240 """ 241 Use the first connected video port and ensures it plugged. 242 243 It is used via a with statement, like the following: 244 245 finder = ChameleonVideoInputFinder(chameleon_board, display_facade) 246 with finder.use_first_port() as chameleon_port: 247 # chameleon_port is automatically plugged before this line. 248 do_some_test_on(chameleon_port) 249 # chameleon_port is automatically unplugged after this line. 250 251 @yields the first connected ChameleonVideoInput which is ensured plugged 252 before yeilding. 253 254 @raises TestFail if no connected video port. 255 256 """ 257 for port in self._yield_all_ports(raise_error=True): 258 yield port 259 break 260 261 262 def find_all_ports(self): 263 """ 264 @returns a named tuple ChameleonPorts() containing a list of connected 265 video inputs as the first element and failed ports as second 266 element. 267 268 """ 269 dut_failed_ports = [] 270 connected_ports = list(self._yield_all_ports(dut_failed_ports)) 271 self.connected = connected_ports 272 self.failed = dut_failed_ports 273 274 return ChameleonPorts(connected_ports, dut_failed_ports) 275 276 277class ChameleonAudioInputFinder(ChameleonInputFinder): 278 """ 279 Responsible for finding all audio inputs connected to the chameleon board. 280 281 It does not verify if these ports are connected to DUT. 282 283 """ 284 285 def find_all_ports(self): 286 """ 287 @returns a named tuple ChameleonPorts() containing a list of connected 288 audio inputs as the first element and failed ports as second 289 element. 290 291 """ 292 all_ports = super(ChameleonAudioInputFinder, self).find_all_ports() 293 self.connected = [chameleon.ChameleonAudioInput(port) 294 for port in all_ports.connected 295 if port.has_audio_support()] 296 self.failed = [] 297 298 return ChameleonPorts(self.connected, self.failed) 299 300 301class ChameleonAudioOutputFinder(ChameleonOutputFinder): 302 """ 303 Responsible for finding all audio outputs connected to the chameleon board. 304 305 It does not verify if these ports are connected to DUT. 306 307 """ 308 309 def find_all_ports(self): 310 """ 311 @returns a named tuple ChameleonPorts() containing a list of connected 312 audio outputs as the first element and failed ports as second 313 element. 314 315 """ 316 all_ports = super(ChameleonAudioOutputFinder, self).find_all_ports() 317 self.connected = [chameleon.ChameleonAudioOutput(port) 318 for port in all_ports.connected 319 if port.has_audio_support()] 320 self.failed = [] 321 322 return ChameleonPorts(self.connected, self.failed) 323