Home | History | Annotate | Download | only in display_Tearing
      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_id())
     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.setup_and_reset(self.outputdir)
    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