140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que# Use of this source code is governed by a BSD-style license that can be
340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que# found in the LICENSE file.
43f219a2165c5233fa9a0e6d71e6dbdb3fe06d106Simon Queimport glob, logging, os
540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
63dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedelfrom autotest_lib.client.bin import test, utils
740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Quefrom autotest_lib.client.common_lib import base_utils, error
83dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedelfrom autotest_lib.client.cros.graphics import graphics_utils
940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
1040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Quedef get_percent_difference(file1, file2):
1140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    """
1240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    Performs byte-by-byte comparison of two files, given by their paths |file1|
1340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    and |file2|.  Returns difference as a percentage of the total file size.  If
1440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    one file is larger than the other, the difference is a percentage of
1540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    |file1|.
1640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    """
1740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    files = (file1, file2)
1840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    sizes = {}
1940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    for filename in files:
2040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if not os.path.exists(filename):
2140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            raise error.TestFail('Could not find file \'%s\'.' % filename)
2240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        sizes[filename] = os.path.getsize(filename)
2340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if sizes[filename] == 0:
2440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            raise error.TestFail('File \'%s\' has zero size.' % filename)
2540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
2640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    diff_bytes = int(utils.system_output('cmp -l %s %s | wc -l' % files))
2740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
2840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    return round(100. * diff_bytes / sizes[file1])
2940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
3040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
313dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedelclass graphics_VTSwitch(test.test):
323dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel    """
333dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel    Verify that VT switching works.
343dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel    """
3540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    version = 1
363dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel    GSC = None
3740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    # TODO(crosbug.com/36417): Need to handle more than one display screen.
3840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
393f219a2165c5233fa9a0e6d71e6dbdb3fe06d106Simon Que    def setup(self):
403f219a2165c5233fa9a0e6d71e6dbdb3fe06d106Simon Que        self.job.setup_dep(['gfxtest'])
413f219a2165c5233fa9a0e6d71e6dbdb3fe06d106Simon Que
423dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel    def initialize(self):
433dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        self.GSC = graphics_utils.GraphicsStateChecker()
443dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel
453dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel    def cleanup(self):
463dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        # Return to VT1 when done.  Ideally, the screen should already be in VT1
473dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        # but the test might fail and terminate while in VT2.
483dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        self._switch_to_vt(1)
493dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        if self.GSC:
503dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel            self.GSC.finalize()
513f219a2165c5233fa9a0e6d71e6dbdb3fe06d106Simon Que
5240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    def run_once(self,
5336eff4686c6781384f184cdff65510a5d6234429Simon Que                 num_iterations=2,
5440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                 similarity_percent_threshold=95,
5540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                 difference_percent_threshold=5):
56fd45dc71cfed09edcd30a39ffbd6a4cd441b89ceIlja H. Friedel        # TODO(ihf): Remove this once VTSwitch works on freon.
57fd45dc71cfed09edcd30a39ffbd6a4cd441b89ceIlja H. Friedel        if utils.is_freon():
58fd45dc71cfed09edcd30a39ffbd6a4cd441b89ceIlja H. Friedel            raise error.TestNAError(
59fd45dc71cfed09edcd30a39ffbd6a4cd441b89ceIlja H. Friedel                    'Test needs work on Freon. See crbug.com/413088.')
60fd45dc71cfed09edcd30a39ffbd6a4cd441b89ceIlja H. Friedel
6140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        self._num_errors = 0
6240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals = {}
6340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
6440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Make sure we start in VT1
6540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if not self._switch_to_vt(1):
6640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            raise error.TestFail('Could not switch to VT1')
6740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
6840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Take screenshot of sign-in screen.
6940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        logged_out_screenshot = self._take_current_vt_screenshot()
7040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
7140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals['num_iterations'] = num_iterations
7240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
7340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Go to VT2 and take a screenshot.
7440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if not self._switch_to_vt(2):
7540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            raise error.TestFail('Could not switch to VT2')
7640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        vt2_screenshot = self._take_current_vt_screenshot()
7740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
7840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Make sure VT1 and VT2 are sufficiently different.
7940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        diff = get_percent_difference(logged_out_screenshot, vt2_screenshot)
8040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals['percent_initial_VT1_VT2_difference'] = diff
8140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if not diff >= difference_percent_threshold:
8240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            self._num_errors += 1
8340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            logging.error('VT1 and VT2 screenshots only differ by ' + \
8440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                          '%d %%: %s vs %s' %
8540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                          (diff, logged_out_screenshot, vt2_screenshot))
8640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
8740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        num_identical_vt1_screenshots = 0
8840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        num_identical_vt2_screenshots = 0
8940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        max_vt1_difference_percent = 0
9040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        max_vt2_difference_percent = 0
9140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
9240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Repeatedly switch between VT1 and VT2.
9340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        for iteration in xrange(num_iterations):
943dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel            logging.info('Iteration #%d', iteration)
9540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
9640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            # Go to VT1 and take a screenshot.
9740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            self._switch_to_vt(1)
9840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            current_vt1_screenshot = self._take_current_vt_screenshot()
9940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
10040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            # Make sure the current VT1 screenshot is the same as (or similar
10140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            # to) the original login screen screenshot.
10240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            diff = get_percent_difference(logged_out_screenshot,
10340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                                          current_vt1_screenshot)
10440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            if not diff < similarity_percent_threshold:
10540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                max_vt1_difference_percent = \
10640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                    max(diff, max_vt1_difference_percent)
10740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                self._num_errors += 1
1083dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                logging.error('VT1 screenshots differ by %d %%: %s vs %s',
1093dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                              diff, logged_out_screenshot,
1103dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                              current_vt1_screenshot)
11140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            else:
11240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                num_identical_vt1_screenshots += 1
11340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
11440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            # Go to VT2 and take a screenshot.
11540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            self._switch_to_vt(2)
11640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            current_vt2_screenshot = self._take_current_vt_screenshot()
11740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
11840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            # Make sure the current VT2 screenshot is the same as (or similar
11940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            # to) the first VT2 screenshot.
12040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            diff = get_percent_difference(vt2_screenshot,
12140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                                          current_vt2_screenshot)
12240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            if not diff <= similarity_percent_threshold:
12340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                max_vt2_difference_percent = \
12440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                    max(diff, max_vt2_difference_percent)
12540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                self._num_errors += 1
12640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                logging.error(
1273dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                    'VT2 screenshots differ by %d %%: %s vs %s',
1283dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                    diff, vt2_screenshot, current_vt2_screenshot)
12940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            else:
13040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                num_identical_vt2_screenshots += 1
13140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
13240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        self._switch_to_vt(1)
13340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
13440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals['percent_VT1_screenshot_max_difference'] = \
13540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            max_vt1_difference_percent
13640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals['percent_VT2_screenshot_max_difference'] = \
13740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            max_vt2_difference_percent
13840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals['num_identical_vt1_screenshots'] = num_identical_vt1_screenshots
13940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        keyvals['num_identical_vt2_screenshots'] = num_identical_vt2_screenshots
14040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
14140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        self.write_perf_keyval(keyvals)
14240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
14340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if self._num_errors > 0:
14440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            raise error.TestError('Test failed with %d errors' %
14540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que                                  self._num_errors)
14640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
14740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
14840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    def _take_current_vt_screenshot(self):
14940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        """
150aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que        Captures a screenshot of the current VT screen in BMP format.
15140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        Returns the path of the screenshot file.
15240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        """
15340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        current_vt = int(utils.system_output('fgconsole'))
154aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que        extension = 'bmp'
15540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
1563dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        # In VT1, X is running so use that screenshot function.
15740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if current_vt == 1:
1583dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel            return graphics_utils.take_screenshot(self.resultsdir,
1593dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                                                  'graphics_VTSwitch_VT1',
1603dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                                                  extension)
16140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
16240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Otherwise, grab the framebuffer using DRM.
16340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        prefix = 'graphics_VTSwitch_VT2'
16440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        next_index = len(glob.glob(
16540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            os.path.join(self.resultsdir, '%s-*.%s' % (prefix, extension))))
166aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que        filename = '%s-%d.%s' % (prefix, next_index, extension)
16740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        output_path = os.path.join(self.resultsdir, filename)
16840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        return self._take_drm_screenshot(output_path)
16940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
17040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
17140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    def _take_drm_screenshot(self, output_path):
1723dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        """
1733dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        Takes drm screenshot.
1743dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        """
17540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        autotest_deps_path = os.path.join(self.autodir, 'deps')
17640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        getfb_path = os.path.join(autotest_deps_path, 'gfxtest', 'getfb')
177aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que        output = utils.system_output('%s %s.rgba' % (getfb_path, output_path))
1785de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que        for line in output.split('\n'):
1795de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que            # Parse the getfb output for info about framebuffer size.  The line
1805de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que            # should looks omething like:
1815de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que            #   Framebuffer info: 1024x768, 32bpp
1825de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que            if line.startswith('Framebuffer info:'):
1835de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que                size = line.split(':')[1].split(',')[0].strip()
1845de8c55bcd38b3317419098d9459e19f1d49a7f9Simon Que                break
185aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que        utils.system('convert -depth 8 -size %s %s.rgba %s' %
186aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que                     (size, output_path, output_path))
18740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
1883dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        logging.info('Saving screenshot to %s', output_path)
189aba1f72618fec4b790af63794f60062fe0c49cd9Simon Que        return output_path
19040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
19140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
19240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que    def _switch_to_vt(self, vt):
19340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        """
19440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        Switches to virtual terminal given by |vt| (1, 2, etc) and checks that
19540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        the switch was successful by calling fgconsole.
19640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
19740b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        Returns True if fgconsole returned the new vt number, False otherwise.
19840b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        """
19940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        utils.system_output('chvt %d' % vt)
20040b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que
20140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        # Verify that the VT switch was successful.
20240b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        current_vt = base_utils.wait_for_value(
20340b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            lambda: int(utils.system_output('fgconsole')),
20440b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            expected_value=vt)
20540b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        if vt != current_vt:
20640b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            self._num_errors += 1
2073dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel            logging.error('Current VT %d does not match expected VT %d',
2083dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel                          current_vt, vt)
20940b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que            return False
2103dab8e583cce64f14f728e656798c48ed2ea4143Ilja Friedel        logging.info('Switched to VT%d', vt)
21140b289060ac9d8bc97299dc6a6f7515fc326a13cSimon Que        return True
212