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 |