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
5import logging
6import os
7import time
8
9from autotest_lib.client.bin import test
10from autotest_lib.client.bin import utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib.cros import chrome
13from autotest_lib.client.cros.graphics import graphics_utils
14from autotest_lib.client.cros.image_comparison import pdiff_image_comparer
15from autotest_lib.client.cros.input_playback import input_playback
16
17def get_percent_difference(file1, file2):
18    """
19    Performs pixel comparison of two files, given by their paths |file1|
20    and |file2| using terminal tool 'perceptualdiff' and returns percentage
21    difference of the total file size.
22
23    @param file1: path to image
24    @param file2: path to secondary image
25    @return: percentage difference of total file size.
26    @raise ValueError: if image dimensions are not the same
27    @raise OSError: if file does not exist or cannot be opened.
28
29    """
30    # Using pdiff image comparer to compare the two images. This class
31    # invokes the terminal tool perceptualdiff.
32    pdi = pdiff_image_comparer.PdiffImageComparer()
33    diff_bytes = pdi.compare(file1, file2)[0]
34    return round(100. * diff_bytes / os.path.getsize(file1))
35
36
37class graphics_VTSwitch(test.test):
38    """
39    Verify that VT switching works.
40    """
41    version = 2
42    GSC = None
43    _WAIT = 5
44    # TODO(crosbug.com/36417): Need to handle more than one display screen.
45
46    def initialize(self):
47        self.GSC = graphics_utils.GraphicsStateChecker()
48        self._player = input_playback.InputPlayback()
49        self._player.emulate(input_type='keyboard')
50        self._player.find_connected_inputs()
51
52    def _open_vt2(self):
53        """Use keyboard shortcut to open VT2."""
54        self._player.blocking_playback_of_default_file(
55            input_type='keyboard', filename='keyboard_ctrl+alt+f2')
56        time.sleep(self._WAIT)
57
58    def _open_vt1(self):
59        """Use keyboard shortcut to close VT2."""
60        self._player.blocking_playback_of_default_file(
61            input_type='keyboard', filename='keyboard_ctrl+alt+f1')
62        time.sleep(self._WAIT)
63
64    def run_once(self,
65                 num_iterations=2,
66                 similarity_percent_threshold=95,
67                 difference_percent_threshold=5):
68
69        # Check for chromebook type devices
70        if not utils.get_board_type() == 'CHROMEBOOK':
71            raise error.TestNAError('DUT is not Chromebook. Test Skipped.')
72
73        self._num_errors = 0
74        keyvals = {}
75
76        # Make sure we start in VT1.
77        self._open_vt1()
78
79        with chrome.Chrome():
80
81            # Take screenshot of browser.
82            vt1_screenshot = self._take_current_vt_screenshot(1)
83
84            keyvals['num_iterations'] = num_iterations
85
86            # Go to VT2 and take a screenshot.
87            self._open_vt2()
88            vt2_screenshot = self._take_current_vt_screenshot(2)
89
90            # Make sure VT1 and VT2 are sufficiently different.
91            diff = get_percent_difference(vt1_screenshot, vt2_screenshot)
92            keyvals['percent_initial_VT1_VT2_difference'] = diff
93            if not diff >= difference_percent_threshold:
94                self._num_errors += 1
95                logging.error('VT1 and VT2 screenshots only differ by ' + \
96                              '%d %%: %s vs %s' %
97                              (diff, vt1_screenshot, vt2_screenshot))
98
99            num_identical_vt1_screenshots = 0
100            num_identical_vt2_screenshots = 0
101            max_vt1_difference_percent = 0
102            max_vt2_difference_percent = 0
103
104            # Repeatedly switch between VT1 and VT2.
105            for iteration in xrange(num_iterations):
106                logging.info('Iteration #%d', iteration)
107
108                # Go to VT1 and take a screenshot.
109                self._open_vt1()
110                current_vt1_screenshot = self._take_current_vt_screenshot(1)
111
112                # Make sure the current VT1 screenshot is the same as (or similar
113                # to) the original login screen screenshot.
114                diff = get_percent_difference(vt1_screenshot,
115                                              current_vt1_screenshot)
116                if not diff < similarity_percent_threshold:
117                    max_vt1_difference_percent = \
118                        max(diff, max_vt1_difference_percent)
119                    self._num_errors += 1
120                    logging.error('VT1 screenshots differ by %d %%: %s vs %s',
121                                  diff, vt1_screenshot,
122                                  current_vt1_screenshot)
123                else:
124                    num_identical_vt1_screenshots += 1
125
126                # Go to VT2 and take a screenshot.
127                self._open_vt2()
128                current_vt2_screenshot = self._take_current_vt_screenshot(2)
129
130                # Make sure the current VT2 screenshot is the same as (or
131                # similar to) the first VT2 screenshot.
132                diff = get_percent_difference(vt2_screenshot,
133                                              current_vt2_screenshot)
134                if not diff <= similarity_percent_threshold:
135                    max_vt2_difference_percent = \
136                        max(diff, max_vt2_difference_percent)
137                    self._num_errors += 1
138                    logging.error(
139                        'VT2 screenshots differ by %d %%: %s vs %s',
140                        diff, vt2_screenshot, current_vt2_screenshot)
141                else:
142                    num_identical_vt2_screenshots += 1
143
144        self._open_vt1()
145
146        keyvals['percent_VT1_screenshot_max_difference'] = \
147            max_vt1_difference_percent
148        keyvals['percent_VT2_screenshot_max_difference'] = \
149            max_vt2_difference_percent
150        keyvals['num_identical_vt1_screenshots'] = num_identical_vt1_screenshots
151        keyvals['num_identical_vt2_screenshots'] = num_identical_vt2_screenshots
152
153        self.write_perf_keyval(keyvals)
154
155        if self._num_errors > 0:
156            raise error.TestFail('Failed: saw %d VT switching errors.' %
157                                  self._num_errors)
158
159    def _take_current_vt_screenshot(self, current_vt):
160        """
161        Captures a screenshot of the current VT screen in BMP format.
162
163        @param current_vt: desired vt for screenshot.
164
165        @returns the path of the screenshot file.
166
167        """
168        extension = 'bmp'
169
170        # In VT1, X is running so use that screenshot function.
171        if current_vt == 1:
172            return graphics_utils.take_screenshot(self.resultsdir,
173                                                  'graphics_VTSwitch_VT1',
174                                                  extension)
175
176        # Otherwise, take a screenshot using screenshot.py.
177        prefix = 'graphics_VTSwitch_VT2'
178        next_index = len(glob.glob(
179            os.path.join(self.resultsdir, '%s-*.%s' % (prefix, extension))))
180        filename = '%s-%d.%s' % (prefix, next_index, extension)
181        output_path = os.path.join(self.resultsdir, filename)
182        return self._take_screenshot(output_path)
183
184    def _take_screenshot(self, output_path):
185        """
186        Takes screenshot.
187
188        @param output_path: screenshot output path.
189
190        @returns output_path where screenshot was saved.
191
192        """
193        screenshot_path = os.path.join(self.autodir, 'bin', 'screenshot.py')
194        utils.system('%s %s' % (screenshot_path, output_path),
195                     ignore_status=True)
196        utils.system('sync', ignore_status=True)
197        time.sleep(self._WAIT)
198        logging.info('Saving screenshot to %s', output_path)
199        return output_path
200
201    def cleanup(self):
202        # Return to VT1 when done.  Ideally, the screen should already be in VT1
203        # but the test might fail and terminate while in VT2.
204        self._open_vt1()
205        if self.GSC:
206            self.GSC.finalize()
207        self._player.close()
208