| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 | 2 |
| 3 """ | 3 """ |
| 4 Copyright 2014 Google Inc. | 4 Copyright 2014 Google Inc. |
| 5 | 5 |
| 6 Use of this source code is governed by a BSD-style license that can be | 6 Use of this source code is governed by a BSD-style license that can be |
| 7 found in the LICENSE file. | 7 found in the LICENSE file. |
| 8 | 8 |
| 9 Compare results of two render_pictures runs. | 9 Compare results of two render_pictures runs. |
| 10 |
| 11 EPOGER: Rename this module, because it is more generally useful now... it can do
wnload and compare any types of images. |
| 10 """ | 12 """ |
| 11 | 13 |
| 12 # System-level imports | 14 # System-level imports |
| 13 import logging | 15 import logging |
| 14 import os | 16 import os |
| 17 import shutil |
| 18 import tempfile |
| 15 import time | 19 import time |
| 16 | 20 |
| 17 # Must fix up PYTHONPATH before importing from within Skia | 21 # Must fix up PYTHONPATH before importing from within Skia |
| 18 import fix_pythonpath # pylint: disable=W0611 | 22 import fix_pythonpath # pylint: disable=W0611 |
| 19 | 23 |
| 20 # Imports from within Skia | 24 # Imports from within Skia |
| 21 from py.utils import url_utils | 25 from py.utils import url_utils |
| 26 import buildbot_globals |
| 22 import gm_json | 27 import gm_json |
| 23 import imagediffdb | 28 import imagediffdb |
| 24 import imagepair | 29 import imagepair |
| 25 import imagepairset | 30 import imagepairset |
| 26 import results | 31 import results |
| 27 | 32 |
| 28 # URL under which all render_pictures images can be found in Google Storage. | 33 # URL under which all render_pictures images can be found in Google Storage. |
| 29 # | 34 # |
| 30 # pylint: disable=C0301 | 35 # EPOGER: In order to allow live-view of GMs and other images, read this from th
e input summary files, or allow the caller to set it within the GET_live_results
call? |
| 31 # TODO(epoger): Move this default value into | 36 DEFAULT_IMAGE_BASE_GS_URL = 'gs://' + buildbot_globals.Get('skp_images_bucket') |
| 32 # https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.j
son | |
| 33 # pylint: enable=C0301 | |
| 34 DEFAULT_IMAGE_BASE_URL = ( | |
| 35 'http://chromium-skia-gm.commondatastorage.googleapis.com/' | |
| 36 'render_pictures/images') | |
| 37 | 37 |
| 38 | 38 |
| 39 class RenderedPicturesComparisons(results.BaseComparisons): | 39 class RenderedPicturesComparisons(results.BaseComparisons): |
| 40 """Loads results from two different render_pictures runs into an ImagePairSet. | 40 """Loads results from multiple render_pictures runs into an ImagePairSet. |
| 41 """ | 41 """ |
| 42 | 42 |
| 43 def __init__(self, subdirs, actuals_root, | 43 def __init__(self, actuals_dirs, expectations_dirs, image_diff_db, |
| 44 generated_images_root=results.DEFAULT_GENERATED_IMAGES_ROOT, | 44 image_base_gs_url=DEFAULT_IMAGE_BASE_GS_URL, |
| 45 image_base_url=DEFAULT_IMAGE_BASE_URL, | 45 diff_base_url=None, actuals_label='actuals', |
| 46 diff_base_url=None): | 46 expectations_label='expectations'): |
| 47 """ | 47 """ |
| 48 Args: | 48 Args: |
| 49 actuals_root: root directory containing all render_pictures-generated | 49 actuals_dirs: list of root directories to copy all JSON summaries from, |
| 50 JSON files | 50 and to use as actual results |
| 51 subdirs: (string, string) tuple; pair of subdirectories within | 51 expectations_dirs: list of root directories to copy all JSON summaries |
| 52 actuals_root to compare | 52 from, and to use as expected results |
| 53 generated_images_root: directory within which to create all pixel diffs; | 53 image_diff_db: ImageDiffDB instance |
| 54 if this directory does not yet exist, it will be created | 54 image_base_gs_url: "gs://" URL pointing at the Google Storage bucket/dir |
| 55 image_base_url: URL under which all render_pictures result images can | 55 under which all render_pictures result images can |
| 56 be found; this will be used to read images for comparison within | 56 be found; this will be used to read images for comparison within |
| 57 this code, and included in the ImagePairSet so its consumers know | 57 this code, and included in the ImagePairSet (as an HTTP URL) so its |
| 58 where to download the images from | 58 consumers know where to download the images from |
| 59 diff_base_url: base URL within which the client should look for diff | 59 diff_base_url: base URL within which the client should look for diff |
| 60 images; if not specified, defaults to a "file:///" URL representation | 60 images; if not specified, defaults to a "file:///" URL representation |
| 61 of generated_images_root | 61 of image_diff_db's storage_root |
| 62 actuals_label: description to use for actual results |
| 63 expectations_label: description to use for expected results |
| 62 """ | 64 """ |
| 63 time_start = int(time.time()) | 65 self._image_diff_db = image_diff_db |
| 64 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root) | 66 self._image_base_gs_url = image_base_gs_url |
| 65 self._image_base_url = image_base_url | |
| 66 self._diff_base_url = ( | 67 self._diff_base_url = ( |
| 67 diff_base_url or | 68 diff_base_url or |
| 68 url_utils.create_filepath_url(generated_images_root)) | 69 url_utils.create_filepath_url(image_diff_db.storage_root)) |
| 69 self._load_result_pairs(actuals_root, subdirs) | 70 self._actuals_label = actuals_label |
| 70 self._timestamp = int(time.time()) | 71 self._expectations_label = expectations_label |
| 71 logging.info('Results complete; took %d seconds.' % | |
| 72 (self._timestamp - time_start)) | |
| 73 | 72 |
| 74 def _load_result_pairs(self, actuals_root, subdirs): | 73 tempdir = tempfile.mkdtemp() |
| 75 """Loads all JSON files found within two subdirs in actuals_root, | 74 try: |
| 76 compares across those two subdirs, and stores the summary in self._results. | 75 actuals_root = os.path.join(tempdir, 'actuals') |
| 76 expectations_root = os.path.join(tempdir, 'expectations') |
| 77 # Copy all summary files into actuals_root and expectations_root |
| 78 # EPOGER: this won't work when *_dirs have more than one entry |
| 79 for dir in actuals_dirs: |
| 80 shutil.copytree(dir, actuals_root) |
| 81 for dir in expectations_dirs: |
| 82 shutil.copytree(dir, expectations_root) |
| 83 |
| 84 time_start = int(time.time()) |
| 85 self._load_result_pairs(actuals_root, expectations_root) |
| 86 self._timestamp = int(time.time()) |
| 87 logging.info('Results complete; took %d seconds.' % |
| 88 (self._timestamp - time_start)) |
| 89 finally: |
| 90 shutil.rmtree(tempdir) |
| 91 |
| 92 def _load_result_pairs(self, actuals_root, expectations_root): |
| 93 """Loads all JSON image summaries from 2 directory trees and compares them. |
| 94 |
| 95 The summary of all image diff results is stored in in self._results. |
| 96 EPOGER: we don't want to store the results in self._results anymore... now,
we just want to return them |
| 77 | 97 |
| 78 Args: | 98 Args: |
| 79 actuals_root: root directory containing all render_pictures-generated | 99 actuals_root: root directory containing JSON summaries of actual results |
| 80 JSON files | 100 expectations_root: root dir containing JSON summaries of expected results |
| 81 subdirs: (string, string) tuple; pair of subdirectories within | |
| 82 actuals_root to compare | |
| 83 """ | 101 """ |
| 84 logging.info( | 102 logging.info('Reading JSON image summaries from dirs %s and %s...' % ( |
| 85 'Reading actual-results JSON files from %s subdirs within %s...' % ( | 103 actuals_root, expectations_root)) |
| 86 subdirs, actuals_root)) | 104 actuals_dicts = self._read_dicts_from_root(actuals_root) |
| 87 subdirA, subdirB = subdirs | 105 expectations_dicts = self._read_dicts_from_root(expectations_root) |
| 88 subdirA_dicts = self._read_dicts_from_root( | 106 logging.info('Comparing summary dicts...') |
| 89 os.path.join(actuals_root, subdirA)) | |
| 90 subdirB_dicts = self._read_dicts_from_root( | |
| 91 os.path.join(actuals_root, subdirB)) | |
| 92 logging.info('Comparing subdirs %s and %s...' % (subdirA, subdirB)) | |
| 93 | 107 |
| 94 all_image_pairs = imagepairset.ImagePairSet( | 108 all_image_pairs = imagepairset.ImagePairSet( |
| 95 descriptions=subdirs, | 109 descriptions=(self._actuals_label, self._expectations_label), |
| 96 diff_base_url=self._diff_base_url) | 110 diff_base_url=self._diff_base_url) |
| 97 failing_image_pairs = imagepairset.ImagePairSet( | 111 failing_image_pairs = imagepairset.ImagePairSet( |
| 98 descriptions=subdirs, | 112 descriptions=(self._actuals_label, self._expectations_label), |
| 99 diff_base_url=self._diff_base_url) | 113 diff_base_url=self._diff_base_url) |
| 100 | 114 |
| 101 all_image_pairs.ensure_extra_column_values_in_summary( | 115 all_image_pairs.ensure_extra_column_values_in_summary( |
| 102 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ | 116 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ |
| 103 results.KEY__RESULT_TYPE__FAILED, | 117 results.KEY__RESULT_TYPE__FAILED, |
| 104 results.KEY__RESULT_TYPE__NOCOMPARISON, | 118 results.KEY__RESULT_TYPE__NOCOMPARISON, |
| 105 results.KEY__RESULT_TYPE__SUCCEEDED, | 119 results.KEY__RESULT_TYPE__SUCCEEDED, |
| 106 ]) | 120 ]) |
| 107 failing_image_pairs.ensure_extra_column_values_in_summary( | 121 failing_image_pairs.ensure_extra_column_values_in_summary( |
| 108 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ | 122 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ |
| 109 results.KEY__RESULT_TYPE__FAILED, | 123 results.KEY__RESULT_TYPE__FAILED, |
| 110 results.KEY__RESULT_TYPE__NOCOMPARISON, | 124 results.KEY__RESULT_TYPE__NOCOMPARISON, |
| 111 ]) | 125 ]) |
| 112 | 126 |
| 113 common_dict_paths = sorted(set(subdirA_dicts.keys() + subdirB_dicts.keys())) | 127 # Every actuals_dict should be paired up with a corresponding |
| 128 # expectations_dict. |
| 129 actuals_dict_paths = sorted(actuals_dicts.keys()) |
| 130 expectations_dict_paths = sorted(expectations_dicts.keys()) |
| 131 if actuals_dict_paths != expectations_dict_paths: |
| 132 raise Exception('actuals_dict_paths %s != expectations_dict_paths %s' % ( |
| 133 actuals_dict_paths, expectations_dict_paths)) |
| 134 common_dict_paths = actuals_dict_paths |
| 135 |
| 114 num_common_dict_paths = len(common_dict_paths) | 136 num_common_dict_paths = len(common_dict_paths) |
| 115 dict_num = 0 | 137 dict_num = 0 |
| 116 for dict_path in common_dict_paths: | 138 for dict_path in common_dict_paths: |
| 117 dict_num += 1 | 139 dict_num += 1 |
| 118 logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' % | 140 logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' % |
| 119 (dict_num, num_common_dict_paths, dict_path)) | 141 (dict_num, num_common_dict_paths, dict_path)) |
| 120 dictA = subdirA_dicts[dict_path] | 142 dictA = actuals_dicts[dict_path] |
| 121 dictB = subdirB_dicts[dict_path] | 143 dictB = expectations_dicts[dict_path] |
| 122 self._validate_dict_version(dictA) | 144 self._validate_dict_version(dictA) |
| 123 self._validate_dict_version(dictB) | 145 self._validate_dict_version(dictB) |
| 124 dictA_results = dictA[gm_json.JSONKEY_ACTUALRESULTS] | 146 dictA_results = dictA[gm_json.JSONKEY_ACTUALRESULTS] |
| 125 dictB_results = dictB[gm_json.JSONKEY_ACTUALRESULTS] | 147 dictB_results = dictB[gm_json.JSONKEY_ACTUALRESULTS] |
| 126 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) | 148 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) |
| 127 for skp_name in skp_names: | 149 for skp_name in skp_names: |
| 128 imagepairs_for_this_skp = [] | 150 imagepairs_for_this_skp = [] |
| 129 | 151 |
| 130 whole_image_A = RenderedPicturesComparisons.get_multilevel( | 152 whole_image_A = RenderedPicturesComparisons.get_multilevel( |
| 131 dictA_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) | 153 dictA_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 results.KEY__EXTRACOLUMNS__TEST: test, | 250 results.KEY__EXTRACOLUMNS__TEST: test, |
| 229 # TODO(epoger): Right now, the client UI crashes if it receives | 251 # TODO(epoger): Right now, the client UI crashes if it receives |
| 230 # results that do not include this column. | 252 # results that do not include this column. |
| 231 # Until we fix that, keep the client happy. | 253 # Until we fix that, keep the client happy. |
| 232 results.KEY__EXTRACOLUMNS__BUILDER: 'TODO', | 254 results.KEY__EXTRACOLUMNS__BUILDER: 'TODO', |
| 233 } | 255 } |
| 234 | 256 |
| 235 try: | 257 try: |
| 236 return imagepair.ImagePair( | 258 return imagepair.ImagePair( |
| 237 image_diff_db=self._image_diff_db, | 259 image_diff_db=self._image_diff_db, |
| 238 base_url=self._image_base_url, | 260 base_url=self._image_base_gs_url, |
| 239 imageA_relative_url=imageA_relative_url, | 261 imageA_relative_url=imageA_relative_url, |
| 240 imageB_relative_url=imageB_relative_url, | 262 imageB_relative_url=imageB_relative_url, |
| 241 extra_columns=extra_columns_dict) | 263 extra_columns=extra_columns_dict) |
| 242 except (KeyError, TypeError): | 264 except (KeyError, TypeError): |
| 243 logging.exception( | 265 logging.exception( |
| 244 'got exception while creating ImagePair for' | 266 'got exception while creating ImagePair for' |
| 245 ' test="%s", config="%s", urlPair=("%s","%s")' % ( | 267 ' test="%s", config="%s", urlPair=("%s","%s")' % ( |
| 246 test, config, imageA_relative_url, imageB_relative_url)) | 268 test, config, imageA_relative_url, imageB_relative_url)) |
| 247 return None | 269 return None |
| 248 | |
| 249 | |
| 250 # TODO(epoger): Add main() so this can be called by vm_run_skia_try.sh | |
| OLD | NEW |