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 7 import logging 8 import time 9 10 from autotest_lib.client.common_lib import error 11 from autotest_lib.client.cros.chameleon import chameleon_port_finder 12 from autotest_lib.server import test 13 from autotest_lib.server.cros.multimedia import remote_facade_factory 14 15 16 class 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_index()) 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.reset() 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