| # 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))) |