• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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
5"""This is a test for screen tearing using the Chameleon board."""
6
7import logging
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.cros.chameleon import chameleon_port_finder
12from autotest_lib.server import test
13from autotest_lib.server.cros.multimedia import remote_facade_factory
14
15
16class display_Tearing(test.test):
17    """Display tearing test by multi-color full screen animation.
18
19    This test talks to a Chameleon board and a DUT to set up, run, and verify
20    DUT behavior response to a series of multi-color full screen switch.
21    """
22
23    version = 1
24
25    # Time to wait for Chameleon to save images into RAM.
26    # Current value is decided by experiments.
27    CHAMELEON_CAPTURE_WAIT_TIME_SEC = 1
28
29    # The initial background color to set for a new tab.
30    INITIAL_BACKGROUND_COLOR = 0xFFFFFF
31
32    # Time in seconds to wait for notation bubbles, including bubbles for
33    # external detection, mirror mode and fullscreen, to disappear.
34    NEW_PAGE_STABILIZE_TIME = 10
35
36    # 1. Since it is difficult to distinguish repeated frames
37    #    generated from delay from real repeated frames, make
38    #    sure that there are no successive repeated colors in
39    #    TEST_COLOR_SEQUENCE. In fact, if so, the repeated ones
40    #    will be discarded.
41    # 2. Similarly make sure that the the first element of
42    #    TEST_COLOR_SEQUENCE is not INITIAL_BACKGROUND_COLOR.
43    # 3. Notice that the hash function in Chameleon used for
44    #    checksums is weak, so it is possible to encounter
45    #    hash collision. If it happens, an error will be raised
46    #    during execution time of _display_and_get_checksum_table().
47    TEST_COLOR_SEQUENCE = [0x010000, 0x002300, 0x000045, 0x670000,
48                           0x008900, 0x0000AB, 0xCD0000, 0x00EF00] * 20
49
50    def _open_color_sequence_tab(self, test_mirrored):
51        """Sets up a new empty page for displaying color sequence.
52
53        @param test_mirrored: True to test mirrored mode. False not to.
54        """
55        self._test_tab_descriptor = self._display_facade.load_url('about:blank')
56        if not test_mirrored:
57            self._display_facade.move_to_display(
58                    self._display_facade.get_first_external_display_id())
59        self._display_facade.set_fullscreen(True)
60        logging.info('Waiting for the new tab to stabilize...')
61        time.sleep(self.NEW_PAGE_STABILIZE_TIME)
62
63    def _get_single_color_checksum(self, chameleon_port, color):
64        """Gets the frame checksum of the full screen of the given color.
65
66        @param chameleon_port: A general ChameleonPort object.
67        @param color: the given color.
68        @return The frame checksum mentioned above, which is a tuple.
69        """
70        try:
71            chameleon_port.start_capturing_video()
72            self._display_facade.load_color_sequence(self._test_tab_descriptor,
73                                                     [color])
74            time.sleep(self.CHAMELEON_CAPTURE_WAIT_TIME_SEC)
75        finally:
76            chameleon_port.stop_capturing_video()
77        # Gets the checksum of the last one image.
78        last = chameleon_port.get_captured_frame_count() - 1
79        return tuple(chameleon_port.get_captured_checksums(last)[0])
80
81    def _display_and_get_checksum_table(self, chameleon_port, color_sequence):
82        """Makes checksum table, which maps checksums into colors.
83
84        @param chameleon_port: A general ChameleonPort object.
85        @param color_sequence: the color_sequence that will be displayed.
86        @return A dictionary consists of (x: y), y is in color_sequence and
87                x is the checksum of the full screen of pure color y.
88        @raise an error if there is hash collision
89        """
90        # Resets the background color to make sure the screen looks like
91        # what we expect.
92        self._reset_background_color()
93        checksum_table = {}
94        # Makes sure that INITIAL_BACKGROUND_COLOR is in checksum_table,
95        # or it may be misjudged as screen tearing.
96        color_set = set(color_sequence+[self.INITIAL_BACKGROUND_COLOR])
97        for color in color_set:
98            checksum = self._get_single_color_checksum(chameleon_port, color)
99            if checksum in checksum_table:
100                raise error.TestFail('Bad color sequence: hash collision')
101            checksum_table[checksum] = color
102            logging.info('Color %d has checksums %r', (color, checksum))
103        return checksum_table
104
105    def _reset_background_color(self):
106        """Resets the background color for displaying test color sequence."""
107        self._display_facade.load_color_sequence(
108                self._test_tab_descriptor,
109                [self.INITIAL_BACKGROUND_COLOR])
110
111    def _display_and_capture(self, chameleon_port, color_sequence):
112        """Displays the color sequence and captures frames by Chameleon.
113
114        @param chameleon_port: A general ChameleonPort object.
115        @param color_sequence: the color sequence to display.
116        @return (A list of checksums of captured frames,
117                 A list of the timestamp for each switch).
118        """
119        # Resets the background color to make sure the screen looks like
120        # what we expect.
121        self._reset_background_color()
122        try:
123            chameleon_port.start_capturing_video()
124            timestamp_list = (
125                    self._display_facade.load_color_sequence(
126                        self._test_tab_descriptor, color_sequence))
127            time.sleep(self.CHAMELEON_CAPTURE_WAIT_TIME_SEC)
128        finally:
129            chameleon_port.stop_capturing_video()
130
131        captured_checksums = chameleon_port.get_captured_checksums(0)
132        captured_checksums = [tuple(x) for x in captured_checksums]
133        return (captured_checksums, timestamp_list)
134
135    def _tearing_test(self, captured_checksums, checksum_table):
136        """Checks whether some captured frame is teared by checking
137                their checksums.
138
139        @param captured_checksums: A list of checksums of captured
140                                   frames.
141        @param checksum_table: A dictionary of reasonable checksums.
142        @return True if the test passes.
143        """
144        for checksum in captured_checksums:
145            if checksum not in checksum_table:
146                return False
147        return True
148
149    def _correction_test(
150            self, captured_color_sequence, expected_color_sequence):
151        """Checks whether the color sequence is sent to Chameleon correctly.
152
153        Here are the checking steps:
154            1. Discard all successive repeated elements of both sequences.
155            2. If the first element of the captured color sequence is
156               INITIAL_BACKGROUND_COLOR, discard it.
157            3. Check whether the two sequences are equal.
158
159        @param captured_color_sequence: The sequence of colors captured by
160                                        Chameleon, each element of which
161                                        is an integer.
162        @param expected_color_sequence: The sequence of colors expected to
163                                        be displayed.
164        @return True if the test passes.
165        """
166        def _discard_delayed_frames(sequence):
167            return [sequence[i]
168                    for i in xrange(len(sequence))
169                    if i == 0 or sequence[i] != sequence[i-1]]
170
171        captured_color_sequence = _discard_delayed_frames(
172                captured_color_sequence)
173        expected_color_sequence = _discard_delayed_frames(
174                expected_color_sequence)
175
176        if (len(captured_color_sequence) > 0 and
177            captured_color_sequence[0] == self.INITIAL_BACKGROUND_COLOR):
178            captured_color_sequence.pop(0)
179        return captured_color_sequence == expected_color_sequence
180
181    def _test_screen_with_color_sequence(
182            self, test_mirrored, chameleon_port, error_list):
183        """Tests the screen with the predefined color sequence.
184
185        @param test_mirrored: True to test mirrored mode. False not to.
186        @param chameleon_port: A general ChameleonPort object.
187        @param error_list: A list to append the error message to or None.
188        """
189        self._open_color_sequence_tab(test_mirrored)
190        checksum_table = self._display_and_get_checksum_table(
191                chameleon_port, self.TEST_COLOR_SEQUENCE)
192        captured_checksums, timestamp_list = self._display_and_capture(
193                chameleon_port, self.TEST_COLOR_SEQUENCE)
194        self._display_facade.close_tab(self._test_tab_descriptor)
195        delay_time = [timestamp_list[i] - timestamp_list[i-1]
196                      for i in xrange(1, len(timestamp_list))]
197        logging.info('Captured %d frames\n'
198                     'Checksum_table: %s\n'
199                     'Captured_checksums: %s\n'
200                     'Timestamp_list: %s\n'
201                     'Delay informtaion:\n'
202                     'max = %r, min = %r, avg = %r\n',
203                     len(captured_checksums), checksum_table,
204                     captured_checksums, timestamp_list,
205                     max(delay_time), min(delay_time),
206                     sum(delay_time)/len(delay_time))
207
208        error = None
209        if self._tearing_test(
210                captured_checksums, checksum_table) is False:
211            error = 'Detected screen tearing'
212        else:
213            captured_color_sequence = [
214                    checksum_table[checksum]
215                    for checksum in captured_checksums]
216            if self._correction_test(
217                    captured_color_sequence, self.TEST_COLOR_SEQUENCE) is False:
218                error = 'Detected missing, redundant or wrong frame(s)'
219        if error is not None and error_list is not None:
220            error_list.append(error)
221
222    def run_once(self, host, test_mirrored=False):
223        factory = remote_facade_factory.RemoteFacadeFactory(host)
224        self._display_facade = factory.create_display_facade()
225        self._test_tab_descriptor = None
226        chameleon_board = host.chameleon
227
228        chameleon_board.setup_and_reset(self.outputdir)
229        finder = chameleon_port_finder.ChameleonVideoInputFinder(
230                chameleon_board, self._display_facade)
231
232        errors = []
233        for chameleon_port in finder.iterate_all_ports():
234
235            logging.info('Set mirrored: %s', test_mirrored)
236            self._display_facade.set_mirrored(test_mirrored)
237
238            self._test_screen_with_color_sequence(
239                    test_mirrored, chameleon_port, errors)
240
241        if errors:
242            raise error.TestFail('; '.join(set(errors)))
243