blob: 73d0627ba076bcee10397ab9b4bd93147aa9c29a [file] [log] [blame]
#!/usr/bin/python
"""
Copyright 2014 Google Inc.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
Compare results of two render_pictures runs.
"""
# System-level imports
import logging
import os
import re
import time
# Imports from within Skia
import fix_pythonpath # must do this first
from pyutils import url_utils
import gm_json
import imagediffdb
import imagepair
import imagepairset
import results
# 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
DEFAULT_IMAGE_BASE_URL = 'http://chromium-skia-gm.commondatastorage.googleapis.com/render_pictures/images'
class RenderedPicturesComparisons(results.BaseComparisons):
"""Loads results from two different render_pictures runs into an ImagePairSet.
"""
def __init__(self, subdirs, actuals_root,
generated_images_root=results.DEFAULT_GENERATED_IMAGES_ROOT,
image_base_url=DEFAULT_IMAGE_BASE_URL,
diff_base_url=None):
"""
Args:
actuals_root: root directory containing all render_pictures-generated
JSON files
subdirs: (string, string) tuple; pair of subdirectories within
actuals_root to compare
generated_images_root: directory within which to create all pixel diffs;
if this directory does not yet exist, it will be created
image_base_url: URL under which all render_pictures result images can
be found; this will be used to read images for comparison within
this code, and included in the ImagePairSet so its consumers know
where to download the images from
diff_base_url: base URL within which the client should look for diff
images; if not specified, defaults to a "file:///" URL representation
of generated_images_root
"""
time_start = int(time.time())
self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root)
self._image_base_url = image_base_url
self._diff_base_url = (
diff_base_url or
url_utils.create_filepath_url(generated_images_root))
self._load_result_pairs(actuals_root, subdirs)
self._timestamp = int(time.time())
logging.info('Results complete; took %d seconds.' %
(self._timestamp - time_start))
def _load_result_pairs(self, actuals_root, subdirs):
"""Loads all JSON files found within two subdirs in actuals_root,
compares across those two subdirs, and stores the summary in self._results.
Args:
actuals_root: root directory containing all render_pictures-generated
JSON files
subdirs: (string, string) tuple; pair of subdirectories within
actuals_root to compare
"""
logging.info(
'Reading actual-results JSON files from %s subdirs within %s...' % (
subdirs, actuals_root))
subdirA, subdirB = subdirs
subdirA_dicts = self._read_dicts_from_root(
os.path.join(actuals_root, subdirA))
subdirB_dicts = self._read_dicts_from_root(
os.path.join(actuals_root, subdirB))
logging.info('Comparing subdirs %s and %s...' % (subdirA, subdirB))
all_image_pairs = imagepairset.ImagePairSet(
descriptions=subdirs,
diff_base_url=self._diff_base_url)
failing_image_pairs = imagepairset.ImagePairSet(
descriptions=subdirs,
diff_base_url=self._diff_base_url)
all_image_pairs.ensure_extra_column_values_in_summary(
column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[
results.KEY__RESULT_TYPE__FAILED,
results.KEY__RESULT_TYPE__NOCOMPARISON,
results.KEY__RESULT_TYPE__SUCCEEDED,
])
failing_image_pairs.ensure_extra_column_values_in_summary(
column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[
results.KEY__RESULT_TYPE__FAILED,
results.KEY__RESULT_TYPE__NOCOMPARISON,
])
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__EXTRACOLUMNS__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__EXTRACOLUMNS__CONFIG: config,
results.KEY__EXTRACOLUMNS__RESULT_TYPE: result_type,
results.KEY__EXTRACOLUMNS__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__EXTRACOLUMNS__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