Home | History | Annotate | Download | only in chameleon
      1 # Copyright 2014 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 import logging
      6 import time
      7 
      8 from autotest_lib.client.bin import utils
      9 from autotest_lib.client.common_lib import error as error_lib
     10 from autotest_lib.client.cros.chameleon import screen_utility_factory
     11 
     12 
     13 class ChameleonScreenTest(object):
     14     """Utility to test the screen between Chameleon and CrOS.
     15 
     16     This class contains the screen-related testing operations.
     17 
     18     """
     19     # Time in seconds to wait for notation bubbles, including bubbles for
     20     # external detection, mirror mode and fullscreen, to disappear.
     21     _TEST_IMAGE_STABILIZE_TIME = 10
     22 
     23     def __init__(self, host, chameleon_port, display_facade, output_dir):
     24         """Initializes the ScreenUtilityFactory objects."""
     25         self._host = host
     26         self._display_facade = display_facade
     27         factory = screen_utility_factory.ScreenUtilityFactory(
     28                 chameleon_port, display_facade)
     29         self._resolution_comparer = factory.create_resolution_comparer()
     30         self._screen_comparer = factory.create_screen_comparer(output_dir)
     31         self._mirror_comparer = factory.create_mirror_comparer(output_dir)
     32         self._calib_comparer = factory.create_calibration_comparer(output_dir)
     33         self._calibration_image_tab_descriptor = None
     34 
     35 
     36     def test_resolution(self, expected_resolution):
     37         """Tests if the resolution of Chameleon matches with the one of CrOS.
     38 
     39         @param expected_resolution: A tuple (width, height) for the expected
     40                                     resolution.
     41         @return: None if the check passes; otherwise, a string of error message.
     42         """
     43         return self._resolution_comparer.compare(expected_resolution)
     44 
     45 
     46     def test_screen(self, expected_resolution, test_mirrored=None,
     47                     error_list=None):
     48         """Tests if the screen of Chameleon matches with the one of CrOS.
     49 
     50         @param expected_resolution: A tuple (width, height) for the expected
     51                                     resolution.
     52         @param test_mirrored: True to test mirrored mode. False not to. None
     53                               to test mirrored mode iff the current mode is
     54                               mirrored.
     55         @param error_list: A list to append the error message to or None.
     56         @return: None if the check passes; otherwise, a string of error message.
     57         """
     58         if test_mirrored is None:
     59             test_mirrored = self._display_facade.is_mirrored_enabled()
     60 
     61         error = self._resolution_comparer.compare(expected_resolution)
     62         if not error:
     63             # Do two screen comparisons with and without hiding cursor, to
     64             # work-around some devices still showing cursor on CrOS FB.
     65             # TODO: Remove this work-around once crosbug/p/34524 got fixed.
     66             error = self._screen_comparer.compare()
     67             if error:
     68                 logging.info('Hide cursor and do screen comparison again...')
     69                 self._display_facade.hide_cursor()
     70                 error = self._screen_comparer.compare()
     71 
     72             if error:
     73                 # On some ARM device, the internal FB has some issue and it
     74                 # won't get fixed in the near future. As this issue don't
     75                 # affect users, just the tests. So compare it with the
     76                 # calibration image directly as an workaround.
     77                 board = self._host.get_board().replace('board:', '')
     78                 if board in ['kevin']:
     79                     # TODO(waihong): In mirrored mode, using calibration image
     80                     # to compare is not feasible due to the size difference.
     81                     # Skip the error first and think a better way to compare.
     82                     if test_mirrored:
     83                         raise error_lib.TestNAError('Test this item manually! '
     84                                 'Missing CrOS feature, not able to automate.')
     85                     logging.info('Compare the calibration image directly...')
     86                     error = self._calib_comparer.compare()
     87 
     88         if not error and test_mirrored:
     89             error = self._mirror_comparer.compare()
     90         if error and error_list is not None:
     91             error_list.append(error)
     92         return error
     93 
     94 
     95     def load_test_image(self, image_size, test_mirrored=None):
     96         """Loads calibration image on the CrOS with logging
     97 
     98         @param image_size: A tuple (width, height) conforms the resolution.
     99         @param test_mirrored: True to test mirrored mode. False not to. None
    100                               to test mirrored mode iff the current mode is
    101                               mirrored.
    102         """
    103         if test_mirrored is None:
    104             test_mirrored = self._display_facade.is_mirrored_enabled()
    105         self._calibration_image_tab_descriptor = \
    106             self._display_facade.load_calibration_image(image_size)
    107         if not test_mirrored:
    108             self._display_facade.move_to_display(
    109                     self._display_facade.get_first_external_display_id())
    110         self._display_facade.set_fullscreen(True)
    111         logging.info('Waiting for calibration image to stabilize...')
    112         time.sleep(self._TEST_IMAGE_STABILIZE_TIME)
    113 
    114 
    115     def unload_test_image(self):
    116         """Closes the tab in browser to unload the fullscreen test image."""
    117         self._display_facade.close_tab(self._calibration_image_tab_descriptor)
    118 
    119 
    120     def test_screen_with_image(self, expected_resolution, test_mirrored=None,
    121                                error_list=None, chameleon_supported=True,
    122                                retry_count=2):
    123         """Tests the screen with image loaded.
    124 
    125         @param expected_resolution: A tuple (width, height) for the expected
    126                                     resolution.
    127         @param test_mirrored: True to test mirrored mode. False not to. None
    128                               to test mirrored mode iff the current mode is
    129                               mirrored.
    130         @param error_list: A list to append the error message to or None.
    131         @param retry_count: A count to retry the screen test.
    132         @param chameleon_supported: Whether resolution is supported by
    133                                     chameleon. The DP RX doesn't support
    134                                     4K resolution. The max supported resolution
    135                                     is 2560x1600. See crbug/585900.
    136         @return: None if the check passes; otherwise, a string of error message.
    137         """
    138         if test_mirrored is None:
    139             test_mirrored = self._display_facade.is_mirrored_enabled()
    140 
    141         if test_mirrored:
    142             test_image_size = self._display_facade.get_internal_resolution()
    143         else:
    144             # DUT needs time to respond to the plug event
    145             test_image_size = utils.wait_for_value_changed(
    146                     self._display_facade.get_external_resolution,
    147                     old_value=None)
    148         error = None
    149         if test_image_size != expected_resolution:
    150             error = ('Screen size %s is not as expected %s!'
    151                      % (str(test_image_size), str(expected_resolution)))
    152             if test_mirrored:
    153                 # For the case of mirroring, depending on hardware vs
    154                 # software mirroring, screen size can be different.
    155                 logging.info('Warning: %s', error)
    156                 error = None
    157             else:
    158                 error_list.append(error)
    159 
    160         if chameleon_supported:
    161             error = self._resolution_comparer.compare(expected_resolution)
    162             if not error:
    163                 while retry_count:
    164                     retry_count = retry_count - 1
    165                     try:
    166                         self.load_test_image(test_image_size)
    167                         error = self.test_screen(expected_resolution, test_mirrored)
    168                         if error is None:
    169                             return error
    170                         elif retry_count > 0:
    171                             logging.info('Retry screen comparison again...')
    172                     finally:
    173                         self.unload_test_image()
    174 
    175             if error and error_list is not None:
    176                 error_list.append(error)
    177         return error
    178 
    179 
    180     def check_external_display_connected(self, expected_display,
    181                                          error_list=None):
    182         """Checks the given external display connected.
    183 
    184         @param expected_display: Name of the expected display or False
    185                 if no external display is expected.
    186         @param error_list: A list to append the error message to or None.
    187         @return: None if the check passes; otherwise, a string of error message.
    188         """
    189         error = None
    190         if not self._display_facade.wait_external_display_connected(
    191                 expected_display):
    192             error = 'Waited for display %s but timed out' % expected_display
    193 
    194         if error and error_list is not None:
    195             logging.error(error)
    196             error_list.append(error)
    197         return error
    198