Chromium Code Reviews| 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 |
| 11 TODO(epoger): Start using this module to compare ALL images (whether they | 11 TODO(epoger): Start using this module to compare ALL images (whether they |
| 12 were generated from GMs or SKPs), and rename it accordingly. | 12 were generated from GMs or SKPs), and rename it accordingly. |
| 13 """ | 13 """ |
| 14 | 14 |
| 15 # System-level imports | 15 # System-level imports |
| 16 import logging | 16 import logging |
| 17 import os | 17 import os |
| 18 import shutil | 18 import shutil |
| 19 import subprocess | 19 import subprocess |
| 20 import tempfile | 20 import tempfile |
| 21 import time | 21 import time |
| 22 | 22 |
| 23 # Must fix up PYTHONPATH before importing from within Skia | 23 # Must fix up PYTHONPATH before importing from within Skia |
| 24 import fix_pythonpath # pylint: disable=W0611 | 24 import rs_fixpypath # pylint: disable=W0611 |
| 25 | 25 |
| 26 # Imports from within Skia | 26 # Imports from within Skia |
| 27 from py.utils import git_utils | 27 from py.utils import git_utils |
| 28 from py.utils import gs_utils | 28 from py.utils import gs_utils |
| 29 from py.utils import url_utils | 29 from py.utils import url_utils |
| 30 import buildbot_globals | 30 import buildbot_globals |
| 31 import column | 31 import column |
| 32 import gm_json | 32 import gm_json |
| 33 import imagediffdb | 33 import imagediffdb |
| 34 import imagepair | 34 import imagepair |
| 35 import imagepairset | 35 import imagepairset |
| 36 import results | 36 import results |
| 37 | 37 |
| 38 # URL under which all render_pictures images can be found in Google Storage. | 38 # URL under which all render_pictures images can be found in Google Storage. |
| 39 # | 39 # |
| 40 # TODO(epoger): In order to allow live-view of GMs and other images, read this | 40 # TODO(epoger): In order to allow live-view of GMs and other images, read this |
| 41 # from the input summary files, or allow the caller to set it within the | 41 # from the input summary files, or allow the caller to set it within the |
| 42 # GET_live_results call. | 42 # GET_live_results call. |
| 43 DEFAULT_IMAGE_BASE_GS_URL = 'gs://' + buildbot_globals.Get('skp_images_bucket') | 43 DEFAULT_IMAGE_BASE_GS_URL = 'gs://' + buildbot_globals.Get('skp_images_bucket') |
| 44 | 44 |
| 45 # Column descriptors, and display preferences for them. | 45 # Column descriptors, and display preferences for them. |
| 46 COLUMN__RESULT_TYPE = results.KEY__EXTRACOLUMNS__RESULT_TYPE | 46 COLUMN__RESULT_TYPE = results.KEY__EXTRACOLUMNS__RESULT_TYPE |
| 47 COLUMN__SOURCE_SKP = 'sourceSkpFile' | 47 COLUMN__SOURCE_SKP = 'sourceSkpFile' |
| 48 COLUMN__TILED_OR_WHOLE = 'tiledOrWhole' | 48 COLUMN__TILED_OR_WHOLE = 'tiledOrWhole' |
| 49 COLUMN__TILENUM = 'tilenum' | 49 COLUMN__TILENUM = 'tilenum' |
| 50 COLUMN__BUILDER_A = 'builderA' | |
| 51 COLUMN__RENDER_MODE_A = 'renderModeA' | |
| 52 COLUMN__BUILDER_B = 'builderB' | |
| 53 COLUMN__RENDER_MODE_B = 'renderModeB' | |
| 54 | |
| 50 FREEFORM_COLUMN_IDS = [ | 55 FREEFORM_COLUMN_IDS = [ |
| 51 COLUMN__SOURCE_SKP, | 56 COLUMN__SOURCE_SKP, |
| 52 COLUMN__TILENUM, | 57 COLUMN__TILENUM, |
| 53 ] | 58 ] |
| 54 ORDERED_COLUMN_IDS = [ | 59 ORDERED_COLUMN_IDS = [ |
| 55 COLUMN__RESULT_TYPE, | 60 COLUMN__RESULT_TYPE, |
| 56 COLUMN__SOURCE_SKP, | 61 COLUMN__SOURCE_SKP, |
| 57 COLUMN__TILED_OR_WHOLE, | 62 COLUMN__TILED_OR_WHOLE, |
| 58 COLUMN__TILENUM, | 63 COLUMN__TILENUM, |
| 64 COLUMN__BUILDER_A, | |
| 65 COLUMN__RENDER_MODE_A, | |
| 66 COLUMN__BUILDER_B, | |
| 67 COLUMN__RENDER_MODE_B, | |
| 59 ] | 68 ] |
| 60 | 69 |
| 61 # A special "repo:" URL type that we use to refer to Skia repo contents. | 70 # A special "repo:" URL type that we use to refer to Skia repo contents. |
| 62 # (Useful for comparing against expectations files we store in our repo.) | 71 # (Useful for comparing against expectations files we store in our repo.) |
| 63 REPO_URL_PREFIX = 'repo:' | 72 REPO_URL_PREFIX = 'repo:' |
| 64 REPO_BASEPATH = os.path.abspath(os.path.join( | 73 REPO_BASEPATH = os.path.abspath(os.path.join( |
| 65 os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)) | 74 os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)) |
| 66 | 75 |
| 67 # Which sections within a JSON summary file can contain results. | 76 # Which sections within a JSON summary file can contain results. |
| 68 ALLOWED_SECTION_NAMES = [ | 77 ALLOWED_SECTION_NAMES = [ |
| (...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 255 (dict_num, num_union_dict_paths, dict_path)) | 264 (dict_num, num_union_dict_paths, dict_path)) |
| 256 | 265 |
| 257 dictA = self.get_default(setA_dicts, None, dict_path) | 266 dictA = self.get_default(setA_dicts, None, dict_path) |
| 258 self._validate_dict_version(dictA) | 267 self._validate_dict_version(dictA) |
| 259 dictA_results = self.get_default(dictA, {}, setA_section) | 268 dictA_results = self.get_default(dictA, {}, setA_section) |
| 260 | 269 |
| 261 dictB = self.get_default(setB_dicts, None, dict_path) | 270 dictB = self.get_default(setB_dicts, None, dict_path) |
| 262 self._validate_dict_version(dictB) | 271 self._validate_dict_version(dictB) |
| 263 dictB_results = self.get_default(dictB, {}, setB_section) | 272 dictB_results = self.get_default(dictB, {}, setB_section) |
| 264 | 273 |
| 274 # get the builders and render modes for each set | |
| 275 builder_A = self.get_default(dictA, None, | |
|
epoger
2014/08/18 20:34:49
Ravi will cite https://engdoc.corp.google.com/eng/
| |
| 276 gm_json.JSONKEY_DESCRIPTIONS, | |
| 277 gm_json.JSONKEY_DESCRIPTIONS_BUILDER) | |
| 278 render_mode_A = self.get_default(dictA, None, | |
| 279 gm_json.JSONKEY_DESCRIPTIONS, | |
| 280 gm_json.JSONKEY_DESCRIPTIONS_RENDER_MODE) | |
| 281 builder_B = self.get_default(dictB, None, | |
| 282 gm_json.JSONKEY_DESCRIPTIONS, | |
| 283 gm_json.JSONKEY_DESCRIPTIONS_BUILDER) | |
| 284 render_mode_B = self.get_default(dictB, None, | |
| 285 gm_json.JSONKEY_DESCRIPTIONS, | |
| 286 gm_json.JSONKEY_DESCRIPTIONS_RENDER_MODE) | |
| 287 | |
| 265 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) | 288 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) |
| 266 # Just for manual testing... truncate to an arbitrary subset. | 289 # Just for manual testing... truncate to an arbitrary subset. |
| 267 if self.truncate_results: | 290 if self.truncate_results: |
| 268 skp_names = skp_names[1:3] | 291 skp_names = skp_names[1:3] |
| 269 for skp_name in skp_names: | 292 for skp_name in skp_names: |
| 270 imagepairs_for_this_skp = [] | 293 imagepairs_for_this_skp = [] |
| 271 | 294 |
| 272 whole_image_A = self.get_default( | 295 whole_image_A = self.get_default( |
| 273 dictA_results, None, | 296 dictA_results, None, |
| 274 skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) | 297 skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) |
| 275 whole_image_B = self.get_default( | 298 whole_image_B = self.get_default( |
| 276 dictB_results, None, | 299 dictB_results, None, |
| 277 skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) | 300 skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) |
| 278 imagepairs_for_this_skp.append(self._create_image_pair( | 301 imagepairs_for_this_skp.append(self._create_image_pair( |
| 279 image_dict_A=whole_image_A, image_dict_B=whole_image_B, | 302 image_dict_A=whole_image_A, image_dict_B=whole_image_B, |
| 303 builder_A=builder_A, render_mode_A=render_mode_A, | |
| 304 builder_B=builder_B, render_mode_B=render_mode_B, | |
| 280 source_skp_name=skp_name, tilenum=None)) | 305 source_skp_name=skp_name, tilenum=None)) |
| 281 | 306 |
| 282 tiled_images_A = self.get_default( | 307 tiled_images_A = self.get_default( |
| 283 dictA_results, [], | 308 dictA_results, [], |
| 284 skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES) | 309 skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES) |
| 285 tiled_images_B = self.get_default( | 310 tiled_images_B = self.get_default( |
| 286 dictB_results, [], | 311 dictB_results, [], |
| 287 skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES) | 312 skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES) |
| 288 if tiled_images_A or tiled_images_B: | 313 if tiled_images_A or tiled_images_B: |
| 289 num_tiles_A = len(tiled_images_A) | 314 num_tiles_A = len(tiled_images_A) |
| 290 num_tiles_B = len(tiled_images_B) | 315 num_tiles_B = len(tiled_images_B) |
| 291 num_tiles = max(num_tiles_A, num_tiles_B) | 316 num_tiles = max(num_tiles_A, num_tiles_B) |
| 292 for tile_num in range(num_tiles): | 317 for tile_num in range(num_tiles): |
| 293 imagepairs_for_this_skp.append(self._create_image_pair( | 318 imagepairs_for_this_skp.append(self._create_image_pair( |
| 294 image_dict_A=(tiled_images_A[tile_num] | 319 image_dict_A=(tiled_images_A[tile_num] |
| 295 if tile_num < num_tiles_A else None), | 320 if tile_num < num_tiles_A else None), |
| 296 image_dict_B=(tiled_images_B[tile_num] | 321 image_dict_B=(tiled_images_B[tile_num] |
| 297 if tile_num < num_tiles_B else None), | 322 if tile_num < num_tiles_B else None), |
| 323 builder_A=builder_A, render_mode_A=render_mode_A, | |
| 324 builder_B=builder_B, render_mode_B=render_mode_B, | |
| 298 source_skp_name=skp_name, tilenum=tile_num)) | 325 source_skp_name=skp_name, tilenum=tile_num)) |
| 299 | 326 |
| 300 for one_imagepair in imagepairs_for_this_skp: | 327 for one_imagepair in imagepairs_for_this_skp: |
| 301 if one_imagepair: | 328 if one_imagepair: |
| 302 all_image_pairs.add_image_pair(one_imagepair) | 329 all_image_pairs.add_image_pair(one_imagepair) |
| 303 result_type = one_imagepair.extra_columns_dict\ | 330 result_type = one_imagepair.extra_columns_dict\ |
| 304 [COLUMN__RESULT_TYPE] | 331 [COLUMN__RESULT_TYPE] |
| 305 if result_type != results.KEY__RESULT_TYPE__SUCCEEDED: | 332 if result_type != results.KEY__RESULT_TYPE__SUCCEEDED: |
| 306 failing_image_pairs.add_image_pair(one_imagepair) | 333 failing_image_pairs.add_image_pair(one_imagepair) |
| 307 | 334 |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 333 header = result_dict[gm_json.JSONKEY_HEADER] | 360 header = result_dict[gm_json.JSONKEY_HEADER] |
| 334 header_type = header[gm_json.JSONKEY_HEADER_TYPE] | 361 header_type = header[gm_json.JSONKEY_HEADER_TYPE] |
| 335 if header_type != expected_header_type: | 362 if header_type != expected_header_type: |
| 336 raise Exception('expected header_type "%s", but got "%s"' % ( | 363 raise Exception('expected header_type "%s", but got "%s"' % ( |
| 337 expected_header_type, header_type)) | 364 expected_header_type, header_type)) |
| 338 header_revision = header[gm_json.JSONKEY_HEADER_REVISION] | 365 header_revision = header[gm_json.JSONKEY_HEADER_REVISION] |
| 339 if header_revision != expected_header_revision: | 366 if header_revision != expected_header_revision: |
| 340 raise Exception('expected header_revision %d, but got %d' % ( | 367 raise Exception('expected header_revision %d, but got %d' % ( |
| 341 expected_header_revision, header_revision)) | 368 expected_header_revision, header_revision)) |
| 342 | 369 |
| 343 def _create_image_pair(self, image_dict_A, image_dict_B, source_skp_name, | 370 def _create_image_pair(self, image_dict_A, image_dict_B, |
| 371 builder_A, render_mode_A, | |
| 372 builder_B, render_mode_B, | |
| 373 source_skp_name, | |
| 344 tilenum): | 374 tilenum): |
| 345 """Creates an ImagePair object for this pair of images. | 375 """Creates an ImagePair object for this pair of images. |
| 346 | 376 |
| 347 Args: | 377 Args: |
| 348 image_dict_A: dict with JSONKEY_IMAGE_* keys, or None if no image | 378 image_dict_A: dict with JSONKEY_IMAGE_* keys, or None if no image |
| 349 image_dict_B: dict with JSONKEY_IMAGE_* keys, or None if no image | 379 image_dict_B: dict with JSONKEY_IMAGE_* keys, or None if no image |
| 380 builder_A: builder that created image set A or None if unknow | |
| 381 render_mode_A: render mode used to generate image set A or None if | |
| 382 unknown. | |
| 383 builder_B: builder that created image set A or None if unknow | |
| 384 render_mode_B: render mode used to generate image set A or None if | |
| 385 unknown. | |
| 350 source_skp_name: string; name of the source SKP file | 386 source_skp_name: string; name of the source SKP file |
| 351 tilenum: which tile, or None if a wholeimage | 387 tilenum: which tile, or None if a wholeimage |
| 352 | 388 |
| 353 Returns: | 389 Returns: |
| 354 An ImagePair object, or None if both image_dict_A and image_dict_B are | 390 An ImagePair object, or None if both image_dict_A and image_dict_B are |
| 355 None. | 391 None. |
| 356 """ | 392 """ |
| 357 if (not image_dict_A) and (not image_dict_B): | 393 if (not image_dict_A) and (not image_dict_B): |
| 358 return None | 394 return None |
| 359 | 395 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 375 elif not imageB_checksum: | 411 elif not imageB_checksum: |
| 376 result_type = results.KEY__RESULT_TYPE__NOCOMPARISON | 412 result_type = results.KEY__RESULT_TYPE__NOCOMPARISON |
| 377 elif imageA_checksum == imageB_checksum: | 413 elif imageA_checksum == imageB_checksum: |
| 378 result_type = results.KEY__RESULT_TYPE__SUCCEEDED | 414 result_type = results.KEY__RESULT_TYPE__SUCCEEDED |
| 379 else: | 415 else: |
| 380 result_type = results.KEY__RESULT_TYPE__FAILED | 416 result_type = results.KEY__RESULT_TYPE__FAILED |
| 381 | 417 |
| 382 extra_columns_dict = { | 418 extra_columns_dict = { |
| 383 COLUMN__RESULT_TYPE: result_type, | 419 COLUMN__RESULT_TYPE: result_type, |
| 384 COLUMN__SOURCE_SKP: source_skp_name, | 420 COLUMN__SOURCE_SKP: source_skp_name, |
| 421 COLUMN__BUILDER_A: builder_A, | |
| 422 COLUMN__RENDER_MODE_A: render_mode_A, | |
| 423 COLUMN__BUILDER_B: builder_B, | |
| 424 COLUMN__RENDER_MODE_B: render_mode_B, | |
| 385 } | 425 } |
| 386 if tilenum == None: | 426 if tilenum == None: |
| 387 extra_columns_dict[COLUMN__TILED_OR_WHOLE] = 'whole' | 427 extra_columns_dict[COLUMN__TILED_OR_WHOLE] = 'whole' |
| 388 extra_columns_dict[COLUMN__TILENUM] = 'N/A' | 428 extra_columns_dict[COLUMN__TILENUM] = 'N/A' |
| 389 else: | 429 else: |
| 390 extra_columns_dict[COLUMN__TILED_OR_WHOLE] = 'tiled' | 430 extra_columns_dict[COLUMN__TILED_OR_WHOLE] = 'tiled' |
| 391 extra_columns_dict[COLUMN__TILENUM] = str(tilenum) | 431 extra_columns_dict[COLUMN__TILENUM] = str(tilenum) |
| 392 | 432 |
| 393 try: | 433 try: |
| 394 return imagepair.ImagePair( | 434 return imagepair.ImagePair( |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 441 if source_dir.lower().startswith(REPO_URL_PREFIX): | 481 if source_dir.lower().startswith(REPO_URL_PREFIX): |
| 442 repo_dir = os.path.join(REPO_BASEPATH, source_dir[len(REPO_URL_PREFIX):]) | 482 repo_dir = os.path.join(REPO_BASEPATH, source_dir[len(REPO_URL_PREFIX):]) |
| 443 revision = subprocess.check_output( | 483 revision = subprocess.check_output( |
| 444 args=[git_utils.GIT, 'rev-parse', 'HEAD'], cwd=repo_dir).strip() | 484 args=[git_utils.GIT, 'rev-parse', 'HEAD'], cwd=repo_dir).strip() |
| 445 if assert_if_not and revision != assert_if_not: | 485 if assert_if_not and revision != assert_if_not: |
| 446 raise Exception('found revision %s that did not match %s' % ( | 486 raise Exception('found revision %s that did not match %s' % ( |
| 447 revision, assert_if_not)) | 487 revision, assert_if_not)) |
| 448 return revision | 488 return revision |
| 449 else: | 489 else: |
| 450 return None | 490 return None |
| OLD | NEW |