| # Copyright 2014 The Chromium 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 json |
| import logging |
| import os |
| from distutils.version import LooseVersion |
| from PIL import Image |
| |
| from common import cloud_bucket |
| from common import ispy_utils |
| |
| |
| class ISpyApi(object): |
| """The public API for interacting with ISpy.""" |
| |
| def __init__(self, cloud_bucket): |
| """Initializes the utility class. |
| |
| Args: |
| cloud_bucket: a BaseCloudBucket in which to the version file, |
| expectations and results are to be stored. |
| """ |
| self._cloud_bucket = cloud_bucket |
| self._ispy = ispy_utils.ISpyUtils(self._cloud_bucket) |
| self._rebaselineable_cache = {} |
| |
| def UpdateExpectationVersion(self, chrome_version, version_file): |
| """Updates the most recent expectation version to the Chrome version. |
| |
| Should be called after generating a new set of expectations. |
| |
| Args: |
| chrome_version: the chrome version as a string of the form "31.0.123.4". |
| version_file: path to the version file in the cloud bucket. The version |
| file contains a json list of ordered Chrome versions for which |
| expectations exist. |
| """ |
| insert_pos = 0 |
| expectation_versions = [] |
| try: |
| expectation_versions = self._GetExpectationVersionList(version_file) |
| if expectation_versions: |
| try: |
| version = self._GetExpectationVersion( |
| chrome_version, expectation_versions) |
| if version == chrome_version: |
| return |
| insert_pos = expectation_versions.index(version) |
| except: |
| insert_pos = len(expectation_versions) |
| except cloud_bucket.FileNotFoundError: |
| pass |
| expectation_versions.insert(insert_pos, chrome_version) |
| logging.info('Updating expectation version...') |
| self._cloud_bucket.UploadFile( |
| version_file, json.dumps(expectation_versions), |
| 'application/json') |
| |
| def _GetExpectationVersion(self, chrome_version, expectation_versions): |
| """Returns the expectation version for the given Chrome version. |
| |
| Args: |
| chrome_version: the chrome version as a string of the form "31.0.123.4". |
| expectation_versions: Ordered list of Chrome versions for which |
| expectations exist, as stored in the version file. |
| |
| Returns: |
| Expectation version string. |
| """ |
| # Find the closest version that is not greater than the chrome version. |
| for version in expectation_versions: |
| if LooseVersion(version) <= LooseVersion(chrome_version): |
| return version |
| raise Exception('No expectation exists for Chrome %s' % chrome_version) |
| |
| def _GetExpectationVersionList(self, version_file): |
| """Gets the list of expectation versions from google storage. |
| |
| Args: |
| version_file: path to the version file in the cloud bucket. The version |
| file contains a json list of ordered Chrome versions for which |
| expectations exist. |
| |
| Returns: |
| Ordered list of Chrome versions. |
| """ |
| try: |
| return json.loads(self._cloud_bucket.DownloadFile(version_file)) |
| except: |
| return [] |
| |
| def _GetExpectationNameWithVersion(self, device_type, expectation, |
| chrome_version, version_file): |
| """Get the expectation to be used with the current Chrome version. |
| |
| Args: |
| device_type: string identifier for the device type. |
| expectation: name for the expectation to generate. |
| chrome_version: the chrome version as a string of the form "31.0.123.4". |
| |
| Returns: |
| Version as an integer. |
| """ |
| version = self._GetExpectationVersion( |
| chrome_version, self._GetExpectationVersionList(version_file)) |
| return self._CreateExpectationName(device_type, expectation, version) |
| |
| def _CreateExpectationName(self, device_type, expectation, version): |
| """Create the full expectation name from the expectation and version. |
| |
| Args: |
| device_type: string identifier for the device type, example: mako |
| expectation: base name for the expectation, example: google.com |
| version: expectation version, example: 31.0.23.1 |
| |
| Returns: |
| Full expectation name as a string, example: mako:google.com(31.0.23.1) |
| """ |
| return '%s:%s(%s)' % (device_type, expectation, version) |
| |
| def GenerateExpectation(self, device_type, expectation, chrome_version, |
| version_file, screenshots): |
| """Create an expectation for I-Spy. |
| |
| Args: |
| device_type: string identifier for the device type. |
| expectation: name for the expectation to generate. |
| chrome_version: the chrome version as a string of the form "31.0.123.4". |
| screenshots: a list of similar PIL.Images. |
| """ |
| # https://code.google.com/p/chromedriver/issues/detail?id=463 |
| expectation_with_version = self._CreateExpectationName( |
| device_type, expectation, chrome_version) |
| if self._ispy.ExpectationExists(expectation_with_version): |
| logging.warning( |
| 'I-Spy expectation \'%s\' already exists, overwriting.', |
| expectation_with_version) |
| logging.info('Generating I-Spy expectation...') |
| self._ispy.GenerateExpectation(expectation_with_version, screenshots) |
| |
| def PerformComparison(self, test_run, device_type, expectation, |
| chrome_version, version_file, screenshot): |
| """Compare a screenshot with the given expectation in I-Spy. |
| |
| Args: |
| test_run: name for the test run. |
| device_type: string identifier for the device type. |
| expectation: name for the expectation to compare against. |
| chrome_version: the chrome version as a string of the form "31.0.123.4". |
| screenshot: a PIL.Image to compare. |
| """ |
| # https://code.google.com/p/chromedriver/issues/detail?id=463 |
| logging.info('Performing I-Spy comparison...') |
| self._ispy.PerformComparison( |
| test_run, |
| self._GetExpectationNameWithVersion( |
| device_type, expectation, chrome_version, version_file), |
| screenshot) |
| |
| def CanRebaselineToTestRun(self, test_run): |
| """Returns whether the test run has associated expectations. |
| |
| Returns: |
| True if RebaselineToTestRun() can be called for this test run. |
| """ |
| if test_run in self._rebaselineable_cache: |
| return True |
| return self._cloud_bucket.FileExists( |
| ispy_utils.GetTestRunPath(test_run, 'rebaseline.txt')) |
| |
| def RebaselineToTestRun(self, test_run): |
| """Update the version file to use expectations associated with |test_run|. |
| |
| Args: |
| test_run: The name of the test run to rebaseline. |
| """ |
| rebaseline_path = ispy_utils.GetTestRunPath(test_run, 'rebaseline.txt') |
| rebaseline_attrib = json.loads( |
| self._cloud_bucket.DownloadFile(rebaseline_path)) |
| self.UpdateExpectationVersion( |
| rebaseline_attrib['version'], rebaseline_attrib['version_file']) |
| self._cloud_bucket.RemoveFile(rebaseline_path) |
| |
| def _SetTestRunRebaselineable(self, test_run, chrome_version, version_file): |
| """Writes a JSON file containing the data needed to rebaseline. |
| |
| Args: |
| test_run: The name of the test run to add the rebaseline file to. |
| chrome_version: the chrome version that can be rebaselined to (must have |
| associated Expectations). |
| version_file: the path of the version file associated with the test run. |
| """ |
| self._rebaselineable_cache[test_run] = True |
| self._cloud_bucket.UploadFile( |
| ispy_utils.GetTestRunPath(test_run, 'rebaseline.txt'), |
| json.dumps({ |
| 'version': chrome_version, |
| 'version_file': version_file}), |
| 'application/json') |
| |
| def PerformComparisonAndPrepareExpectation(self, test_run, device_type, |
| expectation, chrome_version, |
| version_file, screenshots): |
| """Perform comparison and generate an expectation that can used later. |
| |
| The test run web UI will have a button to set the Expectations generated for |
| this version as the expectation for comparison with later versions. |
| |
| Args: |
| test_run: The name of the test run to add the rebaseline file to. |
| device_type: string identifier for the device type. |
| chrome_version: the chrome version that can be rebaselined to (must have |
| associated Expectations). |
| version_file: the path of the version file associated with the test run. |
| screenshot: a list of similar PIL.Images. |
| """ |
| if not self.CanRebaselineToTestRun(test_run): |
| self._SetTestRunRebaselineable(test_run, chrome_version, version_file) |
| expectation_with_version = self._CreateExpectationName( |
| device_type, expectation, chrome_version) |
| self._ispy.GenerateExpectation(expectation_with_version, screenshots) |
| self._ispy.PerformComparison( |
| test_run, |
| self._GetExpectationNameWithVersion( |
| device_type, expectation, chrome_version, version_file), |
| screenshots[-1]) |
| |