| 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 | 10 |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 57 | 57 |
| 58 | 58 |
| 59 class RenderedPicturesComparisons(results.BaseComparisons): | 59 class RenderedPicturesComparisons(results.BaseComparisons): |
| 60 """Loads results from multiple render_pictures runs into an ImagePairSet. | 60 """Loads results from multiple render_pictures runs into an ImagePairSet. |
| 61 """ | 61 """ |
| 62 | 62 |
| 63 def __init__(self, setA_dirs, setB_dirs, image_diff_db, | 63 def __init__(self, setA_dirs, setB_dirs, image_diff_db, |
| 64 image_base_gs_url=DEFAULT_IMAGE_BASE_GS_URL, | 64 image_base_gs_url=DEFAULT_IMAGE_BASE_GS_URL, |
| 65 diff_base_url=None, setA_label='setA', | 65 diff_base_url=None, setA_label='setA', |
| 66 setB_label='setB', gs=None, | 66 setB_label='setB', gs=None, |
| 67 truncate_results=False): | 67 truncate_results=False, prefetch_only=False, |
| 68 """ | 68 download_all_images=False): |
| 69 """Constructor: downloads images and generates diffs. |
| 70 |
| 71 Once the object has been created (which may take a while), you can call its |
| 72 get_packaged_results_of_type() method to quickly retrieve the results... |
| 73 unless you have set prefetch_only to True, in which case we will |
| 74 asynchronously warm up the ImageDiffDB cache but not fill in self._results. |
| 75 |
| 69 Args: | 76 Args: |
| 70 setA_dirs: list of root directories to copy all JSON summaries from, | 77 setA_dirs: list of root directories to copy all JSON summaries from, |
| 71 and to use as setA within the comparisons | 78 and to use as setA within the comparisons |
| 72 setB_dirs: list of root directories to copy all JSON summaries from, | 79 setB_dirs: list of root directories to copy all JSON summaries from, |
| 73 and to use as setB within the comparisons | 80 and to use as setB within the comparisons |
| 74 image_diff_db: ImageDiffDB instance | 81 image_diff_db: ImageDiffDB instance |
| 75 image_base_gs_url: "gs://" URL pointing at the Google Storage bucket/dir | 82 image_base_gs_url: "gs://" URL pointing at the Google Storage bucket/dir |
| 76 under which all render_pictures result images can | 83 under which all render_pictures result images can |
| 77 be found; this will be used to read images for comparison within | 84 be found; this will be used to read images for comparison within |
| 78 this code, and included in the ImagePairSet (as an HTTP URL) so its | 85 this code, and included in the ImagePairSet (as an HTTP URL) so its |
| 79 consumers know where to download the images from | 86 consumers know where to download the images from |
| 80 diff_base_url: base URL within which the client should look for diff | 87 diff_base_url: base URL within which the client should look for diff |
| 81 images; if not specified, defaults to a "file:///" URL representation | 88 images; if not specified, defaults to a "file:///" URL representation |
| 82 of image_diff_db's storage_root | 89 of image_diff_db's storage_root |
| 83 setA_label: description to use for results in setA | 90 setA_label: description to use for results in setA |
| 84 setB_label: description to use for results in setB | 91 setB_label: description to use for results in setB |
| 85 gs: instance of GSUtils object we can use to download summary files | 92 gs: instance of GSUtils object we can use to download summary files |
| 86 truncate_results: FOR MANUAL TESTING: if True, truncate the set of images | 93 truncate_results: FOR MANUAL TESTING: if True, truncate the set of images |
| 87 we process, to speed up testing. | 94 we process, to speed up testing. |
| 95 prefetch_only: if True, return the new object as quickly as possible |
| 96 with empty self._results (just queue up all the files to process, |
| 97 don't wait around for them to be processed and recorded); otherwise, |
| 98 block until the results have been assembled and recorded in |
| 99 self._results. |
| 100 download_all_images: if True, download all images, even if we don't |
| 101 need them to generate diffs. This will take much longer to complete, |
| 102 but is useful for warming up the bitmap cache on local disk. |
| 88 """ | 103 """ |
| 89 super(RenderedPicturesComparisons, self).__init__() | 104 super(RenderedPicturesComparisons, self).__init__() |
| 90 self._image_diff_db = image_diff_db | 105 self._image_diff_db = image_diff_db |
| 91 self._image_base_gs_url = image_base_gs_url | 106 self._image_base_gs_url = image_base_gs_url |
| 92 self._diff_base_url = ( | 107 self._diff_base_url = ( |
| 93 diff_base_url or | 108 diff_base_url or |
| 94 url_utils.create_filepath_url(image_diff_db.storage_root)) | 109 url_utils.create_filepath_url(image_diff_db.storage_root)) |
| 95 self._setA_label = setA_label | 110 self._setA_label = setA_label |
| 96 self._setB_label = setB_label | 111 self._setB_label = setB_label |
| 97 self._gs = gs | 112 self._gs = gs |
| 98 self.truncate_results = truncate_results | 113 self.truncate_results = truncate_results |
| 114 self._prefetch_only = prefetch_only |
| 115 self._download_all_images = download_all_images |
| 99 | 116 |
| 100 tempdir = tempfile.mkdtemp() | 117 tempdir = tempfile.mkdtemp() |
| 101 try: | 118 try: |
| 102 setA_root = os.path.join(tempdir, 'setA') | 119 setA_root = os.path.join(tempdir, 'setA') |
| 103 setB_root = os.path.join(tempdir, 'setB') | 120 setB_root = os.path.join(tempdir, 'setB') |
| 104 for source_dir in setA_dirs: | 121 for source_dir in setA_dirs: |
| 105 self._copy_dir_contents(source_dir=source_dir, dest_dir=setA_root) | 122 self._copy_dir_contents(source_dir=source_dir, dest_dir=setA_root) |
| 106 for source_dir in setB_dirs: | 123 for source_dir in setB_dirs: |
| 107 self._copy_dir_contents(source_dir=source_dir, dest_dir=setB_root) | 124 self._copy_dir_contents(source_dir=source_dir, dest_dir=setB_root) |
| 108 | 125 |
| 109 time_start = int(time.time()) | 126 time_start = int(time.time()) |
| 110 # TODO(epoger): For now, this assumes that we are always comparing two | 127 # TODO(epoger): For now, this assumes that we are always comparing two |
| 111 # sets of actual results, not actuals vs expectations. Allow the user | 128 # sets of actual results, not actuals vs expectations. Allow the user |
| 112 # to control this. | 129 # to control this. |
| 113 self._results = self._load_result_pairs( | 130 self._results = self._load_result_pairs( |
| 114 setA_root=setA_root, setA_section=gm_json.JSONKEY_ACTUALRESULTS, | 131 setA_root=setA_root, setA_section=gm_json.JSONKEY_ACTUALRESULTS, |
| 115 setB_root=setB_root, setB_section=gm_json.JSONKEY_ACTUALRESULTS) | 132 setB_root=setB_root, setB_section=gm_json.JSONKEY_ACTUALRESULTS) |
| 116 self._timestamp = int(time.time()) | 133 if self._results: |
| 117 logging.info('Number of download file collisions: %s' % | 134 self._timestamp = int(time.time()) |
| 118 imagediffdb.global_file_collisions) | 135 logging.info('Number of download file collisions: %s' % |
| 119 logging.info('Results complete; took %d seconds.' % | 136 imagediffdb.global_file_collisions) |
| 120 (self._timestamp - time_start)) | 137 logging.info('Results complete; took %d seconds.' % |
| 138 (self._timestamp - time_start)) |
| 121 finally: | 139 finally: |
| 122 shutil.rmtree(tempdir) | 140 shutil.rmtree(tempdir) |
| 123 | 141 |
| 124 def _load_result_pairs(self, setA_root, setA_section, setB_root, | 142 def _load_result_pairs(self, setA_root, setA_section, setB_root, |
| 125 setB_section): | 143 setB_section): |
| 126 """Loads all JSON image summaries from 2 directory trees and compares them. | 144 """Loads all JSON image summaries from 2 directory trees and compares them. |
| 127 | 145 |
| 128 Args: | 146 Args: |
| 129 setA_root: root directory containing JSON summaries of rendering results | 147 setA_root: root directory containing JSON summaries of rendering results |
| 130 setA_section: which section (gm_json.JSONKEY_ACTUALRESULTS or | 148 setA_section: which section (gm_json.JSONKEY_ACTUALRESULTS or |
| 131 gm_json.JSONKEY_EXPECTEDRESULTS) to load from the summaries in setA | 149 gm_json.JSONKEY_EXPECTEDRESULTS) to load from the summaries in setA |
| 132 setB_root: root directory containing JSON summaries of rendering results | 150 setB_root: root directory containing JSON summaries of rendering results |
| 133 setB_section: which section (gm_json.JSONKEY_ACTUALRESULTS or | 151 setB_section: which section (gm_json.JSONKEY_ACTUALRESULTS or |
| 134 gm_json.JSONKEY_EXPECTEDRESULTS) to load from the summaries in setB | 152 gm_json.JSONKEY_EXPECTEDRESULTS) to load from the summaries in setB |
| 135 | 153 |
| 136 Returns the summary of all image diff results. | 154 Returns the summary of all image diff results (or None, depending on |
| 155 self._prefetch_only). |
| 137 """ | 156 """ |
| 138 logging.info('Reading JSON image summaries from dirs %s and %s...' % ( | 157 logging.info('Reading JSON image summaries from dirs %s and %s...' % ( |
| 139 setA_root, setB_root)) | 158 setA_root, setB_root)) |
| 140 setA_dicts = self._read_dicts_from_root(setA_root) | 159 setA_dicts = self._read_dicts_from_root(setA_root) |
| 141 setB_dicts = self._read_dicts_from_root(setB_root) | 160 setB_dicts = self._read_dicts_from_root(setB_root) |
| 142 logging.info('Comparing summary dicts...') | 161 logging.info('Comparing summary dicts...') |
| 143 | 162 |
| 144 all_image_pairs = imagepairset.ImagePairSet( | 163 all_image_pairs = imagepairset.ImagePairSet( |
| 145 descriptions=(self._setA_label, self._setB_label), | 164 descriptions=(self._setA_label, self._setB_label), |
| 146 diff_base_url=self._diff_base_url) | 165 diff_base_url=self._diff_base_url) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 167 column_id=COLUMN__RESULT_TYPE, values=[ | 186 column_id=COLUMN__RESULT_TYPE, values=[ |
| 168 results.KEY__RESULT_TYPE__FAILED, | 187 results.KEY__RESULT_TYPE__FAILED, |
| 169 results.KEY__RESULT_TYPE__NOCOMPARISON, | 188 results.KEY__RESULT_TYPE__NOCOMPARISON, |
| 170 ]) | 189 ]) |
| 171 | 190 |
| 172 union_dict_paths = sorted(set(setA_dicts.keys() + setB_dicts.keys())) | 191 union_dict_paths = sorted(set(setA_dicts.keys() + setB_dicts.keys())) |
| 173 num_union_dict_paths = len(union_dict_paths) | 192 num_union_dict_paths = len(union_dict_paths) |
| 174 dict_num = 0 | 193 dict_num = 0 |
| 175 for dict_path in union_dict_paths: | 194 for dict_path in union_dict_paths: |
| 176 dict_num += 1 | 195 dict_num += 1 |
| 177 logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' % | 196 logging.info( |
| 178 (dict_num, num_union_dict_paths, dict_path)) | 197 'Asynchronously requesting pixel diffs for dict #%d of %d, "%s"...' % |
| 198 (dict_num, num_union_dict_paths, dict_path)) |
| 179 | 199 |
| 180 dictA = self.get_default(setA_dicts, None, dict_path) | 200 dictA = self.get_default(setA_dicts, None, dict_path) |
| 181 self._validate_dict_version(dictA) | 201 self._validate_dict_version(dictA) |
| 182 dictA_results = self.get_default(dictA, {}, setA_section) | 202 dictA_results = self.get_default(dictA, {}, setA_section) |
| 183 | 203 |
| 184 dictB = self.get_default(setB_dicts, None, dict_path) | 204 dictB = self.get_default(setB_dicts, None, dict_path) |
| 185 self._validate_dict_version(dictB) | 205 self._validate_dict_version(dictB) |
| 186 dictB_results = self.get_default(dictB, {}, setB_section) | 206 dictB_results = self.get_default(dictB, {}, setB_section) |
| 187 | 207 |
| 188 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) | 208 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 220 source_skp_name=skp_name, tilenum=tile_num)) | 240 source_skp_name=skp_name, tilenum=tile_num)) |
| 221 | 241 |
| 222 for one_imagepair in imagepairs_for_this_skp: | 242 for one_imagepair in imagepairs_for_this_skp: |
| 223 if one_imagepair: | 243 if one_imagepair: |
| 224 all_image_pairs.add_image_pair(one_imagepair) | 244 all_image_pairs.add_image_pair(one_imagepair) |
| 225 result_type = one_imagepair.extra_columns_dict\ | 245 result_type = one_imagepair.extra_columns_dict\ |
| 226 [COLUMN__RESULT_TYPE] | 246 [COLUMN__RESULT_TYPE] |
| 227 if result_type != results.KEY__RESULT_TYPE__SUCCEEDED: | 247 if result_type != results.KEY__RESULT_TYPE__SUCCEEDED: |
| 228 failing_image_pairs.add_image_pair(one_imagepair) | 248 failing_image_pairs.add_image_pair(one_imagepair) |
| 229 | 249 |
| 230 return { | 250 if self._prefetch_only: |
| 231 results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict( | 251 return None |
| 232 column_ids_in_order=ORDERED_COLUMN_IDS), | 252 else: |
| 233 results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict( | 253 return { |
| 234 column_ids_in_order=ORDERED_COLUMN_IDS), | 254 results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict( |
| 235 } | 255 column_ids_in_order=ORDERED_COLUMN_IDS), |
| 256 results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict( |
| 257 column_ids_in_order=ORDERED_COLUMN_IDS), |
| 258 } |
| 236 | 259 |
| 237 def _validate_dict_version(self, result_dict): | 260 def _validate_dict_version(self, result_dict): |
| 238 """Raises Exception if the dict is not the type/version we know how to read. | 261 """Raises Exception if the dict is not the type/version we know how to read. |
| 239 | 262 |
| 240 Args: | 263 Args: |
| 241 result_dict: dictionary holding output of render_pictures; if None, | 264 result_dict: dictionary holding output of render_pictures; if None, |
| 242 this method will return without raising an Exception | 265 this method will return without raising an Exception |
| 243 """ | 266 """ |
| 244 expected_header_type = 'ChecksummedImages' | 267 expected_header_type = 'ChecksummedImages' |
| 245 expected_header_revision = 1 | 268 expected_header_revision = 1 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 305 else: | 328 else: |
| 306 extra_columns_dict[COLUMN__TILED_OR_WHOLE] = 'tiled' | 329 extra_columns_dict[COLUMN__TILED_OR_WHOLE] = 'tiled' |
| 307 extra_columns_dict[COLUMN__TILENUM] = str(tilenum) | 330 extra_columns_dict[COLUMN__TILENUM] = str(tilenum) |
| 308 | 331 |
| 309 try: | 332 try: |
| 310 return imagepair.ImagePair( | 333 return imagepair.ImagePair( |
| 311 image_diff_db=self._image_diff_db, | 334 image_diff_db=self._image_diff_db, |
| 312 base_url=self._image_base_gs_url, | 335 base_url=self._image_base_gs_url, |
| 313 imageA_relative_url=imageA_relative_url, | 336 imageA_relative_url=imageA_relative_url, |
| 314 imageB_relative_url=imageB_relative_url, | 337 imageB_relative_url=imageB_relative_url, |
| 315 extra_columns=extra_columns_dict) | 338 extra_columns=extra_columns_dict, |
| 339 download_all_images=self._download_all_images) |
| 316 except (KeyError, TypeError): | 340 except (KeyError, TypeError): |
| 317 logging.exception( | 341 logging.exception( |
| 318 'got exception while creating ImagePair for' | 342 'got exception while creating ImagePair for' |
| 319 ' urlPair=("%s","%s"), source_skp_name="%s", tilenum="%s"' % ( | 343 ' urlPair=("%s","%s"), source_skp_name="%s", tilenum="%s"' % ( |
| 320 imageA_relative_url, imageB_relative_url, source_skp_name, | 344 imageA_relative_url, imageB_relative_url, source_skp_name, |
| 321 tilenum)) | 345 tilenum)) |
| 322 return None | 346 return None |
| 323 | 347 |
| 324 def _copy_dir_contents(self, source_dir, dest_dir): | 348 def _copy_dir_contents(self, source_dir, dest_dir): |
| 325 """Copy all contents of source_dir into dest_dir, recursing into subdirs. | 349 """Copy all contents of source_dir into dest_dir, recursing into subdirs. |
| 326 | 350 |
| 327 Args: | 351 Args: |
| 328 source_dir: path to source dir (GS URL or local filepath) | 352 source_dir: path to source dir (GS URL or local filepath) |
| 329 dest_dir: path to destination dir (local filepath) | 353 dest_dir: path to destination dir (local filepath) |
| 330 | 354 |
| 331 The copy operates as a "merge with overwrite": any files in source_dir will | 355 The copy operates as a "merge with overwrite": any files in source_dir will |
| 332 be "overlaid" on top of the existing content in dest_dir. Existing files | 356 be "overlaid" on top of the existing content in dest_dir. Existing files |
| 333 with the same names will be overwritten. | 357 with the same names will be overwritten. |
| 334 """ | 358 """ |
| 335 if gs_utils.GSUtils.is_gs_url(source_dir): | 359 if gs_utils.GSUtils.is_gs_url(source_dir): |
| 336 (bucket, path) = gs_utils.GSUtils.split_gs_url(source_dir) | 360 (bucket, path) = gs_utils.GSUtils.split_gs_url(source_dir) |
| 337 self._gs.download_dir_contents(source_bucket=bucket, source_dir=path, | 361 self._gs.download_dir_contents(source_bucket=bucket, source_dir=path, |
| 338 dest_dir=dest_dir) | 362 dest_dir=dest_dir) |
| 339 else: | 363 else: |
| 340 shutil.copytree(source_dir, dest_dir) | 364 shutil.copytree(source_dir, dest_dir) |
| OLD | NEW |