# Copyright 2015 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This is a test for screen tearing using the Chameleon board.""" import logging import time from autotest_lib.client.common_lib import error from autotest_lib.client.cros.chameleon import chameleon_port_finder from autotest_lib.server import test from autotest_lib.server.cros.multimedia import remote_facade_factory class display_Tearing(test.test): """Display tearing test by multi-color full screen animation. This test talks to a Chameleon board and a DUT to set up, run, and verify DUT behavior response to a series of multi-color full screen switch. """ version = 1 # Time to wait for Chameleon to save images into RAM. # Current value is decided by experiments. CHAMELEON_CAPTURE_WAIT_TIME_SEC = 1 # The initial background color to set for a new tab. INITIAL_BACKGROUND_COLOR = 0xFFFFFF # Time in seconds to wait for notation bubbles, including bubbles for # external detection, mirror mode and fullscreen, to disappear. NEW_PAGE_STABILIZE_TIME = 10 # 1. Since it is difficult to distinguish repeated frames # generated from delay from real repeated frames, make # sure that there are no successive repeated colors in # TEST_COLOR_SEQUENCE. In fact, if so, the repeated ones # will be discarded. # 2. Similarly make sure that the the first element of # TEST_COLOR_SEQUENCE is not INITIAL_BACKGROUND_COLOR. # 3. Notice that the hash function in Chameleon used for # checksums is weak, so it is possible to encounter # hash collision. If it happens, an error will be raised # during execution time of _display_and_get_checksum_table(). TEST_COLOR_SEQUENCE = [0x010000, 0x002300, 0x000045, 0x670000, 0x008900, 0x0000AB, 0xCD0000, 0x00EF00] * 20 def _open_color_sequence_tab(self, test_mirrored): """Sets up a new empty page for displaying color sequence. @param test_mirrored: True to test mirrored mode. False not to. """ self._test_tab_descriptor = self._display_facade.load_url('about:blank') if not test_mirrored: self._display_facade.move_to_display( self._display_facade.get_first_external_display_id()) self._display_facade.set_fullscreen(True) logging.info('Waiting for the new tab to stabilize...') time.sleep(self.NEW_PAGE_STABILIZE_TIME) def _get_single_color_checksum(self, chameleon_port, color): """Gets the frame checksum of the full screen of the given color. @param chameleon_port: A general ChameleonPort object. @param color: the given color. @return The frame checksum mentioned above, which is a tuple. """ try: chameleon_port.start_capturing_video() self._display_facade.load_color_sequence(self._test_tab_descriptor, [color]) time.sleep(self.CHAMELEON_CAPTURE_WAIT_TIME_SEC) finally: chameleon_port.stop_capturing_video() # Gets the checksum of the last one image. last = chameleon_port.get_captured_frame_count() - 1 return tuple(chameleon_port.get_captured_checksums(last)[0]) def _display_and_get_checksum_table(self, chameleon_port, color_sequence): """Makes checksum table, which maps checksums into colors. @param chameleon_port: A general ChameleonPort object. @param color_sequence: the color_sequence that will be displayed. @return A dictionary consists of (x: y), y is in color_sequence and x is the checksum of the full screen of pure color y. @raise an error if there is hash collision """ # Resets the background color to make sure the screen looks like # what we expect. self._reset_background_color() checksum_table = {} # Makes sure that INITIAL_BACKGROUND_COLOR is in checksum_table, # or it may be misjudged as screen tearing. color_set = set(color_sequence+[self.INITIAL_BACKGROUND_COLOR]) for color in color_set: checksum = self._get_single_color_checksum(chameleon_port, color) if checksum in checksum_table: raise error.TestFail('Bad color sequence: hash collision') checksum_table[checksum] = color logging.info('Color %d has checksums %r', (color, checksum)) return checksum_table def _reset_background_color(self): """Resets the background color for displaying test color sequence.""" self._display_facade.load_color_sequence( self._test_tab_descriptor, [self.INITIAL_BACKGROUND_COLOR]) def _display_and_capture(self, chameleon_port, color_sequence): """Displays the color sequence and captures frames by Chameleon. @param chameleon_port: A general ChameleonPort object. @param color_sequence: the color sequence to display. @return (A list of checksums of captured frames, A list of the timestamp for each switch). """ # Resets the background color to make sure the screen looks like # what we expect. self._reset_background_color() try: chameleon_port.start_capturing_video() timestamp_list = ( self._display_facade.load_color_sequence( self._test_tab_descriptor, color_sequence)) time.sleep(self.CHAMELEON_CAPTURE_WAIT_TIME_SEC) finally: chameleon_port.stop_capturing_video() captured_checksums = chameleon_port.get_captured_checksums(0) captured_checksums = [tuple(x) for x in captured_checksums] return (captured_checksums, timestamp_list) def _tearing_test(self, captured_checksums, checksum_table): """Checks whether some captured frame is teared by checking their checksums. @param captured_checksums: A list of checksums of captured frames. @param checksum_table: A dictionary of reasonable checksums. @return True if the test passes. """ for checksum in captured_checksums: if checksum not in checksum_table: return False return True def _correction_test( self, captured_color_sequence, expected_color_sequence): """Checks whether the color sequence is sent to Chameleon correctly. Here are the checking steps: 1. Discard all successive repeated elements of both sequences. 2. If the first element of the captured color sequence is INITIAL_BACKGROUND_COLOR, discard it. 3. Check whether the two sequences are equal. @param captured_color_sequence: The sequence of colors captured by Chameleon, each element of which is an integer. @param expected_color_sequence: The sequence of colors expected to be displayed. @return True if the test passes. """ def _discard_delayed_frames(sequence): return [sequence[i] for i in xrange(len(sequence)) if i == 0 or sequence[i] != sequence[i-1]] captured_color_sequence = _discard_delayed_frames( captured_color_sequence) expected_color_sequence = _discard_delayed_frames( expected_color_sequence) if (len(captured_color_sequence) > 0 and captured_color_sequence[0] == self.INITIAL_BACKGROUND_COLOR): captured_color_sequence.pop(0) return captured_color_sequence == expected_color_sequence def _test_screen_with_color_sequence( self, test_mirrored, chameleon_port, error_list): """Tests the screen with the predefined color sequence. @param test_mirrored: True to test mirrored mode. False not to. @param chameleon_port: A general ChameleonPort object. @param error_list: A list to append the error message to or None. """ self._open_color_sequence_tab(test_mirrored) checksum_table = self._display_and_get_checksum_table( chameleon_port, self.TEST_COLOR_SEQUENCE) captured_checksums, timestamp_list = self._display_and_capture( chameleon_port, self.TEST_COLOR_SEQUENCE) self._display_facade.close_tab(self._test_tab_descriptor) delay_time = [timestamp_list[i] - timestamp_list[i-1] for i in xrange(1, len(timestamp_list))] logging.info('Captured %d frames\n' 'Checksum_table: %s\n' 'Captured_checksums: %s\n' 'Timestamp_list: %s\n' 'Delay informtaion:\n' 'max = %r, min = %r, avg = %r\n', len(captured_checksums), checksum_table, captured_checksums, timestamp_list, max(delay_time), min(delay_time), sum(delay_time)/len(delay_time)) error = None if self._tearing_test( captured_checksums, checksum_table) is False: error = 'Detected screen tearing' else: captured_color_sequence = [ checksum_table[checksum] for checksum in captured_checksums] if self._correction_test( captured_color_sequence, self.TEST_COLOR_SEQUENCE) is False: error = 'Detected missing, redundant or wrong frame(s)' if error is not None and error_list is not None: error_list.append(error) def run_once(self, host, test_mirrored=False): factory = remote_facade_factory.RemoteFacadeFactory(host) self._display_facade = factory.create_display_facade() self._test_tab_descriptor = None chameleon_board = host.chameleon chameleon_board.setup_and_reset(self.outputdir) finder = chameleon_port_finder.ChameleonVideoInputFinder( chameleon_board, self._display_facade) errors = [] for chameleon_port in finder.iterate_all_ports(): logging.info('Set mirrored: %s', test_mirrored) self._display_facade.set_mirrored(test_mirrored) self._test_screen_with_color_sequence( test_mirrored, chameleon_port, errors) if errors: raise error.TestFail('; '.join(set(errors)))