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