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