普通文本  |  196行  |  6.71 KB

# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import abc
import datetime
import os
import urllib2

from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error, file_utils, lsbrelease_utils
from autotest_lib.client.cros import constants
from autotest_lib.client.cros.image_comparison import image_comparison_factory
from PIL import Image
from PIL import ImageDraw

class ui_TestBase(test.test):
    """ Encapsulates steps needed to collect screenshots for ui pieces.

    Each child class must implement:
    1. Abstract method capture_screenshot()
    Each child class will define its own custom way of capturing the screenshot
    of the piece it cares about.

    E.g Child class ui_SystemTray will capture system tray screenshot,
    ui_SettingsPage for the Chrome Settings page, etc.

    2. Abstract property test_area:
    This will get appended to screenshot file names so we know what image it is.

    Flow at runtime:
    At run time, autotest will call run_once() method on a particular child
    class object, call it Y.

    Say X is a parent of Y.

    Y.run_once() will save any values passed from control file so as to use them
    later.

    Y.run_once() will then call the parent's X.run_screenshot_comparison_test()

    This is the template algorithm for collecting screenshots.

    Y.run_screenshot_comparison_test will execute its steps. It will then call
    X.test_area to get custom string to use for project name and filename.

     It will execute more steps and then call capture_screenshot(). X doesn't
     implement that, but Y does, so the method will get called on Y to produce
     Y's custom behavior.

     Control will be returned to Y run_screenshot_comparison_test() which will
     execute remainder steps.

    """

    __metaclass__ = abc.ABCMeta

    WORKING_DIR = '/tmp/test'
    REMOTE_DIR = 'http://storage.googleapis.com/chromiumos-test-assets-public'
    AUTOTEST_CROS_UI_DIR = '/usr/local/autotest/cros/ui'
    IMG_COMP_CONF_FILE = 'image_comparison.conf'

    version = 2


    def run_screenshot_comparison_test(self):
        """
        Template method to run screenshot comparison tests for ui pieces.

        1. Set up test dirs.
        2. Create folder name
        3. Download golden image.
        4. Capture test image.
        5. Compare images locally, if FAIL upload to remote for analysis later.
        6. Clean up test dirs.

        """

        img_comp_conf_path = os.path.join(ui_TestBase.AUTOTEST_CROS_UI_DIR,
                                          ui_TestBase.IMG_COMP_CONF_FILE)

        img_comp_factory = image_comparison_factory.ImageComparisonFactory(
                img_comp_conf_path)

        golden_image_local_dir = os.path.join(ui_TestBase.WORKING_DIR,
                                              'golden_images')

        file_utils.make_leaf_dir(golden_image_local_dir)

        filename = '%s.png' % self.tagged_testname

        golden_image_remote_path = os.path.join(
                ui_TestBase.REMOTE_DIR,
                'ui',
                lsbrelease_utils.get_chrome_milestone(),
                self.folder_name,
                filename)

        golden_image_local_path = os.path.join(golden_image_local_dir, filename)

        test_image_filepath = os.path.join(ui_TestBase.WORKING_DIR, filename)

        try:
            file_utils.download_file(golden_image_remote_path,
                                     golden_image_local_path)
        except urllib2.HTTPError as e:
            warn = "No screenshot found for {0} on milestone {1}. ".format(
                self.tagged_testname, lsbrelease_utils.get_chrome_milestone())
            warn += e.msg
            raise error.TestWarn(warn)

        self.capture_screenshot(test_image_filepath)



        comparer = img_comp_factory.make_pdiff_comparer()
        comp_res = comparer.compare(golden_image_local_path,
                                    test_image_filepath)

        if comp_res.diff_pixel_count > img_comp_factory.pixel_thres:
            publisher = img_comp_factory.make_imagediff_publisher(
                    self.resultsdir)

            # get chrome version
            version_string = utils.system_output(
                constants.CHROME_VERSION_COMMAND, ignore_status=True)
            version_string = utils.parse_chrome_version(version_string)[0]

            # tags for publishing
            tags = {
                'testname': self.tagged_testname,
                'chromeos_version': utils.get_chromeos_release_version(),
                'chrome_version': version_string,
                'board':  utils.get_board(),
                'date': datetime.date.today().strftime("%m/%d/%y"),
                'diff_pixels': comp_res.diff_pixel_count
            }

            publisher.publish(golden_image_local_path,
                                    test_image_filepath,
                                    comp_res.pdiff_image_path, tags)

            raise error.TestFail('Test Failed. Please see image comparison '
                                 'result by opening index.html from the '
                                 'results directory.')

        file_utils.rm_dir_if_exists(ui_TestBase.WORKING_DIR)


    @property
    def folder_name(self):
        """
        Computes the folder name to look for golden images in
        based on the current test area.

        If we have tagged our testcase, it removes the tag to
        get the base testname.

        E.g if we add the tag 'guest' to the ui_SystemTray class,
        the tagged test name will be ui_SystemTray.guest

        This removes the tag if it was added
        """

        return self.tagged_testname.split('.')[0]


    @abc.abstractmethod
    def capture_screenshot(self, filepath):
        """
        Abstract method to capture a screenshot.
        Child classes must implement a custom way to take screenshots.
        This is because each will want to crop to different areas of the screen.

        @param filepath: string, complete path to save the screenshot.

        """
        pass

    def draw_image_mask(self, filepath, rectangle, fill='white'):
        """
        Used to draw a mask over selected portions of the captured screenshot.
        This allows us to mask out things that change between runs while
        letting us focus on the parts we do care about.

        @param filepath: string, the complete path to the image
        @param rectangle: tuple, the top left and bottom right coordinates
        @param fill: string, the color to fill the mask with

        """

        im = Image.open(filepath)
        draw = ImageDraw.Draw(im)
        draw.rectangle(rectangle, fill=fill)
        im.save(filepath)