diff options
author | bsalomon <bsalomon@google.com> | 2015-01-16 12:08:52 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-16 12:08:54 -0800 |
commit | 15b125d40122e966bd723d23e82c3224b1da4898 (patch) | |
tree | 8acf6fcd9bc9146680e8ca09047f5cbf210dc09e /gm/rebaseline_server | |
parent | f7094c4ed01d500f31b993ed2620c2737093e383 (diff) |
delete old things!
NOTREECHECKS=true
Review URL: https://codereview.chromium.org/855003006
Diffstat (limited to 'gm/rebaseline_server')
99 files changed, 0 insertions, 11895 deletions
diff --git a/gm/rebaseline_server/__init__.py b/gm/rebaseline_server/__init__.py deleted file mode 100644 index 02d71e90af..0000000000 --- a/gm/rebaseline_server/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Needed so that test_all.py will recurse into this directory. diff --git a/gm/rebaseline_server/base_unittest.py b/gm/rebaseline_server/base_unittest.py deleted file mode 100755 index b8a653866b..0000000000 --- a/gm/rebaseline_server/base_unittest.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -A wrapper around the standard Python unittest library, adding features we need -for various unittests within this directory. -""" - -# System-level imports. -import os -import sys - -PARENT_DIR = os.path.abspath(os.path.dirname(__file__)) -TRUNK_DIR = os.path.abspath(os.path.join(PARENT_DIR, os.pardir, os.pardir)) - -# Import the superclass base_unittest module from the tools dir. -# -# TODO(epoger): If I don't put this at the beginning of sys.path, the import of -# tests.base_unittest fails. That's bad. I need to come up with a cleaner way -# of doing this... I think this will involve changing how we import the "boto" -# library in gs_utils.py, within the common repo. -TOOLS_DIR = os.path.join(TRUNK_DIR, 'tools') -if TOOLS_DIR != sys.path[0]: - sys.path.insert(0, TOOLS_DIR) -import tests.base_unittest as superclass_module - - -class TestCase(superclass_module.TestCase): - - def __init__(self, *args, **kwargs): - super(TestCase, self).__init__(*args, **kwargs) - # Some of the tests within this package want their output validated, - # so we declare where the expected and actual output will be. - self._testdata_dir = os.path.join(PARENT_DIR, 'testdata') - -def main(*args, **kwargs): - superclass_module.main(*args, **kwargs) diff --git a/gm/rebaseline_server/column.py b/gm/rebaseline_server/column.py deleted file mode 100644 index 1b9d0bf1c5..0000000000 --- a/gm/rebaseline_server/column.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -ColumnHeaderFactory class (see class docstring for details) -""" - -# Keys used within dictionary representation of each column header. -# NOTE: Keep these in sync with static/constants.js -KEY__EXTRACOLUMNHEADERS__HEADER_TEXT = 'headerText' -KEY__EXTRACOLUMNHEADERS__HEADER_URL = 'headerUrl' -KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE = 'isFilterable' -KEY__EXTRACOLUMNHEADERS__IS_SORTABLE = 'isSortable' -KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER = 'useFreeformFilter' -KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS = 'valuesAndCounts' - - -class ColumnHeaderFactory(object): - """Factory which assembles the header for a single column of data.""" - - def __init__(self, header_text, header_url=None, - is_filterable=True, is_sortable=True, - use_freeform_filter=False): - """ - Args: - header_text: string; text the client should display within column header. - header_url: string; target URL if user clicks on column header. - If None, nothing to click on. - is_filterable: boolean; whether client should allow filtering on this - column. - is_sortable: boolean; whether client should allow sorting on this column. - use_freeform_filter: boolean; *recommendation* to the client indicating - whether to allow freeform text matching, as opposed to listing all - values alongside checkboxes. If is_filterable==false, this is - meaningless. - """ - self._header_text = header_text - self._header_url = header_url - self._is_filterable = is_filterable - self._is_sortable = is_sortable - self._use_freeform_filter = use_freeform_filter - - def create_as_dict(self, values_and_counts_dict=None): - """Creates the header for this column, in dictionary form. - - Creates the header for this column in dictionary form, as needed when - constructing the JSON representation. Uses the KEY__EXTRACOLUMNHEADERS__* - constants as keys. - - Args: - values_and_counts_dict: dictionary mapping each possible column value - to its count (how many entries in the column have this value), or - None if this information is not available. - """ - asdict = { - KEY__EXTRACOLUMNHEADERS__HEADER_TEXT: self._header_text, - KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE: self._is_filterable, - KEY__EXTRACOLUMNHEADERS__IS_SORTABLE: self._is_sortable, - KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER: self._use_freeform_filter, - } - if self._header_url: - asdict[KEY__EXTRACOLUMNHEADERS__HEADER_URL] = self._header_url - if values_and_counts_dict: - asdict[KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS] = sorted( - values_and_counts_dict.items()) - return asdict diff --git a/gm/rebaseline_server/compare_configs.py b/gm/rebaseline_server/compare_configs.py deleted file mode 100755 index 36c7f86530..0000000000 --- a/gm/rebaseline_server/compare_configs.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Compare GM results for two configs, across all builders. -""" - -# System-level imports -import argparse -import logging -import time - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import url_utils -import gm_json -import imagediffdb -import imagepair -import imagepairset -import results - - -class ConfigComparisons(results.BaseComparisons): - """Loads results from two different configurations into an ImagePairSet. - - Loads actual and expected results from all builders, except for those skipped - by _ignore_builder(). - """ - - def __init__(self, configs, actuals_root=results.DEFAULT_ACTUALS_DIR, - generated_images_root=results.DEFAULT_GENERATED_IMAGES_ROOT, - diff_base_url=None, builder_regex_list=None): - """ - Args: - configs: (string, string) tuple; pair of configs to compare - actuals_root: root directory containing all actual-results.json files - generated_images_root: directory within which to create all pixel diffs; - if this directory does not yet exist, it will be created - diff_base_url: base URL within which the client should look for diff - images; if not specified, defaults to a "file:///" URL representation - of generated_images_root - builder_regex_list: List of regular expressions specifying which builders - we will process. If None, process all builders. - """ - super(ConfigComparisons, self).__init__() - time_start = int(time.time()) - if builder_regex_list != None: - self.set_match_builders_pattern_list(builder_regex_list) - self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root) - self._diff_base_url = ( - diff_base_url or - url_utils.create_filepath_url(generated_images_root)) - self._actuals_root = actuals_root - self._load_config_pairs(configs) - self._timestamp = int(time.time()) - logging.info('Results complete; took %d seconds.' % - (self._timestamp - time_start)) - - def _load_config_pairs(self, configs): - """Loads the results of all tests, across all builders (based on the - files within self._actuals_root), compares them across two configs, - and stores the summary in self._results. - - Args: - configs: tuple of strings; pair of configs to compare - """ - logging.info('Reading actual-results JSON files from %s...' % - self._actuals_root) - actual_builder_dicts = self._read_builder_dicts_from_root( - self._actuals_root) - configA, configB = configs - logging.info('Comparing configs %s and %s...' % (configA, configB)) - - all_image_pairs = imagepairset.ImagePairSet( - descriptions=configs, - diff_base_url=self._diff_base_url) - failing_image_pairs = imagepairset.ImagePairSet( - descriptions=configs, - diff_base_url=self._diff_base_url) - - all_image_pairs.ensure_extra_column_values_in_summary( - column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ - results.KEY__RESULT_TYPE__FAILED, - results.KEY__RESULT_TYPE__NOCOMPARISON, - results.KEY__RESULT_TYPE__SUCCEEDED, - ]) - failing_image_pairs.ensure_extra_column_values_in_summary( - column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ - results.KEY__RESULT_TYPE__FAILED, - results.KEY__RESULT_TYPE__NOCOMPARISON, - ]) - - builders = sorted(actual_builder_dicts.keys()) - num_builders = len(builders) - builder_num = 0 - for builder in builders: - builder_num += 1 - logging.info('Generating pixel diffs for builder #%d of %d, "%s"...' % - (builder_num, num_builders, builder)) - actual_results_for_this_builder = ( - actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS]) - for result_type in sorted(actual_results_for_this_builder.keys()): - results_of_this_type = actual_results_for_this_builder[result_type] - if not results_of_this_type: - continue - - tests_found = set() - for image_name in sorted(results_of_this_type.keys()): - (test, _) = results.IMAGE_FILENAME_RE.match(image_name).groups() - tests_found.add(test) - - for test in tests_found: - # Get image_relative_url (or None) for each of configA, configB - image_name_A = results.IMAGE_FILENAME_FORMATTER % (test, configA) - configA_image_relative_url = ConfigComparisons._create_relative_url( - hashtype_and_digest=results_of_this_type.get(image_name_A), - test_name=test) - image_name_B = results.IMAGE_FILENAME_FORMATTER % (test, configB) - configB_image_relative_url = ConfigComparisons._create_relative_url( - hashtype_and_digest=results_of_this_type.get(image_name_B), - test_name=test) - - # If we have images for at least one of these two configs, - # add them to our list. - if configA_image_relative_url or configB_image_relative_url: - if configA_image_relative_url == configB_image_relative_url: - result_type = results.KEY__RESULT_TYPE__SUCCEEDED - elif not configA_image_relative_url: - result_type = results.KEY__RESULT_TYPE__NOCOMPARISON - elif not configB_image_relative_url: - result_type = results.KEY__RESULT_TYPE__NOCOMPARISON - else: - result_type = results.KEY__RESULT_TYPE__FAILED - - extra_columns_dict = { - results.KEY__EXTRACOLUMNS__RESULT_TYPE: result_type, - results.KEY__EXTRACOLUMNS__BUILDER: builder, - results.KEY__EXTRACOLUMNS__TEST: test, - # TODO(epoger): Right now, the client UI crashes if it receives - # results that do not include a 'config' column. - # Until we fix that, keep the client happy. - results.KEY__EXTRACOLUMNS__CONFIG: 'TODO', - } - - try: - image_pair = imagepair.ImagePair( - image_diff_db=self._image_diff_db, - imageA_base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL, - imageB_base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL, - imageA_relative_url=configA_image_relative_url, - imageB_relative_url=configB_image_relative_url, - extra_columns=extra_columns_dict) - all_image_pairs.add_image_pair(image_pair) - if result_type != results.KEY__RESULT_TYPE__SUCCEEDED: - failing_image_pairs.add_image_pair(image_pair) - except (KeyError, TypeError): - logging.exception( - 'got exception while creating ImagePair for test ' - '"%s", builder "%s"' % (test, builder)) - - # pylint: disable=W0201 - self._results = { - results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(), - results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(), - } - - -def main(): - logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - datefmt='%m/%d/%Y %H:%M:%S', - level=logging.INFO) - parser = argparse.ArgumentParser() - parser.add_argument( - '--actuals', default=results.DEFAULT_ACTUALS_DIR, - help='Directory containing all actual-result JSON files; defaults to ' - '\'%(default)s\' .') - parser.add_argument( - 'config', nargs=2, - help='Two configurations to compare (8888, gpu, etc.).') - parser.add_argument( - '--outfile', required=True, - help='File to write result summary into, in JSON format.') - parser.add_argument( - '--results', default=results.KEY__HEADER__RESULTS_FAILURES, - help='Which result types to include. Defaults to \'%(default)s\'; ' - 'must be one of ' + - str([results.KEY__HEADER__RESULTS_FAILURES, - results.KEY__HEADER__RESULTS_ALL])) - parser.add_argument( - '--workdir', default=results.DEFAULT_GENERATED_IMAGES_ROOT, - help='Directory within which to download images and generate diffs; ' - 'defaults to \'%(default)s\' .') - args = parser.parse_args() - results_obj = ConfigComparisons(configs=args.config, - actuals_root=args.actuals, - generated_images_root=args.workdir) - gm_json.WriteToFile( - results_obj.get_packaged_results_of_type(results_type=args.results), - args.outfile) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/compare_configs_test.py b/gm/rebaseline_server/compare_configs_test.py deleted file mode 100755 index 612be99837..0000000000 --- a/gm/rebaseline_server/compare_configs_test.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test compare_configs.py - -TODO(epoger): Create a command to update the expected results (in -self._output_dir_expected) when appropriate. For now, you should: -1. examine the results in self.output_dir_actual and make sure they are ok -2. rm -rf self._output_dir_expected -3. mv self.output_dir_actual self._output_dir_expected -Although, if you're using an SVN checkout, this will blow away .svn directories -within self._output_dir_expected, which wouldn't be good... - -""" - -# System-level imports -import os - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -import base_unittest -import compare_configs -import gm_json -import results - - -class CompareConfigsTest(base_unittest.TestCase): - - def test_gm(self): - """Process results of a GM run with the ConfigComparisons object.""" - results_obj = compare_configs.ConfigComparisons( - configs=('8888', 'gpu'), - actuals_root=os.path.join(self.input_dir, 'gm-actuals'), - generated_images_root=self.temp_dir, - diff_base_url='/static/generated-images') - results_obj.get_timestamp = mock_get_timestamp - gm_json.WriteToFile( - results_obj.get_packaged_results_of_type( - results.KEY__HEADER__RESULTS_ALL), - os.path.join(self.output_dir_actual, 'gm.json')) - - -def mock_get_timestamp(): - """Mock version of BaseComparisons.get_timestamp() for testing.""" - return 12345678 - - -def main(): - base_unittest.main(CompareConfigsTest) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/compare_rendered_pictures.py b/gm/rebaseline_server/compare_rendered_pictures.py deleted file mode 100755 index 73cb36b430..0000000000 --- a/gm/rebaseline_server/compare_rendered_pictures.py +++ /dev/null @@ -1,504 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Compare results of two render_pictures runs. - -TODO(epoger): Start using this module to compare ALL images (whether they -were generated from GMs or SKPs), and rename it accordingly. -""" - -# System-level imports -import logging -import os -import shutil -import subprocess -import tempfile -import time - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import git_utils -from py.utils import gs_utils -from py.utils import url_utils -import buildbot_globals -import column -import gm_json -import imagediffdb -import imagepair -import imagepairset -import results - -# URL under which all render_pictures images can be found in Google Storage. -# -# TODO(epoger): In order to allow live-view of GMs and other images, read this -# from the input summary files, or allow the caller to set it within the -# GET_live_results call. -DEFAULT_IMAGE_BASE_GS_URL = 'gs://' + buildbot_globals.Get('skp_images_bucket') - -# Column descriptors, and display preferences for them. -COLUMN__RESULT_TYPE = results.KEY__EXTRACOLUMNS__RESULT_TYPE -COLUMN__SOURCE_SKP = 'sourceSkpFile' -COLUMN__TILED_OR_WHOLE = 'tiledOrWhole' -COLUMN__TILENUM = 'tilenum' -COLUMN__BUILDER_A = 'builderA' -COLUMN__RENDER_MODE_A = 'renderModeA' -COLUMN__BUILDER_B = 'builderB' -COLUMN__RENDER_MODE_B = 'renderModeB' -# Known values for some of those columns. -COLUMN__TILED_OR_WHOLE__TILED = 'tiled' -COLUMN__TILED_OR_WHOLE__WHOLE = 'whole' - -FREEFORM_COLUMN_IDS = [ - COLUMN__SOURCE_SKP, - COLUMN__TILENUM, -] -ORDERED_COLUMN_IDS = [ - COLUMN__RESULT_TYPE, - COLUMN__SOURCE_SKP, - COLUMN__TILED_OR_WHOLE, - COLUMN__TILENUM, - COLUMN__BUILDER_A, - COLUMN__RENDER_MODE_A, - COLUMN__BUILDER_B, - COLUMN__RENDER_MODE_B, -] - -# A special "repo:" URL type that we use to refer to Skia repo contents. -# (Useful for comparing against expectations files we store in our repo.) -REPO_URL_PREFIX = 'repo:' -REPO_BASEPATH = os.path.abspath(os.path.join( - os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)) - -# Which sections within a JSON summary file can contain results. -ALLOWED_SECTION_NAMES = [ - gm_json.JSONKEY_ACTUALRESULTS, - gm_json.JSONKEY_EXPECTEDRESULTS, -] - - -class RenderedPicturesComparisons(results.BaseComparisons): - """Loads results from multiple render_pictures runs into an ImagePairSet. - """ - - def __init__(self, - setA_dir, setB_dir, - setA_section, setB_section, - image_diff_db, - image_base_gs_url=DEFAULT_IMAGE_BASE_GS_URL, diff_base_url=None, - setA_label=None, setB_label=None, - gs=None, truncate_results=False, prefetch_only=False, - download_all_images=False): - """Constructor: downloads images and generates diffs. - - Once the object has been created (which may take a while), you can call its - get_packaged_results_of_type() method to quickly retrieve the results... - unless you have set prefetch_only to True, in which case we will - asynchronously warm up the ImageDiffDB cache but not fill in self._results. - - Args: - setA_dir: root directory to copy all JSON summaries from, and to use as - setA within the comparisons. This directory may be specified as a - gs:// URL, special "repo:" URL, or local filepath. - setB_dir: root directory to copy all JSON summaries from, and to use as - setB within the comparisons. This directory may be specified as a - gs:// URL, special "repo:" URL, or local filepath. - setA_section: which section within setA to examine; must be one of - ALLOWED_SECTION_NAMES - setB_section: which section within setB to examine; must be one of - ALLOWED_SECTION_NAMES - image_diff_db: ImageDiffDB instance - image_base_gs_url: "gs://" URL pointing at the Google Storage bucket/dir - under which all render_pictures result images can - be found; this will be used to read images for comparison within - this code, and included in the ImagePairSet (as an HTTP URL) so its - consumers know where to download the images from - diff_base_url: base URL within which the client should look for diff - images; if not specified, defaults to a "file:///" URL representation - of image_diff_db's storage_root - setA_label: description to use for results in setA; if None, will be - set to a reasonable default - setB_label: description to use for results in setB; if None, will be - set to a reasonable default - gs: instance of GSUtils object we can use to download summary files - truncate_results: FOR MANUAL TESTING: if True, truncate the set of images - we process, to speed up testing. - prefetch_only: if True, return the new object as quickly as possible - with empty self._results (just queue up all the files to process, - don't wait around for them to be processed and recorded); otherwise, - block until the results have been assembled and recorded in - self._results. - download_all_images: if True, download all images, even if we don't - need them to generate diffs. This will take much longer to complete, - but is useful for warming up the bitmap cache on local disk. - """ - super(RenderedPicturesComparisons, self).__init__() - self._image_diff_db = image_diff_db - self._image_base_gs_url = image_base_gs_url - self._diff_base_url = ( - diff_base_url or - url_utils.create_filepath_url(image_diff_db.storage_root)) - self._gs = gs - self.truncate_results = truncate_results - self._prefetch_only = prefetch_only - self._download_all_images = download_all_images - - # If we are comparing two different section types, we can use those - # as the default labels for setA and setB. - if setA_section != setB_section: - self._setA_label = setA_label or setA_section - self._setB_label = setB_label or setB_section - else: - self._setA_label = setA_label or 'setA' - self._setB_label = setB_label or 'setB' - - tempdir = tempfile.mkdtemp() - try: - setA_root = os.path.join(tempdir, 'setA') - setB_root = os.path.join(tempdir, 'setB') - # TODO(stephana): There is a potential race condition here... we copy - # the contents out of the source_dir, and THEN we get the commithash - # of source_dir. If source_dir points at a git checkout, and that - # checkout is updated (by a different thread/process) during this - # operation, then the contents and commithash will be out of sync. - self._copy_dir_contents(source_dir=setA_dir, dest_dir=setA_root) - setA_repo_revision = self._get_repo_revision(source_dir=setA_dir) - self._copy_dir_contents(source_dir=setB_dir, dest_dir=setB_root) - setB_repo_revision = self._get_repo_revision(source_dir=setB_dir) - - self._setA_descriptions = { - results.KEY__SET_DESCRIPTIONS__DIR: setA_dir, - results.KEY__SET_DESCRIPTIONS__REPO_REVISION: setA_repo_revision, - results.KEY__SET_DESCRIPTIONS__SECTION: setA_section, - } - self._setB_descriptions = { - results.KEY__SET_DESCRIPTIONS__DIR: setB_dir, - results.KEY__SET_DESCRIPTIONS__REPO_REVISION: setB_repo_revision, - results.KEY__SET_DESCRIPTIONS__SECTION: setB_section, - } - - time_start = int(time.time()) - self._results = self._load_result_pairs( - setA_root=setA_root, setB_root=setB_root, - setA_section=setA_section, setB_section=setB_section) - if self._results: - self._timestamp = int(time.time()) - logging.info('Number of download file collisions: %s' % - imagediffdb.global_file_collisions) - logging.info('Results complete; took %d seconds.' % - (self._timestamp - time_start)) - finally: - shutil.rmtree(tempdir) - - def _load_result_pairs(self, setA_root, setB_root, - setA_section, setB_section): - """Loads all JSON image summaries from 2 directory trees and compares them. - - TODO(stephana): This method is only called from within __init__(); it might - make more sense to just roll the content of this method into __init__(). - - Args: - setA_root: root directory containing JSON summaries of rendering results - setB_root: root directory containing JSON summaries of rendering results - setA_section: which section (gm_json.JSONKEY_ACTUALRESULTS or - gm_json.JSONKEY_EXPECTEDRESULTS) to load from the summaries in setA - setB_section: which section (gm_json.JSONKEY_ACTUALRESULTS or - gm_json.JSONKEY_EXPECTEDRESULTS) to load from the summaries in setB - - Returns the summary of all image diff results (or None, depending on - self._prefetch_only). - """ - logging.info('Reading JSON image summaries from dirs %s and %s...' % ( - setA_root, setB_root)) - setA_dicts = self.read_dicts_from_root(setA_root) - setB_dicts = self.read_dicts_from_root(setB_root) - logging.info('Comparing summary dicts...') - - all_image_pairs = imagepairset.ImagePairSet( - descriptions=(self._setA_label, self._setB_label), - diff_base_url=self._diff_base_url) - failing_image_pairs = imagepairset.ImagePairSet( - descriptions=(self._setA_label, self._setB_label), - diff_base_url=self._diff_base_url) - - # Override settings for columns that should be filtered using freeform text. - for column_id in FREEFORM_COLUMN_IDS: - factory = column.ColumnHeaderFactory( - header_text=column_id, use_freeform_filter=True) - all_image_pairs.set_column_header_factory( - column_id=column_id, column_header_factory=factory) - failing_image_pairs.set_column_header_factory( - column_id=column_id, column_header_factory=factory) - - all_image_pairs.ensure_extra_column_values_in_summary( - column_id=COLUMN__RESULT_TYPE, values=[ - results.KEY__RESULT_TYPE__FAILED, - results.KEY__RESULT_TYPE__NOCOMPARISON, - results.KEY__RESULT_TYPE__SUCCEEDED, - ]) - failing_image_pairs.ensure_extra_column_values_in_summary( - column_id=COLUMN__RESULT_TYPE, values=[ - results.KEY__RESULT_TYPE__FAILED, - results.KEY__RESULT_TYPE__NOCOMPARISON, - ]) - - logging.info('Starting to add imagepairs to queue.') - self._image_diff_db.log_queue_size_if_changed(limit_verbosity=False) - - union_dict_paths = sorted(set(setA_dicts.keys() + setB_dicts.keys())) - num_union_dict_paths = len(union_dict_paths) - dict_num = 0 - for dict_path in union_dict_paths: - dict_num += 1 - logging.info( - 'Asynchronously requesting pixel diffs for dict #%d of %d, "%s"...' % - (dict_num, num_union_dict_paths, dict_path)) - - dictA = self.get_default(setA_dicts, None, dict_path) - self._validate_dict_version(dictA) - dictA_results = self.get_default(dictA, {}, setA_section) - - dictB = self.get_default(setB_dicts, None, dict_path) - self._validate_dict_version(dictB) - dictB_results = self.get_default(dictB, {}, setB_section) - - image_A_base_url = self.get_default( - setA_dicts, self._image_base_gs_url, dict_path, - gm_json.JSONKEY_IMAGE_BASE_GS_URL) - image_B_base_url = self.get_default( - setB_dicts, self._image_base_gs_url, dict_path, - gm_json.JSONKEY_IMAGE_BASE_GS_URL) - - # get the builders and render modes for each set - builder_A = self.get_default(dictA, None, - gm_json.JSONKEY_DESCRIPTIONS, - gm_json.JSONKEY_DESCRIPTIONS_BUILDER) - render_mode_A = self.get_default(dictA, None, - gm_json.JSONKEY_DESCRIPTIONS, - gm_json.JSONKEY_DESCRIPTIONS_RENDER_MODE) - builder_B = self.get_default(dictB, None, - gm_json.JSONKEY_DESCRIPTIONS, - gm_json.JSONKEY_DESCRIPTIONS_BUILDER) - render_mode_B = self.get_default(dictB, None, - gm_json.JSONKEY_DESCRIPTIONS, - gm_json.JSONKEY_DESCRIPTIONS_RENDER_MODE) - - skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) - # Just for manual testing... truncate to an arbitrary subset. - if self.truncate_results: - skp_names = skp_names[1:3] - for skp_name in skp_names: - imagepairs_for_this_skp = [] - - whole_image_A = self.get_default( - dictA_results, None, - skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) - whole_image_B = self.get_default( - dictB_results, None, - skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) - - imagepairs_for_this_skp.append(self._create_image_pair( - image_dict_A=whole_image_A, image_dict_B=whole_image_B, - image_A_base_url=image_A_base_url, - image_B_base_url=image_B_base_url, - builder_A=builder_A, render_mode_A=render_mode_A, - builder_B=builder_B, render_mode_B=render_mode_B, - source_json_file=dict_path, - source_skp_name=skp_name, tilenum=None)) - - tiled_images_A = self.get_default( - dictA_results, [], - skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES) - tiled_images_B = self.get_default( - dictB_results, [], - skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES) - if tiled_images_A or tiled_images_B: - num_tiles_A = len(tiled_images_A) - num_tiles_B = len(tiled_images_B) - num_tiles = max(num_tiles_A, num_tiles_B) - for tile_num in range(num_tiles): - imagepairs_for_this_skp.append(self._create_image_pair( - image_dict_A=(tiled_images_A[tile_num] - if tile_num < num_tiles_A else None), - image_dict_B=(tiled_images_B[tile_num] - if tile_num < num_tiles_B else None), - image_A_base_url=image_A_base_url, - image_B_base_url=image_B_base_url, - builder_A=builder_A, render_mode_A=render_mode_A, - builder_B=builder_B, render_mode_B=render_mode_B, - source_json_file=dict_path, - source_skp_name=skp_name, tilenum=tile_num)) - - for one_imagepair in imagepairs_for_this_skp: - if one_imagepair: - all_image_pairs.add_image_pair(one_imagepair) - result_type = one_imagepair.extra_columns_dict\ - [COLUMN__RESULT_TYPE] - if result_type != results.KEY__RESULT_TYPE__SUCCEEDED: - failing_image_pairs.add_image_pair(one_imagepair) - - logging.info('Finished adding imagepairs to queue.') - self._image_diff_db.log_queue_size_if_changed(limit_verbosity=False) - - if self._prefetch_only: - return None - else: - return { - results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict( - column_ids_in_order=ORDERED_COLUMN_IDS), - results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict( - column_ids_in_order=ORDERED_COLUMN_IDS), - } - - def _validate_dict_version(self, result_dict): - """Raises Exception if the dict is not the type/version we know how to read. - - Args: - result_dict: dictionary holding output of render_pictures; if None, - this method will return without raising an Exception - """ - # TODO(stephana): These values should be defined as constants somewhere, - # to be kept in sync between this file and writable_expectations.py - expected_header_type = 'ChecksummedImages' - expected_header_revision = 1 - - if result_dict == None: - return - header = result_dict[gm_json.JSONKEY_HEADER] - header_type = header[gm_json.JSONKEY_HEADER_TYPE] - if header_type != expected_header_type: - raise Exception('expected header_type "%s", but got "%s"' % ( - expected_header_type, header_type)) - header_revision = header[gm_json.JSONKEY_HEADER_REVISION] - if header_revision != expected_header_revision: - raise Exception('expected header_revision %d, but got %d' % ( - expected_header_revision, header_revision)) - - def _create_image_pair(self, image_dict_A, image_dict_B, - image_A_base_url, image_B_base_url, - builder_A, render_mode_A, - builder_B, render_mode_B, - source_json_file, - source_skp_name, tilenum): - """Creates an ImagePair object for this pair of images. - - Args: - image_dict_A: dict with JSONKEY_IMAGE_* keys, or None if no image - image_dict_B: dict with JSONKEY_IMAGE_* keys, or None if no image - image_A_base_url: base URL for image A - image_B_base_url: base URL for image B - builder_A: builder that created image set A or None if unknow - render_mode_A: render mode used to generate image set A or None if - unknown. - builder_B: builder that created image set A or None if unknow - render_mode_B: render mode used to generate image set A or None if - unknown. - source_json_file: string; relative path of the JSON file where this - result came from, within setA and setB. - source_skp_name: string; name of the source SKP file - tilenum: which tile, or None if a wholeimage - - Returns: - An ImagePair object, or None if both image_dict_A and image_dict_B are - None. - """ - if (not image_dict_A) and (not image_dict_B): - return None - - def _checksum_and_relative_url(dic): - if dic: - return ((dic[gm_json.JSONKEY_IMAGE_CHECKSUMALGORITHM], - int(dic[gm_json.JSONKEY_IMAGE_CHECKSUMVALUE])), - dic[gm_json.JSONKEY_IMAGE_FILEPATH]) - else: - return None, None - - imageA_checksum, imageA_relative_url = _checksum_and_relative_url( - image_dict_A) - imageB_checksum, imageB_relative_url = _checksum_and_relative_url( - image_dict_B) - - if not imageA_checksum: - result_type = results.KEY__RESULT_TYPE__NOCOMPARISON - elif not imageB_checksum: - result_type = results.KEY__RESULT_TYPE__NOCOMPARISON - elif imageA_checksum == imageB_checksum: - result_type = results.KEY__RESULT_TYPE__SUCCEEDED - else: - result_type = results.KEY__RESULT_TYPE__FAILED - - extra_columns_dict = { - COLUMN__RESULT_TYPE: result_type, - COLUMN__SOURCE_SKP: source_skp_name, - COLUMN__BUILDER_A: builder_A, - COLUMN__RENDER_MODE_A: render_mode_A, - COLUMN__BUILDER_B: builder_B, - COLUMN__RENDER_MODE_B: render_mode_B, - } - if tilenum == None: - extra_columns_dict[COLUMN__TILED_OR_WHOLE] = COLUMN__TILED_OR_WHOLE__WHOLE - extra_columns_dict[COLUMN__TILENUM] = 'N/A' - else: - extra_columns_dict[COLUMN__TILED_OR_WHOLE] = COLUMN__TILED_OR_WHOLE__TILED - extra_columns_dict[COLUMN__TILENUM] = str(tilenum) - - try: - return imagepair.ImagePair( - image_diff_db=self._image_diff_db, - imageA_base_url=image_A_base_url, - imageB_base_url=image_B_base_url, - imageA_relative_url=imageA_relative_url, - imageB_relative_url=imageB_relative_url, - extra_columns=extra_columns_dict, - source_json_file=source_json_file, - download_all_images=self._download_all_images) - except (KeyError, TypeError): - logging.exception( - 'got exception while creating ImagePair for' - ' urlPair=("%s","%s"), source_skp_name="%s", tilenum="%s"' % ( - imageA_relative_url, imageB_relative_url, source_skp_name, - tilenum)) - return None - - def _copy_dir_contents(self, source_dir, dest_dir): - """Copy all contents of source_dir into dest_dir, recursing into subdirs. - - Args: - source_dir: path to source dir (GS URL, local filepath, or a special - "repo:" URL type that points at a file within our Skia checkout) - dest_dir: path to destination dir (local filepath) - - The copy operates as a "merge with overwrite": any files in source_dir will - be "overlaid" on top of the existing content in dest_dir. Existing files - with the same names will be overwritten. - """ - if gs_utils.GSUtils.is_gs_url(source_dir): - (bucket, path) = gs_utils.GSUtils.split_gs_url(source_dir) - self._gs.download_dir_contents(source_bucket=bucket, source_dir=path, - dest_dir=dest_dir) - elif source_dir.lower().startswith(REPO_URL_PREFIX): - repo_dir = os.path.join(REPO_BASEPATH, source_dir[len(REPO_URL_PREFIX):]) - shutil.copytree(repo_dir, dest_dir) - else: - shutil.copytree(source_dir, dest_dir) - - def _get_repo_revision(self, source_dir): - """Get the commit hash of source_dir, IF it refers to a git checkout. - - Args: - source_dir: path to source dir (GS URL, local filepath, or a special - "repo:" URL type that points at a file within our Skia checkout; - only the "repo:" URL type will have a commit hash. - """ - if source_dir.lower().startswith(REPO_URL_PREFIX): - repo_dir = os.path.join(REPO_BASEPATH, source_dir[len(REPO_URL_PREFIX):]) - return subprocess.check_output( - args=[git_utils.GIT, 'rev-parse', 'HEAD'], cwd=repo_dir).strip() - else: - return None diff --git a/gm/rebaseline_server/compare_rendered_pictures_test.py b/gm/rebaseline_server/compare_rendered_pictures_test.py deleted file mode 100755 index 2b15462111..0000000000 --- a/gm/rebaseline_server/compare_rendered_pictures_test.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test compare_rendered_pictures.py - -TODO(epoger): Create a command to update the expected results (in -self._output_dir_expected) when appropriate. For now, you should: -1. examine the results in self.output_dir_actual and make sure they are ok -2. rm -rf self._output_dir_expected -3. mv self.output_dir_actual self._output_dir_expected -Although, if you're using an SVN checkout, this will blow away .svn directories -within self._output_dir_expected, which wouldn't be good... - -""" - -# System-level imports -import os -import posixpath -import subprocess - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -import base_unittest -import compare_rendered_pictures -import find_run_binary -import gm_json -import imagediffdb -import imagepairset -import results - - -class CompareRenderedPicturesTest(base_unittest.TestCase): - - def test_endToEnd(self): - """Generate two sets of SKPs, run render_pictures over both, and compare - the results.""" - setA_subdir = 'before_patch' - setB_subdir = 'after_patch' - self._generate_skps_and_run_render_pictures( - subdir=setA_subdir, skpdict={ - 'changed.skp': 200, - 'unchanged.skp': 100, - 'only-in-before.skp': 128, - }) - self._generate_skps_and_run_render_pictures( - subdir=setB_subdir, skpdict={ - 'changed.skp': 201, - 'unchanged.skp': 100, - 'only-in-after.skp': 128, - }) - - results_obj = compare_rendered_pictures.RenderedPicturesComparisons( - setA_dir=os.path.join(self.temp_dir, setA_subdir), - setB_dir=os.path.join(self.temp_dir, setB_subdir), - setA_section=gm_json.JSONKEY_ACTUALRESULTS, - setB_section=gm_json.JSONKEY_ACTUALRESULTS, - image_diff_db=imagediffdb.ImageDiffDB(self.temp_dir), - image_base_gs_url='gs://fakebucket/fake/path', - diff_base_url='/static/generated-images') - results_obj.get_timestamp = mock_get_timestamp - - # Overwrite elements within the results that change from one test run - # to the next. - # pylint: disable=W0212 - results_obj._setA_descriptions[results.KEY__SET_DESCRIPTIONS__DIR] = [ - 'before-patch-fake-dir'] - results_obj._setB_descriptions[results.KEY__SET_DESCRIPTIONS__DIR] = [ - 'after-patch-fake-dir'] - - gm_json.WriteToFile( - results_obj.get_packaged_results_of_type( - results.KEY__HEADER__RESULTS_ALL), - os.path.join(self.output_dir_actual, 'compare_rendered_pictures.json')) - - def test_endToEnd_withImageBaseGSUrl(self): - """Generate two sets of SKPs, run render_pictures over both, and compare - the results.""" - setA_subdir = 'before_patch' - setB_subdir = 'after_patch' - imageA_gs_base = 'superman/kent-camera/pictures' - imageB_gs_base = 'batman/batarang/pictures' - self._generate_skps_and_run_render_pictures( - subdir=setA_subdir, skpdict={ - 'changed.skp': 200, - 'unchanged.skp': 100, - 'only-in-before.skp': 128, - }, - image_base_gs_url='gs://%s' % imageA_gs_base) - self._generate_skps_and_run_render_pictures( - subdir=setB_subdir, skpdict={ - 'changed.skp': 201, - 'unchanged.skp': 100, - 'only-in-after.skp': 128, - }, - image_base_gs_url='gs://%s' % imageB_gs_base) - - results_obj = compare_rendered_pictures.RenderedPicturesComparisons( - setA_dir=os.path.join(self.temp_dir, setA_subdir), - setB_dir=os.path.join(self.temp_dir, setB_subdir), - setA_section=gm_json.JSONKEY_ACTUALRESULTS, - setB_section=gm_json.JSONKEY_ACTUALRESULTS, - image_diff_db=imagediffdb.ImageDiffDB(self.temp_dir), - image_base_gs_url='gs://fakebucket/fake/path', - diff_base_url='/static/generated-images') - results_obj.get_timestamp = mock_get_timestamp - - output_dict = results_obj.get_packaged_results_of_type( - results.KEY__HEADER__RESULTS_ALL) - # Assert that the baseURLs are as expected. - self.assertEquals( - output_dict[imagepairset.KEY__ROOT__IMAGESETS] - [imagepairset.KEY__IMAGESETS__SET__IMAGE_A] - [imagepairset.KEY__IMAGESETS__FIELD__BASE_URL], - 'http://storage.cloud.google.com/%s' % imageA_gs_base) - self.assertEquals( - output_dict[imagepairset.KEY__ROOT__IMAGESETS] - [imagepairset.KEY__IMAGESETS__SET__IMAGE_B] - [imagepairset.KEY__IMAGESETS__FIELD__BASE_URL], - 'http://storage.cloud.google.com/%s' % imageB_gs_base) - # Overwrite elements within the results that change from one test run - # to the next. - # pylint: disable=W0212 - results_obj._setA_descriptions[results.KEY__SET_DESCRIPTIONS__DIR] = [ - 'before-patch-fake-dir'] - results_obj._setB_descriptions[results.KEY__SET_DESCRIPTIONS__DIR] = [ - 'after-patch-fake-dir'] - - gm_json.WriteToFile( - output_dict, - os.path.join(self.output_dir_actual, - 'compare_rendered_pictures.json')) - - def test_repo_url(self): - """Use repo: URL to specify summary files.""" - base_repo_url = 'repo:gm/rebaseline_server/testdata/inputs/skp-summaries' - results_obj = compare_rendered_pictures.RenderedPicturesComparisons( - setA_dir=posixpath.join(base_repo_url, 'expectations'), - setB_dir=posixpath.join(base_repo_url, 'actuals'), - setA_section=gm_json.JSONKEY_EXPECTEDRESULTS, - setB_section=gm_json.JSONKEY_ACTUALRESULTS, - image_diff_db=imagediffdb.ImageDiffDB(self.temp_dir), - image_base_gs_url='gs://fakebucket/fake/path', - diff_base_url='/static/generated-images') - results_obj.get_timestamp = mock_get_timestamp - - # Overwrite elements within the results that change from one test run - # to the next. - # pylint: disable=W0212 - results_obj._setA_descriptions\ - [results.KEY__SET_DESCRIPTIONS__REPO_REVISION] = 'fake-repo-revision' - results_obj._setB_descriptions\ - [results.KEY__SET_DESCRIPTIONS__REPO_REVISION] = 'fake-repo-revision' - - gm_json.WriteToFile( - results_obj.get_packaged_results_of_type( - results.KEY__HEADER__RESULTS_ALL), - os.path.join(self.output_dir_actual, 'compare_rendered_pictures.json')) - - def _generate_skps_and_run_render_pictures(self, subdir, skpdict, - image_base_gs_url=None): - """Generate SKPs and run render_pictures on them. - - Args: - subdir: subdirectory (within self.temp_dir) to write all files into - skpdict: {skpname: redvalue} dictionary describing the SKP files to render - """ - out_path = os.path.join(self.temp_dir, subdir) - os.makedirs(out_path) - for skpname, redvalue in skpdict.iteritems(): - self._run_skpmaker( - output_path=os.path.join(out_path, skpname), red=redvalue) - - # TODO(epoger): Add --mode tile 256 256 --writeWholeImage to the unittest, - # and fix its result! (imageURLs within whole-image entries are wrong when - # I tried adding that) - binary = find_run_binary.find_path_to_program('render_pictures') - render_pictures_cmd = [ - binary, - '--config', '8888', - '-r', out_path, - '--writeChecksumBasedFilenames', - '--writeJsonSummaryPath', os.path.join(out_path, 'summary.json'), - '--writePath', out_path] - if image_base_gs_url: - render_pictures_cmd.extend(['--imageBaseGSUrl', image_base_gs_url]) - return subprocess.check_output(render_pictures_cmd) - - def _run_skpmaker(self, output_path, red=0, green=0, blue=0, - width=640, height=400): - """Runs the skpmaker binary to generate SKP with known characteristics. - - Args: - output_path: Filepath to write the SKP into. - red: Value of red color channel in image, 0-255. - green: Value of green color channel in image, 0-255. - blue: Value of blue color channel in image, 0-255. - width: Width of canvas to create. - height: Height of canvas to create. - """ - binary = find_run_binary.find_path_to_program('skpmaker') - return subprocess.check_output([ - binary, - '--red', str(red), - '--green', str(green), - '--blue', str(blue), - '--width', str(width), - '--height', str(height), - '--writePath', str(output_path)]) - -def mock_get_timestamp(): - """Mock version of BaseComparisons.get_timestamp() for testing.""" - return 12345678 - - -def main(): - base_unittest.main(CompareRenderedPicturesTest) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/compare_to_expectations.py b/gm/rebaseline_server/compare_to_expectations.py deleted file mode 100755 index 303294c0ae..0000000000 --- a/gm/rebaseline_server/compare_to_expectations.py +++ /dev/null @@ -1,415 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2013 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Repackage expected/actual GM results as needed by our HTML rebaseline viewer. -""" - -# System-level imports -import argparse -import fnmatch -import logging -import os -import time - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import url_utils -import column -import gm_json -import imagediffdb -import imagepair -import imagepairset -import results - -EXPECTATION_FIELDS_PASSED_THRU_VERBATIM = [ - results.KEY__EXPECTATIONS__BUGS, - results.KEY__EXPECTATIONS__IGNOREFAILURE, - results.KEY__EXPECTATIONS__REVIEWED, -] -FREEFORM_COLUMN_IDS = [ - results.KEY__EXTRACOLUMNS__BUILDER, - results.KEY__EXTRACOLUMNS__TEST, -] -ORDERED_COLUMN_IDS = [ - results.KEY__EXTRACOLUMNS__RESULT_TYPE, - results.KEY__EXTRACOLUMNS__BUILDER, - results.KEY__EXTRACOLUMNS__TEST, - results.KEY__EXTRACOLUMNS__CONFIG, -] - -TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) -DEFAULT_EXPECTATIONS_DIR = os.path.join(TRUNK_DIRECTORY, 'expectations', 'gm') -DEFAULT_IGNORE_FAILURES_FILE = 'ignored-tests.txt' - -IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image') - - -class ExpectationComparisons(results.BaseComparisons): - """Loads actual and expected GM results into an ImagePairSet. - - Loads actual and expected results from all builders, except for those skipped - by _ignore_builder(). - - Once this object has been constructed, the results (in self._results[]) - are immutable. If you want to update the results based on updated JSON - file contents, you will need to create a new ExpectationComparisons object.""" - - def __init__(self, image_diff_db, actuals_root=results.DEFAULT_ACTUALS_DIR, - expected_root=DEFAULT_EXPECTATIONS_DIR, - ignore_failures_file=DEFAULT_IGNORE_FAILURES_FILE, - diff_base_url=None, builder_regex_list=None): - """ - Args: - image_diff_db: instance of ImageDiffDB we use to cache the image diffs - actuals_root: root directory containing all actual-results.json files - expected_root: root directory containing all expected-results.json files - ignore_failures_file: if a file with this name is found within - expected_root, ignore failures for any tests listed in the file - diff_base_url: base URL within which the client should look for diff - images; if not specified, defaults to a "file:///" URL representation - of image_diff_db's storage_root - builder_regex_list: List of regular expressions specifying which builders - we will process. If None, process all builders. - """ - super(ExpectationComparisons, self).__init__() - time_start = int(time.time()) - if builder_regex_list != None: - self.set_match_builders_pattern_list(builder_regex_list) - self._image_diff_db = image_diff_db - self._diff_base_url = ( - diff_base_url or - url_utils.create_filepath_url(image_diff_db.storage_root)) - self._actuals_root = actuals_root - self._expected_root = expected_root - self._ignore_failures_on_these_tests = [] - if ignore_failures_file: - self._ignore_failures_on_these_tests = ( - ExpectationComparisons._read_noncomment_lines( - os.path.join(expected_root, ignore_failures_file))) - self._load_actual_and_expected() - self._timestamp = int(time.time()) - logging.info('Results complete; took %d seconds.' % - (self._timestamp - time_start)) - - def edit_expectations(self, modifications): - """Edit the expectations stored within this object and write them back - to disk. - - Note that this will NOT update the results stored in self._results[] ; - in order to see those updates, you must instantiate a new - ExpectationComparisons object based on the (now updated) files on disk. - - Args: - modifications: a list of dictionaries, one for each expectation to update: - - [ - { - imagepair.KEY__IMAGEPAIRS__EXPECTATIONS: { - results.KEY__EXPECTATIONS__BUGS: [123, 456], - results.KEY__EXPECTATIONS__IGNOREFAILURE: false, - results.KEY__EXPECTATIONS__REVIEWED: true, - }, - imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: { - results.KEY__EXTRACOLUMNS__BUILDER: 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug', - results.KEY__EXTRACOLUMNS__CONFIG: '8888', - results.KEY__EXTRACOLUMNS__TEST: 'bigmatrix', - }, - results.KEY__IMAGEPAIRS__IMAGE_B_URL: 'bitmap-64bitMD5/bigmatrix/10894408024079689926.png', - }, - ... - ] - - """ - expected_builder_dicts = self._read_builder_dicts_from_root( - self._expected_root) - for mod in modifications: - image_name = results.IMAGE_FILENAME_FORMATTER % ( - mod[imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS] - [results.KEY__EXTRACOLUMNS__TEST], - mod[imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS] - [results.KEY__EXTRACOLUMNS__CONFIG]) - _, hash_type, hash_digest = gm_json.SplitGmRelativeUrl( - mod[imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL]) - allowed_digests = [[hash_type, int(hash_digest)]] - new_expectations = { - gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS: allowed_digests, - } - for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM: - value = mod[imagepair.KEY__IMAGEPAIRS__EXPECTATIONS].get(field) - if value is not None: - new_expectations[field] = value - builder_dict = expected_builder_dicts[ - mod[imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS] - [results.KEY__EXTRACOLUMNS__BUILDER]] - builder_expectations = builder_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS) - if not builder_expectations: - builder_expectations = {} - builder_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = builder_expectations - builder_expectations[image_name] = new_expectations - ExpectationComparisons._write_dicts_to_root( - expected_builder_dicts, self._expected_root) - - @staticmethod - def _write_dicts_to_root(meta_dict, root, pattern='*.json'): - """Write all per-builder dictionaries within meta_dict to files under - the root path. - - Security note: this will only write to files that already exist within - the root path (as found by os.walk() within root), so we don't need to - worry about malformed content writing to disk outside of root. - However, the data written to those files is not double-checked, so it - could contain poisonous data. - - Args: - meta_dict: a builder-keyed meta-dictionary containing all the JSON - dictionaries we want to write out - root: path to root of directory tree within which to write files - pattern: which files to write within root (fnmatch-style pattern) - - Raises: - IOError if root does not refer to an existing directory - KeyError if the set of per-builder dictionaries written out was - different than expected - """ - if not os.path.isdir(root): - raise IOError('no directory found at path %s' % root) - actual_builders_written = [] - for dirpath, _, filenames in os.walk(root): - for matching_filename in fnmatch.filter(filenames, pattern): - builder = os.path.basename(dirpath) - per_builder_dict = meta_dict.get(builder) - if per_builder_dict is not None: - fullpath = os.path.join(dirpath, matching_filename) - gm_json.WriteToFile(per_builder_dict, fullpath) - actual_builders_written.append(builder) - - # Check: did we write out the set of per-builder dictionaries we - # expected to? - expected_builders_written = sorted(meta_dict.keys()) - actual_builders_written.sort() - if expected_builders_written != actual_builders_written: - raise KeyError( - 'expected to write dicts for builders %s, but actually wrote them ' - 'for builders %s' % ( - expected_builders_written, actual_builders_written)) - - def _load_actual_and_expected(self): - """Loads the results of all tests, across all builders (based on the - files within self._actuals_root and self._expected_root), - and stores them in self._results. - """ - logging.info('Reading actual-results JSON files from %s...' % - self._actuals_root) - actual_builder_dicts = self._read_builder_dicts_from_root( - self._actuals_root) - logging.info('Reading expected-results JSON files from %s...' % - self._expected_root) - expected_builder_dicts = self._read_builder_dicts_from_root( - self._expected_root) - - all_image_pairs = imagepairset.ImagePairSet( - descriptions=IMAGEPAIR_SET_DESCRIPTIONS, - diff_base_url=self._diff_base_url) - failing_image_pairs = imagepairset.ImagePairSet( - descriptions=IMAGEPAIR_SET_DESCRIPTIONS, - diff_base_url=self._diff_base_url) - - # Override settings for columns that should be filtered using freeform text. - for column_id in FREEFORM_COLUMN_IDS: - factory = column.ColumnHeaderFactory( - header_text=column_id, use_freeform_filter=True) - all_image_pairs.set_column_header_factory( - column_id=column_id, column_header_factory=factory) - failing_image_pairs.set_column_header_factory( - column_id=column_id, column_header_factory=factory) - - all_image_pairs.ensure_extra_column_values_in_summary( - column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ - results.KEY__RESULT_TYPE__FAILED, - results.KEY__RESULT_TYPE__FAILUREIGNORED, - results.KEY__RESULT_TYPE__NOCOMPARISON, - results.KEY__RESULT_TYPE__SUCCEEDED, - ]) - failing_image_pairs.ensure_extra_column_values_in_summary( - column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ - results.KEY__RESULT_TYPE__FAILED, - results.KEY__RESULT_TYPE__FAILUREIGNORED, - results.KEY__RESULT_TYPE__NOCOMPARISON, - ]) - - # Only consider builders we have both expected and actual results for. - # Fixes http://skbug.com/2486 ('rebaseline_server shows actual results - # (but not expectations) for Test-Ubuntu12-ShuttleA-NoGPU-x86_64-Debug - # builder') - actual_builder_set = set(actual_builder_dicts.keys()) - expected_builder_set = set(expected_builder_dicts.keys()) - builders = sorted(actual_builder_set.intersection(expected_builder_set)) - - num_builders = len(builders) - builder_num = 0 - for builder in builders: - builder_num += 1 - logging.info('Generating pixel diffs for builder #%d of %d, "%s"...' % - (builder_num, num_builders, builder)) - actual_results_for_this_builder = ( - actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS]) - for result_type in sorted(actual_results_for_this_builder.keys()): - results_of_this_type = actual_results_for_this_builder[result_type] - if not results_of_this_type: - continue - for image_name in sorted(results_of_this_type.keys()): - (test, config) = results.IMAGE_FILENAME_RE.match(image_name).groups() - actual_image_relative_url = ( - ExpectationComparisons._create_relative_url( - hashtype_and_digest=results_of_this_type[image_name], - test_name=test)) - - # Default empty expectations; overwrite these if we find any real ones - expectations_per_test = None - expected_image_relative_url = None - expectations_dict = None - try: - expectations_per_test = ( - expected_builder_dicts - [builder][gm_json.JSONKEY_EXPECTEDRESULTS][image_name]) - # TODO(epoger): assumes a single allowed digest per test, which is - # fine; see https://code.google.com/p/skia/issues/detail?id=1787 - expected_image_hashtype_and_digest = ( - expectations_per_test - [gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0]) - expected_image_relative_url = ( - ExpectationComparisons._create_relative_url( - hashtype_and_digest=expected_image_hashtype_and_digest, - test_name=test)) - expectations_dict = {} - for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM: - expectations_dict[field] = expectations_per_test.get(field) - except (KeyError, TypeError): - # There are several cases in which we would expect to find - # no expectations for a given test: - # - # 1. result_type == NOCOMPARISON - # There are no expectations for this test yet! - # - # 2. alternate rendering mode failures (e.g. serialized) - # In cases like - # https://code.google.com/p/skia/issues/detail?id=1684 - # ('tileimagefilter GM test failing in serialized render mode'), - # the gm-actuals will list a failure for the alternate - # rendering mode even though we don't have explicit expectations - # for the test (the implicit expectation is that it must - # render the same in all rendering modes). - # - # Don't log type 1, because it is common. - # Log other types, because they are rare and we should know about - # them, but don't throw an exception, because we need to keep our - # tools working in the meanwhile! - if result_type != results.KEY__RESULT_TYPE__NOCOMPARISON: - logging.warning('No expectations found for test: %s' % { - results.KEY__EXTRACOLUMNS__BUILDER: builder, - results.KEY__EXTRACOLUMNS__RESULT_TYPE: result_type, - 'image_name': image_name, - }) - - # If this test was recently rebaselined, it will remain in - # the 'failed' set of actuals until all the bots have - # cycled (although the expectations have indeed been set - # from the most recent actuals). Treat these as successes - # instead of failures. - # - # TODO(epoger): Do we need to do something similar in - # other cases, such as when we have recently marked a test - # as ignoreFailure but it still shows up in the 'failed' - # category? Maybe we should not rely on the result_type - # categories recorded within the gm_actuals AT ALL, and - # instead evaluate the result_type ourselves based on what - # we see in expectations vs actual checksum? - if expected_image_relative_url == actual_image_relative_url: - updated_result_type = results.KEY__RESULT_TYPE__SUCCEEDED - elif ((result_type == results.KEY__RESULT_TYPE__FAILED) and - (test in self._ignore_failures_on_these_tests)): - updated_result_type = results.KEY__RESULT_TYPE__FAILUREIGNORED - else: - updated_result_type = result_type - extra_columns_dict = { - results.KEY__EXTRACOLUMNS__RESULT_TYPE: updated_result_type, - results.KEY__EXTRACOLUMNS__BUILDER: builder, - results.KEY__EXTRACOLUMNS__TEST: test, - results.KEY__EXTRACOLUMNS__CONFIG: config, - } - try: - image_pair = imagepair.ImagePair( - image_diff_db=self._image_diff_db, - imageA_base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL, - imageB_base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL, - imageA_relative_url=expected_image_relative_url, - imageB_relative_url=actual_image_relative_url, - expectations=expectations_dict, - extra_columns=extra_columns_dict) - all_image_pairs.add_image_pair(image_pair) - if updated_result_type != results.KEY__RESULT_TYPE__SUCCEEDED: - failing_image_pairs.add_image_pair(image_pair) - except Exception: - logging.exception('got exception while creating new ImagePair') - - # pylint: disable=W0201 - self._results = { - results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict( - column_ids_in_order=ORDERED_COLUMN_IDS), - results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict( - column_ids_in_order=ORDERED_COLUMN_IDS), - } - - -def main(): - logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - datefmt='%m/%d/%Y %H:%M:%S', - level=logging.INFO) - parser = argparse.ArgumentParser() - parser.add_argument( - '--actuals', default=results.DEFAULT_ACTUALS_DIR, - help='Directory containing all actual-result JSON files; defaults to ' - '\'%(default)s\' .') - parser.add_argument( - '--expectations', default=DEFAULT_EXPECTATIONS_DIR, - help='Directory containing all expected-result JSON files; defaults to ' - '\'%(default)s\' .') - parser.add_argument( - '--ignore-failures-file', default=DEFAULT_IGNORE_FAILURES_FILE, - help='If a file with this name is found within the EXPECTATIONS dir, ' - 'ignore failures for any tests listed in the file; defaults to ' - '\'%(default)s\' .') - parser.add_argument( - '--outfile', required=True, - help='File to write result summary into, in JSON format.') - parser.add_argument( - '--results', default=results.KEY__HEADER__RESULTS_FAILURES, - help='Which result types to include. Defaults to \'%(default)s\'; ' - 'must be one of ' + - str([results.KEY__HEADER__RESULTS_FAILURES, - results.KEY__HEADER__RESULTS_ALL])) - parser.add_argument( - '--workdir', default=results.DEFAULT_GENERATED_IMAGES_ROOT, - help='Directory within which to download images and generate diffs; ' - 'defaults to \'%(default)s\' .') - args = parser.parse_args() - image_diff_db = imagediffdb.ImageDiffDB(storage_root=args.workdir) - results_obj = ExpectationComparisons( - image_diff_db=image_diff_db, - actuals_root=args.actuals, - expected_root=args.expectations, - ignore_failures_file=args.ignore_failures_file) - gm_json.WriteToFile( - results_obj.get_packaged_results_of_type(results_type=args.results), - args.outfile) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/compare_to_expectations_test.py b/gm/rebaseline_server/compare_to_expectations_test.py deleted file mode 100755 index 2997cde67f..0000000000 --- a/gm/rebaseline_server/compare_to_expectations_test.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2013 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test compare_to_expectations.py - -TODO(epoger): Create a command to update the expected results (in -self._output_dir_expected) when appropriate. For now, you should: -1. examine the results in self.output_dir_actual and make sure they are ok -2. rm -rf self._output_dir_expected -3. mv self.output_dir_actual self._output_dir_expected -Although, if you're using an SVN checkout, this will blow away .svn directories -within self._output_dir_expected, which wouldn't be good... - -""" - -import os - -# Imports from within Skia -import base_unittest -import compare_to_expectations -import imagediffdb -import results -import gm_json # must import results first, so that gm_json will be in sys.path - - -class CompareToExpectationsTest(base_unittest.TestCase): - - def test_gm(self): - """Process results of a GM run with the ExpectationComparisons object.""" - image_diff_db = imagediffdb.ImageDiffDB(storage_root=self.temp_dir) - results_obj = compare_to_expectations.ExpectationComparisons( - image_diff_db=image_diff_db, - actuals_root=os.path.join(self.input_dir, 'gm-actuals'), - expected_root=os.path.join(self.input_dir, 'gm-expectations'), - diff_base_url='/static/generated-images') - results_obj.get_timestamp = mock_get_timestamp - gm_json.WriteToFile( - results_obj.get_packaged_results_of_type( - results.KEY__HEADER__RESULTS_ALL), - os.path.join(self.output_dir_actual, 'gm.json')) - - -def mock_get_timestamp(): - """Mock version of BaseComparisons.get_timestamp() for testing.""" - return 12345678 - - -def main(): - base_unittest.main(CompareToExpectationsTest) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/download_actuals.py b/gm/rebaseline_server/download_actuals.py deleted file mode 100755 index 0e60289573..0000000000 --- a/gm/rebaseline_server/download_actuals.py +++ /dev/null @@ -1,289 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Download actual GM results for a particular builder. -""" - -# System-level imports -import httplib -import logging -import optparse -import os -import posixpath -import re -import urllib2 - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import gs_utils -from py.utils import url_utils -import buildbot_globals -import gm_json - - -GM_SUMMARIES_BUCKET = buildbot_globals.Get('gm_summaries_bucket') -DEFAULT_ACTUALS_BASE_URL = ( - 'http://storage.googleapis.com/%s' % GM_SUMMARIES_BUCKET) -DEFAULT_JSON_FILENAME = 'actual-results.json' - - -class Download(object): - - def __init__(self, actuals_base_url=DEFAULT_ACTUALS_BASE_URL, - json_filename=DEFAULT_JSON_FILENAME, - gm_actuals_root_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL): - """ - Args: - actuals_base_url: URL pointing at the root directory - containing all actual-results.json files, e.g., - http://domain.name/path/to/dir OR - file:///absolute/path/to/localdir - json_filename: The JSON filename to read from within each directory. - gm_actuals_root_url: Base URL under which the actually-generated-by-bots - GM images are stored. - """ - self._actuals_base_url = actuals_base_url - self._json_filename = json_filename - self._gm_actuals_root_url = gm_actuals_root_url - self._image_filename_re = re.compile(gm_json.IMAGE_FILENAME_PATTERN) - - def fetch(self, builder_name, dest_dir): - """ Downloads actual GM results for a particular builder. - - Args: - builder_name: which builder to download results of - dest_dir: path to directory where the image files will be written; - if the directory does not exist yet, it will be created - - TODO(epoger): Display progress info. Right now, it can take a long time - to download all of the results, and there is no indication of progress. - - TODO(epoger): Download multiple images in parallel to speed things up. - """ - json_url = posixpath.join(self._actuals_base_url, builder_name, - self._json_filename) - json_contents = urllib2.urlopen(json_url).read() - results_dict = gm_json.LoadFromString(json_contents) - - actual_results_dict = results_dict[gm_json.JSONKEY_ACTUALRESULTS] - for result_type in sorted(actual_results_dict.keys()): - results_of_this_type = actual_results_dict[result_type] - if not results_of_this_type: - continue - for image_name in sorted(results_of_this_type.keys()): - (test, config) = self._image_filename_re.match(image_name).groups() - (hash_type, hash_digest) = results_of_this_type[image_name] - source_url = gm_json.CreateGmActualUrl( - test_name=test, hash_type=hash_type, hash_digest=hash_digest, - gm_actuals_root_url=self._gm_actuals_root_url) - dest_path = os.path.join(dest_dir, config, test + '.png') - url_utils.copy_contents(source_url=source_url, dest_path=dest_path, - create_subdirs_if_needed=True) - - -def get_builders_list(summaries_bucket=GM_SUMMARIES_BUCKET): - """ Returns the list of builders we have actual results for. - - Args: - summaries_bucket: Google Cloud Storage bucket containing the summary - JSON files - """ - dirs, _ = gs_utils.GSUtils().list_bucket_contents(bucket=GM_SUMMARIES_BUCKET) - return dirs - - -class ActualLocation(object): - def __init__(self, bucket, path, generation): - self.bucket = bucket - self.path = path - self.generation = generation - - -class TipOfTreeActuals(object): - def __init__(self, summaries_bucket=GM_SUMMARIES_BUCKET, - json_filename=DEFAULT_JSON_FILENAME): - """ - Args: - summaries_bucket: URL pointing at the root directory - containing all actual-results.json files, e.g., - http://domain.name/path/to/dir OR - file:///absolute/path/to/localdir - json_filename: The JSON filename to read from within each directory. - """ - self._json_filename = json_filename - self._summaries_bucket = summaries_bucket - - def description(self): - return 'gm_summaries_bucket %s' % (self._summaries_bucket,) - - def get_builders(self): - """ Returns the list of builders we have actual results for. - {builder:string -> ActualLocation} - """ - dirs = get_builders_list(self._summaries_bucket) - result = dict() - for builder in dirs: - result[builder] = ActualLocation( - self._summaries_bucket, - "%s/%s" % (builder, self._json_filename), - None) - return result - - -class RietveldIssueActuals(object): - def __init__(self, issue, json_filename=DEFAULT_JSON_FILENAME): - """ - Args: - issue: The rietveld issue from which to obtain actuals. - json_filename: The JSON filename to read from within each directory. - """ - self._issue = issue - self._json_filename = json_filename - - def description(self): - return 'rietveld issue %s' % (self._issue,) - - def get_builders(self): - """ Returns the actuals for the given rietveld issue's tryjobs. - {builder:string -> ActualLocation} - - e.g. - {'Test-Android-Xoom-Tegra2-Arm7-Release': ( - 'chromium-skia-gm-summaries', - 'Test-Android-Xoom-Tegra2-Arm7-Release-Trybot/actual-results.json', - '1415041165535000')} - """ - result = dict() - json_filename_re = re.compile( - 'Created: gs://([^/]+)/((?:[^/]+/)+%s)#(\d+)' - % re.escape(self._json_filename)) - codereview_api_url = 'https://codereview.chromium.org/api' - upload_gm_step_url = '/steps/Upload GM Results/logs/stdio' - - logging.info('Fetching issue %s ...' % (self._issue,)) - json_issue_url = '%s/%s' % (codereview_api_url, self._issue) - json_issue_data = urllib2.urlopen(json_issue_url).read() - issue_dict = gm_json.LoadFromString(json_issue_data) - - patchsets = issue_dict.get("patchsets", []) - patchset = patchsets[-1] - if not patchset: - logging.warning('No patchsets for rietveld issue %s.' % (self._issue,)) - return result - - logging.info('Fetching issue %s patch %s...' % (self._issue, patchset)) - json_patchset_url = '%s/%s/%s' % (codereview_api_url, self._issue, patchset) - json_patchset_data = urllib2.urlopen(json_patchset_url).read() - patchset_dict = gm_json.LoadFromString(json_patchset_data) - - # try_job_results is ordered reverse chronologically - try_job_results = patchset_dict.get('try_job_results', []) - for try_job_result in try_job_results: - try_builder = try_job_result.get('builder', '<bad builder>') - if not try_builder.endswith('-Trybot'): - logging.warning('Builder %s is not a trybot?' % (try_builder,)) - continue - builder = try_builder[:-len('-Trybot')] - if builder in result: - continue - - logging.info('Fetching issue %s patch %s try %s...' % - (self._issue, patchset, try_builder)) - build_url = try_job_result.get('url', '<bad url>') - if build_url is None: - logging.warning('Builder %s has not started.' % (try_builder,)) - continue - gm_upload_output_url = build_url + urllib2.quote(upload_gm_step_url) - logging.info('Fetching %s ...' % (gm_upload_output_url,)) - - # Tryjobs might not produce the step, but don't let that fail everything. - gm_upload_output = None - try: - gm_upload_output = urllib2.urlopen(gm_upload_output_url).read() - except (urllib2.HTTPError, urllib2.URLError, httplib.HTTPException) as e: - logging.warning(e) - except Exception: - logging.exception('Error opening %s .' % (gm_upload_output_url,)) - if not gm_upload_output: - logging.warning('Could not fetch %s .' % (gm_upload_output_url,)) - continue - - json_filename_match = json_filename_re.search(gm_upload_output) - if json_filename_match: - logging.info('Found issue %s patch %s try %s result gs://%s/%s#%s .' % - (self._issue, patchset, builder, - json_filename_match.group(1), - json_filename_match.group(2), - json_filename_match.group(3))) - result[builder] = ActualLocation(json_filename_match.group(1), - json_filename_match.group(2), - json_filename_match.group(3)) - else: - logging.warning('Did not find %s for issue %s patch %s try %s.' % - (self._json_filename, self._issue, patchset, try_builder)) - - return result - - -def main(): - parser = optparse.OptionParser() - required_params = [] - parser.add_option('--actuals-base-url', - action='store', type='string', - default=DEFAULT_ACTUALS_BASE_URL, - help=('Base URL from which to read files containing JSON ' - 'summaries of actual GM results; defaults to ' - '"%default".')) - required_params.append('builder') - # TODO(epoger): Before https://codereview.chromium.org/309653005 , when this - # tool downloaded the JSON summaries from skia-autogen, it had the ability - # to get results as of a specific revision number. We should add similar - # functionality when retrieving the summaries from Google Storage. - parser.add_option('--builder', - action='store', type='string', - help=('REQUIRED: Which builder to download results for. ' - 'To see a list of builders, run with the ' - '--list-builders option set.')) - required_params.append('dest_dir') - parser.add_option('--dest-dir', - action='store', type='string', - help=('REQUIRED: Directory where all images should be ' - 'written. If this directory does not exist yet, it ' - 'will be created.')) - parser.add_option('--json-filename', - action='store', type='string', - default=DEFAULT_JSON_FILENAME, - help=('JSON summary filename to read for each builder; ' - 'defaults to "%default".')) - parser.add_option('--list-builders', action='store_true', - help=('List all available builders.')) - (params, remaining_args) = parser.parse_args() - - if params.list_builders: - print '\n'.join(get_builders_list()) - return - - # Make sure all required options were set, - # and that there were no items left over in the command line. - for required_param in required_params: - if not getattr(params, required_param): - raise Exception('required option \'%s\' was not set' % required_param) - if len(remaining_args) is not 0: - raise Exception('extra items specified in the command line: %s' % - remaining_args) - - downloader = Download(actuals_base_url=params.actuals_base_url) - downloader.fetch(builder_name=params.builder, - dest_dir=params.dest_dir) - - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/download_actuals_test.py b/gm/rebaseline_server/download_actuals_test.py deleted file mode 100755 index b9822245a3..0000000000 --- a/gm/rebaseline_server/download_actuals_test.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test download.py - -TODO(epoger): Create a command to update the expected results (in -self._output_dir_expected) when appropriate. For now, you should: -1. examine the results in self.output_dir_actual and make sure they are ok -2. rm -rf self._output_dir_expected -3. mv self.output_dir_actual self._output_dir_expected -Although, if you're using an SVN checkout, this will blow away .svn directories -within self._output_dir_expected, which wouldn't be good... - -""" - -# System-level imports -import os - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import url_utils -import base_unittest -import download_actuals - - -class DownloadTest(base_unittest.TestCase): - - def test_fetch(self): - """Tests fetch() of GM results from actual-results.json .""" - downloader = download_actuals.Download( - actuals_base_url=url_utils.create_filepath_url( - os.path.join(self.input_dir, 'gm-actuals')), - gm_actuals_root_url=url_utils.create_filepath_url( - os.path.join(self.input_dir, 'fake-gm-imagefiles'))) - downloader.fetch( - builder_name='Test-Android-GalaxyNexus-SGX540-Arm7-Release', - dest_dir=self.output_dir_actual) - - -def main(): - base_unittest.main(DownloadTest) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/imagediffdb.py b/gm/rebaseline_server/imagediffdb.py deleted file mode 100644 index 0bc75cfca4..0000000000 --- a/gm/rebaseline_server/imagediffdb.py +++ /dev/null @@ -1,477 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2013 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Calulate differences between image pairs, and store them in a database. -""" - -# System-level imports -import contextlib -import errno -import json -import logging -import os -import Queue -import re -import shutil -import tempfile -import threading -import time -import urllib - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -import find_run_binary -from py.utils import gs_utils - - -SKPDIFF_BINARY = find_run_binary.find_path_to_program('skpdiff') - -DEFAULT_IMAGE_SUFFIX = '.png' -DEFAULT_IMAGES_SUBDIR = 'images' -# TODO(epoger): Figure out a better default number of threads; for now, -# using a conservative default value. -DEFAULT_NUM_WORKER_THREADS = 1 - -DISALLOWED_FILEPATH_CHAR_REGEX = re.compile('[^\w\-]') - -RGBDIFFS_SUBDIR = 'diffs' -WHITEDIFFS_SUBDIR = 'whitediffs' - -# Keys used within DiffRecord dictionary representations. -# NOTE: Keep these in sync with static/constants.js -KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL = 'maxDiffPerChannel' -KEY__DIFFERENCES__NUM_DIFF_PIXELS = 'numDifferingPixels' -KEY__DIFFERENCES__PERCENT_DIFF_PIXELS = 'percentDifferingPixels' -KEY__DIFFERENCES__PERCEPTUAL_DIFF = 'perceptualDifference' -KEY__DIFFERENCES__DIFF_URL = 'diffUrl' -KEY__DIFFERENCES__WHITE_DIFF_URL = 'whiteDiffUrl' - -# Special values within ImageDiffDB._diff_dict -_DIFFRECORD_FAILED = 'failed' -_DIFFRECORD_PENDING = 'pending' - -# How often to report tasks_queue size -QUEUE_LOGGING_GRANULARITY = 1000 - -# Temporary variable to keep track of how many times we download -# the same file in multiple threads. -# TODO(epoger): Delete this, once we see that the number stays close to 0. -global_file_collisions = 0 - - -class DiffRecord(object): - """ Record of differences between two images. """ - - def __init__(self, gs, storage_root, - expected_image_url, expected_image_locator, - actual_image_url, actual_image_locator, - expected_images_subdir=DEFAULT_IMAGES_SUBDIR, - actual_images_subdir=DEFAULT_IMAGES_SUBDIR, - image_suffix=DEFAULT_IMAGE_SUFFIX): - """Download this pair of images (unless we already have them on local disk), - and prepare a DiffRecord for them. - - Args: - gs: instance of GSUtils object we can use to download images - storage_root: root directory on local disk within which we store all - images - expected_image_url: file, GS, or HTTP url from which we will download the - expected image - expected_image_locator: a unique ID string under which we will store the - expected image within storage_root (probably including a checksum to - guarantee uniqueness) - actual_image_url: file, GS, or HTTP url from which we will download the - actual image - actual_image_locator: a unique ID string under which we will store the - actual image within storage_root (probably including a checksum to - guarantee uniqueness) - expected_images_subdir: the subdirectory expected images are stored in. - actual_images_subdir: the subdirectory actual images are stored in. - image_suffix: the suffix of images. - """ - expected_image_locator = _sanitize_locator(expected_image_locator) - actual_image_locator = _sanitize_locator(actual_image_locator) - - # Download the expected/actual images, if we don't have them already. - expected_image_file = os.path.join( - storage_root, expected_images_subdir, - str(expected_image_locator) + image_suffix) - actual_image_file = os.path.join( - storage_root, actual_images_subdir, - str(actual_image_locator) + image_suffix) - for image_file, image_url in [ - (expected_image_file, expected_image_url), - (actual_image_file, actual_image_url)]: - if image_file and image_url: - try: - _download_file(gs, image_file, image_url) - except Exception: - logging.exception('unable to download image_url %s to file %s' % - (image_url, image_file)) - raise - - # Return early if we do not need to generate diffs. - if (expected_image_url == actual_image_url or - not expected_image_url or not actual_image_url): - return - - # Get all diff images and values using the skpdiff binary. - skpdiff_output_dir = tempfile.mkdtemp() - try: - skpdiff_summary_file = os.path.join(skpdiff_output_dir, - 'skpdiff-output.json') - skpdiff_rgbdiff_dir = os.path.join(storage_root, RGBDIFFS_SUBDIR) - skpdiff_whitediff_dir = os.path.join(storage_root, WHITEDIFFS_SUBDIR) - _mkdir_unless_exists(skpdiff_rgbdiff_dir) - _mkdir_unless_exists(skpdiff_rgbdiff_dir) - - # TODO(epoger): Consider calling skpdiff ONCE for all image pairs, - # instead of calling it separately for each image pair. - # Pro: we'll incur less overhead from making repeated system calls, - # spinning up the skpdiff binary, etc. - # Con: we would have to wait until all image pairs were loaded before - # generating any of the diffs? - # Note(stephana): '--longnames' was added to allow for this - # case (multiple files at once) versus specifying output diffs - # directly. - find_run_binary.run_command( - [SKPDIFF_BINARY, '-p', expected_image_file, actual_image_file, - '--jsonp', 'false', - '--longnames', 'true', - '--output', skpdiff_summary_file, - '--differs', 'perceptual', 'different_pixels', - '--rgbDiffDir', skpdiff_rgbdiff_dir, - '--whiteDiffDir', skpdiff_whitediff_dir, - ]) - - # Get information out of the skpdiff_summary_file. - with contextlib.closing(open(skpdiff_summary_file)) as fp: - data = json.load(fp) - - # For now, we can assume there is only one record in the output summary, - # since we passed skpdiff only one pair of images. - record = data['records'][0] - self._width = record['width'] - self._height = record['height'] - self._diffUrl = os.path.split(record['rgbDiffPath'])[1] - self._whiteDiffUrl = os.path.split(record['whiteDiffPath'])[1] - - # TODO: make max_diff_per_channel a tuple instead of a list, because the - # structure is meaningful (first element is red, second is green, etc.) - # See http://stackoverflow.com/a/626871 - self._max_diff_per_channel = [ - record['maxRedDiff'], record['maxGreenDiff'], record['maxBlueDiff']] - per_differ_stats = record['diffs'] - for stats in per_differ_stats: - differ_name = stats['differName'] - if differ_name == 'different_pixels': - self._num_pixels_differing = stats['pointsOfInterest'] - elif differ_name == 'perceptual': - perceptual_similarity = stats['result'] - - # skpdiff returns the perceptual similarity; convert it to get the - # perceptual difference percentage. - # skpdiff outputs -1 if the images are different sizes. Treat any - # output that does not lie in [0, 1] as having 0% perceptual - # similarity. - if not 0 <= perceptual_similarity <= 1: - perceptual_similarity = 0 - self._perceptual_difference = 100 - (perceptual_similarity * 100) - finally: - shutil.rmtree(skpdiff_output_dir) - - # TODO(epoger): Use properties instead of getters throughout. - # See http://stackoverflow.com/a/6618176 - def get_num_pixels_differing(self): - """Returns the absolute number of pixels that differ.""" - return self._num_pixels_differing - - def get_percent_pixels_differing(self): - """Returns the percentage of pixels that differ, as a float between - 0 and 100 (inclusive).""" - return ((float(self._num_pixels_differing) * 100) / - (self._width * self._height)) - - def get_perceptual_difference(self): - """Returns the perceptual difference percentage.""" - return self._perceptual_difference - - def get_max_diff_per_channel(self): - """Returns the maximum difference between the expected and actual images - for each R/G/B channel, as a list.""" - return self._max_diff_per_channel - - def as_dict(self): - """Returns a dictionary representation of this DiffRecord, as needed when - constructing the JSON representation.""" - return { - KEY__DIFFERENCES__NUM_DIFF_PIXELS: self._num_pixels_differing, - KEY__DIFFERENCES__PERCENT_DIFF_PIXELS: - self.get_percent_pixels_differing(), - KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL: self._max_diff_per_channel, - KEY__DIFFERENCES__PERCEPTUAL_DIFF: self._perceptual_difference, - KEY__DIFFERENCES__DIFF_URL: self._diffUrl, - KEY__DIFFERENCES__WHITE_DIFF_URL: self._whiteDiffUrl, - } - - - -class ImageDiffDB(object): - """ Calculates differences between image pairs, maintaining a database of - them for download.""" - - def __init__(self, storage_root, gs=None, - num_worker_threads=DEFAULT_NUM_WORKER_THREADS): - """ - Args: - storage_root: string; root path within the DB will store all of its stuff - gs: instance of GSUtils object we can use to download images - num_worker_threads: how many threads that download images and - generate diffs simultaneously - """ - self._storage_root = storage_root - self._gs = gs - - # Mechanism for reporting queue size periodically. - self._last_queue_size_reported = None - self._queue_size_report_lock = threading.RLock() - - # Dictionary of DiffRecords, keyed by (expected_image_locator, - # actual_image_locator) tuples. - # Values can also be _DIFFRECORD_PENDING, _DIFFRECORD_FAILED. - # - # Any thread that modifies _diff_dict must first acquire - # _diff_dict_writelock! - # - # TODO(epoger): Disk is limitless, but RAM is not... so, we should probably - # remove items from self._diff_dict if they haven't been accessed for a - # long time. We can always regenerate them by diffing the images we - # previously downloaded to local disk. - # I guess we should figure out how expensive it is to download vs diff the - # image pairs... if diffing them is expensive too, we can write these - # _diff_dict objects out to disk if there's too many to hold in RAM. - # Or we could use virtual memory to handle that automatically. - self._diff_dict = {} - self._diff_dict_writelock = threading.RLock() - - # Set up the queue for asynchronously loading DiffRecords, and start the - # worker threads reading from it. - # The queue maxsize must be 0 (infinite size queue), so that asynchronous - # calls can return as soon as possible. - self._tasks_queue = Queue.Queue(maxsize=0) - self._workers = [] - for i in range(num_worker_threads): - worker = threading.Thread(target=self.worker, args=(i,)) - worker.daemon = True - worker.start() - self._workers.append(worker) - - def log_queue_size_if_changed(self, limit_verbosity=True): - """Log the size of self._tasks_queue, if it has changed since the last call. - - Reports the current queue size, using log.info(), unless the queue is the - same size as the last time we reported it. - - Args: - limit_verbosity: if True, only log if the queue size is a multiple of - QUEUE_LOGGING_GRANULARITY - """ - # Acquire the lock, to synchronize access to self._last_queue_size_reported - self._queue_size_report_lock.acquire() - try: - size = self._tasks_queue.qsize() - if size == self._last_queue_size_reported: - return - if limit_verbosity and (size % QUEUE_LOGGING_GRANULARITY != 0): - return - logging.info('tasks_queue size is %d' % size) - self._last_queue_size_reported = size - finally: - self._queue_size_report_lock.release() - - def worker(self, worker_num): - """Launch a worker thread that pulls tasks off self._tasks_queue. - - Args: - worker_num: (integer) which worker this is - """ - while True: - self.log_queue_size_if_changed() - params = self._tasks_queue.get() - key, expected_image_url, actual_image_url = params - try: - diff_record = DiffRecord( - self._gs, self._storage_root, - expected_image_url=expected_image_url, - expected_image_locator=key[0], - actual_image_url=actual_image_url, - actual_image_locator=key[1]) - except Exception: - logging.exception( - 'exception while creating DiffRecord for key %s' % str(key)) - diff_record = _DIFFRECORD_FAILED - self._diff_dict_writelock.acquire() - try: - self._diff_dict[key] = diff_record - finally: - self._diff_dict_writelock.release() - - @property - def storage_root(self): - return self._storage_root - - def add_image_pair(self, - expected_image_url, expected_image_locator, - actual_image_url, actual_image_locator): - """Asynchronously prepare a DiffRecord for a pair of images. - - This method will return quickly; calls to get_diff_record() will block - until the DiffRecord is available (or we have given up on creating it). - - If we already have a DiffRecord for this particular image pair, no work - will be done. - - If expected_image_url (or its locator) is None, just download actual_image. - If actual_image_url (or its locator) is None, just download expected_image. - - Args: - expected_image_url: file, GS, or HTTP url from which we will download the - expected image - expected_image_locator: a unique ID string under which we will store the - expected image within storage_root (probably including a checksum to - guarantee uniqueness) - actual_image_url: file, GS, or HTTP url from which we will download the - actual image - actual_image_locator: a unique ID string under which we will store the - actual image within storage_root (probably including a checksum to - guarantee uniqueness) - """ - expected_image_locator = _sanitize_locator(expected_image_locator) - actual_image_locator = _sanitize_locator(actual_image_locator) - key = (expected_image_locator, actual_image_locator) - must_add_to_queue = False - - self._diff_dict_writelock.acquire() - try: - if not key in self._diff_dict: - # If we have already requested a diff between these two images, - # we don't need to request it again. - must_add_to_queue = True - self._diff_dict[key] = _DIFFRECORD_PENDING - finally: - self._diff_dict_writelock.release() - - if must_add_to_queue: - self._tasks_queue.put((key, expected_image_url, actual_image_url)) - self.log_queue_size_if_changed() - - def get_diff_record(self, expected_image_locator, actual_image_locator): - """Returns the DiffRecord for this image pair. - - This call will block until the diff record is available, or we were unable - to generate it. - - Args: - expected_image_locator: a unique ID string under which we will store the - expected image within storage_root (probably including a checksum to - guarantee uniqueness) - actual_image_locator: a unique ID string under which we will store the - actual image within storage_root (probably including a checksum to - guarantee uniqueness) - - Returns the DiffRecord for this image pair, or None if we were unable to - generate one. - """ - key = (_sanitize_locator(expected_image_locator), - _sanitize_locator(actual_image_locator)) - diff_record = self._diff_dict[key] - - # If we have no results yet, block until we do. - while diff_record == _DIFFRECORD_PENDING: - time.sleep(1) - diff_record = self._diff_dict[key] - - # Once we have the result... - if diff_record == _DIFFRECORD_FAILED: - logging.error( - 'failed to create a DiffRecord for expected_image_locator=%s , ' - 'actual_image_locator=%s' % ( - expected_image_locator, actual_image_locator)) - return None - else: - return diff_record - - -# Utility functions - -def _download_file(gs, local_filepath, url): - """Download a file from url to local_filepath, unless it is already there. - - Args: - gs: instance of GSUtils object, in case the url points at Google Storage - local_filepath: path on local disk where the image should be stored - url: HTTP or GS URL from which we can download the image if we don't have - it yet - """ - global global_file_collisions - if not os.path.exists(local_filepath): - _mkdir_unless_exists(os.path.dirname(local_filepath)) - - # First download the file contents into a unique filename, and - # then rename that file. That way, if multiple threads are downloading - # the same filename at the same time, they won't interfere with each - # other (they will both download the file, and one will "win" in the end) - temp_filename = '%s-%d' % (local_filepath, - threading.current_thread().ident) - if gs_utils.GSUtils.is_gs_url(url): - (bucket, path) = gs_utils.GSUtils.split_gs_url(url) - gs.download_file(source_bucket=bucket, source_path=path, - dest_path=temp_filename) - else: - with contextlib.closing(urllib.urlopen(url)) as url_handle: - with open(temp_filename, 'wb') as file_handle: - shutil.copyfileobj(fsrc=url_handle, fdst=file_handle) - - # Rename the file to its real filename. - # Keep count of how many colliding downloads we encounter; - # if it's a large number, we may want to change our download strategy - # to minimize repeated downloads. - if os.path.exists(local_filepath): - global_file_collisions += 1 - else: - os.rename(temp_filename, local_filepath) - - -def _mkdir_unless_exists(path): - """Unless path refers to an already-existing directory, create it. - - Args: - path: path on local disk - """ - try: - os.makedirs(path) - except OSError as e: - if e.errno == errno.EEXIST: - pass - - -def _sanitize_locator(locator): - """Returns a sanitized version of a locator (one in which we know none of the - characters will have special meaning in filenames). - - Args: - locator: string, or something that can be represented as a string. - If None or '', it is returned without modification, because empty - locators have a particular meaning ("there is no image for this") - """ - if locator: - return DISALLOWED_FILEPATH_CHAR_REGEX.sub('_', str(locator)) - else: - return locator diff --git a/gm/rebaseline_server/imagediffdb_test.py b/gm/rebaseline_server/imagediffdb_test.py deleted file mode 100755 index 186b2f1324..0000000000 --- a/gm/rebaseline_server/imagediffdb_test.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2013 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test imagediffdb.py -""" - -# System-level imports -import shutil -import tempfile -import unittest - -# Local imports -import imagediffdb - - -IMG_URL_BASE = ('http://chromium-skia-gm.commondatastorage.googleapis.com/gm/' - 'bitmap-64bitMD5/') - - -class ImageDiffDbTest(unittest.TestCase): - - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - self.maxDiff = None - - def tearDown(self): - shutil.rmtree(self.temp_dir) - - def shortDescription(self): - """Tell unittest framework to not print docstrings for test cases.""" - return None - - def test_sanitize_locator(self): - """Test _sanitize_locator().""" - # pylint: disable=W0212 - self.assertEqual(imagediffdb._sanitize_locator('simple'), 'simple') - self.assertEqual(imagediffdb._sanitize_locator(1234), '1234') - self.assertEqual(imagediffdb._sanitize_locator('one/two'), 'one_two') - self.assertEqual(imagediffdb._sanitize_locator('one\\two'), 'one_two') - self.assertEqual(imagediffdb._sanitize_locator('one_two'), 'one_two') - - def test_simple(self): - """Test ImageDiffDB, downloading real known images from Google Storage. - - TODO(epoger): Instead of hitting Google Storage, we should read image - files from local disk using a file:// IMG_URL_BASE. - """ - # params for each self-test: - # 0. expected image locator - # 1. expected image URL - # 2. actual image locator - # 3. actual image URL - # 4. expected percent_pixels_differing (as a string, to 4 decimal places) - # 5. expected perceptual difference (as a string, to 4 decimal places) - # 6. expected max_diff_per_channel - selftests = [ - [ - 'arcofzorro/16206093933823793653', - IMG_URL_BASE + 'arcofzorro/16206093933823793653.png', - 'arcofzorro/13786535001616823825', - IMG_URL_BASE + 'arcofzorro/13786535001616823825.png', - '0.0662', '0.0662', [255, 255, 247], - ], - [ - 'gradients_degenerate_2pt/10552995703607727960', - IMG_URL_BASE + 'gradients_degenerate_2pt/10552995703607727960.png', - 'gradients_degenerate_2pt/11198253335583713230', - IMG_URL_BASE + 'gradients_degenerate_2pt/11198253335583713230.png', - '100.0000', '100.0000', [255, 0, 255], - ], - ] - - # Add all image pairs to the database - db = imagediffdb.ImageDiffDB(self.temp_dir) - for selftest in selftests: - db.add_image_pair( - expected_image_locator=selftest[0], expected_image_url=selftest[1], - actual_image_locator=selftest[2], actual_image_url=selftest[3]) - - # Fetch each image pair from the database - for selftest in selftests: - record = db.get_diff_record(expected_image_locator=selftest[0], - actual_image_locator=selftest[2]) - self.assertEqual('%.4f' % record.get_percent_pixels_differing(), - selftest[4]) - self.assertEqual('%.4f' % record.get_perceptual_difference(), selftest[5]) - self.assertEqual(record.get_max_diff_per_channel(), selftest[6]) - - -def main(): - suite = unittest.TestLoader().loadTestsFromTestCase(ImageDiffDbTest) - unittest.TextTestRunner(verbosity=2).run(suite) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/imagepair.py b/gm/rebaseline_server/imagepair.py deleted file mode 100644 index e85c21948b..0000000000 --- a/gm/rebaseline_server/imagepair.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -ImagePair class (see class docstring for details) -""" - -import posixpath - - -# Keys used within ImagePair dictionary representations. -# NOTE: Keep these in sync with static/constants.js -KEY__IMAGEPAIRS__DIFFERENCES = 'differenceData' -KEY__IMAGEPAIRS__EXPECTATIONS = 'expectations' -KEY__IMAGEPAIRS__EXTRACOLUMNS = 'extraColumns' -KEY__IMAGEPAIRS__IMAGE_A_URL = 'imageAUrl' -KEY__IMAGEPAIRS__IMAGE_B_URL = 'imageBUrl' -KEY__IMAGEPAIRS__IS_DIFFERENT = 'isDifferent' -KEY__IMAGEPAIRS__SOURCE_JSON_FILE = 'sourceJsonFile' - -# If self._diff_record is set to this, we haven't asked ImageDiffDB for the -# image diff details yet. -_DIFF_RECORD_STILL_LOADING = 'still_loading' - - -class ImagePair(object): - """Describes a pair of images, pixel difference info, and optional metadata. - """ - - def __init__(self, image_diff_db, - imageA_base_url, imageB_base_url, - imageA_relative_url, imageB_relative_url, - expectations=None, extra_columns=None, source_json_file=None, - download_all_images=False): - """ - Args: - image_diff_db: ImageDiffDB instance we use to generate/store image diffs - imageA_base_url: string; base URL for image A - imageB_base_url: string; base URL for image B - imageA_relative_url: string; URL pointing at an image, relative to - imageA_base_url; or None, if this image is missing - imageB_relative_url: string; URL pointing at an image, relative to - imageB_base_url; or None, if this image is missing - expectations: optional dictionary containing expectations-specific - metadata (ignore-failure, bug numbers, etc.) - extra_columns: optional dictionary containing more metadata (test name, - builder name, etc.) - source_json_file: relative path of the JSON file where each image came - from; this will be the same for both imageA and imageB, within their - respective directories - download_all_images: if True, download any images associated with this - image pair, even if we don't need them to generate diffs - (imageA == imageB, or one of them is missing) - """ - self._image_diff_db = image_diff_db - self.imageA_base_url = imageA_base_url - self.imageB_base_url = imageB_base_url - self.imageA_relative_url = imageA_relative_url - self.imageB_relative_url = imageB_relative_url - self.expectations_dict = expectations - self.extra_columns_dict = extra_columns - self.source_json_file = source_json_file - if not imageA_relative_url or not imageB_relative_url: - self._is_different = True - self._diff_record = None - elif imageA_relative_url == imageB_relative_url: - self._is_different = False - self._diff_record = None - else: - # Tell image_diff_db to add an entry for this diff asynchronously. - # Later on, we will call image_diff_db.get_diff_record() to find it. - self._is_different = True - self._diff_record = _DIFF_RECORD_STILL_LOADING - - if self._diff_record != None or download_all_images: - image_diff_db.add_image_pair( - expected_image_locator=imageA_relative_url, - expected_image_url=self.posixpath_join(imageA_base_url, - imageA_relative_url), - actual_image_locator=imageB_relative_url, - actual_image_url=self.posixpath_join(imageB_base_url, - imageB_relative_url)) - - def as_dict(self): - """Returns a dictionary describing this ImagePair. - - Uses the KEY__IMAGEPAIRS__* constants as keys. - """ - asdict = { - KEY__IMAGEPAIRS__IMAGE_A_URL: self.imageA_relative_url, - KEY__IMAGEPAIRS__IMAGE_B_URL: self.imageB_relative_url, - } - asdict[KEY__IMAGEPAIRS__IS_DIFFERENT] = self._is_different - if self.expectations_dict: - asdict[KEY__IMAGEPAIRS__EXPECTATIONS] = self.expectations_dict - if self.extra_columns_dict: - asdict[KEY__IMAGEPAIRS__EXTRACOLUMNS] = self.extra_columns_dict - if self.source_json_file: - asdict[KEY__IMAGEPAIRS__SOURCE_JSON_FILE] = self.source_json_file - if self._diff_record is _DIFF_RECORD_STILL_LOADING: - # We have waited as long as we can to ask ImageDiffDB for details of - # this image diff. Now we must block until ImageDiffDB can provide - # those details. - # - # TODO(epoger): Is it wasteful for every imagepair to have its own - # reference to image_diff_db? If so, we could pass an image_diff_db - # reference into this method call instead... - self._diff_record = self._image_diff_db.get_diff_record( - expected_image_locator=self.imageA_relative_url, - actual_image_locator=self.imageB_relative_url) - if self._diff_record != None: - asdict[KEY__IMAGEPAIRS__DIFFERENCES] = self._diff_record.as_dict() - return asdict - - @staticmethod - def posixpath_join(*args): - """Wrapper around posixpath.join(). - - Returns posixpath.join(*args), or None if any arg is None. - """ - for arg in args: - if arg == None: - return None - return posixpath.join(*args) diff --git a/gm/rebaseline_server/imagepair_test.py b/gm/rebaseline_server/imagepair_test.py deleted file mode 100755 index 773f6a376c..0000000000 --- a/gm/rebaseline_server/imagepair_test.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test imagepair.py -""" - -# System-level imports -import shutil -import tempfile -import unittest - -# Local imports -import imagediffdb -import imagepair - - -IMG_URL_BASE = ('http://chromium-skia-gm.commondatastorage.googleapis.com/' - 'gm/bitmap-64bitMD5/') - - -class ImagePairTest(unittest.TestCase): - - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - self.maxDiff = None - - def tearDown(self): - shutil.rmtree(self.temp_dir) - - def shortDescription(self): - """Tells unittest framework to not print docstrings for test cases.""" - return None - - def test_endToEnd(self): - """Tests ImagePair, using a real ImageDiffDB to download real images. - - TODO(epoger): Either in addition to or instead of this end-to-end test, - we should perform some tests using either: - 1. a mock ImageDiffDB, or - 2. a real ImageDiffDB that doesn't hit Google Storage looking for input - image files (maybe a file:// IMG_URL_BASE) - """ - # params for each self-test: - # - # inputs: - # 0. imageA_relative_URL - # 1. imageB_relative_URL - # 2. expectations dict - # 3. extra_columns dict - # expected output: - # 4. expected result of ImagePair.as_dict() - selftests = [ - [ - # inputs: - 'arcofzorro/16206093933823793653.png', - 'arcofzorro/16206093933823793653.png', - None, - { - 'builder': 'MyBuilder', - 'test': 'MyTest', - }, - # expected output: - { - 'extraColumns': { - 'builder': 'MyBuilder', - 'test': 'MyTest', - }, - 'imageAUrl': 'arcofzorro/16206093933823793653.png', - 'imageBUrl': 'arcofzorro/16206093933823793653.png', - 'isDifferent': False, - }, - ], - - [ - # inputs: - 'arcofzorro/16206093933823793653.png', - 'arcofzorro/13786535001616823825.png', - None, - None, - # expected output: - { - 'differenceData': { - 'maxDiffPerChannel': [255, 255, 247], - 'numDifferingPixels': 662, - 'percentDifferingPixels': 0.0662, - 'perceptualDifference': 0.06620300000000157, - 'diffUrl': 'arcofzorro_16206093933823793653_png_png-vs-' + - 'arcofzorro_13786535001616823825_png_png.png', - 'whiteDiffUrl': 'arcofzorro_16206093933823793653_png_png' + - '-vs-arcofzorro_13786535001616823825_png_png.png', - }, - 'imageAUrl': 'arcofzorro/16206093933823793653.png', - 'imageBUrl': 'arcofzorro/13786535001616823825.png', - 'isDifferent': True, - }, - ], - - [ - # inputs: - 'gradients_degenerate_2pt/10552995703607727960.png', - 'gradients_degenerate_2pt/11198253335583713230.png', - { - 'ignoreFailure': True, - 'bugs': [1001, 1002], - }, - { - 'builder': 'MyBuilder', - 'test': 'MyTest', - }, - # expected output: - { - 'differenceData': { - 'maxDiffPerChannel': [255, 0, 255], - 'numDifferingPixels': 102400, - 'percentDifferingPixels': 100.00, - 'perceptualDifference': 100.00, - 'diffUrl': 'gradients_degenerate_2pt_10552995703607727960' + - '_png_png-vs-gradients_degenerate_2pt_' + - '11198253335583713230_png_png.png', - 'whiteDiffUrl': 'gradients_degenerate_2pt_' + - '10552995703607727960_png_png-vs-' + - 'gradients_degenerate_2pt_11198253335583713230' + - '_png_png.png' - }, - 'expectations': { - 'bugs': [1001, 1002], - 'ignoreFailure': True, - }, - 'extraColumns': { - 'builder': 'MyBuilder', - 'test': 'MyTest', - }, - 'imageAUrl': - 'gradients_degenerate_2pt/10552995703607727960.png', - 'imageBUrl': - 'gradients_degenerate_2pt/11198253335583713230.png', - 'isDifferent': True, - }, - ], - - # Test fix for http://skbug.com/2368 -- how do we handle an ImagePair - # missing one of its images? - [ - # inputs: - 'arcofzorro/16206093933823793653.png', - 'nonexistentDir/111111.png', - { - 'ignoreFailure': True, - 'bugs': [1001, 1002], - }, - { - 'builder': 'MyBuilder', - 'test': 'MyTest', - }, - # expected output: - { - 'expectations': { - 'bugs': [1001, 1002], - 'ignoreFailure': True, - }, - 'extraColumns': { - 'builder': 'MyBuilder', - 'test': 'MyTest', - }, - 'imageAUrl': 'arcofzorro/16206093933823793653.png', - 'imageBUrl': 'nonexistentDir/111111.png', - 'isDifferent': True, - }, - ], - - # One of the two images is missing, but download_all_images=True so we - # should download it anyway. - [ - # inputs: - None, - 'arcofzorro/13786535001616823825.png', - None, - None, - # expected output: - { - 'imageAUrl': None, - 'imageBUrl': 'arcofzorro/13786535001616823825.png', - 'isDifferent': True, - }, - ], - - ] - - db = imagediffdb.ImageDiffDB(self.temp_dir) - for selftest in selftests: - image_pair = imagepair.ImagePair( - image_diff_db=db, - imageA_base_url=IMG_URL_BASE, - imageB_base_url=IMG_URL_BASE, - imageA_relative_url=selftest[0], - imageB_relative_url=selftest[1], - expectations=selftest[2], - extra_columns=selftest[3], - download_all_images=True) - self.assertEqual(image_pair.as_dict(), selftest[4]) - - -def main(): - suite = unittest.TestLoader().loadTestsFromTestCase(ImagePairTest) - unittest.TextTestRunner(verbosity=2).run(suite) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/imagepairset.py b/gm/rebaseline_server/imagepairset.py deleted file mode 100644 index b492d9f021..0000000000 --- a/gm/rebaseline_server/imagepairset.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -ImagePairSet class; see its docstring below. -""" - -# System-level imports -import posixpath - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -import column -import imagediffdb -from py.utils import gs_utils - -# Keys used within dictionary representation of ImagePairSet. -# NOTE: Keep these in sync with static/constants.js -KEY__ROOT__EXTRACOLUMNHEADERS = 'extraColumnHeaders' -KEY__ROOT__EXTRACOLUMNORDER = 'extraColumnOrder' -KEY__ROOT__HEADER = 'header' -KEY__ROOT__IMAGEPAIRS = 'imagePairs' -KEY__ROOT__IMAGESETS = 'imageSets' -KEY__IMAGESETS__FIELD__BASE_URL = 'baseUrl' -KEY__IMAGESETS__FIELD__DESCRIPTION = 'description' -KEY__IMAGESETS__SET__DIFFS = 'diffs' -KEY__IMAGESETS__SET__IMAGE_A = 'imageA' -KEY__IMAGESETS__SET__IMAGE_B = 'imageB' -KEY__IMAGESETS__SET__WHITEDIFFS = 'whiteDiffs' - -DEFAULT_DESCRIPTIONS = ('setA', 'setB') - - -class ImagePairSet(object): - """A collection of ImagePairs, representing two arbitrary sets of images. - - These could be: - - images generated before and after a code patch - - expected and actual images for some tests - - or any other pairwise set of images. - """ - - def __init__(self, diff_base_url, descriptions=None): - """ - Args: - diff_base_url: base URL indicating where diff images can be loaded from - descriptions: a (string, string) tuple describing the two image sets. - If not specified, DEFAULT_DESCRIPTIONS will be used. - """ - self._column_header_factories = {} - self._descriptions = descriptions or DEFAULT_DESCRIPTIONS - self._extra_column_tallies = {} # maps column_id -> values - # -> instances_per_value - self._imageA_base_url = None - self._imageB_base_url = None - self._diff_base_url = diff_base_url - - # We build self._image_pair_objects incrementally as calls come into - # add_image_pair(); self._image_pair_dicts is filled in lazily (so that - # we put off asking ImageDiffDB for results as long as possible). - self._image_pair_objects = [] - self._image_pair_dicts = None - - def add_image_pair(self, image_pair): - """Adds an ImagePair; this may be repeated any number of times.""" - # Special handling when we add the first ImagePair... - if not self._image_pair_objects: - self._imageA_base_url = image_pair.imageA_base_url - self._imageB_base_url = image_pair.imageB_base_url - - if(image_pair.imageA_base_url != self._imageA_base_url): - raise Exception('added ImagePair with base_url "%s" instead of "%s"' % ( - image_pair.imageA_base_url, self._imageA_base_url)) - if(image_pair.imageB_base_url != self._imageB_base_url): - raise Exception('added ImagePair with base_url "%s" instead of "%s"' % ( - image_pair.imageB_base_url, self._imageB_base_url)) - self._image_pair_objects.append(image_pair) - extra_columns_dict = image_pair.extra_columns_dict - if extra_columns_dict: - for column_id, value in extra_columns_dict.iteritems(): - self._add_extra_column_value_to_summary(column_id, value) - - def set_column_header_factory(self, column_id, column_header_factory): - """Overrides the default settings for one of the extraColumn headers. - - Args: - column_id: string; unique ID of this column (must match a key within - an ImagePair's extra_columns dictionary) - column_header_factory: a ColumnHeaderFactory object - """ - self._column_header_factories[column_id] = column_header_factory - - def get_column_header_factory(self, column_id): - """Returns the ColumnHeaderFactory object for a particular extraColumn. - - Args: - column_id: string; unique ID of this column (must match a key within - an ImagePair's extra_columns dictionary) - """ - column_header_factory = self._column_header_factories.get(column_id, None) - if not column_header_factory: - column_header_factory = column.ColumnHeaderFactory(header_text=column_id) - self._column_header_factories[column_id] = column_header_factory - return column_header_factory - - def ensure_extra_column_values_in_summary(self, column_id, values): - """Ensure this column_id/value pair is part of the extraColumns summary. - - Args: - column_id: string; unique ID of this column - value: string; a possible value for this column - """ - for value in values: - self._add_extra_column_value_to_summary( - column_id=column_id, value=value, addend=0) - - def _add_extra_column_value_to_summary(self, column_id, value, addend=1): - """Records one column_id/value extraColumns pair found within an ImagePair. - - We use this information to generate tallies within the column header - (how many instances we saw of a particular value, within a particular - extraColumn). - - Args: - column_id: string; unique ID of this column (must match a key within - an ImagePair's extra_columns dictionary) - value: string; a possible value for this column - addend: integer; how many instances to add to the tally - """ - known_values_for_column = self._extra_column_tallies.get(column_id, None) - if not known_values_for_column: - known_values_for_column = {} - self._extra_column_tallies[column_id] = known_values_for_column - instances_of_this_value = known_values_for_column.get(value, 0) - instances_of_this_value += addend - known_values_for_column[value] = instances_of_this_value - - def _column_headers_as_dict(self): - """Returns all column headers as a dictionary.""" - asdict = {} - for column_id, values_for_column in self._extra_column_tallies.iteritems(): - column_header_factory = self.get_column_header_factory(column_id) - asdict[column_id] = column_header_factory.create_as_dict( - values_for_column) - return asdict - - def as_dict(self, column_ids_in_order=None): - """Returns a dictionary describing this package of ImagePairs. - - Uses the KEY__* constants as keys. - - Args: - column_ids_in_order: A list of all extracolumn IDs in the desired display - order. If unspecified, they will be displayed in alphabetical order. - If specified, this list must contain all the extracolumn IDs! - (It may contain extra column IDs; they will be ignored.) - """ - all_column_ids = set(self._extra_column_tallies.keys()) - if column_ids_in_order == None: - column_ids_in_order = sorted(all_column_ids) - else: - # Make sure the caller listed all column IDs, and throw away any extras. - specified_column_ids = set(column_ids_in_order) - forgotten_column_ids = all_column_ids - specified_column_ids - assert not forgotten_column_ids, ( - 'column_ids_in_order %s missing these column_ids: %s' % ( - column_ids_in_order, forgotten_column_ids)) - column_ids_in_order = [c for c in column_ids_in_order - if c in all_column_ids] - - key_description = KEY__IMAGESETS__FIELD__DESCRIPTION - key_base_url = KEY__IMAGESETS__FIELD__BASE_URL - if gs_utils.GSUtils.is_gs_url(self._imageA_base_url): - valueA_base_url = self._convert_gs_url_to_http_url(self._imageA_base_url) - else: - valueA_base_url = self._imageA_base_url - if gs_utils.GSUtils.is_gs_url(self._imageB_base_url): - valueB_base_url = self._convert_gs_url_to_http_url(self._imageB_base_url) - else: - valueB_base_url = self._imageB_base_url - - # We've waited as long as we can to ask ImageDiffDB for details of the - # image diffs, so that it has time to compute them. - if self._image_pair_dicts == None: - self._image_pair_dicts = [ip.as_dict() for ip in self._image_pair_objects] - - return { - KEY__ROOT__EXTRACOLUMNHEADERS: self._column_headers_as_dict(), - KEY__ROOT__EXTRACOLUMNORDER: column_ids_in_order, - KEY__ROOT__IMAGEPAIRS: self._image_pair_dicts, - KEY__ROOT__IMAGESETS: { - KEY__IMAGESETS__SET__IMAGE_A: { - key_description: self._descriptions[0], - key_base_url: valueA_base_url, - }, - KEY__IMAGESETS__SET__IMAGE_B: { - key_description: self._descriptions[1], - key_base_url: valueB_base_url, - }, - KEY__IMAGESETS__SET__DIFFS: { - key_description: 'color difference per channel', - key_base_url: posixpath.join( - self._diff_base_url, imagediffdb.RGBDIFFS_SUBDIR), - }, - KEY__IMAGESETS__SET__WHITEDIFFS: { - key_description: 'differing pixels in white', - key_base_url: posixpath.join( - self._diff_base_url, imagediffdb.WHITEDIFFS_SUBDIR), - }, - }, - } - - @staticmethod - def _convert_gs_url_to_http_url(gs_url): - """Returns HTTP URL that can be used to download this Google Storage file. - - TODO(epoger): Create functionality like this within gs_utils.py instead of - here? See https://codereview.chromium.org/428493005/ ('create - anyfile_utils.py for copying files between HTTP/GS/local filesystem') - - Args: - gs_url: "gs://bucket/path" format URL - """ - bucket, path = gs_utils.GSUtils.split_gs_url(gs_url) - http_url = 'http://storage.cloud.google.com/' + bucket - if path: - http_url += '/' + path - return http_url diff --git a/gm/rebaseline_server/imagepairset_test.py b/gm/rebaseline_server/imagepairset_test.py deleted file mode 100755 index a931e047aa..0000000000 --- a/gm/rebaseline_server/imagepairset_test.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test imagepairset.py -""" - -# System-level imports -import unittest - -# Local imports -import column -import imagepair -import imagepairset - - -BASE_URL_1 = 'http://base/url/1' -BASE_URL_2 = 'http://base/url/2' -DIFF_BASE_URL = 'http://diff/base/url' -IMAGEPAIR_1_AS_DICT = { - imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: { - 'builder': 'MyBuilder', - 'test': 'test1', - }, - imagepair.KEY__IMAGEPAIRS__IMAGE_A_URL: 'test1/1111.png', - imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL: 'test1/1111.png', - imagepair.KEY__IMAGEPAIRS__IS_DIFFERENT: False, -} -IMAGEPAIR_2_AS_DICT = { - imagepair.KEY__IMAGEPAIRS__DIFFERENCES: { - 'maxDiffPerChannel': [1, 2, 3], - 'numDifferingPixels': 111, - 'percentDifferingPixels': 22.222, - }, - imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: { - 'builder': 'MyBuilder', - 'test': 'test2', - }, - imagepair.KEY__IMAGEPAIRS__IMAGE_A_URL: 'test2/2222.png', - imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL: 'test2/22223.png', - imagepair.KEY__IMAGEPAIRS__IS_DIFFERENT: True, -} -IMAGEPAIR_3_AS_DICT = { - imagepair.KEY__IMAGEPAIRS__DIFFERENCES: { - 'maxDiffPerChannel': [4, 5, 6], - 'numDifferingPixels': 111, - 'percentDifferingPixels': 44.444, - }, - imagepair.KEY__IMAGEPAIRS__EXPECTATIONS: { - 'bugs': [1001, 1002], - 'ignoreFailure': True, - }, - imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: { - 'builder': 'MyBuilder', - 'test': 'test3', - }, - imagepair.KEY__IMAGEPAIRS__IMAGE_A_URL: 'test3/3333.png', - imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL: 'test3/33334.png', - imagepair.KEY__IMAGEPAIRS__IS_DIFFERENT: True, -} -SET_A_DESCRIPTION = 'expectations' -SET_B_DESCRIPTION = 'actuals' - - -class ImagePairSetTest(unittest.TestCase): - - def setUp(self): - self.maxDiff = None # do not truncate diffs when tests fail - - def shortDescription(self): - """Tells unittest framework to not print docstrings for test cases.""" - return None - - def test_success(self): - """Assembles some ImagePairs into an ImagePairSet, and validates results. - """ - image_pairs = [ - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_1_AS_DICT), - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_2_AS_DICT), - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_3_AS_DICT), - ] - expected_imageset_dict = { - 'extraColumnHeaders': { - 'builder': { - 'headerText': 'builder', - 'isFilterable': True, - 'isSortable': True, - 'useFreeformFilter': False, - 'valuesAndCounts': [('MyBuilder', 3)], - }, - 'test': { - 'headerText': 'which GM test', - 'headerUrl': 'http://learn/about/gm/tests', - 'isFilterable': True, - 'isSortable': False, - 'useFreeformFilter': False, - 'valuesAndCounts': [('test1', 1), - ('test2', 1), - ('test3', 1)], - }, - }, - 'extraColumnOrder': ['builder', 'test'], - 'imagePairs': [ - IMAGEPAIR_1_AS_DICT, - IMAGEPAIR_2_AS_DICT, - IMAGEPAIR_3_AS_DICT, - ], - 'imageSets': { - 'imageA': { - 'baseUrl': BASE_URL_1, - 'description': SET_A_DESCRIPTION, - }, - 'imageB': { - 'baseUrl': BASE_URL_1, - 'description': SET_B_DESCRIPTION, - }, - 'diffs': { - 'baseUrl': DIFF_BASE_URL + '/diffs', - 'description': 'color difference per channel', - }, - 'whiteDiffs': { - 'baseUrl': DIFF_BASE_URL + '/whitediffs', - 'description': 'differing pixels in white', - }, - }, - } - - image_pair_set = imagepairset.ImagePairSet( - descriptions=(SET_A_DESCRIPTION, SET_B_DESCRIPTION), - diff_base_url=DIFF_BASE_URL) - for image_pair in image_pairs: - image_pair_set.add_image_pair(image_pair) - # The 'builder' column header uses the default settings, - # but the 'test' column header has manual adjustments. - image_pair_set.set_column_header_factory( - 'test', - column.ColumnHeaderFactory( - header_text='which GM test', - header_url='http://learn/about/gm/tests', - is_filterable=True, - is_sortable=False)) - self.assertEqual(image_pair_set.as_dict(), expected_imageset_dict) - - def test_mismatched_base_url(self): - """Confirms that mismatched base_urls will cause an exception.""" - image_pair_set = imagepairset.ImagePairSet( - diff_base_url=DIFF_BASE_URL) - image_pair_set.add_image_pair( - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_1_AS_DICT)) - image_pair_set.add_image_pair( - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_2_AS_DICT)) - with self.assertRaises(Exception): - image_pair_set.add_image_pair( - MockImagePair(imageA_base_url=BASE_URL_2, imageB_base_url=BASE_URL_2, - dict_to_return=IMAGEPAIR_3_AS_DICT)) - - def test_missing_column_ids(self): - """Confirms that passing truncated column_ids_in_order to as_dict() - will cause an exception.""" - image_pair_set = imagepairset.ImagePairSet( - diff_base_url=DIFF_BASE_URL) - image_pair_set.add_image_pair( - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_1_AS_DICT)) - image_pair_set.add_image_pair( - MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1, - dict_to_return=IMAGEPAIR_2_AS_DICT)) - # Call as_dict() with default or reasonable column_ids_in_order. - image_pair_set.as_dict() - image_pair_set.as_dict(column_ids_in_order=['test', 'builder']) - image_pair_set.as_dict(column_ids_in_order=['test', 'builder', 'extra']) - # Call as_dict() with not enough column_ids. - with self.assertRaises(Exception): - image_pair_set.as_dict(column_ids_in_order=['builder']) - - -class MockImagePair(object): - """Mock ImagePair object, which will return canned results.""" - def __init__(self, imageA_base_url, imageB_base_url, dict_to_return): - """ - Args: - base_url: base_url attribute for this object - dict_to_return: dictionary to return from as_dict() - """ - self.imageA_base_url = imageA_base_url - self.imageB_base_url = imageB_base_url - self.extra_columns_dict = dict_to_return.get( - imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS, None) - self._dict_to_return = dict_to_return - - def as_dict(self): - return self._dict_to_return - - -def main(): - suite = unittest.TestLoader().loadTestsFromTestCase(ImagePairSetTest) - unittest.TextTestRunner(verbosity=2).run(suite) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/results.py b/gm/rebaseline_server/results.py deleted file mode 100755 index b0027d22af..0000000000 --- a/gm/rebaseline_server/results.py +++ /dev/null @@ -1,343 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2013 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Repackage expected/actual GM results as needed by our HTML rebaseline viewer. -""" - -# System-level imports -import fnmatch -import os -import re - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -import gm_json -import imagepairset - -# Keys used to link an image to a particular GM test. -# NOTE: Keep these in sync with static/constants.js -VALUE__HEADER__SCHEMA_VERSION = 5 -KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS -KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE -KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED -KEY__EXTRACOLUMNS__BUILDER = 'builder' -KEY__EXTRACOLUMNS__CONFIG = 'config' -KEY__EXTRACOLUMNS__RESULT_TYPE = 'resultType' -KEY__EXTRACOLUMNS__TEST = 'test' -KEY__HEADER__DATAHASH = 'dataHash' -KEY__HEADER__IS_EDITABLE = 'isEditable' -KEY__HEADER__IS_EXPORTED = 'isExported' -KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' -KEY__HEADER__RESULTS_ALL = 'all' -KEY__HEADER__RESULTS_FAILURES = 'failures' -KEY__HEADER__SCHEMA_VERSION = 'schemaVersion' -KEY__HEADER__SET_A_DESCRIPTIONS = 'setA' -KEY__HEADER__SET_B_DESCRIPTIONS = 'setB' -KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' -KEY__HEADER__TIME_UPDATED = 'timeUpdated' -KEY__HEADER__TYPE = 'type' -KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED -KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED -KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON -KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED -KEY__SET_DESCRIPTIONS__DIR = 'dir' -KEY__SET_DESCRIPTIONS__REPO_REVISION = 'repoRevision' -KEY__SET_DESCRIPTIONS__SECTION = 'section' - -IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) -IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) - -PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) -DEFAULT_ACTUALS_DIR = '.gm-actuals' -DEFAULT_GENERATED_IMAGES_ROOT = os.path.join( - PARENT_DIRECTORY, '.generated-images') - -# Define the default set of builders we will process expectations/actuals for. -# This allows us to ignore builders for which we don't maintain expectations -# (trybots, Valgrind, ASAN, TSAN), and avoid problems like -# https://code.google.com/p/skia/issues/detail?id=2036 ('rebaseline_server -# produces error when trying to add baselines for ASAN/TSAN builders') -DEFAULT_MATCH_BUILDERS_PATTERN_LIST = ['.*'] -DEFAULT_SKIP_BUILDERS_PATTERN_LIST = [ - '.*-Trybot', '.*Valgrind.*', '.*TSAN.*', '.*ASAN.*'] - - -class BaseComparisons(object): - """Base class for generating summary of comparisons between two image sets. - """ - - def __init__(self): - """Base constructor; most subclasses will override.""" - self._setA_descriptions = None - self._setB_descriptions = None - - def get_results_of_type(self, results_type): - """Return results of some/all tests (depending on 'results_type' parameter). - - Args: - results_type: string describing which types of results to include; must - be one of the RESULTS_* constants - - Results are returned in a dictionary as output by ImagePairSet.as_dict(). - """ - return self._results[results_type] - - def get_packaged_results_of_type(self, results_type, reload_seconds=None, - is_editable=False, is_exported=True): - """Package the results of some/all tests as a complete response_dict. - - Args: - results_type: string indicating which set of results to return; - must be one of the RESULTS_* constants - reload_seconds: if specified, note that new results may be available once - these results are reload_seconds old - is_editable: whether clients are allowed to submit new baselines - is_exported: whether these results are being made available to other - network hosts - """ - response_dict = self._results[results_type] - time_updated = self.get_timestamp() - header_dict = { - KEY__HEADER__SCHEMA_VERSION: ( - VALUE__HEADER__SCHEMA_VERSION), - - # Timestamps: - # 1. when this data was last updated - # 2. when the caller should check back for new data (if ever) - KEY__HEADER__TIME_UPDATED: time_updated, - KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( - (time_updated+reload_seconds) if reload_seconds else None), - - # The type we passed to get_results_of_type() - KEY__HEADER__TYPE: results_type, - - # Hash of dataset, which the client must return with any edits-- - # this ensures that the edits were made to a particular dataset. - KEY__HEADER__DATAHASH: str(hash(repr( - response_dict[imagepairset.KEY__ROOT__IMAGEPAIRS]))), - - # Whether the server will accept edits back. - KEY__HEADER__IS_EDITABLE: is_editable, - - # Whether the service is accessible from other hosts. - KEY__HEADER__IS_EXPORTED: is_exported, - } - if self._setA_descriptions: - header_dict[KEY__HEADER__SET_A_DESCRIPTIONS] = self._setA_descriptions - if self._setB_descriptions: - header_dict[KEY__HEADER__SET_B_DESCRIPTIONS] = self._setB_descriptions - response_dict[imagepairset.KEY__ROOT__HEADER] = header_dict - return response_dict - - def get_timestamp(self): - """Return the time at which this object was created, in seconds past epoch - (UTC). - """ - return self._timestamp - - _match_builders_pattern_list = [ - re.compile(p) for p in DEFAULT_MATCH_BUILDERS_PATTERN_LIST] - _skip_builders_pattern_list = [ - re.compile(p) for p in DEFAULT_SKIP_BUILDERS_PATTERN_LIST] - - def set_match_builders_pattern_list(self, pattern_list): - """Override the default set of builders we should process. - - The default is DEFAULT_MATCH_BUILDERS_PATTERN_LIST . - - Note that skip_builders_pattern_list overrides this; regardless of whether a - builder is in the "match" list, if it's in the "skip" list, we will skip it. - - Args: - pattern_list: list of regex patterns; process builders that match any - entry within this list - """ - if pattern_list == None: - pattern_list = [] - self._match_builders_pattern_list = [re.compile(p) for p in pattern_list] - - def set_skip_builders_pattern_list(self, pattern_list): - """Override the default set of builders we should skip while processing. - - The default is DEFAULT_SKIP_BUILDERS_PATTERN_LIST . - - This overrides match_builders_pattern_list; regardless of whether a - builder is in the "match" list, if it's in the "skip" list, we will skip it. - - Args: - pattern_list: list of regex patterns; skip builders that match any - entry within this list - """ - if pattern_list == None: - pattern_list = [] - self._skip_builders_pattern_list = [re.compile(p) for p in pattern_list] - - def _ignore_builder(self, builder): - """Returns True if we should skip processing this builder. - - Args: - builder: name of this builder, as a string - - Returns: - True if we should ignore expectations and actuals for this builder. - """ - for pattern in self._skip_builders_pattern_list: - if pattern.match(builder): - return True - for pattern in self._match_builders_pattern_list: - if pattern.match(builder): - return False - return True - - def _read_builder_dicts_from_root(self, root, pattern='*.json'): - """Read all JSON dictionaries within a directory tree. - - Skips any dictionaries belonging to a builder we have chosen to ignore. - - Args: - root: path to root of directory tree - pattern: which files to read within root (fnmatch-style pattern) - - Returns: - A meta-dictionary containing all the JSON dictionaries found within - the directory tree, keyed by builder name (the basename of the directory - where each JSON dictionary was found). - - Raises: - IOError if root does not refer to an existing directory - """ - # I considered making this call read_dicts_from_root(), but I decided - # it was better to prune out the ignored builders within the os.walk(). - if not os.path.isdir(root): - raise IOError('no directory found at path %s' % root) - meta_dict = {} - for dirpath, _, filenames in os.walk(root): - for matching_filename in fnmatch.filter(filenames, pattern): - builder = os.path.basename(dirpath) - if self._ignore_builder(builder): - continue - full_path = os.path.join(dirpath, matching_filename) - meta_dict[builder] = gm_json.LoadFromFile(full_path) - return meta_dict - - @staticmethod - def read_dicts_from_root(root, pattern='*.json'): - """Read all JSON dictionaries within a directory tree. - - TODO(stephana): Factor this out into a utility module, as a standalone - function (not part of a class). - - Args: - root: path to root of directory tree - pattern: which files to read within root (fnmatch-style pattern) - - Returns: - A meta-dictionary containing all the JSON dictionaries found within - the directory tree, keyed by the pathname (relative to root) of each JSON - dictionary. - - Raises: - IOError if root does not refer to an existing directory - """ - if not os.path.isdir(root): - raise IOError('no directory found at path %s' % root) - meta_dict = {} - for abs_dirpath, _, filenames in os.walk(root): - rel_dirpath = os.path.relpath(abs_dirpath, root) - for matching_filename in fnmatch.filter(filenames, pattern): - abs_path = os.path.join(abs_dirpath, matching_filename) - rel_path = os.path.join(rel_dirpath, matching_filename) - meta_dict[rel_path] = gm_json.LoadFromFile(abs_path) - return meta_dict - - @staticmethod - def _read_noncomment_lines(path): - """Return a list of all noncomment lines within a file. - - (A "noncomment" line is one that does not start with a '#'.) - - Args: - path: path to file - """ - lines = [] - with open(path, 'r') as fh: - for line in fh: - if not line.startswith('#'): - lines.append(line.strip()) - return lines - - @staticmethod - def _create_relative_url(hashtype_and_digest, test_name): - """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL. - - If we don't have a record of this image, returns None. - - Args: - hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we - don't have a record of this image - test_name: string; name of the GM test that created this image - """ - if not hashtype_and_digest: - return None - return gm_json.CreateGmRelativeUrl( - test_name=test_name, - hash_type=hashtype_and_digest[0], - hash_digest=hashtype_and_digest[1]) - - @staticmethod - def combine_subdicts(input_dict): - """ Flatten out a dictionary structure by one level. - - Input: - { - KEY_A1 : { - KEY_B1 : VALUE_B1, - }, - KEY_A2 : { - KEY_B2 : VALUE_B2, - } - } - - Output: - { - KEY_B1 : VALUE_B1, - KEY_B2 : VALUE_B2, - } - - If this would result in any repeated keys, it will raise an Exception. - """ - output_dict = {} - for subdict in input_dict.values(): - for subdict_key, subdict_value in subdict.iteritems(): - if subdict_key in output_dict: - raise Exception('duplicate key %s in combine_subdicts' % subdict_key) - output_dict[subdict_key] = subdict_value - return output_dict - - @staticmethod - def get_default(input_dict, default_value, *keys): - """Returns input_dict[key1][key2][...], or default_value. - - If input_dict is None, or any key is missing along the way, this returns - default_value. - - Args: - input_dict: dictionary to look within - key: key indicating which value to return from input_dict - default_value: value to return if input_dict is None or any key cannot - be found along the way - """ - if input_dict == None: - return default_value - for key in keys: - input_dict = input_dict.get(key, None) - if input_dict == None: - return default_value - return input_dict diff --git a/gm/rebaseline_server/results_test.py b/gm/rebaseline_server/results_test.py deleted file mode 100755 index f22e833fe3..0000000000 --- a/gm/rebaseline_server/results_test.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Test results.py - -""" - -# Imports from within Skia -import base_unittest -import results - - -class ResultsTest(base_unittest.TestCase): - - def test_ignore_builder(self): - """Test _ignore_builder().""" - results_obj = results.BaseComparisons() - self.assertEqual(results_obj._ignore_builder('SomethingTSAN'), True) - self.assertEqual(results_obj._ignore_builder('Something-Trybot'), True) - self.assertEqual(results_obj._ignore_builder( - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release'), False) - results_obj.set_skip_builders_pattern_list(['.*TSAN.*', '.*GTX660.*']) - self.assertEqual(results_obj._ignore_builder('SomethingTSAN'), True) - self.assertEqual(results_obj._ignore_builder('Something-Trybot'), False) - self.assertEqual(results_obj._ignore_builder( - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release'), True) - results_obj.set_skip_builders_pattern_list(None) - self.assertEqual(results_obj._ignore_builder('SomethingTSAN'), False) - self.assertEqual(results_obj._ignore_builder('Something-Trybot'), False) - self.assertEqual(results_obj._ignore_builder( - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release'), False) - results_obj.set_match_builders_pattern_list(['.*TSAN']) - self.assertEqual(results_obj._ignore_builder('SomethingTSAN'), False) - self.assertEqual(results_obj._ignore_builder('Something-Trybot'), True) - self.assertEqual(results_obj._ignore_builder( - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release'), True) - - def test_combine_subdicts_typical(self): - """Test combine_subdicts() with no merge conflicts. """ - input_dict = { - "failed" : { - "changed.png" : [ "bitmap-64bitMD5", 8891695120562235492 ], - }, - "no-comparison" : { - "unchanged.png" : [ "bitmap-64bitMD5", 11092453015575919668 ], - } - } - expected_output_dict = { - "changed.png" : [ "bitmap-64bitMD5", 8891695120562235492 ], - "unchanged.png" : [ "bitmap-64bitMD5", 11092453015575919668 ], - } - actual_output_dict = results.BaseComparisons.combine_subdicts( - input_dict=input_dict) - self.assertEqual(actual_output_dict, expected_output_dict) - - def test_combine_subdicts_with_merge_conflict(self): - """Test combine_subdicts() with a merge conflict. """ - input_dict = { - "failed" : { - "changed.png" : [ "bitmap-64bitMD5", 8891695120562235492 ], - }, - "no-comparison" : { - "changed.png" : [ "bitmap-64bitMD5", 11092453015575919668 ], - } - } - with self.assertRaises(Exception): - actual_output_dict = results.BaseComparisons.combine_subdicts( - input_dict=input_dict) - - -def main(): - base_unittest.main(ResultsTest) - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/rs_fixpypath.py b/gm/rebaseline_server/rs_fixpypath.py deleted file mode 100755 index cc32f4a6bc..0000000000 --- a/gm/rebaseline_server/rs_fixpypath.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Adds possibly-needed directories to PYTHONPATH, if they aren't already there. -""" - -import os -import sys - -TRUNK_DIRECTORY = os.path.abspath(os.path.join( - os.path.dirname(__file__), os.pardir, os.pardir)) -for subdir in ['common', 'gm', 'tools']: - fullpath = os.path.join(TRUNK_DIRECTORY, subdir) - if fullpath not in sys.path: - sys.path.append(fullpath) diff --git a/gm/rebaseline_server/server.py b/gm/rebaseline_server/server.py deleted file mode 100755 index 85874eb666..0000000000 --- a/gm/rebaseline_server/server.py +++ /dev/null @@ -1,967 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2013 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -HTTP server for our HTML rebaseline viewer. -""" - -# System-level imports -import argparse -import BaseHTTPServer -import json -import logging -import os -import posixpath -import re -import shutil -import socket -import subprocess -import thread -import threading -import time -import urllib -import urlparse - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import gs_utils -import buildbot_globals -import gm_json - -# Imports from local dir -# -# pylint: disable=C0301 -# Note: we import results under a different name, to avoid confusion with the -# Server.results() property. See discussion at -# https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.py#newcode44 -# pylint: enable=C0301 -import compare_configs -import compare_rendered_pictures -import compare_to_expectations -import download_actuals -import imagediffdb -import imagepairset -import results as results_mod -import writable_expectations as writable_expectations_mod - - -PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') - -# A simple dictionary of file name extensions to MIME types. The empty string -# entry is used as the default when no extension was given or if the extension -# has no entry in this dictionary. -MIME_TYPE_MAP = {'': 'application/octet-stream', - 'html': 'text/html', - 'css': 'text/css', - 'png': 'image/png', - 'js': 'application/javascript', - 'json': 'application/json' - } - -# Keys that server.py uses to create the toplevel content header. -# NOTE: Keep these in sync with static/constants.js -KEY__EDITS__MODIFICATIONS = 'modifications' -KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' -KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' -KEY__LIVE_EDITS__MODIFICATIONS = 'modifications' -KEY__LIVE_EDITS__SET_A_DESCRIPTIONS = 'setA' -KEY__LIVE_EDITS__SET_B_DESCRIPTIONS = 'setB' - -DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR -DEFAULT_GM_SUMMARIES_BUCKET = download_actuals.GM_SUMMARIES_BUCKET -DEFAULT_JSON_FILENAME = download_actuals.DEFAULT_JSON_FILENAME -DEFAULT_PORT = 8888 - -PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) -TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) - -# Directory, relative to PARENT_DIRECTORY, within which the server will serve -# out static files. -STATIC_CONTENTS_SUBDIR = 'static' -# All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR -GENERATED_HTML_SUBDIR = 'generated-html' -GENERATED_IMAGES_SUBDIR = 'generated-images' -GENERATED_JSON_SUBDIR = 'generated-json' - -# Directives associated with various HTTP GET requests. -GET__LIVE_RESULTS = 'live-results' -GET__PRECOMPUTED_RESULTS = 'results' -GET__PREFETCH_RESULTS = 'prefetch' -GET__STATIC_CONTENTS = 'static' - -# Parameters we use within do_GET_live_results() and do_GET_prefetch_results() -LIVE_PARAM__DOWNLOAD_ONLY_DIFFERING = 'downloadOnlyDifferingImages' -LIVE_PARAM__SET_A_DIR = 'setADir' -LIVE_PARAM__SET_A_SECTION = 'setASection' -LIVE_PARAM__SET_B_DIR = 'setBDir' -LIVE_PARAM__SET_B_SECTION = 'setBSection' - -# How often (in seconds) clients should reload while waiting for initial -# results to load. -RELOAD_INTERVAL_UNTIL_READY = 10 - -_GM_SUMMARY_TYPES = [ - results_mod.KEY__HEADER__RESULTS_FAILURES, - results_mod.KEY__HEADER__RESULTS_ALL, -] -# If --compare-configs is specified, compare these configs. -CONFIG_PAIRS_TO_COMPARE = [('8888', 'gpu')] - -# SKP results that are available to compare. -# -# TODO(stephana): We don't actually want to maintain this list of platforms. -# We are just putting them in here for now, as "convenience" links for testing -# SKP diffs. -# Ultimately, we will depend on buildbot steps linking to their own diffs on -# the shared rebaseline_server instance. -_SKP_BASE_GS_URL = 'gs://' + buildbot_globals.Get('skp_summaries_bucket') -_SKP_BASE_REPO_URL = ( - compare_rendered_pictures.REPO_URL_PREFIX + posixpath.join( - 'expectations', 'skp')) -_SKP_PLATFORMS = [ - 'Test-Mac10.8-MacMini4.1-GeForce320M-x86_64-Debug', - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release', -] - -_HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' -_HTTP_HEADER_CONTENT_TYPE = 'Content-Type' - -_SERVER = None # This gets filled in by main() - - -def _run_command(args, directory): - """Runs a command and returns stdout as a single string. - - Args: - args: the command to run, as a list of arguments - directory: directory within which to run the command - - Returns: stdout, as a string - - Raises an Exception if the command failed (exited with nonzero return code). - """ - logging.debug('_run_command: %s in directory %s' % (args, directory)) - proc = subprocess.Popen(args, cwd=directory, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - if proc.returncode is not 0: - raise Exception('command "%s" failed in dir "%s": %s' % - (args, directory, stderr)) - return stdout - - -def _get_routable_ip_address(): - """Returns routable IP address of this host (the IP address of its network - interface that would be used for most traffic, not its localhost - interface). See http://stackoverflow.com/a/166589 """ - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.connect(('8.8.8.8', 80)) - host = sock.getsockname()[0] - sock.close() - return host - - -def _create_index(file_path, config_pairs): - """Creates an index file linking to all results available from this server. - - Prior to https://codereview.chromium.org/215503002 , we had a static - index.html within our repo. But now that the results may or may not include - config comparisons, index.html needs to be generated differently depending - on which results are included. - - TODO(epoger): Instead of including raw HTML within the Python code, - consider restoring the index.html file as a template and using django (or - similar) to fill in dynamic content. - - Args: - file_path: path on local disk to write index to; any directory components - of this path that do not already exist will be created - config_pairs: what pairs of configs (if any) we compare actual results of - """ - dir_path = os.path.dirname(file_path) - if not os.path.isdir(dir_path): - os.makedirs(dir_path) - with open(file_path, 'w') as file_handle: - file_handle.write( - '<!DOCTYPE html><html>' - '<head><title>rebaseline_server</title></head>' - '<body><ul>') - - if _GM_SUMMARY_TYPES: - file_handle.write('<li>GM Expectations vs Actuals</li><ul>') - for summary_type in _GM_SUMMARY_TYPES: - file_handle.write( - '\n<li><a href="/{static_directive}/view.html#/view.html?' - 'resultsToLoad=/{results_directive}/{summary_type}">' - '{summary_type}</a></li>'.format( - results_directive=GET__PRECOMPUTED_RESULTS, - static_directive=GET__STATIC_CONTENTS, - summary_type=summary_type)) - file_handle.write('</ul>') - - if config_pairs: - file_handle.write( - '\n<li>Comparing configs within actual GM results</li><ul>') - for config_pair in config_pairs: - file_handle.write('<li>%s vs %s:' % config_pair) - for summary_type in _GM_SUMMARY_TYPES: - file_handle.write( - ' <a href="/%s/view.html#/view.html?' - 'resultsToLoad=/%s/%s/%s-vs-%s_%s.json">%s</a>' % ( - GET__STATIC_CONTENTS, GET__STATIC_CONTENTS, - GENERATED_JSON_SUBDIR, config_pair[0], config_pair[1], - summary_type, summary_type)) - file_handle.write('</li>') - file_handle.write('</ul>') - - if _SKP_PLATFORMS: - file_handle.write('\n<li>Rendered SKPs:<ul>') - for builder in _SKP_PLATFORMS: - file_handle.write( - '\n<li><a href="../live-view.html#live-view.html?%s">' % - urllib.urlencode({ - LIVE_PARAM__SET_A_SECTION: - gm_json.JSONKEY_EXPECTEDRESULTS, - LIVE_PARAM__SET_A_DIR: - posixpath.join(_SKP_BASE_REPO_URL, builder), - LIVE_PARAM__SET_B_SECTION: - gm_json.JSONKEY_ACTUALRESULTS, - LIVE_PARAM__SET_B_DIR: - posixpath.join(_SKP_BASE_GS_URL, builder), - })) - file_handle.write('expected vs actuals on %s</a></li>' % builder) - file_handle.write( - '\n<li><a href="../live-view.html#live-view.html?%s">' % - urllib.urlencode({ - LIVE_PARAM__SET_A_SECTION: - gm_json.JSONKEY_ACTUALRESULTS, - LIVE_PARAM__SET_A_DIR: - posixpath.join(_SKP_BASE_GS_URL, _SKP_PLATFORMS[0]), - LIVE_PARAM__SET_B_SECTION: - gm_json.JSONKEY_ACTUALRESULTS, - LIVE_PARAM__SET_B_DIR: - posixpath.join(_SKP_BASE_GS_URL, _SKP_PLATFORMS[1]), - })) - file_handle.write('actuals on %s vs %s</a></li>' % ( - _SKP_PLATFORMS[0], _SKP_PLATFORMS[1])) - file_handle.write('</li>') - - file_handle.write('\n</ul></body></html>') - - -class Server(object): - """ HTTP server for our HTML rebaseline viewer. """ - - def __init__(self, - actuals_source, - actuals_dir=DEFAULT_ACTUALS_DIR, - json_filename=DEFAULT_JSON_FILENAME, - port=DEFAULT_PORT, export=False, editable=True, - reload_seconds=0, config_pairs=None, builder_regex_list=None, - boto_file_path=None, - imagediffdb_threads=imagediffdb.DEFAULT_NUM_WORKER_THREADS): - """ - Args: - actuals_source: actuals_source.get_builders() -> - {builder:string -> [ bucket:string, path:string, generation:string ]} - If None, don't fetch new actual-results files - at all, just compare to whatever files are already in actuals_dir - actuals_dir: directory under which we will check out the latest actual - GM results - json_filename: basename of the JSON summary file to load for each builder - port: which TCP port to listen on for HTTP requests - export: whether to allow HTTP clients on other hosts to access this server - editable: whether HTTP clients are allowed to submit new GM baselines - (SKP baseline modifications are performed using an entirely different - mechanism, not affected by this parameter) - reload_seconds: polling interval with which to check for new results; - if 0, don't check for new results at all - config_pairs: List of (string, string) tuples; for each tuple, compare - actual results of these two configs. If None or empty, - don't compare configs at all. - builder_regex_list: List of regular expressions specifying which builders - we will process. If None, process all builders. - boto_file_path: Path to .boto file giving us credentials to access - Google Storage buckets; if None, we will only be able to access - public GS buckets. - imagediffdb_threads: How many threads to spin up within imagediffdb. - """ - self._actuals_source = actuals_source - self._actuals_dir = actuals_dir - self._json_filename = json_filename - self._port = port - self._export = export - self._editable = editable - self._reload_seconds = reload_seconds - self._config_pairs = config_pairs or [] - self._builder_regex_list = builder_regex_list - self.truncate_results = False - - if boto_file_path: - self._gs = gs_utils.GSUtils(boto_file_path=boto_file_path) - else: - self._gs = gs_utils.GSUtils() - - _create_index( - file_path=os.path.join( - PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR, - "index.html"), - config_pairs=config_pairs) - - # Reentrant lock that must be held whenever updating EITHER of: - # 1. self._results - # 2. the expected or actual results on local disk - self.results_rlock = threading.RLock() - - # Create a single ImageDiffDB instance that is used by all our differs. - self._image_diff_db = imagediffdb.ImageDiffDB( - gs=self._gs, - storage_root=os.path.join( - PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, - GENERATED_IMAGES_SUBDIR), - num_worker_threads=imagediffdb_threads) - - # This will be filled in by calls to update_results() - self._results = None - - @property - def results(self): - """ Returns the most recently generated results, or None if we don't have - any valid results (update_results() has not completed yet). """ - return self._results - - @property - def image_diff_db(self): - """ Returns reference to our ImageDiffDB object.""" - return self._image_diff_db - - @property - def gs(self): - """ Returns reference to our GSUtils object.""" - return self._gs - - @property - def is_exported(self): - """ Returns true iff HTTP clients on other hosts are allowed to access - this server. """ - return self._export - - @property - def is_editable(self): - """ True iff HTTP clients are allowed to submit new GM baselines. - - TODO(epoger): This only pertains to GM baselines; SKP baselines are - editable whenever expectations vs actuals are shown. - Once we move the GM baselines to use the same code as the SKP baselines, - we can delete this property. - """ - return self._editable - - @property - def reload_seconds(self): - """ Returns the result reload period in seconds, or 0 if we don't reload - results. """ - return self._reload_seconds - - def update_results(self, invalidate=False): - """ Create or update self._results, based on the latest expectations and - actuals. - - We hold self.results_rlock while we do this, to guarantee that no other - thread attempts to update either self._results or the underlying files at - the same time. - - Args: - invalidate: if True, invalidate self._results immediately upon entry; - otherwise, we will let readers see those results until we - replace them - """ - with self.results_rlock: - if invalidate: - self._results = None - - if self._actuals_source: - logging.info( - 'Updating GM result summaries in %s from %s ...' - % (self._actuals_dir, self._actuals_source.description())) - - # Clean out actuals_dir first, in case some builders have gone away - # since we last ran. - if os.path.isdir(self._actuals_dir): - shutil.rmtree(self._actuals_dir) - - # Get the list of actuals we care about. - all_actuals = self._actuals_source.get_builders() - - if self._builder_regex_list: - matching_builders = [] - for builder in all_actuals: - for regex in self._builder_regex_list: - if re.match(regex, builder): - matching_builders.append(builder) - break # go on to the next builder, no need to try more regexes - else: - matching_builders = all_actuals.keys() - - # Download the JSON file for each builder we care about. - # - # TODO(epoger): When this is a large number of builders, we would be - # better off downloading them in parallel! - for builder in matching_builders: - self._gs.download_file( - source_bucket=all_actuals[builder].bucket, - source_path=all_actuals[builder].path, - source_generation=all_actuals[builder].generation, - dest_path=os.path.join(self._actuals_dir, builder, - self._json_filename), - create_subdirs_if_needed=True) - - # We only update the expectations dir if the server was run with a - # nonzero --reload argument; otherwise, we expect the user to maintain - # her own expectations as she sees fit. - # - # Because the Skia repo is hosted using git, and git does not - # support updating a single directory tree, we have to update the entire - # repo checkout. - # - # Because Skia uses depot_tools, we have to update using "gclient sync" - # instead of raw git commands. - # - # TODO(epoger): Fetch latest expectations in some other way. - # Eric points out that our official documentation recommends an - # unmanaged Skia checkout, so "gclient sync" will not bring down updated - # expectations from origin/master-- you'd have to do a "git pull" of - # some sort instead. - # However, the live rebaseline_server at - # http://skia-tree-status.appspot.com/redirect/rebaseline-server (which - # is probably the only user of the --reload flag!) uses a managed - # checkout, so "gclient sync" works in that case. - # Probably the best idea is to avoid all of this nonsense by fetching - # updated expectations into a temp directory, and leaving the rest of - # the checkout alone. This could be done using "git show", or by - # downloading individual expectation JSON files from - # skia.googlesource.com . - if self._reload_seconds: - logging.info( - 'Updating expected GM results in %s by syncing Skia repo ...' % - compare_to_expectations.DEFAULT_EXPECTATIONS_DIR) - _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) - - self._results = compare_to_expectations.ExpectationComparisons( - image_diff_db=self._image_diff_db, - actuals_root=self._actuals_dir, - diff_base_url=posixpath.join( - os.pardir, STATIC_CONTENTS_SUBDIR, GENERATED_IMAGES_SUBDIR), - builder_regex_list=self._builder_regex_list) - - json_dir = os.path.join( - PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_JSON_SUBDIR) - if not os.path.isdir(json_dir): - os.makedirs(json_dir) - - for config_pair in self._config_pairs: - config_comparisons = compare_configs.ConfigComparisons( - configs=config_pair, - actuals_root=self._actuals_dir, - generated_images_root=os.path.join( - PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, - GENERATED_IMAGES_SUBDIR), - diff_base_url=posixpath.join( - os.pardir, GENERATED_IMAGES_SUBDIR), - builder_regex_list=self._builder_regex_list) - for summary_type in _GM_SUMMARY_TYPES: - gm_json.WriteToFile( - config_comparisons.get_packaged_results_of_type( - results_type=summary_type), - os.path.join( - json_dir, '%s-vs-%s_%s.json' % ( - config_pair[0], config_pair[1], summary_type))) - - def _result_loader(self, reload_seconds=0): - """ Call self.update_results(), either once or periodically. - - Params: - reload_seconds: integer; if nonzero, reload results at this interval - (in which case, this method will never return!) - """ - self.update_results() - logging.info('Initial results loaded. Ready for requests on %s' % self._url) - if reload_seconds: - while True: - time.sleep(reload_seconds) - self.update_results() - - def run(self): - arg_tuple = (self._reload_seconds,) # start_new_thread needs a tuple, - # even though it holds just one param - thread.start_new_thread(self._result_loader, arg_tuple) - - if self._export: - server_address = ('', self._port) - host = _get_routable_ip_address() - if self._editable: - logging.warning('Running with combination of "export" and "editable" ' - 'flags. Users on other machines will ' - 'be able to modify your GM expectations!') - else: - host = '127.0.0.1' - server_address = (host, self._port) - # pylint: disable=W0201 - http_server = BaseHTTPServer.HTTPServer(server_address, HTTPRequestHandler) - self._url = 'http://%s:%d' % (host, http_server.server_port) - logging.info('Listening for requests on %s' % self._url) - http_server.serve_forever() - - -class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - """ HTTP request handlers for various types of queries this server knows - how to handle (static HTML and Javascript, expected/actual results, etc.) - """ - def do_GET(self): - """ - Handles all GET requests, forwarding them to the appropriate - do_GET_* dispatcher. - - If we see any Exceptions, return a 404. This fixes http://skbug.com/2147 - """ - try: - logging.debug('do_GET: path="%s"' % self.path) - if self.path == '' or self.path == '/' or self.path == '/index.html' : - self.redirect_to('/%s/%s/index.html' % ( - GET__STATIC_CONTENTS, GENERATED_HTML_SUBDIR)) - return - if self.path == '/favicon.ico' : - self.redirect_to('/%s/favicon.ico' % GET__STATIC_CONTENTS) - return - - # All requests must be of this form: - # /dispatcher/remainder - # where 'dispatcher' indicates which do_GET_* dispatcher to run - # and 'remainder' is the remaining path sent to that dispatcher. - (dispatcher_name, remainder) = PATHSPLIT_RE.match(self.path).groups() - dispatchers = { - GET__LIVE_RESULTS: self.do_GET_live_results, - GET__PRECOMPUTED_RESULTS: self.do_GET_precomputed_results, - GET__PREFETCH_RESULTS: self.do_GET_prefetch_results, - GET__STATIC_CONTENTS: self.do_GET_static, - } - dispatcher = dispatchers[dispatcher_name] - dispatcher(remainder) - except: - self.send_error(404) - raise - - def do_GET_precomputed_results(self, results_type): - """ Handle a GET request for part of the precomputed _SERVER.results object. - - Args: - results_type: string indicating which set of results to return; - must be one of the results_mod.RESULTS_* constants - """ - logging.debug('do_GET_precomputed_results: sending results of type "%s"' % - results_type) - # Since we must make multiple calls to the ExpectationComparisons object, - # grab a reference to it in case it is updated to point at a new - # ExpectationComparisons object within another thread. - # - # TODO(epoger): Rather than using a global variable for the handler - # to refer to the Server object, make Server a subclass of - # HTTPServer, and then it could be available to the handler via - # the handler's .server instance variable. - results_obj = _SERVER.results - if results_obj: - response_dict = results_obj.get_packaged_results_of_type( - results_type=results_type, reload_seconds=_SERVER.reload_seconds, - is_editable=_SERVER.is_editable, is_exported=_SERVER.is_exported) - else: - now = int(time.time()) - response_dict = { - imagepairset.KEY__ROOT__HEADER: { - results_mod.KEY__HEADER__SCHEMA_VERSION: ( - results_mod.VALUE__HEADER__SCHEMA_VERSION), - results_mod.KEY__HEADER__IS_STILL_LOADING: True, - results_mod.KEY__HEADER__TIME_UPDATED: now, - results_mod.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: ( - now + RELOAD_INTERVAL_UNTIL_READY), - }, - } - self.send_json_dict(response_dict) - - def _get_live_results_or_prefetch(self, url_remainder, prefetch_only=False): - """ Handle a GET request for live-generated image diff data. - - Args: - url_remainder: string indicating which image diffs to generate - prefetch_only: if True, the user isn't waiting around for results - """ - param_dict = urlparse.parse_qs(url_remainder) - download_all_images = ( - param_dict.get(LIVE_PARAM__DOWNLOAD_ONLY_DIFFERING, [''])[0].lower() - not in ['1', 'true']) - setA_dir = param_dict[LIVE_PARAM__SET_A_DIR][0] - setB_dir = param_dict[LIVE_PARAM__SET_B_DIR][0] - setA_section = self._validate_summary_section( - param_dict.get(LIVE_PARAM__SET_A_SECTION, [None])[0]) - setB_section = self._validate_summary_section( - param_dict.get(LIVE_PARAM__SET_B_SECTION, [None])[0]) - - # If the sets show expectations vs actuals, always show expectations on - # the left (setA). - if ((setA_section == gm_json.JSONKEY_ACTUALRESULTS) and - (setB_section == gm_json.JSONKEY_EXPECTEDRESULTS)): - setA_dir, setB_dir = setB_dir, setA_dir - setA_section, setB_section = setB_section, setA_section - - # Are we comparing some actuals against expectations stored in the repo? - # If so, we can allow the user to submit new baselines. - is_editable = ( - (setA_section == gm_json.JSONKEY_EXPECTEDRESULTS) and - (setA_dir.startswith(compare_rendered_pictures.REPO_URL_PREFIX)) and - (setB_section == gm_json.JSONKEY_ACTUALRESULTS)) - - results_obj = compare_rendered_pictures.RenderedPicturesComparisons( - setA_dir=setA_dir, setB_dir=setB_dir, - setA_section=setA_section, setB_section=setB_section, - image_diff_db=_SERVER.image_diff_db, - diff_base_url='/static/generated-images', - gs=_SERVER.gs, truncate_results=_SERVER.truncate_results, - prefetch_only=prefetch_only, download_all_images=download_all_images) - if prefetch_only: - self.send_response(200) - else: - self.send_json_dict(results_obj.get_packaged_results_of_type( - results_type=results_mod.KEY__HEADER__RESULTS_ALL, - is_editable=is_editable)) - - def do_GET_live_results(self, url_remainder): - """ Handle a GET request for live-generated image diff data. - - Args: - url_remainder: string indicating which image diffs to generate - """ - logging.debug('do_GET_live_results: url_remainder="%s"' % url_remainder) - self._get_live_results_or_prefetch( - url_remainder=url_remainder, prefetch_only=False) - - def do_GET_prefetch_results(self, url_remainder): - """ Prefetch image diff data for a future do_GET_live_results() call. - - Args: - url_remainder: string indicating which image diffs to generate - """ - logging.debug('do_GET_prefetch_results: url_remainder="%s"' % url_remainder) - self._get_live_results_or_prefetch( - url_remainder=url_remainder, prefetch_only=True) - - def do_GET_static(self, path): - """ Handle a GET request for a file under STATIC_CONTENTS_SUBDIR . - Only allow serving of files within STATIC_CONTENTS_SUBDIR that is a - filesystem sibling of this script. - - Args: - path: path to file (within STATIC_CONTENTS_SUBDIR) to retrieve - """ - # Strip arguments ('?resultsToLoad=all') from the path - path = urlparse.urlparse(path).path - - logging.debug('do_GET_static: sending file "%s"' % path) - static_dir = os.path.realpath(os.path.join( - PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR)) - full_path = os.path.realpath(os.path.join(static_dir, path)) - if full_path.startswith(static_dir): - self.send_file(full_path) - else: - logging.error( - 'Attempted do_GET_static() of path [%s] outside of static dir [%s]' - % (full_path, static_dir)) - self.send_error(404) - - def do_POST(self): - """ Handles all POST requests, forwarding them to the appropriate - do_POST_* dispatcher. """ - # All requests must be of this form: - # /dispatcher - # where 'dispatcher' indicates which do_POST_* dispatcher to run. - logging.debug('do_POST: path="%s"' % self.path) - normpath = posixpath.normpath(self.path) - dispatchers = { - '/edits': self.do_POST_edits, - '/live-edits': self.do_POST_live_edits, - } - try: - dispatcher = dispatchers[normpath] - dispatcher() - except: - self.send_error(404) - raise - - def do_POST_edits(self): - """ Handle a POST request with modifications to GM expectations, in this - format: - - { - KEY__EDITS__OLD_RESULTS_TYPE: 'all', # type of results that the client - # loaded and then made - # modifications to - KEY__EDITS__OLD_RESULTS_HASH: 39850913, # hash of results when the client - # loaded them (ensures that the - # client and server apply - # modifications to the same base) - KEY__EDITS__MODIFICATIONS: [ - # as needed by compare_to_expectations.edit_expectations() - ... - ], - } - - Raises an Exception if there were any problems. - """ - if not _SERVER.is_editable: - raise Exception('this server is not running in --editable mode') - - content_type = self.headers[_HTTP_HEADER_CONTENT_TYPE] - if content_type != 'application/json;charset=UTF-8': - raise Exception('unsupported %s [%s]' % ( - _HTTP_HEADER_CONTENT_TYPE, content_type)) - - content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH]) - json_data = self.rfile.read(content_length) - data = json.loads(json_data) - logging.debug('do_POST_edits: received new GM expectations data [%s]' % - data) - - # Update the results on disk with the information we received from the - # client. - # We must hold _SERVER.results_rlock while we do this, to guarantee that - # no other thread updates expectations (from the Skia repo) while we are - # updating them (using the info we received from the client). - with _SERVER.results_rlock: - oldResultsType = data[KEY__EDITS__OLD_RESULTS_TYPE] - oldResults = _SERVER.results.get_results_of_type(oldResultsType) - oldResultsHash = str(hash(repr( - oldResults[imagepairset.KEY__ROOT__IMAGEPAIRS]))) - if oldResultsHash != data[KEY__EDITS__OLD_RESULTS_HASH]: - raise Exception('results of type "%s" changed while the client was ' - 'making modifications. The client should reload the ' - 'results and submit the modifications again.' % - oldResultsType) - _SERVER.results.edit_expectations(data[KEY__EDITS__MODIFICATIONS]) - - # Read the updated results back from disk. - # We can do this in a separate thread; we should return our success message - # to the UI as soon as possible. - thread.start_new_thread(_SERVER.update_results, (True,)) - self.send_response(200) - - def do_POST_live_edits(self): - """ Handle a POST request with modifications to SKP expectations, in this - format: - - { - KEY__LIVE_EDITS__SET_A_DESCRIPTIONS: { - # setA descriptions from the original data - }, - KEY__LIVE_EDITS__SET_B_DESCRIPTIONS: { - # setB descriptions from the original data - }, - KEY__LIVE_EDITS__MODIFICATIONS: [ - # as needed by writable_expectations.modify() - ], - } - - Raises an Exception if there were any problems. - """ - content_type = self.headers[_HTTP_HEADER_CONTENT_TYPE] - if content_type != 'application/json;charset=UTF-8': - raise Exception('unsupported %s [%s]' % ( - _HTTP_HEADER_CONTENT_TYPE, content_type)) - - content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH]) - json_data = self.rfile.read(content_length) - data = json.loads(json_data) - logging.debug('do_POST_live_edits: received new GM expectations data [%s]' % - data) - with writable_expectations_mod.WritableExpectations( - data[KEY__LIVE_EDITS__SET_A_DESCRIPTIONS]) as writable_expectations: - writable_expectations.modify(data[KEY__LIVE_EDITS__MODIFICATIONS]) - diffs = writable_expectations.get_diffs() - # TODO(stephana): Move to a simpler web framework so we don't have to - # call these functions. See http://skbug.com/2856 ('rebaseline_server: - # Refactor server to use a simple web framework') - self.send_response(200) - self.send_header('Content-type', 'text/plain') - self.end_headers() - self.wfile.write(diffs) - - def redirect_to(self, url): - """ Redirect the HTTP client to a different url. - - Args: - url: URL to redirect the HTTP client to - """ - self.send_response(301) - self.send_header('Location', url) - self.end_headers() - - def send_file(self, path): - """ Send the contents of the file at this path, with a mimetype based - on the filename extension. - - Args: - path: path of file whose contents to send to the HTTP client - """ - # Grab the extension if there is one - extension = os.path.splitext(path)[1] - if len(extension) >= 1: - extension = extension[1:] - - # Determine the MIME type of the file from its extension - mime_type = MIME_TYPE_MAP.get(extension, MIME_TYPE_MAP['']) - - # Open the file and send it over HTTP - if os.path.isfile(path): - with open(path, 'rb') as sending_file: - self.send_response(200) - self.send_header('Content-type', mime_type) - self.end_headers() - self.wfile.write(sending_file.read()) - else: - self.send_error(404) - - def send_json_dict(self, json_dict): - """ Send the contents of this dictionary in JSON format, with a JSON - mimetype. - - Args: - json_dict: dictionary to send - """ - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - json.dump(json_dict, self.wfile) - - def _validate_summary_section(self, section_name): - """Validates the section we have been requested to read within JSON summary. - - Args: - section_name: which section of the JSON summary file has been requested - - Returns: the validated section name - - Raises: Exception if an invalid section_name was requested. - """ - if section_name not in compare_rendered_pictures.ALLOWED_SECTION_NAMES: - raise Exception('requested section name "%s" not in allowed list %s' % ( - section_name, compare_rendered_pictures.ALLOWED_SECTION_NAMES)) - return section_name - - -def main(): - logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - datefmt='%m/%d/%Y %H:%M:%S', - level=logging.INFO) - parser = argparse.ArgumentParser() - parser.add_argument('--actuals-dir', - help=('Directory into which we will check out the latest ' - 'actual GM results. If this directory does not ' - 'exist, it will be created. Defaults to %(default)s'), - default=DEFAULT_ACTUALS_DIR) - parser.add_argument('--boto', - help=('Path to .boto file giving us credentials to access ' - 'Google Storage buckets. If not specified, we will ' - 'only be able to access public GS buckets (and thus ' - 'won\'t be able to download SKP images).'), - default='') - # TODO(epoger): Before https://codereview.chromium.org/310093003 , - # when this tool downloaded the JSON summaries from skia-autogen, - # it had an --actuals-revision the caller could specify to download - # actual results as of a specific point in time. We should add similar - # functionality when retrieving the summaries from Google Storage. - parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+', - help=('Only process builders matching these regular ' - 'expressions. If unspecified, process all ' - 'builders.')) - parser.add_argument('--compare-configs', action='store_true', - help=('In addition to generating differences between ' - 'expectations and actuals, also generate ' - 'differences between these config pairs: ' - + str(CONFIG_PAIRS_TO_COMPARE))) - parser.add_argument('--editable', action='store_true', - help=('Allow HTTP clients to submit new GM baselines; ' - 'SKP baselines can be edited regardless of this ' - 'setting.')) - parser.add_argument('--export', action='store_true', - help=('Instead of only allowing access from HTTP clients ' - 'on localhost, allow HTTP clients on other hosts ' - 'to access this server. WARNING: doing so will ' - 'allow users on other hosts to modify your ' - 'GM expectations, if combined with --editable.')) - parser.add_argument('--rietveld-issue', - help=('Download json_filename files from latest trybot' - 'runs on this codereview.chromium.org issue.' - 'Overrides --gm-summaries-bucket.')) - parser.add_argument('--gm-summaries-bucket', - help=('Google Cloud Storage bucket to download ' - 'JSON_FILENAME files from. ' - 'Defaults to %(default)s ; if set to ' - 'empty string, just compare to actual-results ' - 'already found in ACTUALS_DIR.'), - default=DEFAULT_GM_SUMMARIES_BUCKET) - parser.add_argument('--json-filename', - help=('JSON summary filename to read for each builder; ' - 'defaults to %(default)s.'), - default=DEFAULT_JSON_FILENAME) - parser.add_argument('--port', type=int, - help=('Which TCP port to listen on for HTTP requests; ' - 'defaults to %(default)s'), - default=DEFAULT_PORT) - parser.add_argument('--reload', type=int, - help=('How often (a period in seconds) to update the ' - 'results. If specified, both expected and actual ' - 'results will be updated by running "gclient sync" ' - 'on your Skia checkout as a whole. ' - 'By default, we do not reload at all, and you ' - 'must restart the server to pick up new data.'), - default=0) - parser.add_argument('--threads', type=int, - help=('How many parallel threads we use to download ' - 'images and generate diffs; defaults to ' - '%(default)s'), - default=imagediffdb.DEFAULT_NUM_WORKER_THREADS) - parser.add_argument('--truncate', action='store_true', - help=('FOR TESTING ONLY: truncate the set of images we ' - 'process, to speed up testing.')) - args = parser.parse_args() - if args.compare_configs: - config_pairs = CONFIG_PAIRS_TO_COMPARE - else: - config_pairs = None - - if args.rietveld_issue: - actuals_source = download_actuals.RietveldIssueActuals(args.rietveld_issue, - args.json_filename) - else: - actuals_source = download_actuals.TipOfTreeActuals(args.gm_summaries_bucket, - args.json_filename) - - global _SERVER - _SERVER = Server(actuals_source, - actuals_dir=args.actuals_dir, - json_filename=args.json_filename, - port=args.port, export=args.export, editable=args.editable, - reload_seconds=args.reload, config_pairs=config_pairs, - builder_regex_list=args.builders, boto_file_path=args.boto, - imagediffdb_threads=args.threads) - if args.truncate: - _SERVER.truncate_results = True - _SERVER.run() - - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/static/.gitignore b/gm/rebaseline_server/static/.gitignore deleted file mode 100644 index c8e67d128f..0000000000 --- a/gm/rebaseline_server/static/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -generated-html/ -generated-images/ -generated-json/ diff --git a/gm/rebaseline_server/static/constants.js b/gm/rebaseline_server/static/constants.js deleted file mode 100644 index a9601ece71..0000000000 --- a/gm/rebaseline_server/static/constants.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Constants used by our imagediff-viewing Javascript code. - */ -var module = angular.module( - 'ConstantsModule', - [] -); - -module.constant('constants', (function() { - return { - // NOTE: Keep these in sync with ../column.py - KEY__EXTRACOLUMNHEADERS__HEADER_TEXT: 'headerText', - KEY__EXTRACOLUMNHEADERS__HEADER_URL: 'headerUrl', - KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE: 'isFilterable', - KEY__EXTRACOLUMNHEADERS__IS_SORTABLE: 'isSortable', - KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER: 'useFreeformFilter', - KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS: 'valuesAndCounts', - - // NOTE: Keep these in sync with ../imagediffdb.py - KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL: 'maxDiffPerChannel', - KEY__DIFFERENCES__NUM_DIFF_PIXELS: 'numDifferingPixels', - KEY__DIFFERENCES__PERCENT_DIFF_PIXELS: 'percentDifferingPixels', - KEY__DIFFERENCES__PERCEPTUAL_DIFF: 'perceptualDifference', - KEY__DIFFERENCES__DIFF_URL: 'diffUrl', - KEY__DIFFERENCES__WHITE_DIFF_URL: 'whiteDiffUrl', - - // NOTE: Keep these in sync with ../imagepair.py - KEY__IMAGEPAIRS__DIFFERENCES: 'differenceData', - KEY__IMAGEPAIRS__EXPECTATIONS: 'expectations', - KEY__IMAGEPAIRS__EXTRACOLUMNS: 'extraColumns', - KEY__IMAGEPAIRS__IMAGE_A_URL: 'imageAUrl', - KEY__IMAGEPAIRS__IMAGE_B_URL: 'imageBUrl', - KEY__IMAGEPAIRS__IS_DIFFERENT: 'isDifferent', - KEY__IMAGEPAIRS__SOURCE_JSON_FILE: 'sourceJsonFile', - - // NOTE: Keep these in sync with ../imagepairset.py - KEY__ROOT__EXTRACOLUMNHEADERS: 'extraColumnHeaders', - KEY__ROOT__EXTRACOLUMNORDER: 'extraColumnOrder', - KEY__ROOT__HEADER: 'header', - KEY__ROOT__IMAGEPAIRS: 'imagePairs', - KEY__ROOT__IMAGESETS: 'imageSets', - // - KEY__IMAGESETS__FIELD__BASE_URL: 'baseUrl', - KEY__IMAGESETS__FIELD__DESCRIPTION: 'description', - KEY__IMAGESETS__SET__DIFFS: 'diffs', - KEY__IMAGESETS__SET__IMAGE_A: 'imageA', - KEY__IMAGESETS__SET__IMAGE_B: 'imageB', - KEY__IMAGESETS__SET__WHITEDIFFS: 'whiteDiffs', - - // NOTE: Keep these in sync with ../results.py - KEY__EXPECTATIONS__BUGS: 'bugs', - KEY__EXPECTATIONS__IGNOREFAILURE: 'ignore-failure', - KEY__EXPECTATIONS__REVIEWED: 'reviewed-by-human', - // - KEY__EXTRACOLUMNS__BUILDER: 'builder', - KEY__EXTRACOLUMNS__CONFIG: 'config', - KEY__EXTRACOLUMNS__RESULT_TYPE: 'resultType', - KEY__EXTRACOLUMNS__TEST: 'test', - // - KEY__HEADER__DATAHASH: 'dataHash', - KEY__HEADER__IS_EDITABLE: 'isEditable', - KEY__HEADER__IS_EXPORTED: 'isExported', - KEY__HEADER__IS_STILL_LOADING: 'resultsStillLoading', - KEY__HEADER__RESULTS_ALL: 'all', - KEY__HEADER__RESULTS_FAILURES: 'failures', - KEY__HEADER__SCHEMA_VERSION: 'schemaVersion', - KEY__HEADER__SET_A_DESCRIPTIONS: 'setA', - KEY__HEADER__SET_B_DESCRIPTIONS: 'setB', - KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: 'timeNextUpdateAvailable', - KEY__HEADER__TIME_UPDATED: 'timeUpdated', - KEY__HEADER__TYPE: 'type', - VALUE__HEADER__SCHEMA_VERSION: 5, - // - KEY__RESULT_TYPE__FAILED: 'failed', - KEY__RESULT_TYPE__FAILUREIGNORED: 'failure-ignored', - KEY__RESULT_TYPE__NOCOMPARISON: 'no-comparison', - KEY__RESULT_TYPE__SUCCEEDED: 'succeeded', - // - KEY__SET_DESCRIPTIONS__DIR: 'dir', - KEY__SET_DESCRIPTIONS__REPO_REVISION: 'repoRevision', - KEY__SET_DESCRIPTIONS__SECTION: 'section', - - // NOTE: Keep these in sync with ../server.py - KEY__EDITS__MODIFICATIONS: 'modifications', - KEY__EDITS__OLD_RESULTS_HASH: 'oldResultsHash', - KEY__EDITS__OLD_RESULTS_TYPE: 'oldResultsType', - KEY__LIVE_EDITS__MODIFICATIONS: 'modifications', - KEY__LIVE_EDITS__SET_A_DESCRIPTIONS: 'setA', - KEY__LIVE_EDITS__SET_B_DESCRIPTIONS: 'setB', - - // These are just used on the client side, no need to sync with server code. - KEY__IMAGEPAIRS__ROWSPAN: 'rowspan', - URL_KEY__SCHEMA_VERSION: 'urlSchemaVersion', - URL_VALUE__SCHEMA_VERSION__CURRENT: 1, - - // Utility constants only used on the client side. - ASC: 'asc', - DESC: 'desc', - } -})()) diff --git a/gm/rebaseline_server/static/live-loader.js b/gm/rebaseline_server/static/live-loader.js deleted file mode 100644 index ab15aee41a..0000000000 --- a/gm/rebaseline_server/static/live-loader.js +++ /dev/null @@ -1,1024 +0,0 @@ -/* - * Loader: - * Reads GM result reports written out by results.py, and imports - * them into $scope.extraColumnHeaders and $scope.imagePairs . - */ -var Loader = angular.module( - 'Loader', - ['ConstantsModule'] -); - -// This configuration is needed to allow downloads of the diff patch. -// See https://github.com/angular/angular.js/issues/3889 -Loader.config(['$compileProvider', function($compileProvider) { - $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|blob):/); -}]); - -Loader.directive( - 'resultsUpdatedCallbackDirective', - ['$timeout', - function($timeout) { - return function(scope, element, attrs) { - if (scope.$last) { - $timeout(function() { - scope.resultsUpdatedCallback(); - }); - } - }; - } - ] -); - -// TODO(epoger): Combine ALL of our filtering operations (including -// truncation) into this one filter, so that runs most efficiently? -// (We would have to make sure truncation still took place after -// sorting, though.) -Loader.filter( - 'removeHiddenImagePairs', - function(constants) { - return function(unfilteredImagePairs, filterableColumnNames, showingColumnValues, - viewingTab) { - var filteredImagePairs = []; - for (var i = 0; i < unfilteredImagePairs.length; i++) { - var imagePair = unfilteredImagePairs[i]; - var extraColumnValues = imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]; - var allColumnValuesAreVisible = true; - // Loop over all columns, and if any of them contain values not found in - // showingColumnValues[columnName], don't include this imagePair. - // - // We use this same filtering mechanism regardless of whether each column - // has USE_FREEFORM_FILTER set or not; if that flag is set, then we will - // have already used the freeform text entry block to populate - // showingColumnValues[columnName]. - for (var j = 0; j < filterableColumnNames.length; j++) { - var columnName = filterableColumnNames[j]; - var columnValue = extraColumnValues[columnName]; - if (!showingColumnValues[columnName][columnValue]) { - allColumnValuesAreVisible = false; - break; - } - } - if (allColumnValuesAreVisible && (viewingTab == imagePair.tab)) { - filteredImagePairs.push(imagePair); - } - } - return filteredImagePairs; - }; - } -); - -/** - * Limit the input imagePairs to some max number, and merge identical rows - * (adjacent rows which have the same (imageA, imageB) pair). - * - * @param unfilteredImagePairs imagePairs to filter - * @param maxPairs maximum number of pairs to output, or <0 for no limit - * @param mergeIdenticalRows if true, merge identical rows by setting - * ROWSPAN>1 on the first merged row, and ROWSPAN=0 for the rest - */ -Loader.filter( - 'mergeAndLimit', - function(constants) { - return function(unfilteredImagePairs, maxPairs, mergeIdenticalRows) { - var numPairs = unfilteredImagePairs.length; - if ((maxPairs > 0) && (maxPairs < numPairs)) { - numPairs = maxPairs; - } - var filteredImagePairs = []; - if (!mergeIdenticalRows || (numPairs == 1)) { - // Take a shortcut if we're not merging identical rows. - // We still need to set ROWSPAN to 1 for each row, for the HTML viewer. - for (var i = numPairs-1; i >= 0; i--) { - var imagePair = unfilteredImagePairs[i]; - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; - filteredImagePairs[i] = imagePair; - } - } else if (numPairs > 1) { - // General case--there are at least 2 rows, so we may need to merge some. - // Work from the bottom up, so we can keep a running total of how many - // rows should be merged, and set ROWSPAN of the top row accordingly. - var imagePair = unfilteredImagePairs[numPairs-1]; - var nextRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]; - var nextRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; - filteredImagePairs[numPairs-1] = imagePair; - for (var i = numPairs-2; i >= 0; i--) { - imagePair = unfilteredImagePairs[i]; - var thisRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]; - var thisRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - if ((thisRowImageAUrl == nextRowImageAUrl) && - (thisRowImageBUrl == nextRowImageBUrl)) { - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = - filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] + 1; - filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] = 0; - } else { - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; - nextRowImageAUrl = thisRowImageAUrl; - nextRowImageBUrl = thisRowImageBUrl; - } - filteredImagePairs[i] = imagePair; - } - } else { - // No results. - } - return filteredImagePairs; - }; - } -); - - -Loader.controller( - 'Loader.Controller', - function($scope, $http, $filter, $location, $log, $timeout, constants) { - $scope.readyToDisplay = false; - $scope.constants = constants; - $scope.windowTitle = "Loading GM Results..."; - $scope.setADir = $location.search().setADir; - $scope.setASection = $location.search().setASection; - $scope.setBDir = $location.search().setBDir; - $scope.setBSection = $location.search().setBSection; - $scope.loadingMessage = "please wait..."; - - var currSortAsc = true; - - - /** - * On initial page load, load a full dictionary of results. - * Once the dictionary is loaded, unhide the page elements so they can - * render the data. - */ - $scope.liveQueryUrl = - "/live-results/setADir=" + encodeURIComponent($scope.setADir) + - "&setASection=" + encodeURIComponent($scope.setASection) + - "&setBDir=" + encodeURIComponent($scope.setBDir) + - "&setBSection=" + encodeURIComponent($scope.setBSection); - $http.get($scope.liveQueryUrl).success( - function(data, status, header, config) { - var dataHeader = data[constants.KEY__ROOT__HEADER]; - if (dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] != - constants.VALUE__HEADER__SCHEMA_VERSION) { - $scope.loadingMessage = "ERROR: Got JSON file with schema version " - + dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] - + " but expected schema version " - + constants.VALUE__HEADER__SCHEMA_VERSION; - } else if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) { - // Apply the server's requested reload delay to local time, - // so we will wait the right number of seconds regardless of clock - // skew between client and server. - var reloadDelayInSeconds = - dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] - - dataHeader[constants.KEY__HEADER__TIME_UPDATED]; - var timeNow = new Date().getTime(); - var timeToReload = timeNow + reloadDelayInSeconds * 1000; - $scope.loadingMessage = - "server is still loading results; will retry at " + - $scope.localTimeString(timeToReload / 1000); - $timeout( - function(){location.reload();}, - timeToReload - timeNow); - } else { - $scope.loadingMessage = "processing data, please wait..."; - - $scope.header = dataHeader; - $scope.extraColumnHeaders = data[constants.KEY__ROOT__EXTRACOLUMNHEADERS]; - $scope.orderedColumnNames = data[constants.KEY__ROOT__EXTRACOLUMNORDER]; - $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS]; - $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS]; - - // set the default sort column and make it ascending. - $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES; - $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF; - currSortAsc = true; - - $scope.showSubmitAdvancedSettings = false; - $scope.submitAdvancedSettings = {}; - $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__REVIEWED] = true; - $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false; - $scope.submitAdvancedSettings['bug'] = ''; - - // Create the list of tabs (lists into which the user can file each - // test). This may vary, depending on isEditable. - $scope.tabs = [ - 'Unfiled', 'Hidden' - ]; - if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) { - $scope.tabs = $scope.tabs.concat( - ['Pending Approval']); - } - $scope.defaultTab = $scope.tabs[0]; - $scope.viewingTab = $scope.defaultTab; - - // Track the number of results on each tab. - $scope.numResultsPerTab = {}; - for (var i = 0; i < $scope.tabs.length; i++) { - $scope.numResultsPerTab[$scope.tabs[i]] = 0; - } - $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length; - - // Add index and tab fields to all records. - for (var i = 0; i < $scope.imagePairs.length; i++) { - $scope.imagePairs[i].index = i; - $scope.imagePairs[i].tab = $scope.defaultTab; - } - - // Arrays within which the user can toggle individual elements. - $scope.selectedImagePairs = []; - - // Set up filters. - // - // filterableColumnNames is a list of all column names we can filter on. - // allColumnValues[columnName] is a list of all known values - // for a given column. - // showingColumnValues[columnName] is a set indicating which values - // in a given column would cause us to show a row, rather than hiding it. - // - // columnStringMatch[columnName] is a string used as a pattern to generate - // showingColumnValues[columnName] for columns we filter using free-form text. - // It is ignored for any columns with USE_FREEFORM_FILTER == false. - $scope.filterableColumnNames = []; - $scope.allColumnValues = {}; - $scope.showingColumnValues = {}; - $scope.columnStringMatch = {}; - - angular.forEach( - Object.keys($scope.extraColumnHeaders), - function(columnName) { - var columnHeader = $scope.extraColumnHeaders[columnName]; - if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE]) { - $scope.filterableColumnNames.push(columnName); - $scope.allColumnValues[columnName] = $scope.columnSliceOf2DArray( - columnHeader[constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS], 0); - $scope.showingColumnValues[columnName] = {}; - $scope.toggleValuesInSet($scope.allColumnValues[columnName], - $scope.showingColumnValues[columnName]); - $scope.columnStringMatch[columnName] = ""; - } - } - ); - - // TODO(epoger): Special handling for RESULT_TYPE column: - // by default, show only KEY__RESULT_TYPE__FAILED results - $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE] = {}; - $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE][ - constants.KEY__RESULT_TYPE__FAILED] = true; - - // Set up mapping for URL parameters. - // parameter name -> copier object to load/save parameter value - $scope.queryParameters.map = { - 'setADir': $scope.queryParameters.copiers.simple, - 'setASection': $scope.queryParameters.copiers.simple, - 'setBDir': $scope.queryParameters.copiers.simple, - 'setBSection': $scope.queryParameters.copiers.simple, - 'displayLimitPending': $scope.queryParameters.copiers.simple, - 'showThumbnailsPending': $scope.queryParameters.copiers.simple, - 'mergeIdenticalRowsPending': $scope.queryParameters.copiers.simple, - 'imageSizePending': $scope.queryParameters.copiers.simple, - 'sortColumnSubdict': $scope.queryParameters.copiers.simple, - 'sortColumnKey': $scope.queryParameters.copiers.simple, - }; - // Some parameters are handled differently based on whether they USE_FREEFORM_FILTER. - angular.forEach( - $scope.filterableColumnNames, - function(columnName) { - if ($scope.extraColumnHeaders[columnName] - [constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) { - $scope.queryParameters.map[columnName] = - $scope.queryParameters.copiers.columnStringMatch; - } else { - $scope.queryParameters.map[columnName] = - $scope.queryParameters.copiers.showingColumnValuesSet; - } - } - ); - - // If any defaults were overridden in the URL, get them now. - $scope.queryParameters.load(); - - // Any image URLs which are relative should be relative to the JSON - // file's source directory; absolute URLs should be left alone. - var baseUrlKey = constants.KEY__IMAGESETS__FIELD__BASE_URL; - angular.forEach( - $scope.imageSets, - function(imageSet) { - var baseUrl = imageSet[baseUrlKey]; - if ((baseUrl.substring(0, 1) != '/') && - (baseUrl.indexOf('://') == -1)) { - imageSet[baseUrlKey] = '/' + baseUrl; - } - } - ); - - $scope.readyToDisplay = true; - $scope.updateResults(); - $scope.loadingMessage = ""; - $scope.windowTitle = "Current GM Results"; - - $timeout( function() { - make_results_header_sticky(); - }); - } - } - ).error( - function(data, status, header, config) { - $scope.loadingMessage = "FAILED to load."; - $scope.windowTitle = "Failed to Load GM Results"; - } - ); - - - // - // Select/Clear/Toggle all tests. - // - - /** - * Select all currently showing tests. - */ - $scope.selectAllImagePairs = function() { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = 0; i < numImagePairsShowing; i++) { - var index = $scope.limitedImagePairs[i].index; - if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) { - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - } - } - - /** - * Deselect all currently showing tests. - */ - $scope.clearAllImagePairs = function() { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = 0; i < numImagePairsShowing; i++) { - var index = $scope.limitedImagePairs[i].index; - if ($scope.isValueInArray(index, $scope.selectedImagePairs)) { - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - } - } - - /** - * Toggle selection of all currently showing tests. - */ - $scope.toggleAllImagePairs = function() { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = 0; i < numImagePairsShowing; i++) { - var index = $scope.limitedImagePairs[i].index; - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - } - - /** - * Toggle selection state of a subset of the currently showing tests. - * - * @param startIndex index within $scope.limitedImagePairs of the first - * test to toggle selection state of - * @param num number of tests (in a contiguous block) to toggle - */ - $scope.toggleSomeImagePairs = function(startIndex, num) { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = startIndex; i < startIndex + num; i++) { - var index = $scope.limitedImagePairs[i].index; - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - } - - - // - // Tab operations. - // - - /** - * Change the selected tab. - * - * @param tab (string): name of the tab to select - */ - $scope.setViewingTab = function(tab) { - $scope.viewingTab = tab; - $scope.updateResults(); - } - - /** - * Move the imagePairs in $scope.selectedImagePairs to a different tab, - * and then clear $scope.selectedImagePairs. - * - * @param newTab (string): name of the tab to move the tests to - */ - $scope.moveSelectedImagePairsToTab = function(newTab) { - $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab); - $scope.selectedImagePairs = []; - $scope.updateResults(); - } - - /** - * Move a subset of $scope.imagePairs to a different tab. - * - * @param imagePairIndices (array of ints): indices into $scope.imagePairs - * indicating which test results to move - * @param newTab (string): name of the tab to move the tests to - */ - $scope.moveImagePairsToTab = function(imagePairIndices, newTab) { - var imagePairIndex; - var numImagePairs = imagePairIndices.length; - for (var i = 0; i < numImagePairs; i++) { - imagePairIndex = imagePairIndices[i]; - $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--; - $scope.imagePairs[imagePairIndex].tab = newTab; - } - $scope.numResultsPerTab[newTab] += numImagePairs; - } - - - // - // $scope.queryParameters: - // Transfer parameter values between $scope and the URL query string. - // - $scope.queryParameters = {}; - - // load and save functions for parameters of each type - // (load a parameter value into $scope from nameValuePairs, - // save a parameter value from $scope into nameValuePairs) - $scope.queryParameters.copiers = { - 'simple': { - 'load': function(nameValuePairs, name) { - var value = nameValuePairs[name]; - if (value) { - $scope[name] = value; - } - }, - 'save': function(nameValuePairs, name) { - nameValuePairs[name] = $scope[name]; - } - }, - - 'columnStringMatch': { - 'load': function(nameValuePairs, name) { - var value = nameValuePairs[name]; - if (value) { - $scope.columnStringMatch[name] = value; - } - }, - 'save': function(nameValuePairs, name) { - nameValuePairs[name] = $scope.columnStringMatch[name]; - } - }, - - 'showingColumnValuesSet': { - 'load': function(nameValuePairs, name) { - var value = nameValuePairs[name]; - if (value) { - var valueArray = value.split(','); - $scope.showingColumnValues[name] = {}; - $scope.toggleValuesInSet(valueArray, $scope.showingColumnValues[name]); - } - }, - 'save': function(nameValuePairs, name) { - nameValuePairs[name] = Object.keys($scope.showingColumnValues[name]).join(','); - } - }, - - }; - - // Loads all parameters into $scope from the URL query string; - // any which are not found within the URL will keep their current value. - $scope.queryParameters.load = function() { - var nameValuePairs = $location.search(); - - // If urlSchemaVersion is not specified, we assume the current version. - var urlSchemaVersion = constants.URL_VALUE__SCHEMA_VERSION__CURRENT; - if (constants.URL_KEY__SCHEMA_VERSION in nameValuePairs) { - urlSchemaVersion = nameValuePairs[constants.URL_KEY__SCHEMA_VERSION]; - } else if ('hiddenResultTypes' in nameValuePairs) { - // The combination of: - // - absence of an explicit urlSchemaVersion, and - // - presence of the old 'hiddenResultTypes' field - // tells us that the URL is from the original urlSchemaVersion. - // See https://codereview.chromium.org/367173002/ - urlSchemaVersion = 0; - } - $scope.urlSchemaVersionLoaded = urlSchemaVersion; - - if (urlSchemaVersion != constants.URL_VALUE__SCHEMA_VERSION__CURRENT) { - nameValuePairs = $scope.upconvertUrlNameValuePairs(nameValuePairs, urlSchemaVersion); - } - angular.forEach($scope.queryParameters.map, - function(copier, paramName) { - copier.load(nameValuePairs, paramName); - } - ); - }; - - // Saves all parameters from $scope into the URL query string. - $scope.queryParameters.save = function() { - var nameValuePairs = {}; - nameValuePairs[constants.URL_KEY__SCHEMA_VERSION] = constants.URL_VALUE__SCHEMA_VERSION__CURRENT; - angular.forEach($scope.queryParameters.map, - function(copier, paramName) { - copier.save(nameValuePairs, paramName); - } - ); - $location.search(nameValuePairs); - }; - - /** - * Converts URL name/value pairs that were stored by a previous urlSchemaVersion - * to the currently needed format. - * - * @param oldNValuePairs name/value pairs found in the loaded URL - * @param oldUrlSchemaVersion which version of the schema was used to generate that URL - * - * @returns nameValuePairs as needed by the current URL parser - */ - $scope.upconvertUrlNameValuePairs = function(oldNameValuePairs, oldUrlSchemaVersion) { - var newNameValuePairs = {}; - angular.forEach(oldNameValuePairs, - function(value, name) { - if (oldUrlSchemaVersion < 1) { - if ('hiddenConfigs' == name) { - name = 'config'; - var valueSet = {}; - $scope.toggleValuesInSet(value.split(','), valueSet); - $scope.toggleValuesInSet( - $scope.allColumnValues[constants.KEY__EXTRACOLUMNS__CONFIG], - valueSet); - value = Object.keys(valueSet).join(','); - } else if ('hiddenResultTypes' == name) { - name = 'resultType'; - var valueSet = {}; - $scope.toggleValuesInSet(value.split(','), valueSet); - $scope.toggleValuesInSet( - $scope.allColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE], - valueSet); - value = Object.keys(valueSet).join(','); - } - } - - newNameValuePairs[name] = value; - } - ); - return newNameValuePairs; - } - - - // - // updateResults() and friends. - // - - /** - * Set $scope.areUpdatesPending (to enable/disable the Update Results - * button). - * - * TODO(epoger): We could reduce the amount of code by just setting the - * variable directly (from, e.g., a button's ng-click handler). But when - * I tried that, the HTML elements depending on the variable did not get - * updated. - * It turns out that this is due to variable scoping within an ng-repeat - * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat - * - * @param val boolean value to set $scope.areUpdatesPending to - */ - $scope.setUpdatesPending = function(val) { - $scope.areUpdatesPending = val; - } - - /** - * Update the displayed results, based on filters/settings, - * and call $scope.queryParameters.save() so that the new filter results - * can be bookmarked. - */ - $scope.updateResults = function() { - $scope.renderStartTime = window.performance.now(); - $log.debug("renderStartTime: " + $scope.renderStartTime); - $scope.displayLimit = $scope.displayLimitPending; - $scope.mergeIdenticalRows = $scope.mergeIdenticalRowsPending; - - // For each USE_FREEFORM_FILTER column, populate showingColumnValues. - // This is more efficient than applying the freeform filter within the - // tight loop in removeHiddenImagePairs. - angular.forEach( - $scope.filterableColumnNames, - function(columnName) { - var columnHeader = $scope.extraColumnHeaders[columnName]; - if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) { - var columnStringMatch = $scope.columnStringMatch[columnName]; - var showingColumnValues = {}; - angular.forEach( - $scope.allColumnValues[columnName], - function(columnValue) { - if (-1 != columnValue.indexOf(columnStringMatch)) { - showingColumnValues[columnValue] = true; - } - } - ); - $scope.showingColumnValues[columnName] = showingColumnValues; - } - } - ); - - // TODO(epoger): Every time we apply a filter, AngularJS creates - // another copy of the array. Is there a way we can filter out - // the imagePairs as they are displayed, rather than storing multiple - // array copies? (For better performance.) - - if ($scope.viewingTab == $scope.defaultTab) { - var doReverse = !currSortAsc; - - $scope.filteredImagePairs = - $filter("orderBy")( - $filter("removeHiddenImagePairs")( - $scope.imagePairs, - $scope.filterableColumnNames, - $scope.showingColumnValues, - $scope.viewingTab - ), - [$scope.getSortColumnValue, $scope.getSecondOrderSortValue], - doReverse); - $scope.limitedImagePairs = $filter("mergeAndLimit")( - $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdenticalRows); - } else { - $scope.filteredImagePairs = - $filter("orderBy")( - $filter("filter")( - $scope.imagePairs, - {tab: $scope.viewingTab}, - true - ), - [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]); - $scope.limitedImagePairs = $filter("mergeAndLimit")( - $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows); - } - $scope.showThumbnails = $scope.showThumbnailsPending; - $scope.imageSize = $scope.imageSizePending; - $scope.setUpdatesPending(false); - $scope.queryParameters.save(); - } - - /** - * This function is called when the results have been completely rendered - * after updateResults(). - */ - $scope.resultsUpdatedCallback = function() { - $scope.renderEndTime = window.performance.now(); - $log.debug("renderEndTime: " + $scope.renderEndTime); - } - - /** - * Re-sort the displayed results. - * - * @param subdict (string): which KEY__IMAGEPAIRS__* subdictionary - * the sort column key is within, or 'none' if the sort column - * key is one of KEY__IMAGEPAIRS__* - * @param key (string): sort by value associated with this key in subdict - */ - $scope.sortResultsBy = function(subdict, key) { - // if we are already sorting by this column then toggle between asc/desc - if ((subdict === $scope.sortColumnSubdict) && ($scope.sortColumnKey === key)) { - currSortAsc = !currSortAsc; - } else { - $scope.sortColumnSubdict = subdict; - $scope.sortColumnKey = key; - currSortAsc = true; - } - $scope.updateResults(); - } - - /** - * Returns ASC or DESC (from constants) if currently the data - * is sorted by the provided column. - * - * @param colName: name of the column for which we need to get the class. - */ - - $scope.sortedByColumnsCls = function (colName) { - if ($scope.sortColumnKey !== colName) { - return ''; - } - - var result = (currSortAsc) ? constants.ASC : constants.DESC; - console.log("sort class:", result); - return result; - }; - - /** - * For a particular ImagePair, return the value of the column we are - * sorting on (according to $scope.sortColumnSubdict and - * $scope.sortColumnKey). - * - * @param imagePair: imagePair to get a column value out of. - */ - $scope.getSortColumnValue = function(imagePair) { - if ($scope.sortColumnSubdict in imagePair) { - return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey]; - } else if ($scope.sortColumnKey in imagePair) { - return imagePair[$scope.sortColumnKey]; - } else { - return undefined; - } - }; - - /** - * For a particular ImagePair, return the value we use for the - * second-order sort (tiebreaker when multiple rows have - * the same getSortColumnValue()). - * - * We join the imageA and imageB urls for this value, so that we merge - * adjacent rows as much as possible. - * - * @param imagePair: imagePair to get a column value out of. - */ - $scope.getSecondOrderSortValue = function(imagePair) { - return imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" + - imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - }; - - /** - * Set $scope.columnStringMatch[name] = value, and update results. - * - * @param name - * @param value - */ - $scope.setColumnStringMatch = function(name, value) { - $scope.columnStringMatch[name] = value; - $scope.updateResults(); - }; - - /** - * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName] - * so that ONLY entries with this columnValue are showing, and update the visible results. - * (We update both of those, so we cover both freeform and checkbox filtered columns.) - * - * @param columnName - * @param columnValue - */ - $scope.showOnlyColumnValue = function(columnName, columnValue) { - $scope.columnStringMatch[columnName] = columnValue; - $scope.showingColumnValues[columnName] = {}; - $scope.toggleValueInSet(columnValue, $scope.showingColumnValues[columnName]); - $scope.updateResults(); - }; - - /** - * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName] - * so that ALL entries are showing, and update the visible results. - * (We update both of those, so we cover both freeform and checkbox filtered columns.) - * - * @param columnName - */ - $scope.showAllColumnValues = function(columnName) { - $scope.columnStringMatch[columnName] = ""; - $scope.showingColumnValues[columnName] = {}; - $scope.toggleValuesInSet($scope.allColumnValues[columnName], - $scope.showingColumnValues[columnName]); - $scope.updateResults(); - }; - - - // - // Operations for sending info back to the server. - // - - /** - * Tell the server that the actual results of these particular tests - * are acceptable. - * - * This assumes that the original expectations are in imageSetA, and the - * new expectations are in imageSetB. That's fine, because the server - * mandates that anyway (it will swap the sets if the user requests them - * in the opposite order). - * - * @param imagePairsSubset an array of test results, most likely a subset of - * $scope.imagePairs (perhaps with some modifications) - */ - $scope.submitApprovals = function(imagePairsSubset) { - $scope.submitPending = true; - $scope.diffResults = ""; - - // Convert bug text field to null or 1-item array. - var bugs = null; - var bugNumber = parseInt($scope.submitAdvancedSettings['bug']); - if (!isNaN(bugNumber)) { - bugs = [bugNumber]; - } - - var updatedExpectations = []; - for (var i = 0; i < imagePairsSubset.length; i++) { - var imagePair = imagePairsSubset[i]; - var updatedExpectation = {}; - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = - imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS]; - updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] = - imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]; - updatedExpectation[constants.KEY__IMAGEPAIRS__SOURCE_JSON_FILE] = - imagePair[constants.KEY__IMAGEPAIRS__SOURCE_JSON_FILE]; - // IMAGE_B_URL contains the actual image (which is now the expectation) - updatedExpectation[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] = - imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - - // Advanced settings... - if (null == updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]) { - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = {}; - } - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] - [constants.KEY__EXPECTATIONS__REVIEWED] = - $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__REVIEWED]; - if (true == $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__IGNOREFAILURE]) { - // if it's false, don't send it at all (just keep the default) - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] - [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true; - } - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] - [constants.KEY__EXPECTATIONS__BUGS] = bugs; - - updatedExpectations.push(updatedExpectation); - } - var modificationData = {}; - modificationData[constants.KEY__LIVE_EDITS__MODIFICATIONS] = - updatedExpectations; - modificationData[constants.KEY__LIVE_EDITS__SET_A_DESCRIPTIONS] = - $scope.header[constants.KEY__HEADER__SET_A_DESCRIPTIONS]; - modificationData[constants.KEY__LIVE_EDITS__SET_B_DESCRIPTIONS] = - $scope.header[constants.KEY__HEADER__SET_B_DESCRIPTIONS]; - $http({ - method: "POST", - url: "/live-edits", - data: modificationData - }).success(function(data, status, headers, config) { - $scope.diffResults = data; - var blob = new Blob([$scope.diffResults], {type: 'text/plain'}); - $scope.diffResultsBlobUrl = window.URL.createObjectURL(blob); - $scope.submitPending = false; - }).error(function(data, status, headers, config) { - alert("There was an error submitting your baselines.\n\n" + - "Please see server-side log for details."); - $scope.submitPending = false; - }); - }; - - - // - // Operations we use to mimic Set semantics, in such a way that - // checking for presence within the Set is as fast as possible. - // But getting a list of all values within the Set is not necessarily - // possible. - // TODO(epoger): move into a separate .js file? - // - - /** - * Returns the number of values present within set "set". - * - * @param set an Object which we use to mimic set semantics - */ - $scope.setSize = function(set) { - return Object.keys(set).length; - }; - - /** - * Returns true if value "value" is present within set "set". - * - * @param value a value of any type - * @param set an Object which we use to mimic set semantics - * (this should make isValueInSet faster than if we used an Array) - */ - $scope.isValueInSet = function(value, set) { - return (true == set[value]); - }; - - /** - * If value "value" is already in set "set", remove it; otherwise, add it. - * - * @param value a value of any type - * @param set an Object which we use to mimic set semantics - */ - $scope.toggleValueInSet = function(value, set) { - if (true == set[value]) { - delete set[value]; - } else { - set[value] = true; - } - }; - - /** - * For each value in valueArray, call toggleValueInSet(value, set). - * - * @param valueArray - * @param set - */ - $scope.toggleValuesInSet = function(valueArray, set) { - var arrayLength = valueArray.length; - for (var i = 0; i < arrayLength; i++) { - $scope.toggleValueInSet(valueArray[i], set); - } - }; - - - // - // Array operations; similar to our Set operations, but operate on a - // Javascript Array so we *can* easily get a list of all values in the Set. - // TODO(epoger): move into a separate .js file? - // - - /** - * Returns true if value "value" is present within array "array". - * - * @param value a value of any type - * @param array a Javascript Array - */ - $scope.isValueInArray = function(value, array) { - return (-1 != array.indexOf(value)); - }; - - /** - * If value "value" is already in array "array", remove it; otherwise, - * add it. - * - * @param value a value of any type - * @param array a Javascript Array - */ - $scope.toggleValueInArray = function(value, array) { - var i = array.indexOf(value); - if (-1 == i) { - array.push(value); - } else { - array.splice(i, 1); - } - }; - - - // - // Miscellaneous utility functions. - // TODO(epoger): move into a separate .js file? - // - - /** - * Returns a single "column slice" of a 2D array. - * - * For example, if array is: - * [[A0, A1], - * [B0, B1], - * [C0, C1]] - * and index is 0, this this will return: - * [A0, B0, C0] - * - * @param array a Javascript Array - * @param column (numeric): index within each row array - */ - $scope.columnSliceOf2DArray = function(array, column) { - var slice = []; - var numRows = array.length; - for (var row = 0; row < numRows; row++) { - slice.push(array[row][column]); - } - return slice; - }; - - /** - * Returns a human-readable (in local time zone) time string for a - * particular moment in time. - * - * @param secondsPastEpoch (numeric): seconds past epoch in UTC - */ - $scope.localTimeString = function(secondsPastEpoch) { - var d = new Date(secondsPastEpoch * 1000); - return d.toString(); - }; - - /** - * Returns a hex color string (such as "#aabbcc") for the given RGB values. - * - * @param r (numeric): red channel value, 0-255 - * @param g (numeric): green channel value, 0-255 - * @param b (numeric): blue channel value, 0-255 - */ - $scope.hexColorString = function(r, g, b) { - var rString = r.toString(16); - if (r < 16) { - rString = "0" + rString; - } - var gString = g.toString(16); - if (g < 16) { - gString = "0" + gString; - } - var bString = b.toString(16); - if (b < 16) { - bString = "0" + bString; - } - return '#' + rString + gString + bString; - }; - - /** - * Returns a hex color string (such as "#aabbcc") for the given brightness. - * - * @param brightnessString (string): 0-255, 0 is completely black - * - * TODO(epoger): It might be nice to tint the color when it's not completely - * black or completely white. - */ - $scope.brightnessStringToHexColor = function(brightnessString) { - var v = parseInt(brightnessString); - return $scope.hexColorString(v, v, v); - }; - } -); diff --git a/gm/rebaseline_server/static/live-view.html b/gm/rebaseline_server/static/live-view.html deleted file mode 100644 index 1662adf89c..0000000000 --- a/gm/rebaseline_server/static/live-view.html +++ /dev/null @@ -1,446 +0,0 @@ -<!DOCTYPE html> - -<html ng-app="Loader" ng-controller="Loader.Controller"> - -<head> - <title ng-bind="windowTitle"></title> - <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> - <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.20/angular.js"></script> - <script src="constants.js"></script> - <script src="live-loader.js"></script> - <script src="utils.js"></script> - - <link rel="stylesheet" href="view.css"> -</head> - -<body> - <h2> - Instructions, roadmap, etc. are at - <a href="http://tinyurl.com/SkiaRebaselineServer"> - http://tinyurl.com/SkiaRebaselineServer - </a> - </h2> - - <em ng-show="!readyToDisplay"> - Loading results of query: - <ul> - <li>setA: "{{setASection}}" within {{setADir}}</li> - <li>setB: "{{setBSection}}" within {{setBDir}}</li> - </ul> - <br> - {{loadingMessage}} - </em> - - <div ng-show="readyToDisplay"> - - <div class="warning-div" - ng-show="urlSchemaVersionLoaded != constants.URL_VALUE__SCHEMA_VERSION__CURRENT"> - WARNING! The URL you loaded used schema version {{urlSchemaVersionLoaded}}, rather than - the most recent version {{constants.URL_VALUE__SCHEMA_VERSION__CURRENT}}. It has been - converted to the most recent version on a best-effort basis; you may wish to double-check - which records are displayed. - </div> - - <div ng-show="header[constants.KEY__HEADER__TIME_UPDATED]"> - setA: "{{header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__SECTION]}}" - within {{header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__DIR]}} - <span ng-show="header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__REPO_REVISION]">at <a href="https://skia.googlesource.com/skia/+/{{header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__REPO_REVISION]}}">rev {{header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__REPO_REVISION]}}</a></span> - <br> - setB: "{{header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__SECTION]}}" - within {{header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__DIR]}} - <span ng-show="header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__REPO_REVISION]">at <a href="https://skia.googlesource.com/skia/+/{{header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__REPO_REVISION]}}">rev {{header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__REPO_REVISION]}}</a></span> - <br> - <a href="{{liveQueryUrl}}">latest raw JSON diffs between these two sets</a><br> - These results current as of - {{localTimeString(header[constants.KEY__HEADER__TIME_UPDATED])}} - </div> - - <div class="tab-wrapper"><!-- tabs --> - <div class="tab-spacer" ng-repeat="tab in tabs"> - <div class="tab tab-{{tab == viewingTab}}" - ng-click="setViewingTab(tab)"> - {{tab}} ({{numResultsPerTab[tab]}}) - </div> - <div class="tab-spacer"> - - </div> - </div> - </div><!-- tabs --> - - <div class="tab-main"><!-- main display area of selected tab --> - - <br> - <!-- We only show the filters/settings table on the Unfiled tab. --> - <table ng-show="viewingTab == defaultTab" border="1"> - <tr> - <th colspan="4"> - Filters - </th> - <th> - Settings - </th> - </tr> - <tr valign="top"> - - <!-- filters --> - <td ng-repeat="columnName in orderedColumnNames"> - - <!-- Only display filterable columns here... --> - <div ng-if="extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE]"> - {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}<br> - - <!-- If we filter this column using free-form text match... --> - <div ng-if="extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]"> - <input type="text" - ng-model="columnStringMatch[columnName]" - ng-change="setUpdatesPending(true)"/> - <br> - <button ng-click="setColumnStringMatch(columnName, '')" - ng-disabled="('' == columnStringMatch[columnName])"> - clear (show all) - </button> - </div> - - <!-- If we filter this column using checkboxes... --> - <div ng-if="!extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]"> - <label ng-repeat="valueAndCount in extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS]"> - <input type="checkbox" - name="resultTypes" - value="{{valueAndCount[0]}}" - ng-checked="isValueInSet(valueAndCount[0], showingColumnValues[columnName])" - ng-click="toggleValueInSet(valueAndCount[0], showingColumnValues[columnName]); setUpdatesPending(true)"> - {{valueAndCount[0]}} ({{valueAndCount[1]}})<br> - </label> - <button ng-click="showingColumnValues[columnName] = {}; toggleValuesInSet(allColumnValues[columnName], showingColumnValues[columnName]); updateResults()" - ng-disabled="!readyToDisplay || allColumnValues[columnName].length == setSize(showingColumnValues[columnName])"> - all - </button> - <button ng-click="showingColumnValues[columnName] = {}; updateResults()" - ng-disabled="!readyToDisplay || 0 == setSize(showingColumnValues[columnName])"> - none - </button> - <button ng-click="toggleValuesInSet(allColumnValues[columnName], showingColumnValues[columnName]); updateResults()"> - toggle - </button> - </div> - - </div> - </td> - - <!-- settings --> - <td><table> - <tr><td> - <input type="checkbox" ng-model="showThumbnailsPending" - ng-init="showThumbnailsPending = true" - ng-change="areUpdatesPending = true"/> - Show thumbnails - </td></tr> - <tr><td> - <input type="checkbox" ng-model="mergeIdenticalRowsPending" - ng-init="mergeIdenticalRowsPending = true" - ng-change="areUpdatesPending = true"/> - Merge identical rows - </td></tr> - <tr><td> - Image width - <input type="text" ng-model="imageSizePending" - ng-init="imageSizePending=100" - ng-change="areUpdatesPending = true" - maxlength="4"/> - </td></tr> - <tr><td> - Max records to display - <input type="text" ng-model="displayLimitPending" - ng-init="displayLimitPending=50" - ng-change="areUpdatesPending = true" - maxlength="4"/> - </td></tr> - <tr><td> - <button class="update-results-button" - ng-click="updateResults()" - ng-disabled="!areUpdatesPending"> - Update Results - </button> - </td></tr> - </tr></table></td> - </tr> - </table> - - <p> - - <!-- Submission UI that we only show in the Pending Approval tab. --> - <div ng-show="'Pending Approval' == viewingTab"> - <div style="display:inline-block"> - <button style="font-size:20px" - ng-click="submitApprovals(filteredImagePairs)" - ng-disabled="submitPending || (filteredImagePairs.length == 0)"> - Get a patchfile to update these {{filteredImagePairs.length}} expectations - </button> - </div> - <div style="display:inline-block"> - <div style="font-size:20px" - ng-show="submitPending"> - Submitting, please wait... - </div> - </div> - <div> - Advanced settings... - <input type="checkbox" ng-model="showSubmitAdvancedSettings"> - show - <ul ng-show="showSubmitAdvancedSettings"> - <li ng-repeat="setting in [constants.KEY__EXPECTATIONS__REVIEWED, constants.KEY__EXPECTATIONS__IGNOREFAILURE]"> - {{setting}} - <input type="checkbox" ng-model="submitAdvancedSettings[setting]"> - </li> - <li ng-repeat="setting in ['bug']"> - {{setting}} - <input type="text" ng-model="submitAdvancedSettings[setting]"> - </li> - </ul> - </div> - <div ng-show="diffResults"> - <p> - Here is the patch to apply to your local checkout: - <br> - <textarea rows="8" cols="50">{{diffResults}}</textarea> - <br> - <a download="patch.txt" ng-href="{{diffResultsBlobUrl}}"> - Click here to download that patch as a text file. - </a> - </div> - </div> - - <p> - - <table border="0"><tr><td> <!-- table holding results header + results table --> - <table border="0" width="100%"> <!-- results header --> - <tr> - <td> - Found {{filteredImagePairs.length}} matches; - <span ng-show="filteredImagePairs.length > limitedImagePairs.length"> - displaying the first {{limitedImagePairs.length}}. - </span> - <span ng-show="filteredImagePairs.length <= limitedImagePairs.length"> - displaying them all. - </span> - <span ng-show="renderEndTime > renderStartTime"> - Rendered in {{(renderEndTime - renderStartTime).toFixed(0)}} ms. - </span> - <br> - (click on the column header radio buttons to re-sort by that column) - </td> - <td align="right"> - <div> - all tests shown: - <button ng-click="selectAllImagePairs()"> - select - </button> - <button ng-click="clearAllImagePairs()"> - clear - </button> - <button ng-click="toggleAllImagePairs()"> - toggle - </button> - </div> - <div ng-repeat="otherTab in tabs"> - <button ng-click="moveSelectedImagePairsToTab(otherTab)" - ng-disabled="selectedImagePairs.length == 0" - ng-show="otherTab != viewingTab"> - move {{selectedImagePairs.length}} selected tests to {{otherTab}} tab - </button> - </div> - </td> - </tr> - </table> <!-- results header --> - </td></tr><tr><td> - <table border="1" ng-app="diff_viewer"> <!-- results --> - <tr> - <!-- Most column headers are displayed in a common fashion... --> - <th ng-repeat="columnName in orderedColumnNames"> - <a ng-class="'sort-' + sortedByColumnsCls(columnName)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXTRACOLUMNS, columnName)" - href="" - class="sortable-header"> - {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}} - </a> - </th> - <!-- ... but there are a few columns where we display things differently. --> - <th> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__EXPECTATIONS__BUGS)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXPECTATIONS, constants.KEY__EXPECTATIONS__BUGS)" - href="" - class="sortable-header"> - bugs - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_A_URL)" - ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)" - href="" - title="setA: '{{header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__SECTION]}}' within {{header[constants.KEY__HEADER__SET_A_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__DIR]}}" - class="sortable-header"> - <span ng-show="'Pending Approval' != viewingTab"> - {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}} - </span> - <span ng-show="'Pending Approval' == viewingTab"> - old expectations - </span> - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_B_URL)" - ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)" - href="" - title="setB: '{{header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__SECTION]}}' within {{header[constants.KEY__HEADER__SET_B_DESCRIPTIONS][constants.KEY__SET_DESCRIPTIONS__DIR]}}" - class="sortable-header"> - <span ng-show="'Pending Approval' != viewingTab"> - {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}} - </span> - <span ng-show="'Pending Approval' == viewingTab"> - new expectations - </span> - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)" - href="" - class="sortable-header"> - differing pixels in white - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)" - href="" - class="sortable-header"> - perceptual difference - </a> - <br> - <input type="range" ng-model="pixelDiffBgColorBrightness" - ng-init="pixelDiffBgColorBrightness=64; pixelDiffBgColor=brightnessStringToHexColor(pixelDiffBgColorBrightness)" - ng-change="pixelDiffBgColor=brightnessStringToHexColor(pixelDiffBgColorBrightness)" - title="image background brightness" - min="0" max="255"/> - </th> - <th> - <!-- imagepair-selection checkbox column --> - </th> - </tr> - - <tr ng-repeat="imagePair in limitedImagePairs" valign="top" - ng-class-odd="'results-odd'" ng-class-even="'results-even'" - results-updated-callback-directive> - - <td ng-repeat="columnName in orderedColumnNames"> - {{imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][columnName]}} - <br> - <button class="show-only-button" - ng-show="viewingTab == defaultTab" - ng-disabled="1 == setSize(showingColumnValues[columnName])" - ng-click="showOnlyColumnValue(columnName, imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][columnName])" - title="show only results of {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}} {{imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][columnName]}}"> - show only - </button> - <br> - <button class="show-all-button" - ng-show="viewingTab == defaultTab" - ng-disabled="allColumnValues[columnName].length == setSize(showingColumnValues[columnName])" - ng-click="showAllColumnValues(columnName)" - title="show results of all {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}s"> - show all - </button> - </td> - - <!-- bugs --> - <td> - <a ng-repeat="bug in imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS][constants.KEY__EXPECTATIONS__BUGS]" - href="https://code.google.com/p/skia/issues/detail?id={{bug}}" - target="_blank"> - {{bug}} - </a> - </td> - - <!-- image A --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] != null"> - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]}}" /> - </div> - <div ng-show="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] == null" - style="text-align:center"> - –none– - </div> - </td> - - <!-- image B --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] != null"> - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]}}" /> - </div> - <div ng-show="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] == null" - style="text-align:center"> - –none– - </div> - </td> - - <!-- whitediffs: every differing pixel shown in white --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - title="{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS] | number:0}} of {{(100 * imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS] / imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS]) | number:0}} pixels ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}%) differ from expectation."> - - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__WHITEDIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__WHITE_DIFF_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__WHITEDIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__WHITE_DIFF_URL]}}" /> - <br/> - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}% - ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS]}}) - </div> - <div ng-show="!imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - style="text-align:center"> - –none– - </div> - </td> - - <!-- diffs: per-channel RGB deltas --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - title="Perceptual difference measure is {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}%. Maximum difference per channel: R={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][0]}}, G={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][1]}}, B={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][2]}}"> - - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__DIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__DIFF_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - ng-style="{backgroundColor: pixelDiffBgColor}" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__DIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__DIFF_URL]}}" /> - <br/> - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}% - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL]}} - </div> - <div ng-show="!imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - style="text-align:center"> - –none– - </div> - </td> - - <td ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <br/> - <input type="checkbox" - name="rowSelect" - value="{{imagePair.index}}" - ng-checked="isValueInArray(imagePair.index, selectedImagePairs)" - ng-click="toggleSomeImagePairs($index, imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN])"> - </tr> - </table> <!-- imagePairs --> - </td></tr></table> <!-- table holding results header + imagePairs table --> - - </div><!-- main display area of selected tab --> - </div><!-- everything: hide until readyToDisplay --> - -</body> -</html> diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js deleted file mode 100644 index bfc639e33b..0000000000 --- a/gm/rebaseline_server/static/loader.js +++ /dev/null @@ -1,1035 +0,0 @@ -/* - * Loader: - * Reads GM result reports written out by results.py, and imports - * them into $scope.extraColumnHeaders and $scope.imagePairs . - */ -var Loader = angular.module( - 'Loader', - ['ConstantsModule'] -); - -Loader.directive( - 'resultsUpdatedCallbackDirective', - ['$timeout', - function($timeout) { - return function(scope, element, attrs) { - if (scope.$last) { - $timeout(function() { - scope.resultsUpdatedCallback(); - }); - } - }; - } - ] -); - -// TODO(epoger): Combine ALL of our filtering operations (including -// truncation) into this one filter, so that runs most efficiently? -// (We would have to make sure truncation still took place after -// sorting, though.) -Loader.filter( - 'removeHiddenImagePairs', - function(constants) { - return function(unfilteredImagePairs, filterableColumnNames, showingColumnValues, - viewingTab) { - var filteredImagePairs = []; - for (var i = 0; i < unfilteredImagePairs.length; i++) { - var imagePair = unfilteredImagePairs[i]; - var extraColumnValues = imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]; - var allColumnValuesAreVisible = true; - // Loop over all columns, and if any of them contain values not found in - // showingColumnValues[columnName], don't include this imagePair. - // - // We use this same filtering mechanism regardless of whether each column - // has USE_FREEFORM_FILTER set or not; if that flag is set, then we will - // have already used the freeform text entry block to populate - // showingColumnValues[columnName]. - for (var j = 0; j < filterableColumnNames.length; j++) { - var columnName = filterableColumnNames[j]; - var columnValue = extraColumnValues[columnName]; - if (!showingColumnValues[columnName][columnValue]) { - allColumnValuesAreVisible = false; - break; - } - } - if (allColumnValuesAreVisible && (viewingTab == imagePair.tab)) { - filteredImagePairs.push(imagePair); - } - } - return filteredImagePairs; - }; - } -); - -/** - * Limit the input imagePairs to some max number, and merge identical rows - * (adjacent rows which have the same (imageA, imageB) pair). - * - * @param unfilteredImagePairs imagePairs to filter - * @param maxPairs maximum number of pairs to output, or <0 for no limit - * @param mergeIdenticalRows if true, merge identical rows by setting - * ROWSPAN>1 on the first merged row, and ROWSPAN=0 for the rest - */ -Loader.filter( - 'mergeAndLimit', - function(constants) { - return function(unfilteredImagePairs, maxPairs, mergeIdenticalRows) { - var numPairs = unfilteredImagePairs.length; - if ((maxPairs > 0) && (maxPairs < numPairs)) { - numPairs = maxPairs; - } - var filteredImagePairs = []; - if (!mergeIdenticalRows || (numPairs == 1)) { - // Take a shortcut if we're not merging identical rows. - // We still need to set ROWSPAN to 1 for each row, for the HTML viewer. - for (var i = numPairs-1; i >= 0; i--) { - var imagePair = unfilteredImagePairs[i]; - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; - filteredImagePairs[i] = imagePair; - } - } else if (numPairs > 1) { - // General case--there are at least 2 rows, so we may need to merge some. - // Work from the bottom up, so we can keep a running total of how many - // rows should be merged, and set ROWSPAN of the top row accordingly. - var imagePair = unfilteredImagePairs[numPairs-1]; - var nextRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]; - var nextRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; - filteredImagePairs[numPairs-1] = imagePair; - for (var i = numPairs-2; i >= 0; i--) { - imagePair = unfilteredImagePairs[i]; - var thisRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]; - var thisRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - if ((thisRowImageAUrl == nextRowImageAUrl) && - (thisRowImageBUrl == nextRowImageBUrl)) { - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = - filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] + 1; - filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] = 0; - } else { - imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; - nextRowImageAUrl = thisRowImageAUrl; - nextRowImageBUrl = thisRowImageBUrl; - } - filteredImagePairs[i] = imagePair; - } - } else { - // No results. - } - return filteredImagePairs; - }; - } -); - - -Loader.controller( - 'Loader.Controller', - function($scope, $http, $filter, $location, $log, $timeout, constants) { - $scope.readyToDisplay = false; - $scope.constants = constants; - $scope.windowTitle = "Loading GM Results..."; - $scope.resultsToLoad = $location.search().resultsToLoad; - $scope.loadingMessage = "please wait..."; - - var currSortAsc = true; - - - /** - * On initial page load, load a full dictionary of results. - * Once the dictionary is loaded, unhide the page elements so they can - * render the data. - */ - $http.get($scope.resultsToLoad).success( - function(data, status, header, config) { - var dataHeader = data[constants.KEY__ROOT__HEADER]; - if (dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] != - constants.VALUE__HEADER__SCHEMA_VERSION) { - $scope.loadingMessage = "ERROR: Got JSON file with schema version " - + dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] - + " but expected schema version " - + constants.VALUE__HEADER__SCHEMA_VERSION; - } else if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) { - // Apply the server's requested reload delay to local time, - // so we will wait the right number of seconds regardless of clock - // skew between client and server. - var reloadDelayInSeconds = - dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] - - dataHeader[constants.KEY__HEADER__TIME_UPDATED]; - var timeNow = new Date().getTime(); - var timeToReload = timeNow + reloadDelayInSeconds * 1000; - $scope.loadingMessage = - "server is still loading results; will retry at " + - $scope.localTimeString(timeToReload / 1000); - $timeout( - function(){location.reload();}, - timeToReload - timeNow); - } else { - $scope.loadingMessage = "processing data, please wait..."; - - $scope.header = dataHeader; - $scope.extraColumnHeaders = data[constants.KEY__ROOT__EXTRACOLUMNHEADERS]; - $scope.orderedColumnNames = data[constants.KEY__ROOT__EXTRACOLUMNORDER]; - $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS]; - $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS]; - - // set the default sort column and make it ascending. - $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES; - $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF; - currSortAsc = true; - - $scope.showSubmitAdvancedSettings = false; - $scope.submitAdvancedSettings = {}; - $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__REVIEWED] = true; - $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false; - $scope.submitAdvancedSettings['bug'] = ''; - - // Create the list of tabs (lists into which the user can file each - // test). This may vary, depending on isEditable. - $scope.tabs = [ - 'Unfiled', 'Hidden' - ]; - if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) { - $scope.tabs = $scope.tabs.concat( - ['Pending Approval']); - } - $scope.defaultTab = $scope.tabs[0]; - $scope.viewingTab = $scope.defaultTab; - - // Track the number of results on each tab. - $scope.numResultsPerTab = {}; - for (var i = 0; i < $scope.tabs.length; i++) { - $scope.numResultsPerTab[$scope.tabs[i]] = 0; - } - $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length; - - // Add index and tab fields to all records. - for (var i = 0; i < $scope.imagePairs.length; i++) { - $scope.imagePairs[i].index = i; - $scope.imagePairs[i].tab = $scope.defaultTab; - } - - // Arrays within which the user can toggle individual elements. - $scope.selectedImagePairs = []; - - // Set up filters. - // - // filterableColumnNames is a list of all column names we can filter on. - // allColumnValues[columnName] is a list of all known values - // for a given column. - // showingColumnValues[columnName] is a set indicating which values - // in a given column would cause us to show a row, rather than hiding it. - // - // columnStringMatch[columnName] is a string used as a pattern to generate - // showingColumnValues[columnName] for columns we filter using free-form text. - // It is ignored for any columns with USE_FREEFORM_FILTER == false. - $scope.filterableColumnNames = []; - $scope.allColumnValues = {}; - $scope.showingColumnValues = {}; - $scope.columnStringMatch = {}; - - angular.forEach( - Object.keys($scope.extraColumnHeaders), - function(columnName) { - var columnHeader = $scope.extraColumnHeaders[columnName]; - if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE]) { - $scope.filterableColumnNames.push(columnName); - $scope.allColumnValues[columnName] = $scope.columnSliceOf2DArray( - columnHeader[constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS], 0); - $scope.showingColumnValues[columnName] = {}; - $scope.toggleValuesInSet($scope.allColumnValues[columnName], - $scope.showingColumnValues[columnName]); - $scope.columnStringMatch[columnName] = ""; - } - } - ); - - // TODO(epoger): Special handling for RESULT_TYPE column: - // by default, show only KEY__RESULT_TYPE__FAILED results - $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE] = {}; - $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE][ - constants.KEY__RESULT_TYPE__FAILED] = true; - - // Set up mapping for URL parameters. - // parameter name -> copier object to load/save parameter value - $scope.queryParameters.map = { - 'resultsToLoad': $scope.queryParameters.copiers.simple, - 'displayLimitPending': $scope.queryParameters.copiers.simple, - 'showThumbnailsPending': $scope.queryParameters.copiers.simple, - 'mergeIdenticalRowsPending': $scope.queryParameters.copiers.simple, - 'imageSizePending': $scope.queryParameters.copiers.simple, - 'sortColumnSubdict': $scope.queryParameters.copiers.simple, - 'sortColumnKey': $scope.queryParameters.copiers.simple, - }; - // Some parameters are handled differently based on whether they USE_FREEFORM_FILTER. - angular.forEach( - $scope.filterableColumnNames, - function(columnName) { - if ($scope.extraColumnHeaders[columnName] - [constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) { - $scope.queryParameters.map[columnName] = - $scope.queryParameters.copiers.columnStringMatch; - } else { - $scope.queryParameters.map[columnName] = - $scope.queryParameters.copiers.showingColumnValuesSet; - } - } - ); - - // If any defaults were overridden in the URL, get them now. - $scope.queryParameters.load(); - - // Any image URLs which are relative should be relative to the JSON - // file's source directory; absolute URLs should be left alone. - var baseUrlKey = constants.KEY__IMAGESETS__FIELD__BASE_URL; - angular.forEach( - $scope.imageSets, - function(imageSet) { - var baseUrl = imageSet[baseUrlKey]; - if ((baseUrl.substring(0, 1) != '/') && - (baseUrl.indexOf('://') == -1)) { - imageSet[baseUrlKey] = $scope.resultsToLoad + '/../' + baseUrl; - } - } - ); - - $scope.readyToDisplay = true; - $scope.updateResults(); - $scope.loadingMessage = ""; - $scope.windowTitle = "Current GM Results"; - - $timeout( function() { - make_results_header_sticky(); - }); - } - } - ).error( - function(data, status, header, config) { - $scope.loadingMessage = "FAILED to load."; - $scope.windowTitle = "Failed to Load GM Results"; - } - ); - - - // - // Select/Clear/Toggle all tests. - // - - /** - * Select all currently showing tests. - */ - $scope.selectAllImagePairs = function() { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = 0; i < numImagePairsShowing; i++) { - var index = $scope.limitedImagePairs[i].index; - if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) { - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - } - }; - - /** - * Deselect all currently showing tests. - */ - $scope.clearAllImagePairs = function() { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = 0; i < numImagePairsShowing; i++) { - var index = $scope.limitedImagePairs[i].index; - if ($scope.isValueInArray(index, $scope.selectedImagePairs)) { - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - } - }; - - /** - * Toggle selection of all currently showing tests. - */ - $scope.toggleAllImagePairs = function() { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = 0; i < numImagePairsShowing; i++) { - var index = $scope.limitedImagePairs[i].index; - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - }; - - /** - * Toggle selection state of a subset of the currently showing tests. - * - * @param startIndex index within $scope.limitedImagePairs of the first - * test to toggle selection state of - * @param num number of tests (in a contiguous block) to toggle - */ - $scope.toggleSomeImagePairs = function(startIndex, num) { - var numImagePairsShowing = $scope.limitedImagePairs.length; - for (var i = startIndex; i < startIndex + num; i++) { - var index = $scope.limitedImagePairs[i].index; - $scope.toggleValueInArray(index, $scope.selectedImagePairs); - } - }; - - - // - // Tab operations. - // - - /** - * Change the selected tab. - * - * @param tab (string): name of the tab to select - */ - $scope.setViewingTab = function(tab) { - $scope.viewingTab = tab; - $scope.updateResults(); - }; - - /** - * Move the imagePairs in $scope.selectedImagePairs to a different tab, - * and then clear $scope.selectedImagePairs. - * - * @param newTab (string): name of the tab to move the tests to - */ - $scope.moveSelectedImagePairsToTab = function(newTab) { - $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab); - $scope.selectedImagePairs = []; - $scope.updateResults(); - }; - - /** - * Move a subset of $scope.imagePairs to a different tab. - * - * @param imagePairIndices (array of ints): indices into $scope.imagePairs - * indicating which test results to move - * @param newTab (string): name of the tab to move the tests to - */ - $scope.moveImagePairsToTab = function(imagePairIndices, newTab) { - var imagePairIndex; - var numImagePairs = imagePairIndices.length; - for (var i = 0; i < numImagePairs; i++) { - imagePairIndex = imagePairIndices[i]; - $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--; - $scope.imagePairs[imagePairIndex].tab = newTab; - } - $scope.numResultsPerTab[newTab] += numImagePairs; - }; - - - // - // $scope.queryParameters: - // Transfer parameter values between $scope and the URL query string. - // - $scope.queryParameters = {}; - - // load and save functions for parameters of each type - // (load a parameter value into $scope from nameValuePairs, - // save a parameter value from $scope into nameValuePairs) - $scope.queryParameters.copiers = { - 'simple': { - 'load': function(nameValuePairs, name) { - var value = nameValuePairs[name]; - if (value) { - $scope[name] = value; - } - }, - 'save': function(nameValuePairs, name) { - nameValuePairs[name] = $scope[name]; - } - }, - - 'columnStringMatch': { - 'load': function(nameValuePairs, name) { - var value = nameValuePairs[name]; - if (value) { - $scope.columnStringMatch[name] = value; - } - }, - 'save': function(nameValuePairs, name) { - nameValuePairs[name] = $scope.columnStringMatch[name]; - } - }, - - 'showingColumnValuesSet': { - 'load': function(nameValuePairs, name) { - var value = nameValuePairs[name]; - if (value) { - var valueArray = value.split(','); - $scope.showingColumnValues[name] = {}; - $scope.toggleValuesInSet(valueArray, $scope.showingColumnValues[name]); - } - }, - 'save': function(nameValuePairs, name) { - nameValuePairs[name] = Object.keys($scope.showingColumnValues[name]).join(','); - } - }, - - }; - - // Loads all parameters into $scope from the URL query string; - // any which are not found within the URL will keep their current value. - $scope.queryParameters.load = function() { - var nameValuePairs = $location.search(); - - // If urlSchemaVersion is not specified, we assume the current version. - var urlSchemaVersion = constants.URL_VALUE__SCHEMA_VERSION__CURRENT; - if (constants.URL_KEY__SCHEMA_VERSION in nameValuePairs) { - urlSchemaVersion = nameValuePairs[constants.URL_KEY__SCHEMA_VERSION]; - } else if ('hiddenResultTypes' in nameValuePairs) { - // The combination of: - // - absence of an explicit urlSchemaVersion, and - // - presence of the old 'hiddenResultTypes' field - // tells us that the URL is from the original urlSchemaVersion. - // See https://codereview.chromium.org/367173002/ - urlSchemaVersion = 0; - } - $scope.urlSchemaVersionLoaded = urlSchemaVersion; - - if (urlSchemaVersion != constants.URL_VALUE__SCHEMA_VERSION__CURRENT) { - nameValuePairs = $scope.upconvertUrlNameValuePairs(nameValuePairs, urlSchemaVersion); - } - angular.forEach($scope.queryParameters.map, - function(copier, paramName) { - copier.load(nameValuePairs, paramName); - } - ); - }; - - // Saves all parameters from $scope into the URL query string. - $scope.queryParameters.save = function() { - var nameValuePairs = {}; - nameValuePairs[constants.URL_KEY__SCHEMA_VERSION] = constants.URL_VALUE__SCHEMA_VERSION__CURRENT; - angular.forEach($scope.queryParameters.map, - function(copier, paramName) { - copier.save(nameValuePairs, paramName); - } - ); - $location.search(nameValuePairs); - }; - - /** - * Converts URL name/value pairs that were stored by a previous urlSchemaVersion - * to the currently needed format. - * - * @param oldNValuePairs name/value pairs found in the loaded URL - * @param oldUrlSchemaVersion which version of the schema was used to generate that URL - * - * @returns nameValuePairs as needed by the current URL parser - */ - $scope.upconvertUrlNameValuePairs = function(oldNameValuePairs, oldUrlSchemaVersion) { - var newNameValuePairs = {}; - angular.forEach(oldNameValuePairs, - function(value, name) { - if (oldUrlSchemaVersion < 1) { - if ('hiddenConfigs' == name) { - name = 'config'; - var valueSet = {}; - $scope.toggleValuesInSet(value.split(','), valueSet); - $scope.toggleValuesInSet( - $scope.allColumnValues[constants.KEY__EXTRACOLUMNS__CONFIG], - valueSet); - value = Object.keys(valueSet).join(','); - } else if ('hiddenResultTypes' == name) { - name = 'resultType'; - var valueSet = {}; - $scope.toggleValuesInSet(value.split(','), valueSet); - $scope.toggleValuesInSet( - $scope.allColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE], - valueSet); - value = Object.keys(valueSet).join(','); - } - } - - newNameValuePairs[name] = value; - } - ); - return newNameValuePairs; - } - - - // - // updateResults() and friends. - // - - /** - * Set $scope.areUpdatesPending (to enable/disable the Update Results - * button). - * - * TODO(epoger): We could reduce the amount of code by just setting the - * variable directly (from, e.g., a button's ng-click handler). But when - * I tried that, the HTML elements depending on the variable did not get - * updated. - * It turns out that this is due to variable scoping within an ng-repeat - * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat - * - * @param val boolean value to set $scope.areUpdatesPending to - */ - $scope.setUpdatesPending = function(val) { - $scope.areUpdatesPending = val; - } - - /** - * Update the displayed results, based on filters/settings, - * and call $scope.queryParameters.save() so that the new filter results - * can be bookmarked. - */ - $scope.updateResults = function() { - $scope.renderStartTime = window.performance.now(); - $log.debug("renderStartTime: " + $scope.renderStartTime); - $scope.displayLimit = $scope.displayLimitPending; - $scope.mergeIdenticalRows = $scope.mergeIdenticalRowsPending; - - // For each USE_FREEFORM_FILTER column, populate showingColumnValues. - // This is more efficient than applying the freeform filter within the - // tight loop in removeHiddenImagePairs. - angular.forEach( - $scope.filterableColumnNames, - function(columnName) { - var columnHeader = $scope.extraColumnHeaders[columnName]; - if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) { - var columnStringMatch = $scope.columnStringMatch[columnName]; - var showingColumnValues = {}; - angular.forEach( - $scope.allColumnValues[columnName], - function(columnValue) { - if (-1 != columnValue.indexOf(columnStringMatch)) { - showingColumnValues[columnValue] = true; - } - } - ); - $scope.showingColumnValues[columnName] = showingColumnValues; - } - } - ); - - // TODO(epoger): Every time we apply a filter, AngularJS creates - // another copy of the array. Is there a way we can filter out - // the imagePairs as they are displayed, rather than storing multiple - // array copies? (For better performance.) - if ($scope.viewingTab == $scope.defaultTab) { - var doReverse = !currSortAsc; - - $scope.filteredImagePairs = - $filter("orderBy")( - $filter("removeHiddenImagePairs")( - $scope.imagePairs, - $scope.filterableColumnNames, - $scope.showingColumnValues, - $scope.viewingTab - ), - // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue], - $scope.getSortColumnValue, - doReverse); - $scope.limitedImagePairs = $filter("mergeAndLimit")( - $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdenticalRows); - } else { - $scope.filteredImagePairs = - $filter("orderBy")( - $filter("filter")( - $scope.imagePairs, - {tab: $scope.viewingTab}, - true - ), - // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]); - $scope.getSortColumnValue); - $scope.limitedImagePairs = $filter("mergeAndLimit")( - $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows); - } - $scope.showThumbnails = $scope.showThumbnailsPending; - $scope.imageSize = $scope.imageSizePending; - $scope.setUpdatesPending(false); - $scope.queryParameters.save(); - } - - /** - * This function is called when the results have been completely rendered - * after updateResults(). - */ - $scope.resultsUpdatedCallback = function() { - $scope.renderEndTime = window.performance.now(); - $log.debug("renderEndTime: " + $scope.renderEndTime); - }; - - /** - * Re-sort the displayed results. - * - * @param subdict (string): which KEY__IMAGEPAIRS__* subdictionary - * the sort column key is within, or 'none' if the sort column - * key is one of KEY__IMAGEPAIRS__* - * @param key (string): sort by value associated with this key in subdict - */ - $scope.sortResultsBy = function(subdict, key) { - // if we are already sorting by this column then toggle between asc/desc - if ((subdict === $scope.sortColumnSubdict) && ($scope.sortColumnKey === key)) { - currSortAsc = !currSortAsc; - } else { - $scope.sortColumnSubdict = subdict; - $scope.sortColumnKey = key; - currSortAsc = true; - } - $scope.updateResults(); - }; - - /** - * Returns ASC or DESC (from constants) if currently the data - * is sorted by the provided column. - * - * @param colName: name of the column for which we need to get the class. - */ - - $scope.sortedByColumnsCls = function (colName) { - if ($scope.sortColumnKey !== colName) { - return ''; - } - - var result = (currSortAsc) ? constants.ASC : constants.DESC; - console.log("sort class:", result); - return result; - }; - - /** - * For a particular ImagePair, return the value of the column we are - * sorting on (according to $scope.sortColumnSubdict and - * $scope.sortColumnKey). - * - * @param imagePair: imagePair to get a column value out of. - */ - $scope.getSortColumnValue = function(imagePair) { - if ($scope.sortColumnSubdict in imagePair) { - return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey]; - } else if ($scope.sortColumnKey in imagePair) { - return imagePair[$scope.sortColumnKey]; - } else { - return undefined; - } - }; - - /** - * For a particular ImagePair, return the value we use for the - * second-order sort (tiebreaker when multiple rows have - * the same getSortColumnValue()). - * - * We join the imageA and imageB urls for this value, so that we merge - * adjacent rows as much as possible. - * - * @param imagePair: imagePair to get a column value out of. - */ - $scope.getSecondOrderSortValue = function(imagePair) { - return imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" + - imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - }; - - /** - * Set $scope.columnStringMatch[name] = value, and update results. - * - * @param name - * @param value - */ - $scope.setColumnStringMatch = function(name, value) { - $scope.columnStringMatch[name] = value; - $scope.updateResults(); - }; - - /** - * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName] - * so that ONLY entries with this columnValue are showing, and update the visible results. - * (We update both of those, so we cover both freeform and checkbox filtered columns.) - * - * @param columnName - * @param columnValue - */ - $scope.showOnlyColumnValue = function(columnName, columnValue) { - $scope.columnStringMatch[columnName] = columnValue; - $scope.showingColumnValues[columnName] = {}; - $scope.toggleValueInSet(columnValue, $scope.showingColumnValues[columnName]); - $scope.updateResults(); - }; - - /** - * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName] - * so that ALL entries are showing, and update the visible results. - * (We update both of those, so we cover both freeform and checkbox filtered columns.) - * - * @param columnName - */ - $scope.showAllColumnValues = function(columnName) { - $scope.columnStringMatch[columnName] = ""; - $scope.showingColumnValues[columnName] = {}; - $scope.toggleValuesInSet($scope.allColumnValues[columnName], - $scope.showingColumnValues[columnName]); - $scope.updateResults(); - }; - - - // - // Operations for sending info back to the server. - // - - /** - * Tell the server that the actual results of these particular tests - * are acceptable. - * - * TODO(epoger): This assumes that the original expectations are in - * imageSetA, and the actuals are in imageSetB. - * - * @param imagePairsSubset an array of test results, most likely a subset of - * $scope.imagePairs (perhaps with some modifications) - */ - $scope.submitApprovals = function(imagePairsSubset) { - $scope.submitPending = true; - - // Convert bug text field to null or 1-item array. - var bugs = null; - var bugNumber = parseInt($scope.submitAdvancedSettings['bug']); - if (!isNaN(bugNumber)) { - bugs = [bugNumber]; - } - - // TODO(epoger): This is a suboptimal way to prevent users from - // rebaselining failures in alternative renderModes, but it does work. - // For a better solution, see - // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new - // result type, RenderModeMismatch') - var encounteredComparisonConfig = false; - - var updatedExpectations = []; - for (var i = 0; i < imagePairsSubset.length; i++) { - var imagePair = imagePairsSubset[i]; - var updatedExpectation = {}; - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = - imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS]; - updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] = - imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]; - // IMAGE_B_URL contains the actual image (which is now the expectation) - updatedExpectation[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] = - imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; - if (0 == updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] - [constants.KEY__EXTRACOLUMNS__CONFIG] - .indexOf('comparison-')) { - encounteredComparisonConfig = true; - } - - // Advanced settings... - if (null == updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]) { - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = {}; - } - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] - [constants.KEY__EXPECTATIONS__REVIEWED] = - $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__REVIEWED]; - if (true == $scope.submitAdvancedSettings[ - constants.KEY__EXPECTATIONS__IGNOREFAILURE]) { - // if it's false, don't send it at all (just keep the default) - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] - [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true; - } - updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] - [constants.KEY__EXPECTATIONS__BUGS] = bugs; - - updatedExpectations.push(updatedExpectation); - } - if (encounteredComparisonConfig) { - alert("Approval failed -- you cannot approve results with config " + - "type comparison-*"); - $scope.submitPending = false; - return; - } - var modificationData = {}; - modificationData[constants.KEY__EDITS__MODIFICATIONS] = - updatedExpectations; - modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] = - $scope.header[constants.KEY__HEADER__DATAHASH]; - modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] = - $scope.header[constants.KEY__HEADER__TYPE]; - $http({ - method: "POST", - url: "/edits", - data: modificationData - }).success(function(data, status, headers, config) { - var imagePairIndicesToMove = []; - for (var i = 0; i < imagePairsSubset.length; i++) { - imagePairIndicesToMove.push(imagePairsSubset[i].index); - } - $scope.moveImagePairsToTab(imagePairIndicesToMove, - "HackToMakeSureThisImagePairDisappears"); - $scope.updateResults(); - alert("New baselines submitted successfully!\n\n" + - "You still need to commit the updated expectations files on " + - "the server side to the Skia repo.\n\n" + - "When you click OK, your web UI will reload; after that " + - "completes, you will see the updated data (once the server has " + - "finished loading the update results into memory!) and you can " + - "submit more baselines if you want."); - // I don't know why, but if I just call reload() here it doesn't work. - // Making a timer call it fixes the problem. - $timeout(function(){location.reload();}, 1); - }).error(function(data, status, headers, config) { - alert("There was an error submitting your baselines.\n\n" + - "Please see server-side log for details."); - $scope.submitPending = false; - }); - }; - - - // - // Operations we use to mimic Set semantics, in such a way that - // checking for presence within the Set is as fast as possible. - // But getting a list of all values within the Set is not necessarily - // possible. - // TODO(epoger): move into a separate .js file? - // - - /** - * Returns the number of values present within set "set". - * - * @param set an Object which we use to mimic set semantics - */ - $scope.setSize = function(set) { - return Object.keys(set).length; - }; - - /** - * Returns true if value "value" is present within set "set". - * - * @param value a value of any type - * @param set an Object which we use to mimic set semantics - * (this should make isValueInSet faster than if we used an Array) - */ - $scope.isValueInSet = function(value, set) { - return (true == set[value]); - }; - - /** - * If value "value" is already in set "set", remove it; otherwise, add it. - * - * @param value a value of any type - * @param set an Object which we use to mimic set semantics - */ - $scope.toggleValueInSet = function(value, set) { - if (true == set[value]) { - delete set[value]; - } else { - set[value] = true; - } - }; - - /** - * For each value in valueArray, call toggleValueInSet(value, set). - * - * @param valueArray - * @param set - */ - $scope.toggleValuesInSet = function(valueArray, set) { - var arrayLength = valueArray.length; - for (var i = 0; i < arrayLength; i++) { - $scope.toggleValueInSet(valueArray[i], set); - } - }; - - - // - // Array operations; similar to our Set operations, but operate on a - // Javascript Array so we *can* easily get a list of all values in the Set. - // TODO(epoger): move into a separate .js file? - // - - /** - * Returns true if value "value" is present within array "array". - * - * @param value a value of any type - * @param array a Javascript Array - */ - $scope.isValueInArray = function(value, array) { - return (-1 != array.indexOf(value)); - }; - - /** - * If value "value" is already in array "array", remove it; otherwise, - * add it. - * - * @param value a value of any type - * @param array a Javascript Array - */ - $scope.toggleValueInArray = function(value, array) { - var i = array.indexOf(value); - if (-1 == i) { - array.push(value); - } else { - array.splice(i, 1); - } - }; - - - // - // Miscellaneous utility functions. - // TODO(epoger): move into a separate .js file? - // - - /** - * Returns a single "column slice" of a 2D array. - * - * For example, if array is: - * [[A0, A1], - * [B0, B1], - * [C0, C1]] - * and index is 0, this this will return: - * [A0, B0, C0] - * - * @param array a Javascript Array - * @param column (numeric): index within each row array - */ - $scope.columnSliceOf2DArray = function(array, column) { - var slice = []; - var numRows = array.length; - for (var row = 0; row < numRows; row++) { - slice.push(array[row][column]); - } - return slice; - }; - - /** - * Returns a human-readable (in local time zone) time string for a - * particular moment in time. - * - * @param secondsPastEpoch (numeric): seconds past epoch in UTC - */ - $scope.localTimeString = function(secondsPastEpoch) { - var d = new Date(secondsPastEpoch * 1000); - return d.toString(); - }; - - /** - * Returns a hex color string (such as "#aabbcc") for the given RGB values. - * - * @param r (numeric): red channel value, 0-255 - * @param g (numeric): green channel value, 0-255 - * @param b (numeric): blue channel value, 0-255 - */ - $scope.hexColorString = function(r, g, b) { - var rString = r.toString(16); - if (r < 16) { - rString = "0" + rString; - } - var gString = g.toString(16); - if (g < 16) { - gString = "0" + gString; - } - var bString = b.toString(16); - if (b < 16) { - bString = "0" + bString; - } - return '#' + rString + gString + bString; - }; - - /** - * Returns a hex color string (such as "#aabbcc") for the given brightness. - * - * @param brightnessString (string): 0-255, 0 is completely black - * - * TODO(epoger): It might be nice to tint the color when it's not completely - * black or completely white. - */ - $scope.brightnessStringToHexColor = function(brightnessString) { - var v = parseInt(brightnessString); - return $scope.hexColorString(v, v, v); - }; - - } -); diff --git a/gm/rebaseline_server/static/new/bower.json b/gm/rebaseline_server/static/new/bower.json deleted file mode 100644 index 775213dd56..0000000000 --- a/gm/rebaseline_server/static/new/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "rebasline", - "version": "0.1.0", - "authors": [], - "description": "Rebaseline Server", - "license": "BSD", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "third_party/bower_components", - "test", - "tests" - ], - "dependencies": { - "angular": "1.2.x", - "angular-route": "1.2.x", - "angular-bootstrap": "0.11.x", - "bootstrap": "3.1.x" - } -} diff --git a/gm/rebaseline_server/static/new/css/app.css b/gm/rebaseline_server/static/new/css/app.css deleted file mode 100644 index fb0cc09e5c..0000000000 --- a/gm/rebaseline_server/static/new/css/app.css +++ /dev/null @@ -1,71 +0,0 @@ -/* app css stylesheet */ - -.formPadding { - padding-left: 2em !important; - padding-right: 0 !important; -} - -.controlBox { - border-left: 1px solid #ddd; - border-right: 1px solid #ddd; - border-bottom: 1px solid #ddd; - padding-right: 0; - width: 100%; - padding-top: 2em; -} - -.simpleLegend { - font-size: 16px; - margin-bottom: 3px; - width: 95%; -} - -.settingsForm { - padding-left: 1em; - padding-right: 0; -} - - -.resultsHeaderActions { - float: right; -} - -.sticky { - position: fixed; - top: 2px; - box-shadow: -2px 2px 5px 0 rgba(0,0,0,.45); - background: white; - right: 2px; - padding: 10px; - border: 2px solid #222; -} - -.sortDesc { - background:no-repeat left center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJZAMIcakQZjNtyhFxwEIIRofAookUnapu26t+6KFLYe1TgQ5VwQAOw%3D%3D); -} - -.sortAsc { - background:no-repeat left center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJRBJREKZsxQDsCSGIVzZFnYTGIqktp7fG46uzAn2TAyCMPC9QAQAOw%3D%3D); -} - -.sortableHeader { - padding-right: 3px; - padding-left: 13px; - margin-left: 4px; -} - -.updateBtn { - padding-top: 1em; - margin-left: 0; -} - -.filterBox { - border: 1px solid #DDDDDD; - margin-right: 1em; - padding-top: 5px; - padding-bottom: 5px; -} - -.filterKey { - font-weight: bold; -}
\ No newline at end of file diff --git a/gm/rebaseline_server/static/new/js/app.js b/gm/rebaseline_server/static/new/js/app.js deleted file mode 100644 index 0a1fac0a45..0000000000 --- a/gm/rebaseline_server/static/new/js/app.js +++ /dev/null @@ -1,1130 +0,0 @@ -'use strict'; - -/** - * TODO (stephana@): This is still work in progress. - * It does not offer the same functionality as the current version, but - * will serve as the starting point for a new backend. - * It works with the current backend, but does not support rebaselining. - */ - -/* - * Wrap everything into an IIFE to not polute the global namespace. - */ -(function () { - - // Declare app level module which contains everything of the current app. - // ui.bootstrap refers to directives defined in the AngularJS Bootstrap - // UI package (http://angular-ui.github.io/bootstrap/). - var app = angular.module('rbtApp', ['ngRoute', 'ui.bootstrap']); - - // Configure the different within app views. - app.config(['$routeProvider', function($routeProvider) { - $routeProvider.when('/', {templateUrl: 'partials/index-view.html', - controller: 'IndexCtrl'}); - $routeProvider.when('/view', {templateUrl: 'partials/rebaseline-view.html', - controller: 'RebaselineCrtrl'}); - $routeProvider.otherwise({redirectTo: '/'}); - }]); - - - // TODO (stephana): Some of these constants are 'gm' specific. In the - // next iteration we need to remove those as we move the more generic - // 'dm' testing tool. - // - // Shared constants used here and in the markup. These are exported when - // when used by a controller. - var c = { - // Define different view states as we load the data. - ST_LOADING: 1, - ST_STILL_LOADING: 2, - ST_READY: 3, - - // These column types are used by the Column class. - COL_T_FILTER: 'filter', - COL_T_IMAGE: 'image', - COL_T_REGULAR: 'regular', - - // Request parameters used to select between subsets of results. - RESULTS_ALL: 'all', - RESULTS_FAILURES: 'failures', - - // Filter types are used by the Column class. - FILTER_FREE_FORM: 'free_form', - FILTER_CHECK_BOX: 'checkbox', - - // Columns either provided by the backend response or added in code. - // TODO (stephana): This should go away once we switch to 'dm'. - COL_BUGS: 'bugs', - COL_IGNORE_FAILURE: 'ignore-failure', - COL_REVIEWED_BY_HUMANS: 'reviewed-by-human', - - // Defines the order in which image columns appear. - // TODO (stephana@): needs to be driven by backend data. - IMG_COL_ORDER: [ - { - key: 'imageA', - urlField: ['imageAUrl'] - }, - { - key: 'imageB', - urlField: ['imageBUrl'] - }, - { - key: 'whiteDiffs', - urlField: ['differenceData', 'whiteDiffUrl'], - percentField: ['differenceData', 'percentDifferingPixels'], - valueField: ['differenceData', 'numDifferingPixels'] - }, - { - key: 'diffs', - urlField: ['differenceData', 'diffUrl'], - percentField: ['differenceData', 'perceptualDifference'], - valueField: ['differenceData', 'maxDiffPerChannel'] - } - ], - - // Choice of availabe image size selection. - IMAGE_SIZES: [ - 100, - 200, - 400 - ], - - // Choice of available number of records selection. - MAX_RECORDS: [ - '100', - '200', - '300' - ] - }; // end constants - - /* - * Index Controller - */ - // TODO (stephana): Remove $timeout since it only simulates loading delay. - app.controller('IndexCtrl', ['$scope', '$timeout', 'dataService', - function($scope, $timeout, dataService) { - // init the scope - $scope.c = c; - $scope.state = c.ST_LOADING; - $scope.qStr = dataService.getQueryString; - - // TODO (stephana): Remove and replace with index data generated by the - // backend to reflect the current "known" image sets to compare. - $scope.allSKPs = [ - { - params: { - setBSection: 'actual-results', - setASection: 'expected-results', - setBDir: 'gs://chromium-skia-skp-summaries/' + - 'Test-Mac10.8-MacMini4.1-GeForce320M-x86_64-Debug', - setADir: 'repo:expectations/skp/' + - 'Test-Mac10.8-MacMini4.1-GeForce320M-x86_64-Debug' - }, - title: 'expected vs actuals on ' + - 'Test-Mac10.8-MacMini4.1-GeForce320M-x86_64-Debug' - }, - { - params: { - setBSection: 'actual-results', - setASection: 'expected-results', - setBDir: 'gs://chromium-skia-skp-summaries/' + - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release', - setADir: 'repo:expectations/skp/'+ - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release' - }, - title: 'expected vs actuals on Test-Ubuntu12-ShuttleA-GTX660-x86-Release' - }, - { - params: { - setBSection: 'actual-results', - setASection: 'actual-results', - setBDir: 'gs://chromium-skia-skp-summaries/' + - 'Test-Ubuntu12-ShuttleA-GTX660-x86-Release', - setADir: 'gs://chromium-skia-skp-summaries/' + - 'Test-Mac10.8-MacMini4.1-GeForce320M-x86_64-Debug' - }, - title: 'Actuals on Test-Mac10.8-MacMini4.1-GeForce320M-x86_64-Debug ' + - 'vs Test-Ubuntu12-ShuttleA-GTX660-x86-Release' - } - ]; - - // TODO (stephana): Remove this once we load index data from the server. - $timeout(function () { - $scope.state = c.ST_READY; - }); - }]); - - /* - * RebaselineCtrl - * Controls the main comparison view. - * - * @param {service} dataService Service that encapsulates functions to - * retrieve data from the backend. - * - */ - app.controller('RebaselineCrtrl', ['$scope', '$timeout', 'dataService', - function($scope, $timeout, dataService) { - // determine which to request - // TODO (stephana): This should be extracted from the query parameters. - var target = c.TARGET_GM; - - // process the rquest arguments - // TODO (stephana): This should be determined from the query parameters. - var loadFn = dataService.loadAll; - - // controller state variables - var allData = null; - var filterFuncs = null; - var currentData = null; - var selectedData = null; - - // Index of the column that should provide the sort key - var sortByIdx = 0; - - // Sort in asending (true) or descending (false) order - var sortOrderAsc = true; - - // Array of functions for each column used for comparison during sort. - var compareFunctions = null; - - // Variables to track load and render times - var startTime; - var loadStartTime; - - - /** Load the data from the backend **/ - loadStartTime = Date.now(); - function loadData() { - loadFn().then( - function (serverData) { - $scope.header = serverData.header; - $scope.loadTime = (Date.now() - loadStartTime)/1000; - - // keep polling if the data are not ready yet - if ($scope.header.resultsStillLoading) { - $scope.state = c.ST_STILL_LOADING; - $timeout(loadData, 5000); - return; - } - - // get the filter colunms and an array to hold filter data by user - var fcol = getFilterColumns(serverData); - $scope.filterCols = fcol[0]; - $scope.filterVals = fcol[1]; - - // Add extra columns and retrieve the image columns - var otherCols = [ Column.regular(c.COL_BUGS) ]; - var imageCols = getImageColumns(serverData); - - // Concat to get all columns - // NOTE: The order is important since filters are rendered first, - // followed by regular columns and images - $scope.allCols = $scope.filterCols.concat(otherCols, imageCols); - - // Pre-process the data and get the filter functions. - var dataFilters = getDataAndFilters(serverData, $scope.filterCols, - otherCols, imageCols); - allData = dataFilters[0]; - filterFuncs = dataFilters[1]; - - // Get regular columns (== not image columns) - var regularCols = $scope.filterCols.concat(otherCols); - - // Get the compare functions for regular and image columns. These - // are then used to sort by the respective columns. - compareFunctions = DataRow.getCompareFunctions(regularCols, - imageCols); - - // Filter and sort the results to get them ready for rendering - updateResults(); - - // Data are ready for display - $scope.state = c.ST_READY; - }, - function (httpErrResponse) { - console.log(httpErrResponse); - }); - }; - - /* - * updateResults - * Central render function. Everytime settings/filters/etc. changed - * this function is called to filter, sort and splice the data. - * - * NOTE (stephana): There is room for improvement here: before filtering - * and sorting we could check if this is necessary. But this has not been - * a bottleneck so far. - */ - function updateResults () { - // run digest before we update the results. This allows - // updateResults to be called from functions trigger by ngChange - $scope.updating = true; - startTime = Date.now(); - - // delay by one render cycle so it can be called via ng-change - $timeout(function() { - // filter data - selectedData = filterData(allData, filterFuncs, $scope.filterVals); - - // sort the selected data. - sortData(selectedData, compareFunctions, sortByIdx, sortOrderAsc); - - // only conside the elements that we really need - var nRecords = $scope.settings.nRecords; - currentData = selectedData.slice(0, parseInt(nRecords)); - - DataRow.setRowspanValues(currentData, $scope.mergeIdenticalRows); - - // update the scope with relevant data for rendering. - $scope.data = currentData; - $scope.totalRecords = allData.length; - $scope.showingRecords = currentData.length; - $scope.selectedRecords = selectedData.length; - $scope.updating = false; - - // measure the filter time and total render time (via timeout). - $scope.filterTime = Date.now() - startTime; - $timeout(function() { - $scope.renderTime = Date.now() - startTime; - }); - }); - }; - - /** - * Generate the style value to set the width of images. - * - * @param {Column} col Column that we are trying to render. - * @param {int} paddingPx Number of padding pixels. - * @param {string} defaultVal Default value if not an image column. - * - * @return {string} Value to be used in ng-style element to set the width - * of a image column. - **/ - $scope.getImageWidthStyle = function (col, paddingPx, defaultVal) { - var result = (col.ctype === c.COL_T_IMAGE) ? - ($scope.imageSize + paddingPx + 'px') : defaultVal; - return result; - }; - - /** - * Sets the column by which to sort the data. If called for the - * currently sorted column it will cause the sort to toggle between - * ascending and descending. - * - * @param {int} colIdx Index of the column to use for sorting. - **/ - $scope.sortBy = function (colIdx) { - if (sortByIdx === colIdx) { - sortOrderAsc = !sortOrderAsc; - } else { - sortByIdx = colIdx; - sortOrderAsc = true; - } - updateResults(); - }; - - /** - * Helper function to generate a CSS class indicating whether this column - * is the sort key. If it is a class name with the sort direction (Asc/Desc) is - * return otherwise the default value is returned. In markup we use this - * to display (or not display) an arrow next to the column name. - * - * @param {string} prefix Prefix of the classname to be generated. - * @param {int} idx Index of the target column. - * @param {string} defaultVal Value to return if current column is not used - * for sorting. - * - * @return {string} CSS class name that a combination of the prefix and - * direction indicator ('Asc' or 'Desc') if the column is - * used for sorting. Otherwise the defaultVal is returned. - **/ - $scope.getSortedClass = function (prefix, idx, defaultVal) { - if (idx === sortByIdx) { - return prefix + ((sortOrderAsc) ? 'Asc' : 'Desc'); - } - - return defaultVal; - }; - - /** - * Checkbox to merge identical records has change. Force an update. - **/ - $scope.mergeRowsChanged = function () { - updateResults(); - } - - /** - * Max number of records to display has changed. Force an update. - **/ - $scope.maxRecordsChanged = function () { - updateResults(); - }; - - /** - * Filter settings changed. Force an update. - **/ - $scope.filtersChanged = function () { - updateResults(); - }; - - /** - * Sets all possible values of the specified values to the given value. - * That means all checkboxes are eiter selected or unselected. - * Then force an update. - * - * @param {int} idx Index of the target filter column. - * @param {boolean} val Value to set the filter values to. - * - **/ - $scope.setFilterAll = function (idx, val) { - for(var i=0, len=$scope.filterVals[idx].length; i<len; i++) { - $scope.filterVals[idx][i] = val; - } - updateResults(); - }; - - /** - * Toggle the values of a filter. This toggles all values in a - * filter. - * - * @param {int} idx Index of the target filter column. - **/ - $scope.setFilterToggle = function (idx) { - for(var i=0, len=$scope.filterVals[idx].length; i<len; i++) { - $scope.filterVals[idx][i] = !$scope.filterVals[idx][i]; - } - updateResults(); - }; - - // **************************************** - // Initialize the scope. - // **************************************** - - // Inject the constants into the scope and set the initial state. - $scope.c = c; - $scope.state = c.ST_LOADING; - - // Initial settings - $scope.settings = { - showThumbnails: true, - imageSize: c.IMAGE_SIZES[0], - nRecords: c.MAX_RECORDS[0], - mergeIdenticalRows: true - }; - - // Initial values for filters set in loadData() - $scope.filterVals = []; - - // Information about records - set in loadData() - $scope.totalRecords = 0; - $scope.showingRecords = 0; - $scope.updating = false; - - // Trigger the data loading. - loadData(); - - }]); - - // data structs to interface with markup and backend - /** - * Models a column. It aggregates attributes of all - * columns types. Some might be empty. See convenience - * factory methods below for different column types. - * - * @param {string} key Uniquely identifies this columns - * @param {string} ctype Type of columns. Use COL_* constants. - * @param {string} ctitle Human readable title of the column. - * @param {string} ftype Filter type. Use FILTER_* constants. - * @param {FilterOpt[]} foptions Filter options. For 'checkbox' filters this - is used to render all the checkboxes. - For freeform filters this is a list of all - available values. - * @param {string} baseUrl Baseurl for image columns. All URLs are relative - to this. - * - * @return {Column} Instance of the Column class. - **/ - function Column(key, ctype, ctitle, ftype, foptions, baseUrl) { - this.key = key; - this.ctype = ctype; - this.ctitle = ctitle; - this.ftype = ftype; - this.foptions = foptions; - this.baseUrl = baseUrl; - this.foptionsArr = []; - - // get the array of filter options for lookup in indexOfOptVal - if (this.foptions) { - for(var i=0, len=foptions.length; i<len; i++) { - this.foptionsArr.push(this.foptions[i].value); - } - } - } - - /** - * Find the index of an value in a column with a fixed set - * of options. - * - * @param {string} optVal Value of the column. - * - * @return {int} Index of optVal in this column. - **/ - Column.prototype.indexOfOptVal = function (optVal) { - return this.foptionsArr.indexOf(optVal); - }; - - /** - * Set filter options for this column. - * - * @param {FilterOpt[]} foptions Possible values for this column. - **/ - Column.prototype.setFilterOptions = function (foptions) { - this.foptions = foptions; - }; - - /** - * Factory function to create a filter column. Same args as Column() - **/ - Column.filter = function(key, ctitle, ftype, foptions) { - return new Column(key, c.COL_T_FILTER, ctitle || key, ftype, foptions); - } - - /** - * Factory function to create an image column. Same args as Column() - **/ - Column.image = function (key, ctitle, baseUrl) { - return new Column(key, c.COL_T_IMAGE, ctitle || key, null, null, baseUrl); - }; - - /** - * Factory function to create a regular column. Same args as Column() - **/ - Column.regular = function (key, ctitle) { - return new Column(key, c.COL_T_REGULAR, ctitle || key); - }; - - /** - * Helper class to wrap a single option in a filter. - * - * @param {string} value Option value. - * @param {int} count Number of instances of this option in the dataset. - * - * @return {} Instance of FiltertOpt - **/ - function FilterOpt(value, count) { - this.value = value; - this.count = count; - } - - /** - * Container for a single row in the dataset. - * - * @param {int} rowspan Number of rows (including this and following rows) - that have identical values. - * @param {string[]} dataCols Values of the respective columns (combination - of filter and regular columns) - * @param {ImgVal[]} imageCols Image meta data for the image columns. - * - * @return {DataRow} Instance of DataRow. - **/ - function DataRow(rowspan, dataCols, imageCols) { - this.rowspan = rowspan; - this.dataCols = dataCols; - this.imageCols = imageCols; - } - - /** - * Gets the comparator functions for the columns in this dataset. - * The comparators are then used to sort the dataset by the respective - * column. - * - * @param {Column[]} dataCols Data columns (= non-image columns) - * @param {Column[]} imgCols Image columns. - * - * @return {Function[]} Array of functions that can be used to sort by the - * respective column. - **/ - DataRow.getCompareFunctions = function (dataCols, imgCols) { - var result = []; - for(var i=0, len=dataCols.length; i<len; i++) { - result.push(( function (col, idx) { - return function (a, b) { - return (a.dataCols[idx] < b.dataCols[idx]) ? -1 : - ((a.dataCols[idx] === b.dataCols[idx]) ? 0 : 1); - }; - }(dataCols[i], i) )); - } - - for(var i=0, len=imgCols.length; i<len; i++) { - result.push((function (col, idx) { - return function (a,b) { - var aVal = a.imageCols[idx].percent; - var bVal = b.imageCols[idx].percent; - - return (aVal < bVal) ? -1 : ((aVal === bVal) ? 0 : 1); - }; - }(imgCols[i], i) )); - } - - return result; - }; - - /** - * Set the rowspan values of a given array of DataRow instances. - * - * @param {DataRow[]} data Dataset in desired order (after sorting). - * @param {mergeRows} mergeRows Indicate whether to sort - **/ - DataRow.setRowspanValues = function (data, mergeRows) { - var curIdx, rowspan, cur; - if (mergeRows) { - for(var i=0, len=data.length; i<len;) { - curIdx = i; - cur = data[i]; - rowspan = 1; - for(i++; ((i<len) && (data[i].dataCols === cur.dataCols)); i++) { - rowspan++; - data[i].rowspan=0; - } - data[curIdx].rowspan = rowspan; - } - } else { - for(var i=0, len=data.length; i<len; i++) { - data[i].rowspan = 1; - } - } - }; - - /** - * Wrapper class for image related data. - * - * @param {string} url Relative Url of the image or null if not available. - * @param {float} percent Percent of pixels that are differing. - * @param {int} value Absolute number of pixes differing. - * - * @return {ImgVal} Instance of ImgVal. - **/ - function ImgVal(url, percent, value) { - this.url = url; - this.percent = percent; - this.value = value; - } - - /** - * Extracts the filter columns from the JSON response of the server. - * - * @param {object} data Server response. - * - * @return {Column[]} List of filter columns as described in 'header' field. - **/ - function getFilterColumns(data) { - var result = []; - var vals = []; - var colOrder = data.extraColumnOrder; - var colHeaders = data.extraColumnHeaders; - var fopts, optVals, val; - - for(var i=0, len=colOrder.length; i<len; i++) { - if (colHeaders[colOrder[i]].isFilterable) { - if (colHeaders[colOrder[i]].useFreeformFilter) { - result.push(Column.filter(colOrder[i], - colHeaders[colOrder[i]].headerText, - c.FILTER_FREE_FORM)); - vals.push(''); - } - else { - fopts = []; - optVals = []; - - // extract the different options for this column - for(var j=0, jlen=colHeaders[colOrder[i]].valuesAndCounts.length; - j<jlen; j++) { - val = colHeaders[colOrder[i]].valuesAndCounts[j]; - fopts.push(new FilterOpt(val[0], val[1])); - optVals.push(false); - } - - // ad the column and values - result.push(Column.filter(colOrder[i], - colHeaders[colOrder[i]].headerText, - c.FILTER_CHECK_BOX, - fopts)); - vals.push(optVals); - } - } - } - - return [result, vals]; - } - - /** - * Extracts the image columns from the JSON response of the server. - * - * @param {object} data Server response. - * - * @return {Column[]} List of images columns as described in 'header' field. - **/ - function getImageColumns(data) { - var CO = c.IMG_COL_ORDER; - var imgSet; - var result = []; - for(var i=0, len=CO.length; i<len; i++) { - imgSet = data.imageSets[CO[i].key]; - result.push(Column.image(CO[i].key, - imgSet.description, - ensureTrailingSlash(imgSet.baseUrl))); - } - return result; - } - - /** - * Make sure Url has a trailing '/'. - * - * @param {string} url Base url. - * @return {string} Same url with a trailing '/' or same as input if it - already contained '/'. - **/ - function ensureTrailingSlash(url) { - var result = url.trim(); - - // TODO: remove !!! - result = fixUrl(url); - if (result[result.length-1] !== '/') { - result += '/'; - } - return result; - } - - // TODO: remove. The backend should provide absoute URLs - function fixUrl(url) { - url = url.trim(); - if ('http' === url.substr(0, 4)) { - return url; - } - - var idx = url.indexOf('static'); - if (idx != -1) { - return '/' + url.substr(idx); - } - - return url; - }; - - /** - * Processes that data and returns filter functions. - * - * @param {object} Server response. - * @param {Column[]} filterCols Filter columns. - * @param {Column[]} otherCols Columns that are neither filters nor images. - * @param {Column[]} imageCols Image columns. - * - * @return {[]} Returns a pair [dataRows, filterFunctions] where: - * - dataRows is an array of DataRow instances. - * - filterFunctions is an array of functions that can be used to - * filter the column at the corresponding index. - * - **/ - function getDataAndFilters(data, filterCols, otherCols, imageCols) { - var el; - var result = []; - var lookupIndices = []; - var indexerFuncs = []; - var temp; - - // initialize the lookupIndices - var filterFuncs = initIndices(filterCols, lookupIndices, indexerFuncs); - - // iterate over the data and get the rows - for(var i=0, len=data.imagePairs.length; i<len; i++) { - el = data.imagePairs[i]; - temp = new DataRow(1, getColValues(el, filterCols, otherCols), - getImageValues(el, imageCols)); - result.push(temp); - - // index the row - for(var j=0, jlen=filterCols.length; j < jlen; j++) { - indexerFuncs[j](lookupIndices[j], filterCols[j], temp.dataCols[j], i); - } - } - - setFreeFormFilterOptions(filterCols, lookupIndices); - return [result, filterFuncs]; - } - - /** - * Initiazile the lookup indices and indexer functions for the filter - * columns. - * - * @param {Column} filterCols Filter columns - * @param {[]} lookupIndices Will be filled with datastructures for - fast lookup (output parameter) - * @param {[]} lookupIndices Will be filled with functions to index data - of the column with the corresponding column. - * - * @return {[]} Returns an array of filter functions that can be used to - filter the respective column. - **/ - function initIndices(filterCols, lookupIndices, indexerFuncs) { - var filterFuncs = []; - var temp; - - for(var i=0, len=filterCols.length; i<len; i++) { - if (filterCols[i].ftype === c.FILTER_FREE_FORM) { - lookupIndices.push({}); - indexerFuncs.push(indexFreeFormValue); - filterFuncs.push( - getFreeFormFilterFunc(lookupIndices[lookupIndices.length-1])); - } - else if (filterCols[i].ftype === c.FILTER_CHECK_BOX) { - temp = []; - for(var j=0, jlen=filterCols[i].foptions.length; j<jlen; j++) { - temp.push([]); - } - lookupIndices.push(temp); - indexerFuncs.push(indexDiscreteValue); - filterFuncs.push( - getDiscreteFilterFunc(lookupIndices[lookupIndices.length-1])); - } - } - - return filterFuncs; - } - - /** - * Helper function that extracts the values of free form columns from - * the lookupIndex and injects them into the Column object as FilterOpt - * objects. - **/ - function setFreeFormFilterOptions(filterCols, lookupIndices) { - var temp, k; - for(var i=0, len=filterCols.length; i<len; i++) { - if (filterCols[i].ftype === c.FILTER_FREE_FORM) { - temp = [] - for(k in lookupIndices[i]) { - if (lookupIndices[i].hasOwnProperty(k)) { - temp.push(new FilterOpt(k, lookupIndices[i][k].length)); - } - } - filterCols[i].setFilterOptions(temp); - } - } - } - - /** - * Index a discrete column (column with fixed number of values). - * - **/ - function indexDiscreteValue(lookupIndex, col, dataVal, dataRowIndex) { - var i = col.indexOfOptVal(dataVal); - lookupIndex[i].push(dataRowIndex); - } - - /** - * Index a column with free form text (= not fixed upfront) - * - **/ - function indexFreeFormValue(lookupIndex, col, dataVal, dataRowIndex) { - if (!lookupIndex[dataVal]) { - lookupIndex[dataVal] = []; - } - lookupIndex[dataVal].push(dataRowIndex); - } - - - /** - * Get the function to filter a column with the given lookup index - * for discrete (fixed upfront) values. - * - **/ - function getDiscreteFilterFunc(lookupIndex) { - return function(filterVal) { - var result = []; - for(var i=0, len=lookupIndex.length; i < len; i++) { - if (filterVal[i]) { - // append the indices to the current array - result.push.apply(result, lookupIndex[i]); - } - } - return { nofilter: false, records: result }; - }; - } - - /** - * Get the function to filter a column with the given lookup index - * for free form values. - * - **/ - function getFreeFormFilterFunc(lookupIndex) { - return function(filterVal) { - filterVal = filterVal.trim(); - if (filterVal === '') { - return { nofilter: true }; - } - return { - nofilter: false, - records: lookupIndex[filterVal] || [] - }; - }; - } - - /** - * Filters the data based on the given filterColumns and - * corresponding filter values. - * - * @return {[]} Subset of the input dataset based on the - * filter values. - **/ - function filterData(data, filterFuncs, filterVals) { - var recordSets = []; - var filterResult; - - // run through all the filters - for(var i=0, len=filterFuncs.length; i<len; i++) { - filterResult = filterFuncs[i](filterVals[i]); - if (!filterResult.nofilter) { - recordSets.push(filterResult.records); - } - } - - // If there are no restrictions then return the whole dataset. - if (recordSets.length === 0) { - return data; - } - - // intersect the records returned by filters. - var targets = intersectArrs(recordSets); - var result = []; - for(var i=0, len=targets.length; i<len; i++) { - result.push(data[targets[i]]); - } - - return result; - } - - /** - * Creates an object where the keys are the elements of the input array - * and the values are true. To be used for set operations with integer. - **/ - function arrToObj(arr) { - var o = {}; - var i,len; - for(i=0, len=arr.length; i<len; i++) { - o[arr[i]] = true; - } - return o; - } - - /** - * Converts the keys of an object to an array after converting - * each key to integer. To be used for set operations with integers. - **/ - function objToArr(obj) { - var result = []; - for(var k in obj) { - if (obj.hasOwnProperty(k)) { - result.push(parseInt(k)); - } - } - return result; - } - - /** - * Find the intersection of a set of arrays. - **/ - function intersectArrs(sets) { - var temp, obj; - - if (sets.length === 1) { - return sets[0]; - } - - // sort by size and load the smallest into the object - sets.sort(function(a,b) { return a.length - b.length; }); - obj = arrToObj(sets[0]); - - // shrink the hash as we fail to find elements in the other sets - for(var i=1, len=sets.length; i<len; i++) { - temp = arrToObj(sets[i]); - for(var k in obj) { - if (obj.hasOwnProperty(k) && !temp[k]) { - delete obj[k]; - } - } - } - - return objToArr(obj); - } - - /** - * Extract the column values from an ImagePair (contained in the server - * response) into filter and data columns. - * - * @return {[]} Array of data contained in one data row. - **/ - function getColValues(imagePair, filterCols, otherCols) { - var result = []; - for(var i=0, len=filterCols.length; i<len; i++) { - result.push(imagePair.extraColumns[filterCols[i].key]); - } - - for(var i=0, len=otherCols.length; i<len; i++) { - result.push(get_robust(imagePair, ['expectations', otherCols[i].key])); - } - - return result; - } - - /** - * Extract the image meta data from an Image pair returned by the server. - **/ - function getImageValues(imagePair, imageCols) { - var result=[]; - var url, value, percent, diff; - var CO = c.IMG_COL_ORDER; - - for(var i=0, len=imageCols.length; i<len; i++) { - percent = get_robust(imagePair, CO[i].percentField); - value = get_robust(imagePair, CO[i].valueField); - url = get_robust(imagePair, CO[i].urlField); - if (url) { - url = imageCols[i].baseUrl + url; - } - result.push(new ImgVal(url, percent, value)); - } - - return result; - } - - /** - * Given an object find sub objects for the given index without - * throwing an error if any of the sub objects do not exist. - **/ - function get_robust(obj, idx) { - if (!idx) { - return; - } - - for(var i=0, len=idx.length; i<len; i++) { - if ((typeof obj === 'undefined') || (!idx[i])) { - return; // returns 'undefined' - } - - obj = obj[idx[i]]; - } - - return obj; - } - - /** - * Set all elements in the array to the given value. - **/ - function setArrVals(arr, newVal) { - for(var i=0, len=arr.length; i<len; i++) { - arr[i] = newVal; - } - } - - /** - * Toggle the elements of a boolean array. - * - **/ - function toggleArrVals(arr) { - for(var i=0, len=arr.length; i<len; i++) { - arr[i] = !arr[i]; - } - } - - /** - * Sort the array of DataRow instances with the given compare functions - * and the column at the given index either in ascending or descending order. - **/ - function sortData (allData, compareFunctions, sortByIdx, sortOrderAsc) { - var cmpFn = compareFunctions[sortByIdx]; - var useCmp = cmpFn; - if (!sortOrderAsc) { - useCmp = function ( _ ) { - return -cmpFn.apply(this, arguments); - }; - } - allData.sort(useCmp); - } - - - // ***************************** Services ********************************* - - /** - * Encapsulates all interactions with the backend by handling - * Urls and HTTP requests. Also exposes some utility functions - * related to processing Urls. - */ - app.factory('dataService', [ '$http', function ($http) { - /** Backend related constants **/ - var c = { - /** Url to retrieve failures */ - FAILURES: '/results/failures', - - /** Url to retrieve all GM results */ - ALL: '/results/all' - }; - - /** - * Convenience function to retrieve all results. - * - * @return {Promise} Will resolve to either the data (success) or to - * the HTTP response (error). - **/ - function loadAll() { - return httpGetData(c.ALL); - } - - /** - * Make a HTTP get request with the given query parameters. - * - * @param {} - * @param {} - * - * @return {} - **/ - function httpGetData(url, queryParams) { - var reqConfig = { - method: 'GET', - url: url, - params: queryParams - }; - - return $http(reqConfig).then( - function(successResp) { - return successResp.data; - }); - } - - /** - * Takes an arbitrary number of objects and generates a Url encoded - * query string. - * - **/ - function getQueryString( _params_ ) { - var result = []; - for(var i=0, len=arguments.length; i < len; i++) { - if (arguments[i]) { - for(var k in arguments[i]) { - if (arguments[i].hasOwnProperty(k)) { - result.push(encodeURIComponent(k) + '=' + - encodeURIComponent(arguments[i][k])); - } - } - } - } - return result.join("&"); - } - - // Interface of the service: - return { - getQueryString: getQueryString, - loadAll: loadAll - }; - - }]); - -})(); diff --git a/gm/rebaseline_server/static/new/new-index.html b/gm/rebaseline_server/static/new/new-index.html deleted file mode 100644 index b7067f19dd..0000000000 --- a/gm/rebaseline_server/static/new/new-index.html +++ /dev/null @@ -1,37 +0,0 @@ -<!DOCTYPE html> -<html lang="en" ng-app="rbtApp"> - -<head> - <meta name="viewport" content="width=device-width"> - <meta charset="utf-8"> - <title>Rebaseline Tool</title> - <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css"> - <link rel="stylesheet" href="css/app.css"> -</head> -<body> - - - <div class="container-fluid"> - <div class="pull-right"> - Instructions, roadmap, etc. are at <a href="http://goo.gl/CqeVHq" target="_blank">http://goo.gl/CqeVHq</a> - </div> - </div> - - <!-- Include the different views here. - Make everything fluid to scale to the maximum size of any screen. --> - <div class="container-fluid"> - <div ng-view></div> - </div> - - <!-- do everything local right now: Move to CDN fix when it's a performance issue --> - <script src="bower_components/angular/angular.js"></script> - <script src="bower_components/angular-route/angular-route.js"></script> - - <!-- Local includes external libs --> - <script src="bower_components/angular-bootstrap/ui-bootstrap.js"></script> - <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script> - - <!-- Local JS --> - <script src="js/app.js"></script> -</body> -</html> diff --git a/gm/rebaseline_server/static/new/partials/index-view.html b/gm/rebaseline_server/static/new/partials/index-view.html deleted file mode 100644 index 72db231d08..0000000000 --- a/gm/rebaseline_server/static/new/partials/index-view.html +++ /dev/null @@ -1,22 +0,0 @@ -<div class="container-fluid ng-cloak" ng-cloak> - <div class="row" ng-show="state === c.ST_LOADING"> - <h4>Loading ...</h4> - </div> - - <div class="row" ng-show="state === c.ST_READY"> - <h4>GM Expectations vs Actuals:</h4> - <ul> - <li><a href="#/view?{{ qStr({ resultsToLoad: c.RESULTS_FAILURES }) }}">Failures</a></li> - <li><a href="#/view?{{ qStr({ resultsToLoad: c.RESULTS_ALL }) }}">All</a></li> - </ul> - - <h4>Rendered SKPs:</h4> - <ul> - <li ng-repeat="oneSKP in allSKPs"> - <a href="#/view?{{ qStr(oneSKP.params) }}"> - {{oneSKP.title}} - </a> - </li> - </ul> - </div> -</div> diff --git a/gm/rebaseline_server/static/new/partials/rebaseline-view.html b/gm/rebaseline_server/static/new/partials/rebaseline-view.html deleted file mode 100644 index a2c28f7357..0000000000 --- a/gm/rebaseline_server/static/new/partials/rebaseline-view.html +++ /dev/null @@ -1,207 +0,0 @@ -<div class="container-fluid ng-cloak" ng-cloak> - - <div class="row" ng-show="state === c.ST_LOADING"> - <h4>Loading ...</h4> - </div> - - <div class="row" ng-show="state === c.ST_STILL_LOADING"> - <h4>Still loading from backend.</h4> - <div> - Load time so far: {{ loadTime | number:0 }} s. - </div> - </div> - - <div class="row" ng-show="state === c.ST_READY"> - <tabset> - <tab heading="Unfiled"> - <!-- settings --> - <div class="container controlBox"> - <form class="form-inline settingsForm" novalidate > - <legend class="simpleLegend">Settings</legend> - <div class="checkbox formPadding"> - <label> - <input type="checkbox" - ng-model="settings.showThumbnails">Show thumbnails - </label> - </div> - - <div class="checkbox formPadding"> - <label> - <input type="checkbox" - ng-model="settings.mergeIdenticalRows" - ng-change="mergeRowsChanged(mergeIdenticalRows)"> Merge identical rows - </label> - </div> - - <div class="form-group formPadding"> - <label for="imageWidth">Image Width</label> - <select ng-model="settings.imageSize" - ng-options="iSize for iSize in c.IMAGE_SIZES" - class="form-control input-sm"> - - </select> - </div> - <div class="form-group formPadding"> - <label>Max records</label> - <select ng-model="settings.nRecords" - ng-options="n for n in c.MAX_RECORDS" - ng-change="maxRecordsChanged();" - class="form-control input-sm"> - </select> - </div> - </form> - <br> - - <form class="form settingsForm" novalidate> - <legend class="simpleLegend">Filters</legend> - <div class="container-fluid"> - <div class="col-lg-2 filterBox" ng-repeat="oneCol in filterCols"> - <div class="filterKey">{{ oneCol.key }}</div> - - <!-- If we filter this column using free-form text match... --> - <div ng-if="oneCol.ftype === c.FILTER_FREE_FORM"> - <input type="text" - ng-model="filterVals[$index]" - typeahead="opt.value for opt in oneCol.foptions | filter:$viewValue" - class="form-control input-sm"> - <br> - <a ng-click="filterVals[$index]=''" - ng-disabled="'' === filterVals[$index]" - href=""> - Clear - </a> - </div> - - <!-- If we filter this column using checkboxes... --> - <div ng-if="oneCol.ftype === c.FILTER_CHECK_BOX"> - - <div class="checkbox" ng-repeat="oneOpt in oneCol.foptions"> - <label> - <input type="checkbox" - ng-model="filterVals[$parent.$index][$index]">{{oneOpt.value}} ({{ oneOpt.count }}) - </label> - </div> - <div> - <a ng-click="setFilterAll($index, true)" href="">All</a> - - <a ng-click="setFilterAll($index, False)" href="">None</a> - - <a ng-click="setFilterToggle($index)" href="">Toggle</a> - </div> - </div> - </div> - <br> - </div> - - <div class="container updateBtn"> - <button class="btn btn-success col-lg-4 pull-left" - ng-click="filtersChanged()" - ng-disabled="updating"> - {{ updating && 'Updating ...' || 'Update' }} - </button> - </div> - - </form> - - <br> - - <!-- Rows --> - - <!-- results header --> - <div class="col-lg-12 resultsHeaderActions well"> - <div class="col-lg-6"> - <h4>Showing {{showingRecords}} of {{selectedRecords}} (of {{totalRecords}} total)</h4> - <span ng-show="renderTime > 0"> - Rendered in {{renderTime | number:0 }} ms (filtered and sorted in {{ filterTime | number:0 }} ms). - </span> - <br> - (click on the column header radio buttons to re-sort by that column) - </div> - - - <div class="col-lg-6"> - All tests shown: - <button class="btn btn-default btn-sm" ng-click="selectAllImagePairs()">Select</button> - <button class="btn btn-default btn-sm" ng-click="clearAllImagePairs()">Clear</button> - <button class="btn btn-default btn-sm" ng-click="toggleAllImagePairs()">Toggle</button> - - <div ng-repeat="otherTab in tabs"> - <button class="btn btn-default btn-sm" - ng-click="moveSelectedImagePairsToTab(otherTab)" - ng-disabled="selectedImagePairs.length == 0" - ng-show="otherTab != viewingTab"> - Move {{selectedImagePairs.length}} selected tests to {{otherTab}} tab - </button> - </div> - </div> - <br> - </div> - - <!-- results --> - <table class="table table-bordered"> - <thead> - <tr> - <!-- Most column headers are displayed in a common fashion... --> - <th ng-repeat="oneCol in allCols" ng-style="{ 'min-width': getImageWidthStyle(oneCol, 20, 'auto') }"> - <a ng-class="getSortedClass('sort', $index, '')" - ng-click="sortBy($index)" - href="" - class="sortableHeader"> - {{ oneCol.ctitle }} - </a> - </th> - <th> - <div class="checkbox"> - <label> - <input type="checkbox" ng-model="allChecked" ng-change="checkAll()">All - </label> - </div> - </th> - </tr> - </thead> - <tbody> - <tr ng-repeat="oneRow in data"> - <td ng-repeat="oneColVal in oneRow.dataCols"> - {{oneColVal}} - </td> - - <td ng-repeat="oneCol in oneRow.imageCols" ng-if="oneRow.rowspan > 0" rowspan="{{ oneRow.rowspan }}"> - <div ng-show="oneCol.url"> - <a href="{{ oneCol.url }}" target="_blank">View Image</a><br/> - <img ng-if="settings.showThumbnails" - ng-style="{ width: settings.imageSize+'px' }" - ng-src="{{ oneCol.url }}" /> - <div ng-if="oneCol.percent && oneCol.value"> - {{oneCol.percent}}% ({{ oneCol.value }}) - </div> - </div> - <div ng-hide="oneCol.url" style="text-align:center"> - <span ng-show="oneCol.url === null">–none–</span> - <span ng-hide="oneCol.url === null"> </span> - </div> - </td> - - <td ng-if="oneRow.rowspan > 0" rowspan="{{ oneRow.rowspan }}"> - <div class="checkbox"> - <input type="checkbox" - ng-model="checkRows[$index]" - ng-change="rowCheckChanged($index)"> - </div> - </td> - </tr> - </tbody> - </table> - - </div> - </tab> - - <tab heading="Hidden"> - <h3>Hidden</h3> - </tab> - - <tab heading="Pending Approval"> - <h3>Pending Approval</h3> - </tab> - - </tabset> - - </div> -</div> diff --git a/gm/rebaseline_server/static/utils.js b/gm/rebaseline_server/static/utils.js deleted file mode 100644 index e846b90bd6..0000000000 --- a/gm/rebaseline_server/static/utils.js +++ /dev/null @@ -1,12 +0,0 @@ -function make_results_header_sticky( ) { - element = $(".results-header-actions"); - var pos = element.position(); - $(window).scroll( function() { - var windowPos = $(window).scrollTop(); - if (windowPos > pos.top) { - element.addClass("sticky"); - } else { - element.removeClass("sticky"); - } - }); -} diff --git a/gm/rebaseline_server/static/view.css b/gm/rebaseline_server/static/view.css deleted file mode 100644 index 80f28091c4..0000000000 --- a/gm/rebaseline_server/static/view.css +++ /dev/null @@ -1,104 +0,0 @@ -/* Special alert areas at the top of the page. */ -.todo-div { - background-color: #bbffbb; -} -.warning-div { - background-color: #ffbb00; -} - -.tab-wrapper { - margin-top: 10px; -} - -.tab { - display: inline-block; - font-size: 20px; - padding: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - position: relative; -} - -/* Tab which has been selected. */ -.tab-true { - background-color: #ccccff; - border: 1px solid black; - border-bottom-width: 0px; - bottom: -1px; -} -/* All other tabs. */ -.tab-false { - background-color: #8888ff; - cursor: pointer; -} - -.tab-false:hover { - background-color: #aa88ff; -} - -/* Spacers between tabs. */ -.tab-spacer { - display: inline-block; -} -/* The main working area (connected to the selected tab). */ -.tab-main { - background-color: #ccccff; - border: 1px solid black; -} - -.update-results-button { - font-size: 30px; -} - -/* Odd and even lines within results display. */ -.results-odd { - background-color: #ddffdd; -} -.results-even { - background-color: #ddddff; -} - -.show-only-button { - font-size: 8px; -} -.show-all-button { - font-size: 8px; -} - -.image-link { - text-decoration: none; -} - -.results-header { - overflow: hidden; - padding: 10px; - background-color: #ccccff; -} - -.results-header-actions { - float: right; -} - -.sticky { - position: fixed; - top: 2px; - box-shadow: -2px 2px 5px 0 rgba(0,0,0,.45); - background: white; - right: 2px; - padding: 10px; - border: 2px solid #222; -} - -.sort-desc { - background:no-repeat left center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJZAMIcakQZjNtyhFxwEIIRofAookUnapu26t+6KFLYe1TgQ5VwQAOw%3D%3D); -} - -.sort-asc { - background:no-repeat left center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJRBJREKZsxQDsCSGIVzZFnYTGIqktp7fG46uzAn2TAyCMPC9QAQAOw%3D%3D); -} - -.sortable-header { - padding-right: 3px; - padding-left: 13px; - margin-left: 4px; -} diff --git a/gm/rebaseline_server/static/view.html b/gm/rebaseline_server/static/view.html deleted file mode 100644 index f6ebf5a9a4..0000000000 --- a/gm/rebaseline_server/static/view.html +++ /dev/null @@ -1,417 +0,0 @@ -<!DOCTYPE html> - -<html ng-app="Loader" ng-controller="Loader.Controller"> - -<head> - <title ng-bind="windowTitle"></title> - <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> - <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.20/angular.js"></script> - <script src="constants.js"></script> - <script src="loader.js"></script> - <script src="utils.js"></script> - <link rel="stylesheet" href="view.css"> -</head> - -<body> - <h2> - Instructions, roadmap, etc. are at - <a href="http://tinyurl.com/SkiaRebaselineServer"> - http://tinyurl.com/SkiaRebaselineServer - </a> - </h2> - - <em ng-show="!readyToDisplay"> - Loading results from <a href="{{resultsToLoad}}">{{resultsToLoad}}</a> ... - {{loadingMessage}} - </em> - - <div ng-show="readyToDisplay"> - - <div class="warning-div" - ng-show="urlSchemaVersionLoaded != constants.URL_VALUE__SCHEMA_VERSION__CURRENT"> - WARNING! The URL you loaded used schema version {{urlSchemaVersionLoaded}}, rather than - the most recent version {{constants.URL_VALUE__SCHEMA_VERSION__CURRENT}}. It has been - converted to the most recent version on a best-effort basis; you may wish to double-check - which records are displayed. - </div> - - <div class="warning-div" - ng-show="header[constants.KEY__HEADER__IS_EDITABLE] && header[constants.KEY__HEADER__IS_EXPORTED]"> - WARNING! These results are editable and exported, so any user - who can connect to this server over the network can modify them. - </div> - - <div ng-show="header[constants.KEY__HEADER__TIME_UPDATED]"> - These results, from raw JSON file - <a href="{{resultsToLoad}}">{{resultsToLoad}}</a>, current as of - {{localTimeString(header[constants.KEY__HEADER__TIME_UPDATED])}} - <br> - To see other sets of results (all results, failures only, etc.), - <a href="/">click here</a> - </div> - - <div class="tab-wrapper"><!-- tabs --> - <div class="tab-spacer" ng-repeat="tab in tabs"> - <div class="tab tab-{{tab == viewingTab}}" - ng-click="setViewingTab(tab)"> - {{tab}} ({{numResultsPerTab[tab]}}) - </div> - <div class="tab-spacer"> - - </div> - </div> - </div><!-- tabs --> - - <div class="tab-main"><!-- main display area of selected tab --> - - <br> - <!-- We only show the filters/settings table on the Unfiled tab. --> - <table ng-show="viewingTab == defaultTab" border="1"> - <tr> - <th colspan="4"> - Filters - </th> - <th> - Settings - </th> - </tr> - <tr valign="top"> - - <!-- filters --> - <td ng-repeat="columnName in orderedColumnNames"> - - <!-- Only display filterable columns here... --> - <div ng-if="extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE]"> - {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}<br> - - <!-- If we filter this column using free-form text match... --> - <div ng-if="extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]"> - <input type="text" - ng-model="columnStringMatch[columnName]" - ng-change="setUpdatesPending(true)"/> - <br> - <button ng-click="setColumnStringMatch(columnName, '')" - ng-disabled="('' == columnStringMatch[columnName])"> - clear (show all) - </button> - </div> - - <!-- If we filter this column using checkboxes... --> - <div ng-if="!extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]"> - <label ng-repeat="valueAndCount in extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS]"> - <input type="checkbox" - name="resultTypes" - value="{{valueAndCount[0]}}" - ng-checked="isValueInSet(valueAndCount[0], showingColumnValues[columnName])" - ng-click="toggleValueInSet(valueAndCount[0], showingColumnValues[columnName]); setUpdatesPending(true)"> - {{valueAndCount[0]}} ({{valueAndCount[1]}})<br> - </label> - <button ng-click="showingColumnValues[columnName] = {}; toggleValuesInSet(allColumnValues[columnName], showingColumnValues[columnName]); updateResults()" - ng-disabled="!readyToDisplay || allColumnValues[columnName].length == setSize(showingColumnValues[columnName])"> - all - </button> - <button ng-click="showingColumnValues[columnName] = {}; updateResults()" - ng-disabled="!readyToDisplay || 0 == setSize(showingColumnValues[columnName])"> - none - </button> - <button ng-click="toggleValuesInSet(allColumnValues[columnName], showingColumnValues[columnName]); updateResults()"> - toggle - </button> - </div> - - </div> - </td> - - <!-- settings --> - <td><table> - <tr><td> - <input type="checkbox" ng-model="showThumbnailsPending" - ng-init="showThumbnailsPending = true" - ng-change="areUpdatesPending = true"/> - Show thumbnails - </td></tr> - <tr><td> - <input type="checkbox" ng-model="mergeIdenticalRowsPending" - ng-init="mergeIdenticalRowsPending = true" - ng-change="areUpdatesPending = true"/> - Merge identical rows - </td></tr> - <tr><td> - Image width - <input type="text" ng-model="imageSizePending" - ng-init="imageSizePending=100" - ng-change="areUpdatesPending = true" - maxlength="4"/> - </td></tr> - <tr><td> - Max records to display - <input type="text" ng-model="displayLimitPending" - ng-init="displayLimitPending=50" - ng-change="areUpdatesPending = true" - maxlength="4"/> - </td></tr> - <tr><td> - <button class="update-results-button" - ng-click="updateResults()" - ng-disabled="!areUpdatesPending"> - Update Results - </button> - </td></tr> - </tr></table></td> - </tr> - </table> - - <p> - - <!-- Submission UI that we only show in the Pending Approval tab. --> - <div ng-show="'Pending Approval' == viewingTab"> - <div style="display:inline-block"> - <button style="font-size:20px" - ng-click="submitApprovals(filteredImagePairs)" - ng-disabled="submitPending || (filteredImagePairs.length == 0)"> - Update these {{filteredImagePairs.length}} expectations on the server - </button> - </div> - <div style="display:inline-block"> - <div style="font-size:20px" - ng-show="submitPending"> - Submitting, please wait... - </div> - </div> - <div> - Advanced settings... - <input type="checkbox" ng-model="showSubmitAdvancedSettings"> - show - <ul ng-show="showSubmitAdvancedSettings"> - <li ng-repeat="setting in [constants.KEY__EXPECTATIONS__REVIEWED, constants.KEY__EXPECTATIONS__IGNOREFAILURE]"> - {{setting}} - <input type="checkbox" ng-model="submitAdvancedSettings[setting]"> - </li> - <li ng-repeat="setting in ['bug']"> - {{setting}} - <input type="text" ng-model="submitAdvancedSettings[setting]"> - </li> - </ul> - </div> - </div> - - <p> - - <div class="results-header"> <!-- results header --> - <div class="results-header-actions"> - all tests shown: - <button ng-click="selectAllImagePairs()"> - select - </button> - <button ng-click="clearAllImagePairs()"> - clear - </button> - <button ng-click="toggleAllImagePairs()"> - toggle - </button> - <div ng-repeat="otherTab in tabs"> - <button ng-click="moveSelectedImagePairsToTab(otherTab)" - ng-disabled="selectedImagePairs.length == 0" - ng-show="otherTab != viewingTab"> - move {{selectedImagePairs.length}} selected tests to {{otherTab}} tab - </button> - </div> - </div> - <div class="results-header-stats"> - Found {{filteredImagePairs.length}} matches; - <span ng-show="filteredImagePairs.length > limitedImagePairs.length"> - displaying the first {{limitedImagePairs.length}}. - </span> - <span ng-show="filteredImagePairs.length <= limitedImagePairs.length"> - displaying them all. - </span> - <span ng-show="renderEndTime > renderStartTime"> - Rendered in {{(renderEndTime - renderStartTime).toFixed(0)}} ms. - </span> - <br> - (click on the column header radio buttons to re-sort by that column) - </div> - </div> <!-- results header --> - - <table border="0"><tr><td> <!-- table holding results header + results table --> - </td></tr><tr><td> - <table border="1"> <!-- results --> - <tr> - <!-- Most column headers are displayed in a common fashion... --> - <th ng-repeat="columnName in orderedColumnNames"> - <a ng-class="'sort-' + sortedByColumnsCls(columnName)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXTRACOLUMNS, columnName)" - href="" - class="sortable-header"> - {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}} - </a> - </th> - - <!-- ... but there are a few columns where we display things differently. --> - <th> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__EXPECTATIONS__BUGS)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXPECTATIONS, constants.KEY__EXPECTATIONS__BUGS)" - href="" - class="sortable-header"> - bugs - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_A_URL)" - ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)" - href="" - class="sortable-header"> - {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}} - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_B_URL)" - ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)" - href="" - class="sortable-header"> - {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}} - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)" - href="" - class="sortable-header"> - differing pixels in white - </a> - </th> - <th width="{{imageSize}}"> - <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)" - ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)" - href="" - class="sortable-header"> - perceptual difference - </a> - <br> - <input type="range" ng-model="pixelDiffBgColorBrightness" - ng-init="pixelDiffBgColorBrightness=64; pixelDiffBgColor=brightnessStringToHexColor(pixelDiffBgColorBrightness)" - ng-change="pixelDiffBgColor=brightnessStringToHexColor(pixelDiffBgColorBrightness)" - title="image background brightness" - min="0" max="255"/> - </th> - <th> - <!-- imagepair-selection checkbox column --> - </th> - </tr> - - <tr ng-repeat="imagePair in limitedImagePairs" valign="top" - ng-class-odd="'results-odd'" ng-class-even="'results-even'" - results-updated-callback-directive> - - <td ng-repeat="columnName in orderedColumnNames"> - {{imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][columnName]}} - <br> - <button class="show-only-button" - ng-show="viewingTab == defaultTab" - ng-disabled="1 == setSize(showingColumnValues[columnName])" - ng-click="showOnlyColumnValue(columnName, imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][columnName])" - title="show only results of {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}} {{imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][columnName]}}"> - show only - </button> - <br> - <button class="show-all-button" - ng-show="viewingTab == defaultTab" - ng-disabled="allColumnValues[columnName].length == setSize(showingColumnValues[columnName])" - ng-click="showAllColumnValues(columnName)" - title="show results of all {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}s"> - show all - </button> - </td> - - <!-- bugs --> - <td> - <a ng-repeat="bug in imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS][constants.KEY__EXPECTATIONS__BUGS]" - href="https://code.google.com/p/skia/issues/detail?id={{bug}}" - target="_blank"> - {{bug}} - </a> - </td> - - <!-- image A --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] != null"> - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]}}" /> - </div> - <div ng-show="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] == null" - style="text-align:center"> - –none– - </div> - </td> - - <!-- image B --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] != null"> - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]}}" /> - </div> - <div ng-show="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] == null" - style="text-align:center"> - –none– - </div> - </td> - - <!-- whitediffs: every differing pixel shown in white --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - title="{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS] | number:0}} of {{(100 * imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS] / imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS]) | number:0}} pixels ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}%) differ from expectation."> - - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__WHITEDIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__WHITE_DIFF_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__WHITEDIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__WHITE_DIFF_URL]}}" /> - <br/> - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}% - ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS]}}) - </div> - <div ng-show="!imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - style="text-align:center"> - –none– - </div> - </td> - - <!-- diffs: per-channel RGB deltas --> - <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - title="Perceptual difference measure is {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}%. Maximum difference per channel: R={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][0]}}, G={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][1]}}, B={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][2]}}"> - - <a href="{{imageSets[constants.KEY__IMAGESETS__SET__DIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__DIFF_URL]}}" target="_blank">View Image</a><br/> - <img ng-if="showThumbnails" - ng-style="{backgroundColor: pixelDiffBgColor}" - width="{{imageSize}}" - ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__DIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__DIFF_URL]}}" /> - <br/> - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}% - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL]}} - </div> - <div ng-show="!imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" - style="text-align:center"> - –none– - </div> - </td> - - <td ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> - <br/> - <input type="checkbox" - name="rowSelect" - value="{{imagePair.index}}" - ng-checked="isValueInArray(imagePair.index, selectedImagePairs)" - ng-click="toggleSomeImagePairs($index, imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN])"> - </tr> - </table> <!-- imagePairs --> - </td></tr></table> <!-- table holding results header + imagePairs table --> - - </div><!-- main display area of selected tab --> - </div><!-- everything: hide until readyToDisplay --> - -</body> -</html> diff --git a/gm/rebaseline_server/test_all.py b/gm/rebaseline_server/test_all.py deleted file mode 100755 index 282ec85732..0000000000 --- a/gm/rebaseline_server/test_all.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Run all unittests within this directory tree, recursing into subdirectories. -""" - -import os -import unittest - - -def main(): - suite = unittest.TestLoader().discover(os.path.dirname(__file__), - pattern='*_test.py') - results = unittest.TextTestRunner(verbosity=2).run(suite) - print repr(results) - if not results.wasSuccessful(): - raise Exception('failed one or more unittests') - -if __name__ == '__main__': - main() diff --git a/gm/rebaseline_server/testdata/.gitattributes b/gm/rebaseline_server/testdata/.gitattributes deleted file mode 100644 index 3606445cad..0000000000 --- a/gm/rebaseline_server/testdata/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# All self-test PNG files are fake (human-readable, diffable plaintext files). -*.png text diff -binary diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png deleted file mode 100644 index d3686ff12d..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png deleted file mode 100644 index 656275a920..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/aaclip/14456211900777561488.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/aaclip/14456211900777561488.png deleted file mode 100644 index 371a172ece..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/aaclip/14456211900777561488.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/aaclip/14456211900777561488.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/aaclip/6190901827590820995.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/aaclip/6190901827590820995.png deleted file mode 100644 index 8a01aa825f..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/aaclip/6190901827590820995.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/aaclip/6190901827590820995.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bigblurs/17309852422285247848.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bigblurs/17309852422285247848.png deleted file mode 100644 index 2d78e89be3..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bigblurs/17309852422285247848.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bigblurs/17309852422285247848.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bigblurs/2422083043229439955.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bigblurs/2422083043229439955.png deleted file mode 100644 index 8d0646720a..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bigblurs/2422083043229439955.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bigblurs/2422083043229439955.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bitmapsource/16289727936158057543.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bitmapsource/16289727936158057543.png deleted file mode 100644 index 3c511d5f6a..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bitmapsource/16289727936158057543.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bitmapsource/16289727936158057543.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bitmapsource/17503582803589749280.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bitmapsource/17503582803589749280.png deleted file mode 100644 index d2df08ca3a..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/bitmapsource/17503582803589749280.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bitmapsource/17503582803589749280.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png deleted file mode 100644 index 9364ccdd8a..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png deleted file mode 100644 index 5c91d33e15..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png deleted file mode 100644 index 3c1de492de..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png deleted file mode 100644 index 8760be33fb..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png diff --git a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/texdata/3695033638604474475.png b/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/texdata/3695033638604474475.png deleted file mode 100644 index fee415c72a..0000000000 --- a/gm/rebaseline_server/testdata/inputs/fake-gm-imagefiles/bitmap-64bitMD5/texdata/3695033638604474475.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/texdata/3695033638604474475.png diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Android-GalaxyNexus-SGX540-Arm7-Release/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Android-GalaxyNexus-SGX540-Arm7-Release/actual-results.json deleted file mode 100644 index 38ee68c7ba..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Android-GalaxyNexus-SGX540-Arm7-Release/actual-results.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "actual-results" : { - "failed" : { - "texdata_gpu.png" : [ "bitmap-64bitMD5", 3695033638604474475 ] - }, - "failure-ignored" : { - "filterbitmap_checkerboard_192_192_565.png" : [ "bitmap-64bitMD5", 4719210487426381700 ], - "filterbitmap_checkerboard_192_192_8888.png" : [ "bitmap-64bitMD5", 3154864687054945306 ], - "filterbitmap_checkerboard_32_2_565.png" : [ "bitmap-64bitMD5", 15528304435129737588 ], - "filterbitmap_checkerboard_32_2_8888.png" : [ "bitmap-64bitMD5", 712827739969462165 ] - }, - "no-comparison" : { - "bigblurs_565.png" : [ "bitmap-64bitMD5", 2422083043229439955 ], - "bigblurs_8888.png" : [ "bitmap-64bitMD5", 17309852422285247848 ], - "bitmapsource_565.png" : [ "bitmap-64bitMD5", 17503582803589749280 ], - "bitmapsource_8888.png" : [ "bitmap-64bitMD5", 16289727936158057543 ] - }, - "succeeded" : { - "3x3bitmaprect_565.png" : [ "bitmap-64bitMD5", 16998423976396106083 ], - "3x3bitmaprect_8888.png" : [ "bitmap-64bitMD5", 2054956815327187963 ], - "aaclip_565.png" : [ "bitmap-64bitMD5", 6190901827590820995 ], - "aaclip_8888.png" : [ "bitmap-64bitMD5", 14456211900777561488 ] - } - }, - "expected-results" : { - "3x3bitmaprect_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16998423976396106083 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 2054956815327187963 ] - ], - "ignore-failure" : false - }, - "aaclip_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 6190901827590820995 ] - ], - "ignore-failure" : false - }, - "aaclip_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 14456211900777561488 ] - ], - "ignore-failure" : false - }, - "bigblurs_565.png" : { - "allowed-digests" : null, - "ignore-failure" : false - }, - "bigblurs_8888.png" : { - "allowed-digests" : null, - "ignore-failure" : false - }, - "bitmapsource_565.png" : { - "allowed-digests" : null, - "ignore-failure" : false - }, - "bitmapsource_8888.png" : { - "allowed-digests" : null, - "ignore-failure" : false - }, - "filterbitmap_checkerboard_192_192_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 9917960313903939620 ] - ], - "ignore-failure" : true - }, - "filterbitmap_checkerboard_192_192_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 9917960313903939620 ] - ], - "ignore-failure" : true - }, - "filterbitmap_checkerboard_32_2_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16197252621792695154 ] - ], - "ignore-failure" : true - }, - "filterbitmap_checkerboard_32_2_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 7634650333321761866 ] - ], - "ignore-failure" : true - }, - "texdata_gpu.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 2736593828543197285 ] - ], - "ignore-failure" : false - } - } -} diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Builder-We-Have-No-Expectations-File-For/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Builder-We-Have-No-Expectations-File-For/actual-results.json deleted file mode 100644 index 0a1de74445..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Builder-We-Have-No-Expectations-File-For/actual-results.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "actual-results" : { - "failed" : null, - "failure-ignored" : { - "displacement_565.png" : [ "bitmap-64bitMD5", 4569468668668628191 ], - "displacement_8888.png" : [ "bitmap-64bitMD5", 11401048196735046263 ], - "displacement_gpu.png" : [ "bitmap-64bitMD5", 5698561127291561694 ], - "displacement_pdf-mac.png" : [ "bitmap-64bitMD5", 12901125495691049846 ], - "displacement_pdf-poppler.png" : [ "bitmap-64bitMD5", 16285974094717334658 ] - }, - "no-comparison" : { - "bigblurs_565.png" : [ "bitmap-64bitMD5", 14704206703218007573 ], - "bigblurs_8888.png" : [ "bitmap-64bitMD5", 17309852422285247848 ], - "bigblurs_gpu.png" : [ "bitmap-64bitMD5", 1822195599289208664 ], - "bigblurs_pdf-mac.png" : [ "bitmap-64bitMD5", 16171608477794909861 ], - "bigblurs_pdf-poppler.png" : [ "bitmap-64bitMD5", 6539050160610613353 ] - }, - "succeeded" : { - "3x3bitmaprect_565.png" : [ "bitmap-64bitMD5", 16998423976396106083 ], - "3x3bitmaprect_8888.png" : [ "bitmap-64bitMD5", 2054956815327187963 ], - "3x3bitmaprect_gpu.png" : [ "bitmap-64bitMD5", 2054956815327187963 ], - "3x3bitmaprect_pdf-mac.png" : [ "bitmap-64bitMD5", 8518347971308375604 ], - "3x3bitmaprect_pdf-poppler.png" : [ "bitmap-64bitMD5", 16723580409414313678 ] - } - }, - "expected-results" : { - "3x3bitmaprect_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16998423976396106083 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 2054956815327187963 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_gpu.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 2054956815327187963 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_pdf-mac.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 8518347971308375604 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_pdf-poppler.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16723580409414313678 ] - ], - "ignore-failure" : false - }, - "displacement_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16249664097236120848 ] - ], - "ignore-failure" : true - }, - "displacement_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 12120271618448785903 ] - ], - "ignore-failure" : true - }, - "displacement_gpu.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 14033538044845358121 ] - ], - "ignore-failure" : true - }, - "displacement_pdf-mac.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 10149713146651176710 ] - ], - "ignore-failure" : true - }, - "displacement_pdf-poppler.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 4741823272437589681 ] - ], - "ignore-failure" : true - } - } -} diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug/actual-results.json deleted file mode 100644 index 354f439e7f..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug/actual-results.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "actual-results" : { - "failed" : { - "blanket-ignored_565.png" : [ "bitmap-64bitMD5", 22222222 ], - "blanket-ignored_8888.png" : [ "bitmap-64bitMD5", 22222222 ] - }, - "failure-ignored" : { - "displacement_565.png" : [ "bitmap-64bitMD5", 4569468668668628191 ], - "displacement_8888.png" : [ "bitmap-64bitMD5", 11401048196735046263 ], - "displacement_gpu.png" : [ "bitmap-64bitMD5", 5698561127291561694 ], - "displacement_pdf-mac.png" : [ "bitmap-64bitMD5", 12901125495691049846 ], - "displacement_pdf-poppler.png" : [ "bitmap-64bitMD5", 16285974094717334658 ] - }, - "no-comparison" : { - "bigblurs_565.png" : [ "bitmap-64bitMD5", 14704206703218007573 ], - "bigblurs_8888.png" : [ "bitmap-64bitMD5", 17309852422285247848 ], - "bigblurs_gpu.png" : [ "bitmap-64bitMD5", 1822195599289208664 ], - "bigblurs_pdf-mac.png" : [ "bitmap-64bitMD5", 16171608477794909861 ], - "bigblurs_pdf-poppler.png" : [ "bitmap-64bitMD5", 6539050160610613353 ] - }, - "succeeded" : { - "3x3bitmaprect_565.png" : [ "bitmap-64bitMD5", 16998423976396106083 ], - "3x3bitmaprect_8888.png" : [ "bitmap-64bitMD5", 2054956815327187963 ], - "3x3bitmaprect_gpu.png" : [ "bitmap-64bitMD5", 2054956815327187963 ], - "3x3bitmaprect_pdf-mac.png" : [ "bitmap-64bitMD5", 8518347971308375604 ], - "3x3bitmaprect_pdf-poppler.png" : [ "bitmap-64bitMD5", 16723580409414313678 ] - } - }, - "expected-results" : { - "3x3bitmaprect_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16998423976396106083 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 2054956815327187963 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_gpu.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 2054956815327187963 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_pdf-mac.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 8518347971308375604 ] - ], - "ignore-failure" : false - }, - "3x3bitmaprect_pdf-poppler.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16723580409414313678 ] - ], - "ignore-failure" : false - }, - "blanket-ignored_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 111111111 ] - ], - "ignore-failure" : false - }, - "blanket-ignored_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 111111111 ] - ], - "ignore-failure" : false - }, - "displacement_565.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 16249664097236120848 ] - ], - "ignore-failure" : true - }, - "displacement_8888.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 12120271618448785903 ] - ], - "ignore-failure" : true - }, - "displacement_gpu.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 14033538044845358121 ] - ], - "ignore-failure" : true - }, - "displacement_pdf-mac.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 10149713146651176710 ] - ], - "ignore-failure" : true - }, - "displacement_pdf-poppler.png" : { - "allowed-digests" : [ - [ "bitmap-64bitMD5", 4741823272437589681 ] - ], - "ignore-failure" : true - } - } -} diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu12-ShuttleA-ATI5770-x86_64-Release-Valgrind/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu12-ShuttleA-ATI5770-x86_64-Release-Valgrind/actual-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu12-ShuttleA-ATI5770-x86_64-Release-Valgrind/actual-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-ASAN/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-ASAN/actual-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-ASAN/actual-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-TSAN/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-TSAN/actual-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-TSAN/actual-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Win7-ShuttleA-HD2000-x86_64-Debug-Trybot/actual-results.json b/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Win7-ShuttleA-HD2000-x86_64-Debug-Trybot/actual-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-actuals/Test-Win7-ShuttleA-HD2000-x86_64-Debug-Trybot/actual-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Android-GalaxyNexus-SGX540-Arm7-Release/expected-results.json b/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Android-GalaxyNexus-SGX540-Arm7-Release/expected-results.json deleted file mode 100644 index 75b2663307..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Android-GalaxyNexus-SGX540-Arm7-Release/expected-results.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "actual-results": { - "failed": null, - "failure-ignored": null, - "no-comparison": null, - "succeeded": null - }, - "expected-results": { - "3x3bitmaprect_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 16998423976396106083 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "3x3bitmaprect_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 2054956815327187963 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "aaclip_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 6190901827590820995 - ] - ], - "ignore-failure": false - }, - "aaclip_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 14456211900777561488 - ] - ], - "ignore-failure": false - }, - "filterbitmap_checkerboard_192_192_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 9917960313903939620 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "filterbitmap_checkerboard_192_192_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 9917960313903939620 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "filterbitmap_checkerboard_32_2_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 16197252621792695154 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "filterbitmap_checkerboard_32_2_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 7634650333321761866 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "texdata_gpu.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 2736593828543197285 - ] - ], - "ignore-failure": false - } - } -} diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug/expected-results.json b/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug/expected-results.json deleted file mode 100644 index f3f49bbc3a..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug/expected-results.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "actual-results": { - "failed": null, - "failure-ignored": null, - "no-comparison": null, - "succeeded": null - }, - "expected-results": { - "3x3bitmaprect_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 16998423976396106083 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "3x3bitmaprect_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 2054956815327187963 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "3x3bitmaprect_gpu.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 2054956815327187963 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "3x3bitmaprect_pdf-mac.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 8518347971308375604 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "3x3bitmaprect_pdf-poppler.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 16723580409414313678 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - }, - "blanket-ignored_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 111111111 - ] - ], - "ignore-failure": false - }, - "blanket-ignored_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 111111111 - ] - ], - "ignore-failure": false - }, - "displacement_565.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 4569468668668628191 - ] - ], - "ignore-failure": false - }, - "displacement_8888.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 11401048196735046263 - ] - ], - "ignore-failure": false - }, - "displacement_gpu.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 5698561127291561694 - ] - ], - "ignore-failure": false - }, - "displacement_mesa.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 3844627628059679694 - ] - ], - "ignore-failure": false - }, - "displacement_pdf-mac.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 12901125495691049846 - ] - ], - "ignore-failure": false - }, - "displacement_pdf-poppler.png": { - "allowed-digests": [ - [ - "bitmap-64bitMD5", - 16285974094717334658 - ] - ], - "bugs": [ - 1578 - ], - "reviewed-by-human": false - } - } -} diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu12-ShuttleA-ATI5770-x86_64-Release-Valgrind/expected-results.json b/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu12-ShuttleA-ATI5770-x86_64-Release-Valgrind/expected-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu12-ShuttleA-ATI5770-x86_64-Release-Valgrind/expected-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-ASAN/expected-results.json b/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-ASAN/expected-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-ASAN/expected-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-TSAN/expected-results.json b/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-TSAN/expected-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Ubuntu13-ShuttleA-HD2000-x86_64-Debug-TSAN/expected-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Win7-ShuttleA-HD2000-x86_64-Debug-Trybot/expected-results.json b/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Win7-ShuttleA-HD2000-x86_64-Debug-Trybot/expected-results.json deleted file mode 100644 index a60da4f1db..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/Test-Win7-ShuttleA-HD2000-x86_64-Debug-Trybot/expected-results.json +++ /dev/null @@ -1 +0,0 @@ -This file should not be read by results.py, so its contents don't matter. diff --git a/gm/rebaseline_server/testdata/inputs/gm-expectations/ignored-tests.txt b/gm/rebaseline_server/testdata/inputs/gm-expectations/ignored-tests.txt deleted file mode 100644 index b14eb96bbf..0000000000 --- a/gm/rebaseline_server/testdata/inputs/gm-expectations/ignored-tests.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Failures of any GM tests listed in this file will be ignored [1], as -# if they had been marked "ignore-failure": true in the per-builder -# expected-results.json files. -# - -blanket-ignored - -# "texdata" tests should NOT be ignored, since the next lines is commented out: -# texdata - diff --git a/gm/rebaseline_server/testdata/inputs/skp-summaries/actuals/summary.json b/gm/rebaseline_server/testdata/inputs/skp-summaries/actuals/summary.json deleted file mode 100644 index 67e8409117..0000000000 --- a/gm/rebaseline_server/testdata/inputs/skp-summaries/actuals/summary.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actual-results" : { - "changed.skp" : { - "whole-image" : { - "checksumAlgorithm" : "bitmap-64bitMD5", - "checksumValue" : 13623922271964399662, - "comparisonResult" : "no-comparison", - "filepath" : "changed_skp/bitmap-64bitMD5_13623922271964399662.png" - } - }, - "only-in-after.skp" : { - "whole-image" : { - "checksumAlgorithm" : "bitmap-64bitMD5", - "checksumValue" : 2320185040577047131, - "comparisonResult" : "no-comparison", - "filepath" : "only-in-after_skp/bitmap-64bitMD5_2320185040577047131.png" - } - }, - "unchanged.skp" : { - "whole-image" : { - "checksumAlgorithm" : "bitmap-64bitMD5", - "checksumValue" : 3322248763049618493, - "comparisonResult" : "no-comparison", - "filepath" : "unchanged_skp/bitmap-64bitMD5_3322248763049618493.png" - } - } - }, - "header" : { - "revision" : 1, - "type" : "ChecksummedImages" - } -} diff --git a/gm/rebaseline_server/testdata/inputs/skp-summaries/expectations/summary.json b/gm/rebaseline_server/testdata/inputs/skp-summaries/expectations/summary.json deleted file mode 100644 index 9d64d75365..0000000000 --- a/gm/rebaseline_server/testdata/inputs/skp-summaries/expectations/summary.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "expected-results" : { - "changed.skp" : { - "whole-image" : { - "checksumAlgorithm" : "bitmap-64bitMD5", - "checksumValue" : 3101044995537104462, - "comparisonResult" : "no-comparison", - "filepath" : "changed_skp/bitmap-64bitMD5_3101044995537104462.png" - } - }, - "only-in-before.skp" : { - "whole-image" : { - "checksumAlgorithm" : "bitmap-64bitMD5", - "checksumValue" : 2320185040577047131, - "comparisonResult" : "no-comparison", - "filepath" : "only-in-before_skp/bitmap-64bitMD5_2320185040577047131.png" - } - }, - "unchanged.skp" : { - "whole-image" : { - "checksumAlgorithm" : "bitmap-64bitMD5", - "checksumValue" : 3322248763049618493, - "comparisonResult" : "no-comparison", - "filepath" : "unchanged_skp/bitmap-64bitMD5_3322248763049618493.png" - } - } - }, - "header" : { - "revision" : 1, - "type" : "ChecksummedImages" - } -} diff --git a/gm/rebaseline_server/testdata/outputs/.gitignore b/gm/rebaseline_server/testdata/outputs/.gitignore deleted file mode 100644 index b369696468..0000000000 --- a/gm/rebaseline_server/testdata/outputs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -actual/ diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/3x3bitmaprect.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/3x3bitmaprect.png deleted file mode 100644 index d3686ff12d..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/3x3bitmaprect.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/aaclip.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/aaclip.png deleted file mode 100644 index 8a01aa825f..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/aaclip.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/aaclip/6190901827590820995.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/bigblurs.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/bigblurs.png deleted file mode 100644 index 8d0646720a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/bigblurs.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bigblurs/2422083043229439955.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/bitmapsource.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/bitmapsource.png deleted file mode 100644 index d2df08ca3a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/bitmapsource.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bitmapsource/17503582803589749280.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_192_192.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_192_192.png deleted file mode 100644 index 5c91d33e15..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_192_192.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_32_2.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_32_2.png deleted file mode 100644 index 3c1de492de..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_32_2.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/3x3bitmaprect.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/3x3bitmaprect.png deleted file mode 100644 index 656275a920..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/3x3bitmaprect.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/aaclip.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/aaclip.png deleted file mode 100644 index 371a172ece..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/aaclip.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/aaclip/14456211900777561488.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/bigblurs.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/bigblurs.png deleted file mode 100644 index 2d78e89be3..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/bigblurs.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bigblurs/17309852422285247848.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/bitmapsource.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/bitmapsource.png deleted file mode 100644 index 3c511d5f6a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/bitmapsource.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bitmapsource/16289727936158057543.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_192_192.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_192_192.png deleted file mode 100644 index 9364ccdd8a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_192_192.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_32_2.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_32_2.png deleted file mode 100644 index 8760be33fb..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_32_2.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png diff --git a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/gpu/texdata.png b/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/gpu/texdata.png deleted file mode 100644 index fee415c72a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/download_actuals_test.DownloadTest.test_fetch/gpu/texdata.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/texdata/3695033638604474475.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/compare_configs_test.CompareConfigsTest.test_gm/gm.json b/gm/rebaseline_server/testdata/outputs/expected/compare_configs_test.CompareConfigsTest.test_gm/gm.json deleted file mode 100644 index 048abefd1e..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/compare_configs_test.CompareConfigsTest.test_gm/gm.json +++ /dev/null @@ -1,337 +0,0 @@ -{ - "extraColumnHeaders": { - "builder": { - "headerText": "builder", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - 7 - ], - [ - "Test-Builder-We-Have-No-Expectations-File-For", - 3 - ], - [ - "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - 4 - ] - ] - }, - "config": { - "headerText": "config", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "TODO", - 14 - ] - ] - }, - "resultType": { - "headerText": "resultType", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "failed", - 4 - ], - [ - "no-comparison", - 8 - ], - [ - "succeeded", - 2 - ] - ] - }, - "test": { - "headerText": "test", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "3x3bitmaprect", - 3 - ], - [ - "aaclip", - 1 - ], - [ - "bigblurs", - 3 - ], - [ - "bitmapsource", - 1 - ], - [ - "blanket-ignored", - 1 - ], - [ - "displacement", - 2 - ], - [ - "filterbitmap_checkerboard_192_192", - 1 - ], - [ - "filterbitmap_checkerboard_32_2", - 1 - ], - [ - "texdata", - 1 - ] - ] - } - }, - "extraColumnOrder": [ - "builder", - "config", - "resultType", - "test" - ], - "header": { - "dataHash": "-8474691963189557240", - "isEditable": false, - "isExported": true, - "schemaVersion": 5, - "timeNextUpdateAvailable": null, - "timeUpdated": 12345678, - "type": "all" - }, - "imagePairs": [ - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "texdata" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/texdata/3695033638604474475.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "filterbitmap_checkerboard_32_2" - }, - "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "filterbitmap_checkerboard_192_192" - }, - "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "bitmapsource" - }, - "imageAUrl": "bitmap-64bitMD5/bitmapsource/16289727936158057543.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "TODO", - "resultType": "no-comparison", - "test": "aaclip" - }, - "imageAUrl": "bitmap-64bitMD5/aaclip/14456211900777561488.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_displacement_11401048196735046263_png_png-vs-bitmap-64bitMD5_displacement_5698561127291561694_png_png.png", - "maxDiffPerChannel": [ - 136, - 68, - 34 - ], - "numDifferingPixels": 6081, - "percentDifferingPixels": 2.4324, - "perceptualDifference": 1.9172010000000057, - "whiteDiffUrl": "bitmap-64bitMD5_displacement_11401048196735046263_png_png-vs-bitmap-64bitMD5_displacement_5698561127291561694_png_png.png" - }, - "extraColumns": { - "builder": "Test-Builder-We-Have-No-Expectations-File-For", - "config": "TODO", - "resultType": "failed", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/11401048196735046263.png", - "imageBUrl": "bitmap-64bitMD5/displacement/5698561127291561694.png", - "isDifferent": true - }, - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_bigblurs_17309852422285247848_png_png-vs-bitmap-64bitMD5_bigblurs_1822195599289208664_png_png.png", - "maxDiffPerChannel": [ - 255, - 221, - 221 - ], - "numDifferingPixels": 50097, - "percentDifferingPixels": 30.5767822265625, - "perceptualDifference": 3.391725000000008, - "whiteDiffUrl": "bitmap-64bitMD5_bigblurs_17309852422285247848_png_png-vs-bitmap-64bitMD5_bigblurs_1822195599289208664_png_png.png" - }, - "extraColumns": { - "builder": "Test-Builder-We-Have-No-Expectations-File-For", - "config": "TODO", - "resultType": "failed", - "test": "bigblurs" - }, - "imageAUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", - "imageBUrl": "bitmap-64bitMD5/bigblurs/1822195599289208664.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Builder-We-Have-No-Expectations-File-For", - "config": "TODO", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "isDifferent": false - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "TODO", - "resultType": "no-comparison", - "test": "blanket-ignored" - }, - "imageAUrl": "bitmap-64bitMD5/blanket-ignored/22222222.png", - "imageBUrl": null, - "isDifferent": true - }, - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_displacement_11401048196735046263_png_png-vs-bitmap-64bitMD5_displacement_5698561127291561694_png_png.png", - "maxDiffPerChannel": [ - 136, - 68, - 34 - ], - "numDifferingPixels": 6081, - "percentDifferingPixels": 2.4324, - "perceptualDifference": 1.9172010000000057, - "whiteDiffUrl": "bitmap-64bitMD5_displacement_11401048196735046263_png_png-vs-bitmap-64bitMD5_displacement_5698561127291561694_png_png.png" - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "TODO", - "resultType": "failed", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/11401048196735046263.png", - "imageBUrl": "bitmap-64bitMD5/displacement/5698561127291561694.png", - "isDifferent": true - }, - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_bigblurs_17309852422285247848_png_png-vs-bitmap-64bitMD5_bigblurs_1822195599289208664_png_png.png", - "maxDiffPerChannel": [ - 255, - 221, - 221 - ], - "numDifferingPixels": 50097, - "percentDifferingPixels": 30.5767822265625, - "perceptualDifference": 3.391725000000008, - "whiteDiffUrl": "bitmap-64bitMD5_bigblurs_17309852422285247848_png_png-vs-bitmap-64bitMD5_bigblurs_1822195599289208664_png_png.png" - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "TODO", - "resultType": "failed", - "test": "bigblurs" - }, - "imageAUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", - "imageBUrl": "bitmap-64bitMD5/bigblurs/1822195599289208664.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "TODO", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "isDifferent": false - } - ], - "imageSets": { - "diffs": { - "baseUrl": "/static/generated-images/diffs", - "description": "color difference per channel" - }, - "imageA": { - "baseUrl": "http://chromium-skia-gm.commondatastorage.googleapis.com/gm", - "description": "8888" - }, - "imageB": { - "baseUrl": "http://chromium-skia-gm.commondatastorage.googleapis.com/gm", - "description": "gpu" - }, - "whiteDiffs": { - "baseUrl": "/static/generated-images/whitediffs", - "description": "differing pixels in white" - } - } -}
\ No newline at end of file diff --git a/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_endToEnd/compare_rendered_pictures.json b/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_endToEnd/compare_rendered_pictures.json deleted file mode 100644 index 1ce3718bc6..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_endToEnd/compare_rendered_pictures.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "extraColumnHeaders": { - "builderA": { - "headerText": "builderA", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "builderB": { - "headerText": "builderB", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "renderModeA": { - "headerText": "renderModeA", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "renderModeB": { - "headerText": "renderModeB", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "resultType": { - "headerText": "resultType", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "failed", - 1 - ], - [ - "no-comparison", - 2 - ], - [ - "succeeded", - 1 - ] - ] - }, - "sourceSkpFile": { - "headerText": "sourceSkpFile", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "changed.skp", - 1 - ], - [ - "only-in-after.skp", - 1 - ], - [ - "only-in-before.skp", - 1 - ], - [ - "unchanged.skp", - 1 - ] - ] - }, - "tiledOrWhole": { - "headerText": "tiledOrWhole", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "whole", - 4 - ] - ] - }, - "tilenum": { - "headerText": "tilenum", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "N/A", - 4 - ] - ] - } - }, - "extraColumnOrder": [ - "resultType", - "sourceSkpFile", - "tiledOrWhole", - "tilenum", - "builderA", - "renderModeA", - "builderB", - "renderModeB" - ], - "header": { - "dataHash": "-9145196899696949575", - "isEditable": false, - "isExported": true, - "schemaVersion": 5, - "setA": { - "dir": [ - "before-patch-fake-dir" - ], - "repoRevision": null, - "section": "actual-results" - }, - "setB": { - "dir": [ - "after-patch-fake-dir" - ], - "repoRevision": null, - "section": "actual-results" - }, - "timeNextUpdateAvailable": null, - "timeUpdated": 12345678, - "type": "all" - }, - "imagePairs": [ - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "failed", - "sourceSkpFile": "changed.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "changed_skp/bitmap-64bitMD5_17527938180154630042.png", - "imageBUrl": "changed_skp/bitmap-64bitMD5_2314401992566164894.png", - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "no-comparison", - "sourceSkpFile": "only-in-after.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": null, - "imageBUrl": "only-in-after_skp/bitmap-64bitMD5_6558642089737589931.png", - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "no-comparison", - "sourceSkpFile": "only-in-before.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "only-in-before_skp/bitmap-64bitMD5_6558642089737589931.png", - "imageBUrl": null, - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "succeeded", - "sourceSkpFile": "unchanged.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "unchanged_skp/bitmap-64bitMD5_1094324242976325446.png", - "imageBUrl": "unchanged_skp/bitmap-64bitMD5_1094324242976325446.png", - "isDifferent": false, - "sourceJsonFile": "./summary.json" - } - ], - "imageSets": { - "diffs": { - "baseUrl": "/static/generated-images/diffs", - "description": "color difference per channel" - }, - "imageA": { - "baseUrl": "http://storage.cloud.google.com/fakebucket/fake/path", - "description": "setA" - }, - "imageB": { - "baseUrl": "http://storage.cloud.google.com/fakebucket/fake/path", - "description": "setB" - }, - "whiteDiffs": { - "baseUrl": "/static/generated-images/whitediffs", - "description": "differing pixels in white" - } - } -}
\ No newline at end of file diff --git a/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_endToEnd_withImageBaseGSUrl/compare_rendered_pictures.json b/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_endToEnd_withImageBaseGSUrl/compare_rendered_pictures.json deleted file mode 100644 index 1026014da5..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_endToEnd_withImageBaseGSUrl/compare_rendered_pictures.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "extraColumnHeaders": { - "builderA": { - "headerText": "builderA", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "builderB": { - "headerText": "builderB", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "renderModeA": { - "headerText": "renderModeA", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "renderModeB": { - "headerText": "renderModeB", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "resultType": { - "headerText": "resultType", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "failed", - 1 - ], - [ - "no-comparison", - 2 - ], - [ - "succeeded", - 1 - ] - ] - }, - "sourceSkpFile": { - "headerText": "sourceSkpFile", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "changed.skp", - 1 - ], - [ - "only-in-after.skp", - 1 - ], - [ - "only-in-before.skp", - 1 - ], - [ - "unchanged.skp", - 1 - ] - ] - }, - "tiledOrWhole": { - "headerText": "tiledOrWhole", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "whole", - 4 - ] - ] - }, - "tilenum": { - "headerText": "tilenum", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "N/A", - 4 - ] - ] - } - }, - "extraColumnOrder": [ - "resultType", - "sourceSkpFile", - "tiledOrWhole", - "tilenum", - "builderA", - "renderModeA", - "builderB", - "renderModeB" - ], - "header": { - "dataHash": "-9145196899696949575", - "isEditable": false, - "isExported": true, - "schemaVersion": 5, - "setA": { - "dir": [ - "before-patch-fake-dir" - ], - "repoRevision": null, - "section": "actual-results" - }, - "setB": { - "dir": [ - "after-patch-fake-dir" - ], - "repoRevision": null, - "section": "actual-results" - }, - "timeNextUpdateAvailable": null, - "timeUpdated": 12345678, - "type": "all" - }, - "imagePairs": [ - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "failed", - "sourceSkpFile": "changed.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "changed_skp/bitmap-64bitMD5_17527938180154630042.png", - "imageBUrl": "changed_skp/bitmap-64bitMD5_2314401992566164894.png", - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "no-comparison", - "sourceSkpFile": "only-in-after.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": null, - "imageBUrl": "only-in-after_skp/bitmap-64bitMD5_6558642089737589931.png", - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "no-comparison", - "sourceSkpFile": "only-in-before.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "only-in-before_skp/bitmap-64bitMD5_6558642089737589931.png", - "imageBUrl": null, - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "succeeded", - "sourceSkpFile": "unchanged.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "unchanged_skp/bitmap-64bitMD5_1094324242976325446.png", - "imageBUrl": "unchanged_skp/bitmap-64bitMD5_1094324242976325446.png", - "isDifferent": false, - "sourceJsonFile": "./summary.json" - } - ], - "imageSets": { - "diffs": { - "baseUrl": "/static/generated-images/diffs", - "description": "color difference per channel" - }, - "imageA": { - "baseUrl": "http://storage.cloud.google.com/superman/kent-camera/pictures", - "description": "setA" - }, - "imageB": { - "baseUrl": "http://storage.cloud.google.com/batman/batarang/pictures", - "description": "setB" - }, - "whiteDiffs": { - "baseUrl": "/static/generated-images/whitediffs", - "description": "differing pixels in white" - } - } -}
\ No newline at end of file diff --git a/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_repo_url/compare_rendered_pictures.json b/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_repo_url/compare_rendered_pictures.json deleted file mode 100644 index 5ee60925ab..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/compare_rendered_pictures_test.CompareRenderedPicturesTest.test_repo_url/compare_rendered_pictures.json +++ /dev/null @@ -1,233 +0,0 @@ -{ - "extraColumnHeaders": { - "builderA": { - "headerText": "builderA", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "builderB": { - "headerText": "builderB", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "renderModeA": { - "headerText": "renderModeA", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "renderModeB": { - "headerText": "renderModeB", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - null, - 4 - ] - ] - }, - "resultType": { - "headerText": "resultType", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "failed", - 1 - ], - [ - "no-comparison", - 2 - ], - [ - "succeeded", - 1 - ] - ] - }, - "sourceSkpFile": { - "headerText": "sourceSkpFile", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "changed.skp", - 1 - ], - [ - "only-in-after.skp", - 1 - ], - [ - "only-in-before.skp", - 1 - ], - [ - "unchanged.skp", - 1 - ] - ] - }, - "tiledOrWhole": { - "headerText": "tiledOrWhole", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "whole", - 4 - ] - ] - }, - "tilenum": { - "headerText": "tilenum", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "N/A", - 4 - ] - ] - } - }, - "extraColumnOrder": [ - "resultType", - "sourceSkpFile", - "tiledOrWhole", - "tilenum", - "builderA", - "renderModeA", - "builderB", - "renderModeB" - ], - "header": { - "dataHash": "-5707186260478709107", - "isEditable": false, - "isExported": true, - "schemaVersion": 5, - "setA": { - "dir": "repo:gm/rebaseline_server/testdata/inputs/skp-summaries/expectations", - "repoRevision": "fake-repo-revision", - "section": "expected-results" - }, - "setB": { - "dir": "repo:gm/rebaseline_server/testdata/inputs/skp-summaries/actuals", - "repoRevision": "fake-repo-revision", - "section": "actual-results" - }, - "timeNextUpdateAvailable": null, - "timeUpdated": 12345678, - "type": "all" - }, - "imagePairs": [ - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "failed", - "sourceSkpFile": "changed.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "changed_skp/bitmap-64bitMD5_3101044995537104462.png", - "imageBUrl": "changed_skp/bitmap-64bitMD5_13623922271964399662.png", - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "no-comparison", - "sourceSkpFile": "only-in-after.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": null, - "imageBUrl": "only-in-after_skp/bitmap-64bitMD5_2320185040577047131.png", - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "no-comparison", - "sourceSkpFile": "only-in-before.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "only-in-before_skp/bitmap-64bitMD5_2320185040577047131.png", - "imageBUrl": null, - "isDifferent": true, - "sourceJsonFile": "./summary.json" - }, - { - "extraColumns": { - "builderA": null, - "builderB": null, - "renderModeA": null, - "renderModeB": null, - "resultType": "succeeded", - "sourceSkpFile": "unchanged.skp", - "tiledOrWhole": "whole", - "tilenum": "N/A" - }, - "imageAUrl": "unchanged_skp/bitmap-64bitMD5_3322248763049618493.png", - "imageBUrl": "unchanged_skp/bitmap-64bitMD5_3322248763049618493.png", - "isDifferent": false, - "sourceJsonFile": "./summary.json" - } - ], - "imageSets": { - "diffs": { - "baseUrl": "/static/generated-images/diffs", - "description": "color difference per channel" - }, - "imageA": { - "baseUrl": "http://storage.cloud.google.com/fakebucket/fake/path", - "description": "expected-results" - }, - "imageB": { - "baseUrl": "http://storage.cloud.google.com/fakebucket/fake/path", - "description": "actual-results" - }, - "whiteDiffs": { - "baseUrl": "/static/generated-images/whitediffs", - "description": "differing pixels in white" - } - } -}
\ No newline at end of file diff --git a/gm/rebaseline_server/testdata/outputs/expected/compare_to_expectations_test.CompareToExpectationsTest.test_gm/gm.json b/gm/rebaseline_server/testdata/outputs/expected/compare_to_expectations_test.CompareToExpectationsTest.test_gm/gm.json deleted file mode 100644 index 30d2ab983f..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/compare_to_expectations_test.CompareToExpectationsTest.test_gm/gm.json +++ /dev/null @@ -1,646 +0,0 @@ -{ - "extraColumnHeaders": { - "builder": { - "headerText": "builder", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - 13 - ], - [ - "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - 17 - ] - ] - }, - "config": { - "headerText": "config", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "565", - 10 - ], - [ - "8888", - 10 - ], - [ - "gpu", - 4 - ], - [ - "pdf-mac", - 3 - ], - [ - "pdf-poppler", - 3 - ] - ] - }, - "resultType": { - "headerText": "resultType", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": false, - "valuesAndCounts": [ - [ - "failed", - 1 - ], - [ - "failure-ignored", - 6 - ], - [ - "no-comparison", - 9 - ], - [ - "succeeded", - 14 - ] - ] - }, - "test": { - "headerText": "test", - "isFilterable": true, - "isSortable": true, - "useFreeformFilter": true, - "valuesAndCounts": [ - [ - "3x3bitmaprect", - 7 - ], - [ - "aaclip", - 2 - ], - [ - "bigblurs", - 7 - ], - [ - "bitmapsource", - 2 - ], - [ - "blanket-ignored", - 2 - ], - [ - "displacement", - 5 - ], - [ - "filterbitmap_checkerboard_192_192", - 2 - ], - [ - "filterbitmap_checkerboard_32_2", - 2 - ], - [ - "texdata", - 1 - ] - ] - } - }, - "extraColumnOrder": [ - "resultType", - "builder", - "test", - "config" - ], - "header": { - "dataHash": "6366271140430198826", - "isEditable": false, - "isExported": true, - "schemaVersion": 5, - "timeNextUpdateAvailable": null, - "timeUpdated": 12345678, - "type": "all" - }, - "imagePairs": [ - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_texdata_2736593828543197285_png_png-vs-bitmap-64bitMD5_texdata_3695033638604474475_png_png.png", - "maxDiffPerChannel": [ - 128, - 128, - 128 - ], - "numDifferingPixels": 120000, - "percentDifferingPixels": 75.0, - "perceptualDifference": 50.122499, - "whiteDiffUrl": "bitmap-64bitMD5_texdata_2736593828543197285_png_png-vs-bitmap-64bitMD5_texdata_3695033638604474475_png_png.png" - }, - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "gpu", - "resultType": "failed", - "test": "texdata" - }, - "imageAUrl": "bitmap-64bitMD5/texdata/2736593828543197285.png", - "imageBUrl": "bitmap-64bitMD5/texdata/3695033638604474475.png", - "isDifferent": true - }, - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_filterbitmap_checkerboard_192_192_9917960313903939620_png_png-vs-bitmap-64bitMD5_filterbitmap_checkerboard_192_192_4719210487426381700_png_png.png", - "maxDiffPerChannel": [ - 255, - 255, - 255 - ], - "numDifferingPixels": 765891, - "percentDifferingPixels": 97.38807678222656, - "perceptualDifference": 25.256985, - "whiteDiffUrl": "bitmap-64bitMD5_filterbitmap_checkerboard_192_192_9917960313903939620_png_png-vs-bitmap-64bitMD5_filterbitmap_checkerboard_192_192_4719210487426381700_png_png.png" - }, - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "resultType": "failure-ignored", - "test": "filterbitmap_checkerboard_192_192" - }, - "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/9917960313903939620.png", - "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png", - "isDifferent": true - }, - { - "differenceData": { - "diffUrl": "bitmap-64bitMD5_filterbitmap_checkerboard_192_192_9917960313903939620_png_png-vs-bitmap-64bitMD5_filterbitmap_checkerboard_192_192_3154864687054945306_png_png.png", - "maxDiffPerChannel": [ - 255, - 255, - 255 - ], - "numDifferingPixels": 422432, - "percentDifferingPixels": 53.715006510416664, - "perceptualDifference": 25.120543999999995, - "whiteDiffUrl": "bitmap-64bitMD5_filterbitmap_checkerboard_192_192_9917960313903939620_png_png-vs-bitmap-64bitMD5_filterbitmap_checkerboard_192_192_3154864687054945306_png_png.png" - }, - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "resultType": "failure-ignored", - "test": "filterbitmap_checkerboard_192_192" - }, - "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/9917960313903939620.png", - "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png", - "isDifferent": true - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "resultType": "failure-ignored", - "test": "filterbitmap_checkerboard_32_2" - }, - "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/16197252621792695154.png", - "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png", - "isDifferent": true - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "resultType": "failure-ignored", - "test": "filterbitmap_checkerboard_32_2" - }, - "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/7634650333321761866.png", - "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/2422083043229439955.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "resultType": "no-comparison", - "test": "bitmapsource" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bitmapsource/17503582803589749280.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "resultType": "no-comparison", - "test": "bitmapsource" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bitmapsource/16289727936158057543.png", - "isDifferent": true - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "resultType": "succeeded", - "test": "aaclip" - }, - "imageAUrl": "bitmap-64bitMD5/aaclip/6190901827590820995.png", - "imageBUrl": "bitmap-64bitMD5/aaclip/6190901827590820995.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "resultType": "succeeded", - "test": "aaclip" - }, - "imageAUrl": "bitmap-64bitMD5/aaclip/14456211900777561488.png", - "imageBUrl": "bitmap-64bitMD5/aaclip/14456211900777561488.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "resultType": "failure-ignored", - "test": "blanket-ignored" - }, - "imageAUrl": "bitmap-64bitMD5/blanket-ignored/111111111.png", - "imageBUrl": "bitmap-64bitMD5/blanket-ignored/22222222.png", - "isDifferent": true - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "resultType": "failure-ignored", - "test": "blanket-ignored" - }, - "imageAUrl": "bitmap-64bitMD5/blanket-ignored/111111111.png", - "imageBUrl": "bitmap-64bitMD5/blanket-ignored/22222222.png", - "isDifferent": true - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "resultType": "succeeded", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/4569468668668628191.png", - "imageBUrl": "bitmap-64bitMD5/displacement/4569468668668628191.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "resultType": "succeeded", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/11401048196735046263.png", - "imageBUrl": "bitmap-64bitMD5/displacement/11401048196735046263.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "resultType": "succeeded", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/5698561127291561694.png", - "imageBUrl": "bitmap-64bitMD5/displacement/5698561127291561694.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": null, - "ignore-failure": false, - "reviewed-by-human": null - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "resultType": "succeeded", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/12901125495691049846.png", - "imageBUrl": "bitmap-64bitMD5/displacement/12901125495691049846.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "resultType": "succeeded", - "test": "displacement" - }, - "imageAUrl": "bitmap-64bitMD5/displacement/16285974094717334658.png", - "imageBUrl": "bitmap-64bitMD5/displacement/16285974094717334658.png", - "isDifferent": false - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/14704206703218007573.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/1822195599289208664.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/16171608477794909861.png", - "isDifferent": true - }, - { - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "resultType": "no-comparison", - "test": "bigblurs" - }, - "imageAUrl": null, - "imageBUrl": "bitmap-64bitMD5/bigblurs/6539050160610613353.png", - "isDifferent": true - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/8518347971308375604.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/8518347971308375604.png", - "isDifferent": false - }, - { - "expectations": { - "bugs": [ - 1578 - ], - "ignore-failure": null, - "reviewed-by-human": false - }, - "extraColumns": { - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "resultType": "succeeded", - "test": "3x3bitmaprect" - }, - "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/16723580409414313678.png", - "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/16723580409414313678.png", - "isDifferent": false - } - ], - "imageSets": { - "diffs": { - "baseUrl": "/static/generated-images/diffs", - "description": "color difference per channel" - }, - "imageA": { - "baseUrl": "http://chromium-skia-gm.commondatastorage.googleapis.com/gm", - "description": "expected image" - }, - "imageB": { - "baseUrl": "http://chromium-skia-gm.commondatastorage.googleapis.com/gm", - "description": "actual image" - }, - "whiteDiffs": { - "baseUrl": "/static/generated-images/whitediffs", - "description": "differing pixels in white" - } - } -}
\ No newline at end of file diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/3x3bitmaprect.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/3x3bitmaprect.png deleted file mode 100644 index d3686ff12d..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/3x3bitmaprect.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/aaclip.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/aaclip.png deleted file mode 100644 index 8a01aa825f..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/aaclip.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/aaclip/6190901827590820995.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/bigblurs.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/bigblurs.png deleted file mode 100644 index 8d0646720a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/bigblurs.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bigblurs/2422083043229439955.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/bitmapsource.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/bitmapsource.png deleted file mode 100644 index d2df08ca3a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/bitmapsource.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bitmapsource/17503582803589749280.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_192_192.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_192_192.png deleted file mode 100644 index 5c91d33e15..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_192_192.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_32_2.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_32_2.png deleted file mode 100644 index 3c1de492de..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/565/filterbitmap_checkerboard_32_2.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/3x3bitmaprect.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/3x3bitmaprect.png deleted file mode 100644 index 656275a920..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/3x3bitmaprect.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/aaclip.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/aaclip.png deleted file mode 100644 index 371a172ece..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/aaclip.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/aaclip/14456211900777561488.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/bigblurs.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/bigblurs.png deleted file mode 100644 index 2d78e89be3..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/bigblurs.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bigblurs/17309852422285247848.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/bitmapsource.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/bitmapsource.png deleted file mode 100644 index 3c511d5f6a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/bitmapsource.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/bitmapsource/16289727936158057543.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_192_192.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_192_192.png deleted file mode 100644 index 9364ccdd8a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_192_192.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_32_2.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_32_2.png deleted file mode 100644 index 8760be33fb..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/8888/filterbitmap_checkerboard_32_2.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png diff --git a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/gpu/texdata.png b/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/gpu/texdata.png deleted file mode 100644 index fee415c72a..0000000000 --- a/gm/rebaseline_server/testdata/outputs/expected/download_actuals_test.DownloadTest.test_fetch/gpu/texdata.png +++ /dev/null @@ -1 +0,0 @@ -contents of bitmap-64bitMD5/texdata/3695033638604474475.png diff --git a/gm/rebaseline_server/writable_expectations.py b/gm/rebaseline_server/writable_expectations.py deleted file mode 100644 index 09b9cf7689..0000000000 --- a/gm/rebaseline_server/writable_expectations.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/python - -""" -Copyright 2014 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Expectations on local disk that we can modify. -""" - -# System-level imports -import logging -import os -import re - -# Must fix up PYTHONPATH before importing from within Skia -import rs_fixpypath # pylint: disable=W0611 - -# Imports from within Skia -from py.utils import git_utils -import compare_rendered_pictures -import gm_json -import imagepair -import results - -FILEPATH_RE = re.compile('.+/' + gm_json.IMAGE_FILENAME_PATTERN) - -SKIA_REPO = os.path.abspath(os.path.join( - os.path.dirname(__file__), os.pardir, os.pardir, '.git')) - - -class WritableExpectations(git_utils.NewGitCheckout): - """Expectations on local disk that we can modify.""" - - def __init__(self, set_descriptions): - """Creates a sandbox on local disk containing writable expectations. - - You must use the 'with' statement to create this object in such a way that - it cleans up after itself: - - with WritableExpectations(*args) as writable_expectations: - # make modifications - # use the modified results - # the sandbox on local disk is automatically cleaned up here - - Args: - set_descriptions: SET_DESCRIPTIONS dict describing the set we want to - update expectations within; this tells us the subdirectory within the - Skia repo where we keep these expectations, and the commithash at - which the user evaluated new baselines. - """ - file_section = set_descriptions[results.KEY__SET_DESCRIPTIONS__SECTION] - assert file_section == gm_json.JSONKEY_EXPECTEDRESULTS - - source_dir = _unicode_to_ascii( - set_descriptions[results.KEY__SET_DESCRIPTIONS__DIR]) - assert source_dir.startswith(compare_rendered_pictures.REPO_URL_PREFIX) - repo_subdir = source_dir[len(compare_rendered_pictures.REPO_URL_PREFIX):] - repo_revision = _unicode_to_ascii( - set_descriptions[results.KEY__SET_DESCRIPTIONS__REPO_REVISION]) - - logging.info('Creating a writable Skia checkout at revision "%s"...' % - repo_revision) - super(WritableExpectations, self).__init__( - repository=SKIA_REPO, commit=repo_revision, subdir=repo_subdir) - - def modify(self, modifications): - """Modify the contents of the checkout, using modifications from the UI. - - Args: - modifications: data[KEY__LIVE_EDITS__MODIFICATIONS] coming back from the - rebaseline_server UI frontend - """ - logging.info('Reading in dicts from writable Skia checkout in %s ...' % - self.root) - dicts = results.BaseComparisons.read_dicts_from_root(self.root) - - # Make sure we have expected-results sections in all our output dicts. - for pathname, adict in dicts.iteritems(): - if not adict: - adict = { - # TODO(stephana): These values should be defined as constants - # somewhere, to be kept in sync between this file and - # compare_rendered_pictures.py. - gm_json.JSONKEY_HEADER: { - gm_json.JSONKEY_HEADER_TYPE: 'ChecksummedImages', - gm_json.JSONKEY_HEADER_REVISION: 1, - } - } - if not adict.get(gm_json.JSONKEY_EXPECTEDRESULTS, None): - adict[gm_json.JSONKEY_EXPECTEDRESULTS] = {} - dicts[pathname] = adict - - for modification in modifications: - expectations = modification[imagepair.KEY__IMAGEPAIRS__EXPECTATIONS] - _add_image_info_to_expectations( - expectations=expectations, - filepath=modification[imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL]) - extra_columns = modification[imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS] - dictname = modification[imagepair.KEY__IMAGEPAIRS__SOURCE_JSON_FILE] - dict_to_modify = dicts[dictname][gm_json.JSONKEY_EXPECTEDRESULTS] - test_name = extra_columns[compare_rendered_pictures.COLUMN__SOURCE_SKP] - test_record = dict_to_modify.get(test_name, {}) - if (extra_columns[compare_rendered_pictures.COLUMN__TILED_OR_WHOLE] == - compare_rendered_pictures.COLUMN__TILED_OR_WHOLE__TILED): - test_tiles_list = test_record.get( - gm_json.JSONKEY_SOURCE_TILEDIMAGES, []) - tilenum = int(extra_columns[compare_rendered_pictures.COLUMN__TILENUM]) - _replace_list_item(test_tiles_list, tilenum, expectations) - test_record[gm_json.JSONKEY_SOURCE_TILEDIMAGES] = test_tiles_list - else: - test_record[gm_json.JSONKEY_SOURCE_WHOLEIMAGE] = expectations - dict_to_modify[test_name] = test_record - - # Write the modified files back to disk. - self._write_dicts_to_root(meta_dict=dicts, root=self.root) - - def get_diffs(self): - """Return patchfile describing any modifications to this checkout.""" - return self._run_in_git_root(args=[git_utils.GIT, 'diff']) - - @staticmethod - def _write_dicts_to_root(meta_dict, root): - """Write out multiple dictionaries in JSON format. - - Args: - meta_dict: a builder-keyed meta-dictionary containing all the JSON - dictionaries we want to write out - root: path to root of directory tree within which to write files - """ - if not os.path.isdir(root): - raise IOError('no directory found at path %s' % root) - - for rel_path in meta_dict.keys(): - full_path = os.path.join(root, rel_path) - gm_json.WriteToFile(meta_dict[rel_path], full_path) - - -def _unicode_to_ascii(unicode_string): - """Returns the plain ASCII form of a unicode string. - - TODO(stephana): We created this because we get unicode strings out of the - JSON file, while the git filenames and revision tags are plain ASCII. - There may be a better way to handle this... maybe set the JSON util to just - return ASCII strings? - """ - return unicode_string.encode('ascii', 'ignore') - - -def _replace_list_item(a_list, index, value): - """Replaces value at index "index" within a_list. - - Args: - a_list: a list - index: index indicating which item in a_list to replace - value: value to set a_list[index] to - - If a_list does not contain this index, it will be extended with None entries - to that length. - """ - length = len(a_list) - while index >= length: - a_list.append(None) - length += 1 - a_list[index] = value - - -def _add_image_info_to_expectations(expectations, filepath): - """Add JSONKEY_IMAGE_* info to an existing expectations dictionary. - - TODO(stephana): This assumes that the checksumAlgorithm and checksumValue - can be derived from the filepath, which is currently true but may not always - be true. - - Args: - expectations: the expectations dict to augment - filepath: relative path to the image file - """ - (checksum_algorithm, checksum_value) = FILEPATH_RE.match(filepath).groups() - expectations[gm_json.JSONKEY_IMAGE_CHECKSUMALGORITHM] = checksum_algorithm - expectations[gm_json.JSONKEY_IMAGE_CHECKSUMVALUE] = checksum_value - expectations[gm_json.JSONKEY_IMAGE_FILEPATH] = filepath |