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