| Index: gm/rebaseline_server/compare_rendered_pictures.py
|
| diff --git a/gm/rebaseline_server/compare_rendered_pictures.py b/gm/rebaseline_server/compare_rendered_pictures.py
|
| index 80a42e51f9b1e83c1481c93a7e268d4c7668a0bb..ba621c3a35dabdb3a68c62951d6b2be8f95b1a8c 100755
|
| --- a/gm/rebaseline_server/compare_rendered_pictures.py
|
| +++ b/gm/rebaseline_server/compare_rendered_pictures.py
|
| @@ -39,9 +39,6 @@ import imagepair
|
| import imagepairset
|
| import results
|
|
|
| -# Characters we don't want popping up just anywhere within filenames.
|
| -DISALLOWED_FILEPATH_CHAR_REGEX = re.compile('[^\w\-]')
|
| -
|
| # URL under which all render_pictures images can be found in Google Storage.
|
| # TODO(epoger): Move this default value into
|
| # https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.json
|
| @@ -97,9 +94,9 @@ class RenderedPicturesComparisons(results.BaseComparisons):
|
| 'Reading actual-results JSON files from %s subdirs within %s...' % (
|
| subdirs, actuals_root))
|
| subdirA, subdirB = subdirs
|
| - subdirA_builder_dicts = self._read_dicts_from_root(
|
| + subdirA_dicts = self._read_dicts_from_root(
|
| os.path.join(actuals_root, subdirA))
|
| - subdirB_builder_dicts = self._read_dicts_from_root(
|
| + subdirB_dicts = self._read_dicts_from_root(
|
| os.path.join(actuals_root, subdirB))
|
| logging.info('Comparing subdirs %s and %s...' % (subdirA, subdirB))
|
|
|
| @@ -122,87 +119,140 @@ class RenderedPicturesComparisons(results.BaseComparisons):
|
| results.KEY__RESULT_TYPE__NOCOMPARISON,
|
| ])
|
|
|
| - builders = sorted(set(subdirA_builder_dicts.keys() +
|
| - subdirB_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))
|
| - # TODO(epoger): This will fail if we have results for this builder in
|
| - # subdirA but not subdirB (or vice versa).
|
| - subdirA_results = results.BaseComparisons.combine_subdicts(
|
| - subdirA_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
|
| - subdirB_results = results.BaseComparisons.combine_subdicts(
|
| - subdirB_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
|
| - image_names = sorted(set(subdirA_results.keys() +
|
| - subdirB_results.keys()))
|
| - for image_name in image_names:
|
| - # The image name may contain funny characters or be ridiculously long
|
| - # (see https://code.google.com/p/skia/issues/detail?id=2344#c10 ),
|
| - # so make sure we sanitize it before using it in a URL path.
|
| - #
|
| - # TODO(epoger): Rather than sanitizing/truncating the image name here,
|
| - # do it in render_pictures instead.
|
| - # Reason: we will need to be consistent in applying this rule, so that
|
| - # the process which uploads the files to GS using these paths will
|
| - # match the paths created by downstream processes.
|
| - # So, we should make render_pictures write out images to paths that are
|
| - # "ready to upload" to Google Storage, like gm does.
|
| - sanitized_test_name = DISALLOWED_FILEPATH_CHAR_REGEX.sub(
|
| - '_', image_name)[:30]
|
| -
|
| - subdirA_image_relative_url = (
|
| - results.BaseComparisons._create_relative_url(
|
| - hashtype_and_digest=subdirA_results.get(image_name),
|
| - test_name=sanitized_test_name))
|
| - subdirB_image_relative_url = (
|
| - results.BaseComparisons._create_relative_url(
|
| - hashtype_and_digest=subdirB_results.get(image_name),
|
| - test_name=sanitized_test_name))
|
| -
|
| - # If we have images for at least one of these two subdirs,
|
| - # add them to our list.
|
| - if subdirA_image_relative_url or subdirB_image_relative_url:
|
| - if subdirA_image_relative_url == subdirB_image_relative_url:
|
| - result_type = results.KEY__RESULT_TYPE__SUCCEEDED
|
| - elif not subdirA_image_relative_url:
|
| - result_type = results.KEY__RESULT_TYPE__NOCOMPARISON
|
| - elif not subdirB_image_relative_url:
|
| - result_type = results.KEY__RESULT_TYPE__NOCOMPARISON
|
| - else:
|
| - result_type = results.KEY__RESULT_TYPE__FAILED
|
| -
|
| - extra_columns_dict = {
|
| - results.KEY__EXTRACOLUMN__RESULT_TYPE: result_type,
|
| - results.KEY__EXTRACOLUMN__BUILDER: builder,
|
| - results.KEY__EXTRACOLUMN__TEST: image_name,
|
| - # 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__EXTRACOLUMN__CONFIG: 'TODO',
|
| - }
|
| -
|
| - try:
|
| - image_pair = imagepair.ImagePair(
|
| - image_diff_db=self._image_diff_db,
|
| - base_url=self._image_base_url,
|
| - imageA_relative_url=subdirA_image_relative_url,
|
| - imageB_relative_url=subdirB_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 image_name '
|
| - '"%s", builder "%s"' % (image_name, builder))
|
| + common_dict_paths = sorted(set(subdirA_dicts.keys() + subdirB_dicts.keys()))
|
| + num_common_dict_paths = len(common_dict_paths)
|
| + dict_num = 0
|
| + for dict_path in common_dict_paths:
|
| + dict_num += 1
|
| + logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' %
|
| + (dict_num, num_common_dict_paths, dict_path))
|
| + dictA = subdirA_dicts[dict_path]
|
| + dictB = subdirB_dicts[dict_path]
|
| + self._validate_dict_version(dictA)
|
| + self._validate_dict_version(dictB)
|
| + dictA_results = dictA[gm_json.JSONKEY_ACTUALRESULTS]
|
| + dictB_results = dictB[gm_json.JSONKEY_ACTUALRESULTS]
|
| + skp_names = sorted(set(dictA_results.keys() + dictB_results.keys()))
|
| + for skp_name in skp_names:
|
| + imagepairs_for_this_skp = []
|
| +
|
| + whole_image_A = RenderedPicturesComparisons.get_multilevel(
|
| + dictA_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE)
|
| + whole_image_B = RenderedPicturesComparisons.get_multilevel(
|
| + dictB_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE)
|
| + imagepairs_for_this_skp.append(self._create_image_pair(
|
| + test=skp_name, config=gm_json.JSONKEY_SOURCE_WHOLEIMAGE,
|
| + image_dict_A=whole_image_A, image_dict_B=whole_image_B))
|
| +
|
| + tiled_images_A = RenderedPicturesComparisons.get_multilevel(
|
| + dictA_results, skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES)
|
| + tiled_images_B = RenderedPicturesComparisons.get_multilevel(
|
| + dictB_results, skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES)
|
| + # TODO(epoger): Report an error if we find tiles for A but not B?
|
| + if tiled_images_A and tiled_images_B:
|
| + # TODO(epoger): Report an error if we find a different number of tiles
|
| + # for A and B?
|
| + num_tiles = len(tiled_images_A)
|
| + for tile_num in range(num_tiles):
|
| + imagepairs_for_this_skp.append(self._create_image_pair(
|
| + test=skp_name,
|
| + config='%s-%d' % (gm_json.JSONKEY_SOURCE_TILEDIMAGES, tile_num),
|
| + image_dict_A=tiled_images_A[tile_num],
|
| + image_dict_B=tiled_images_B[tile_num]))
|
| +
|
| + for imagepair in imagepairs_for_this_skp:
|
| + if imagepair:
|
| + all_image_pairs.add_image_pair(imagepair)
|
| + result_type = imagepair.extra_columns_dict\
|
| + [results.KEY__EXTRACOLUMN__RESULT_TYPE]
|
| + if result_type != results.KEY__RESULT_TYPE__SUCCEEDED:
|
| + failing_image_pairs.add_image_pair(imagepair)
|
|
|
| self._results = {
|
| results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(),
|
| results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(),
|
| }
|
|
|
| + 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
|
| + """
|
| + expected_header_type = 'ChecksummedImages'
|
| + expected_header_revision = 1
|
| +
|
| + 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, test, config, image_dict_A, image_dict_B):
|
| + """Creates an ImagePair object for this pair of images.
|
| +
|
| + Args:
|
| + test: string; name of the test
|
| + config: string; name of the config
|
| + 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
|
| +
|
| + 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],
|
| + 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 = {
|
| + results.KEY__EXTRACOLUMN__CONFIG: config,
|
| + results.KEY__EXTRACOLUMN__RESULT_TYPE: result_type,
|
| + results.KEY__EXTRACOLUMN__TEST: test,
|
| + # TODO(epoger): Right now, the client UI crashes if it receives
|
| + # results that do not include this column.
|
| + # Until we fix that, keep the client happy.
|
| + results.KEY__EXTRACOLUMN__BUILDER: 'TODO',
|
| + }
|
| +
|
| + try:
|
| + return imagepair.ImagePair(
|
| + image_diff_db=self._image_diff_db,
|
| + base_url=self._image_base_url,
|
| + imageA_relative_url=imageA_relative_url,
|
| + imageB_relative_url=imageB_relative_url,
|
| + extra_columns=extra_columns_dict)
|
| + except (KeyError, TypeError):
|
| + logging.exception(
|
| + 'got exception while creating ImagePair for'
|
| + ' test="%s", config="%s", urlPair=("%s","%s")' % (
|
| + test, config, imageA_relative_url, imageB_relative_url))
|
| + return None
|
| +
|
|
|
| # TODO(epoger): Add main() so this can be called by vm_run_skia_try.sh
|
|
|