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 5import logging 6import time 7 8from autotest_lib.client.bin import utils 9from autotest_lib.client.common_lib import error as error_lib 10from autotest_lib.client.cros.chameleon import screen_utility_factory 11 12 13class 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 # Avoid browser tab focus issue. See crbug/1035014. 161 self._display_facade.set_fullscreen(True) 162 if chameleon_supported: 163 error = self._resolution_comparer.compare(expected_resolution) 164 if not error: 165 while retry_count: 166 retry_count = retry_count - 1 167 try: 168 self.load_test_image(test_image_size) 169 error = self.test_screen(expected_resolution, test_mirrored) 170 if error is None: 171 return error 172 elif retry_count > 0: 173 logging.info('Retry screen comparison again...') 174 finally: 175 self.unload_test_image() 176 177 if error and error_list is not None: 178 error_list.append(error) 179 return error 180 181 182 def check_external_display_connected(self, expected_display, 183 error_list=None): 184 """Checks the given external display connected. 185 186 @param expected_display: Name of the expected display or False 187 if no external display is expected. 188 @param error_list: A list to append the error message to or None. 189 @return: None if the check passes; otherwise, a string of error message. 190 """ 191 error = None 192 if not self._display_facade.wait_external_display_connected( 193 expected_display): 194 error = 'Waited for display %s but timed out' % expected_display 195 196 if error and error_list is not None: 197 logging.error(error) 198 error_list.append(error) 199 return error 200