1# Copyright (c) 2012 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. 4import glob, logging, os 5 6from autotest_lib.client.bin import test, utils 7from autotest_lib.client.common_lib import base_utils, error 8from autotest_lib.client.cros.graphics import graphics_utils 9 10def get_percent_difference(file1, file2): 11 """ 12 Performs byte-by-byte comparison of two files, given by their paths |file1| 13 and |file2|. Returns difference as a percentage of the total file size. If 14 one file is larger than the other, the difference is a percentage of 15 |file1|. 16 """ 17 files = (file1, file2) 18 sizes = {} 19 for filename in files: 20 if not os.path.exists(filename): 21 raise error.TestFail('Could not find file \'%s\'.' % filename) 22 sizes[filename] = os.path.getsize(filename) 23 if sizes[filename] == 0: 24 raise error.TestFail('File \'%s\' has zero size.' % filename) 25 26 diff_bytes = int(utils.system_output('cmp -l %s %s | wc -l' % files)) 27 28 return round(100. * diff_bytes / sizes[file1]) 29 30 31class graphics_VTSwitch(test.test): 32 """ 33 Verify that VT switching works. 34 """ 35 version = 1 36 GSC = None 37 # TODO(crosbug.com/36417): Need to handle more than one display screen. 38 39 def setup(self): 40 self.job.setup_dep(['gfxtest']) 41 42 def initialize(self): 43 self.GSC = graphics_utils.GraphicsStateChecker() 44 45 def cleanup(self): 46 # Return to VT1 when done. Ideally, the screen should already be in VT1 47 # but the test might fail and terminate while in VT2. 48 self._switch_to_vt(1) 49 if self.GSC: 50 self.GSC.finalize() 51 52 def run_once(self, 53 num_iterations=2, 54 similarity_percent_threshold=95, 55 difference_percent_threshold=5): 56 # TODO(ihf): Remove this once VTSwitch works on freon. 57 if utils.is_freon(): 58 raise error.TestNAError( 59 'Test needs work on Freon. See crbug.com/413088.') 60 61 self._num_errors = 0 62 keyvals = {} 63 64 # Make sure we start in VT1 65 if not self._switch_to_vt(1): 66 raise error.TestFail('Could not switch to VT1') 67 68 # Take screenshot of sign-in screen. 69 logged_out_screenshot = self._take_current_vt_screenshot() 70 71 keyvals['num_iterations'] = num_iterations 72 73 # Go to VT2 and take a screenshot. 74 if not self._switch_to_vt(2): 75 raise error.TestFail('Could not switch to VT2') 76 vt2_screenshot = self._take_current_vt_screenshot() 77 78 # Make sure VT1 and VT2 are sufficiently different. 79 diff = get_percent_difference(logged_out_screenshot, vt2_screenshot) 80 keyvals['percent_initial_VT1_VT2_difference'] = diff 81 if not diff >= difference_percent_threshold: 82 self._num_errors += 1 83 logging.error('VT1 and VT2 screenshots only differ by ' + \ 84 '%d %%: %s vs %s' % 85 (diff, logged_out_screenshot, vt2_screenshot)) 86 87 num_identical_vt1_screenshots = 0 88 num_identical_vt2_screenshots = 0 89 max_vt1_difference_percent = 0 90 max_vt2_difference_percent = 0 91 92 # Repeatedly switch between VT1 and VT2. 93 for iteration in xrange(num_iterations): 94 logging.info('Iteration #%d', iteration) 95 96 # Go to VT1 and take a screenshot. 97 self._switch_to_vt(1) 98 current_vt1_screenshot = self._take_current_vt_screenshot() 99 100 # Make sure the current VT1 screenshot is the same as (or similar 101 # to) the original login screen screenshot. 102 diff = get_percent_difference(logged_out_screenshot, 103 current_vt1_screenshot) 104 if not diff < similarity_percent_threshold: 105 max_vt1_difference_percent = \ 106 max(diff, max_vt1_difference_percent) 107 self._num_errors += 1 108 logging.error('VT1 screenshots differ by %d %%: %s vs %s', 109 diff, logged_out_screenshot, 110 current_vt1_screenshot) 111 else: 112 num_identical_vt1_screenshots += 1 113 114 # Go to VT2 and take a screenshot. 115 self._switch_to_vt(2) 116 current_vt2_screenshot = self._take_current_vt_screenshot() 117 118 # Make sure the current VT2 screenshot is the same as (or similar 119 # to) the first VT2 screenshot. 120 diff = get_percent_difference(vt2_screenshot, 121 current_vt2_screenshot) 122 if not diff <= similarity_percent_threshold: 123 max_vt2_difference_percent = \ 124 max(diff, max_vt2_difference_percent) 125 self._num_errors += 1 126 logging.error( 127 'VT2 screenshots differ by %d %%: %s vs %s', 128 diff, vt2_screenshot, current_vt2_screenshot) 129 else: 130 num_identical_vt2_screenshots += 1 131 132 self._switch_to_vt(1) 133 134 keyvals['percent_VT1_screenshot_max_difference'] = \ 135 max_vt1_difference_percent 136 keyvals['percent_VT2_screenshot_max_difference'] = \ 137 max_vt2_difference_percent 138 keyvals['num_identical_vt1_screenshots'] = num_identical_vt1_screenshots 139 keyvals['num_identical_vt2_screenshots'] = num_identical_vt2_screenshots 140 141 self.write_perf_keyval(keyvals) 142 143 if self._num_errors > 0: 144 raise error.TestError('Test failed with %d errors' % 145 self._num_errors) 146 147 148 def _take_current_vt_screenshot(self): 149 """ 150 Captures a screenshot of the current VT screen in BMP format. 151 Returns the path of the screenshot file. 152 """ 153 current_vt = int(utils.system_output('fgconsole')) 154 extension = 'bmp' 155 156 # In VT1, X is running so use that screenshot function. 157 if current_vt == 1: 158 return graphics_utils.take_screenshot(self.resultsdir, 159 'graphics_VTSwitch_VT1', 160 extension) 161 162 # Otherwise, grab the framebuffer using DRM. 163 prefix = 'graphics_VTSwitch_VT2' 164 next_index = len(glob.glob( 165 os.path.join(self.resultsdir, '%s-*.%s' % (prefix, extension)))) 166 filename = '%s-%d.%s' % (prefix, next_index, extension) 167 output_path = os.path.join(self.resultsdir, filename) 168 return self._take_drm_screenshot(output_path) 169 170 171 def _take_drm_screenshot(self, output_path): 172 """ 173 Takes drm screenshot. 174 """ 175 autotest_deps_path = os.path.join(self.autodir, 'deps') 176 getfb_path = os.path.join(autotest_deps_path, 'gfxtest', 'getfb') 177 output = utils.system_output('%s %s.rgba' % (getfb_path, output_path)) 178 for line in output.split('\n'): 179 # Parse the getfb output for info about framebuffer size. The line 180 # should looks omething like: 181 # Framebuffer info: 1024x768, 32bpp 182 if line.startswith('Framebuffer info:'): 183 size = line.split(':')[1].split(',')[0].strip() 184 break 185 utils.system('convert -depth 8 -size %s %s.rgba %s' % 186 (size, output_path, output_path)) 187 188 logging.info('Saving screenshot to %s', output_path) 189 return output_path 190 191 192 def _switch_to_vt(self, vt): 193 """ 194 Switches to virtual terminal given by |vt| (1, 2, etc) and checks that 195 the switch was successful by calling fgconsole. 196 197 Returns True if fgconsole returned the new vt number, False otherwise. 198 """ 199 utils.system_output('chvt %d' % vt) 200 201 # Verify that the VT switch was successful. 202 current_vt = base_utils.wait_for_value( 203 lambda: int(utils.system_output('fgconsole')), 204 expected_value=vt) 205 if vt != current_vt: 206 self._num_errors += 1 207 logging.error('Current VT %d does not match expected VT %d', 208 current_vt, vt) 209 return False 210 logging.info('Switched to VT%d', vt) 211 return True 212