diff options
-rw-r--r-- | gm/gm_json.py | 52 | ||||
-rw-r--r-- | gm/rebaseline_server/column.py | 1 | ||||
-rw-r--r-- | gm/rebaseline_server/imagediffdb.py | 29 | ||||
-rw-r--r-- | gm/rebaseline_server/imagepair.py | 11 | ||||
-rwxr-xr-x | gm/rebaseline_server/imagepair_test.py | 2 | ||||
-rw-r--r-- | gm/rebaseline_server/imagepairset.py | 28 | ||||
-rwxr-xr-x | gm/rebaseline_server/imagepairset_test.py | 2 | ||||
-rwxr-xr-x | gm/rebaseline_server/results.py | 354 | ||||
-rwxr-xr-x | gm/rebaseline_server/results_test.py | 5 | ||||
-rwxr-xr-x | gm/rebaseline_server/server.py | 74 | ||||
-rw-r--r-- | gm/rebaseline_server/static/constants.js | 69 | ||||
-rw-r--r-- | gm/rebaseline_server/static/loader.js | 317 | ||||
-rw-r--r-- | gm/rebaseline_server/static/view.html | 166 | ||||
-rw-r--r-- | gm/rebaseline_server/testdata/outputs/actual/results_test.ResultsTest.test_gm/gm.json | 526 | ||||
-rw-r--r-- | gm/rebaseline_server/testdata/outputs/expected/results_test.ResultsTest.test_gm/gm.json | 935 |
15 files changed, 1091 insertions, 1480 deletions
diff --git a/gm/gm_json.py b/gm/gm_json.py index 1aac6632f5..6f4b324d46 100644 --- a/gm/gm_json.py +++ b/gm/gm_json.py @@ -15,12 +15,14 @@ __author__ = 'Elliot Poger' import io import json import os +import posixpath +import re # Key strings used in GM results JSON files (both expected-results.json and # actual-results.json). # -# These constants must be kept in sync with the kJsonKey_ constants in +# NOTE: These constants must be kept in sync with the kJsonKey_ constants in # gm_expectations.cpp ! @@ -93,6 +95,14 @@ SKIMAGE_EXPECTATIONS_ROOT = os.path.join('expectations', 'skimage') # Pattern used to assemble each image's filename IMAGE_FILENAME_PATTERN = '(.+)_(.+)\.png' # matches (testname, config) +# Pattern used to create image URLs, relative to some base URL. +GM_RELATIVE_URL_FORMATTER = '%s/%s/%s.png' # pass in (hash_type, test_name, + # hash_digest) +GM_RELATIVE_URL_PATTERN = '(.+)/(.+)/(.+).png' # matches (hash_type, test_name, + # hash_digest) +GM_RELATIVE_URL_RE = re.compile(GM_RELATIVE_URL_PATTERN) + + def CreateGmActualUrl(test_name, hash_type, hash_digest, gm_actuals_root_url=GM_ACTUALS_ROOT_HTTP_URL): """Return the URL we can use to download a particular version of @@ -104,10 +114,40 @@ def CreateGmActualUrl(test_name, hash_type, hash_digest, hash_digest: the hash digest of the image to retrieve gm_actuals_root_url: root url where actual images are stored """ - # TODO(epoger): Maybe use url_or_path.join() so that, for testing, this can - # return either a URL or a local filepath? - return '%s/%s/%s/%s.png' % (gm_actuals_root_url, hash_type, test_name, - hash_digest) + return posixpath.join( + gm_actuals_root_url, CreateGmRelativeUrl( + test_name=test_name, hash_type=hash_type, hash_digest=hash_digest)) + + +def CreateGmRelativeUrl(test_name, hash_type, hash_digest): + """Returns a relative URL pointing at a test result's image. + + Returns the URL we can use to download a particular version of + the actually-generated image for this particular GM test, + relative to the URL root. + + Args: + test_name: name of the test, e.g. 'perlinnoise' + hash_type: string indicating the hash type used to generate hash_digest, + e.g. JSONKEY_HASHTYPE_BITMAP_64BITMD5 + hash_digest: the hash digest of the image to retrieve + """ + return GM_RELATIVE_URL_FORMATTER % (hash_type, test_name, hash_digest) + + +def SplitGmRelativeUrl(url): + """Splits the relative URL into (test_name, hash_type, hash_digest) tuple. + + This is the inverse of CreateGmRelativeUrl(). + + Args: + url: a URL generated with CreateGmRelativeUrl(). + + Returns: (test_name, hash_type, hash_digest) tuple. + """ + hash_type, test_name, hash_digest = GM_RELATIVE_URL_RE.match(url).groups() + return (test_name, hash_type, hash_digest) + def LoadFromString(file_contents): """Loads the JSON summary written out by the GM tool. @@ -119,6 +159,7 @@ def LoadFromString(file_contents): json_dict = json.loads(file_contents) return json_dict + def LoadFromFile(file_path): """Loads the JSON summary written out by the GM tool. Returns a dictionary keyed by the values listed as JSONKEY_ constants @@ -126,6 +167,7 @@ def LoadFromFile(file_path): file_contents = open(file_path, 'r').read() return LoadFromString(file_contents) + def WriteToFile(json_dict, file_path): """Writes the JSON summary in json_dict out to file_path. diff --git a/gm/rebaseline_server/column.py b/gm/rebaseline_server/column.py index 7bce15a250..d8d119d130 100644 --- a/gm/rebaseline_server/column.py +++ b/gm/rebaseline_server/column.py @@ -10,6 +10,7 @@ ColumnHeaderFactory class (see class docstring for details) """ # Keys used within dictionary representation of each column header. +# NOTE: Keep these in sync with static/constants.js KEY__HEADER_TEXT = 'headerText' KEY__HEADER_URL = 'headerUrl' KEY__IS_FILTERABLE = 'isFilterable' diff --git a/gm/rebaseline_server/imagediffdb.py b/gm/rebaseline_server/imagediffdb.py index 8cec46bc94..f3347f75af 100644 --- a/gm/rebaseline_server/imagediffdb.py +++ b/gm/rebaseline_server/imagediffdb.py @@ -43,6 +43,14 @@ WHITEDIFFS_SUBDIR = 'whitediffs' VALUES_PER_BAND = 256 +# Keys used within DiffRecord dictionary representations. +# NOTE: Keep these in sync with static/constants.js +KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL = 'maxDiffPerChannel' +KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS = 'numDifferingPixels' +KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS = 'percentDifferingPixels' +KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF = 'perceptualDifference' +KEY__DIFFERENCE_DATA__WEIGHTED_DIFF = 'weightedDiffMeasure' + class DiffRecord(object): """ Record of differences between two images. """ @@ -186,10 +194,12 @@ class DiffRecord(object): """Returns a dictionary representation of this DiffRecord, as needed when constructing the JSON representation.""" return { - 'numDifferingPixels': self._num_pixels_differing, - 'percentDifferingPixels': self.get_percent_pixels_differing(), - 'weightedDiffMeasure': self.get_weighted_diff_measure(), - 'maxDiffPerChannel': self._max_diff_per_channel, + KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS: self._num_pixels_differing, + KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS: + self.get_percent_pixels_differing(), + KEY__DIFFERENCE_DATA__WEIGHTED_DIFF: self.get_weighted_diff_measure(), + KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL: self._max_diff_per_channel, + KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF: self._perceptual_difference, } @@ -286,6 +296,7 @@ def _calculate_weighted_diff_metric(histogram, num_pixels): total_diff += histogram[index] * (index % VALUES_PER_BAND)**2 return float(100 * total_diff) / max_diff + def _max_per_band(histogram): """Given the histogram of an image, return the maximum value of each band (a.k.a. "color channel", such as R/G/B) across the entire image. @@ -312,6 +323,7 @@ def _max_per_band(histogram): break return max_per_band + def _generate_image_diff(image1, image2): """Wrapper for ImageChops.difference(image1, image2) that will handle some errors automatically, or at least yield more useful error messages. @@ -333,6 +345,7 @@ def _generate_image_diff(image1, image2): repr(image1), repr(image2))) raise + def _download_and_open_image(local_filepath, url): """Open the image at local_filepath; if there is no file at that path, download it from url to that path and then open it. @@ -350,6 +363,7 @@ def _download_and_open_image(local_filepath, url): shutil.copyfileobj(fsrc=url_handle, fdst=file_handle) return _open_image(local_filepath) + def _open_image(filepath): """Wrapper for Image.open(filepath) that yields more useful error messages. @@ -364,6 +378,7 @@ def _open_image(filepath): logging.error('IOError loading image file %s' % filepath) raise + def _save_image(image, filepath, format='PNG'): """Write an image to disk, creating any intermediate directories as needed. @@ -376,6 +391,7 @@ def _save_image(image, filepath, format='PNG'): _mkdir_unless_exists(os.path.dirname(filepath)) image.save(filepath, format) + def _mkdir_unless_exists(path): """Unless path refers to an already-existing directory, create it. @@ -385,6 +401,7 @@ def _mkdir_unless_exists(path): if not os.path.isdir(path): os.makedirs(path) + def _sanitize_locator(locator): """Returns a sanitized version of a locator (one in which we know none of the characters will have special meaning in filenames). @@ -394,10 +411,14 @@ def _sanitize_locator(locator): """ return DISALLOWED_FILEPATH_CHAR_REGEX.sub('_', str(locator)) + def _get_difference_locator(expected_image_locator, actual_image_locator): """Returns the locator string used to look up the diffs between expected_image and actual_image. + We must keep this function in sync with getImageDiffRelativeUrl() in + static/loader.js + Args: expected_image_locator: locator string pointing at expected image actual_image_locator: locator string pointing at actual image diff --git a/gm/rebaseline_server/imagepair.py b/gm/rebaseline_server/imagepair.py index bba36fa8c4..f02704e082 100644 --- a/gm/rebaseline_server/imagepair.py +++ b/gm/rebaseline_server/imagepair.py @@ -12,6 +12,7 @@ ImagePair class (see class docstring for details) import posixpath # Keys used within ImagePair dictionary representations. +# NOTE: Keep these in sync with static/constants.js KEY__DIFFERENCE_DATA = 'differenceData' KEY__EXPECTATIONS_DATA = 'expectations' KEY__EXTRA_COLUMN_VALUES = 'extraColumns' @@ -31,8 +32,10 @@ class ImagePair(object): Args: image_diff_db: ImageDiffDB instance we use to generate/store image diffs base_url: base of all image URLs - imageA_relative_url: URL pointing at an image, relative to base_url - imageB_relative_url: URL pointing at an image, relative to base_url + imageA_relative_url: string; URL pointing at an image, relative to + base_url; or None, if this image is missing + imageB_relative_url: string; URL pointing at an image, relative to + base_url; or None, if this image is missing expectations: optional dictionary containing expectations-specific metadata (ignore-failure, bug numbers, etc.) extra_columns: optional dictionary containing more metadata (test name, @@ -43,7 +46,9 @@ class ImagePair(object): self.imageB_relative_url = imageB_relative_url self.expectations_dict = expectations self.extra_columns_dict = extra_columns - if imageA_relative_url == imageB_relative_url: + if not imageA_relative_url or not imageB_relative_url: + self.diff_record = None + elif imageA_relative_url == imageB_relative_url: self.diff_record = None else: # TODO(epoger): Rather than blocking until image_diff_db can read in diff --git a/gm/rebaseline_server/imagepair_test.py b/gm/rebaseline_server/imagepair_test.py index d29438e530..b2cae31a51 100755 --- a/gm/rebaseline_server/imagepair_test.py +++ b/gm/rebaseline_server/imagepair_test.py @@ -87,6 +87,7 @@ class ImagePairTest(unittest.TestCase): 'maxDiffPerChannel': [255, 255, 247], 'numDifferingPixels': 662, 'percentDifferingPixels': 0.0662, + 'perceptualDifference': 0.06620000000000914, 'weightedDiffMeasure': 0.01127756555171088, }, 'imageAUrl': 'arcofzorro/16206093933823793653.png', @@ -113,6 +114,7 @@ class ImagePairTest(unittest.TestCase): 'maxDiffPerChannel': [255, 0, 255], 'numDifferingPixels': 102400, 'percentDifferingPixels': 100.00, + 'perceptualDifference': 100.00, 'weightedDiffMeasure': 66.66666666666667, }, 'expectations': { diff --git a/gm/rebaseline_server/imagepairset.py b/gm/rebaseline_server/imagepairset.py index 2e173f537f..26c833e1b0 100644 --- a/gm/rebaseline_server/imagepairset.py +++ b/gm/rebaseline_server/imagepairset.py @@ -12,7 +12,8 @@ ImagePairSet class; see its docstring below. import column # Keys used within dictionary representation of ImagePairSet. -KEY__COLUMNHEADERS = 'columnHeaders' +# NOTE: Keep these in sync with static/constants.js +KEY__EXTRACOLUMNHEADERS = 'extraColumnHeaders' KEY__IMAGEPAIRS = 'imagePairs' KEY__IMAGESETS = 'imageSets' KEY__IMAGESETS__BASE_URL = 'baseUrl' @@ -55,7 +56,7 @@ class ImagePairSet(object): extra_columns_dict = image_pair.extra_columns_dict if extra_columns_dict: for column_id, value in extra_columns_dict.iteritems(): - self._add_extra_column_entry(column_id, value) + self._add_extra_column_value_to_summary(column_id, value) def set_column_header_factory(self, column_id, column_header_factory): """Overrides the default settings for one of the extraColumn headers. @@ -80,19 +81,36 @@ class ImagePairSet(object): self._column_header_factories[column_id] = column_header_factory return column_header_factory - def _add_extra_column_entry(self, column_id, value): + def ensure_extra_column_values_in_summary(self, column_id, values): + """Ensure this column_id/value pair is part of the extraColumns summary. + + Args: + column_id: string; unique ID of this column + value: string; a possible value for this column + """ + for value in values: + self._add_extra_column_value_to_summary( + column_id=column_id, value=value, addend=0) + + def _add_extra_column_value_to_summary(self, column_id, value, addend=1): """Records one column_id/value extraColumns pair found within an ImagePair. We use this information to generate tallies within the column header (how many instances we saw of a particular value, within a particular extraColumn). + + Args: + column_id: string; unique ID of this column (must match a key within + an ImagePair's extra_columns dictionary) + value: string; a possible value for this column + addend: integer; how many instances to add to the tally """ known_values_for_column = self._extra_column_tallies.get(column_id, None) if not known_values_for_column: known_values_for_column = {} self._extra_column_tallies[column_id] = known_values_for_column instances_of_this_value = known_values_for_column.get(value, 0) - instances_of_this_value += 1 + instances_of_this_value += addend known_values_for_column[value] = instances_of_this_value def _column_headers_as_dict(self): @@ -110,7 +128,7 @@ class ImagePairSet(object): Uses the KEY__* constants as keys. """ return { - KEY__COLUMNHEADERS: self._column_headers_as_dict(), + KEY__EXTRACOLUMNHEADERS: self._column_headers_as_dict(), KEY__IMAGEPAIRS: self._image_pair_dicts, KEY__IMAGESETS: [{ KEY__IMAGESETS__BASE_URL: self._base_url, diff --git a/gm/rebaseline_server/imagepairset_test.py b/gm/rebaseline_server/imagepairset_test.py index 8f1edfce40..815fd74056 100755 --- a/gm/rebaseline_server/imagepairset_test.py +++ b/gm/rebaseline_server/imagepairset_test.py @@ -85,7 +85,7 @@ class ImagePairSetTest(unittest.TestCase): MockImagePair(base_url=BASE_URL_1, dict_to_return=IMAGEPAIR_3_AS_DICT), ] expected_imageset_dict = { - 'columnHeaders': { + 'extraColumnHeaders': { 'builder': { 'headerText': 'builder', 'isFilterable': True, diff --git a/gm/rebaseline_server/results.py b/gm/rebaseline_server/results.py index 3b57bc1e20..c7915f261d 100755 --- a/gm/rebaseline_server/results.py +++ b/gm/rebaseline_server/results.py @@ -32,27 +32,43 @@ if GM_DIRECTORY not in sys.path: sys.path.append(GM_DIRECTORY) import gm_json import imagediffdb +import imagepair +import imagepairset + +# Keys used to link an image to a particular GM test. +# NOTE: Keep these in sync with static/constants.js +KEY__EXPECTATIONS__BUGS = gm_json.JSONKEY_EXPECTEDRESULTS_BUGS +KEY__EXPECTATIONS__IGNOREFAILURE = gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE +KEY__EXPECTATIONS__REVIEWED = gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED +KEY__EXTRACOLUMN__BUILDER = 'builder' +KEY__EXTRACOLUMN__CONFIG = 'config' +KEY__EXTRACOLUMN__RESULT_TYPE = 'resultType' +KEY__EXTRACOLUMN__TEST = 'test' +KEY__HEADER__RESULTS_ALL = 'all' +KEY__HEADER__RESULTS_FAILURES = 'failures' +KEY__NEW_IMAGE_URL = 'newImageUrl' +KEY__RESULT_TYPE__FAILED = gm_json.JSONKEY_ACTUALRESULTS_FAILED +KEY__RESULT_TYPE__FAILUREIGNORED = gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED +KEY__RESULT_TYPE__NOCOMPARISON = gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON +KEY__RESULT_TYPE__SUCCEEDED = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED + +EXPECTATION_FIELDS_PASSED_THRU_VERBATIM = [ + KEY__EXPECTATIONS__BUGS, + KEY__EXPECTATIONS__IGNOREFAILURE, + KEY__EXPECTATIONS__REVIEWED, +] IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) -FIELDS_PASSED_THRU_VERBATIM = [ - gm_json.JSONKEY_EXPECTEDRESULTS_BUGS, - gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE, - gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED, -] -CATEGORIES_TO_SUMMARIZE = [ - 'builder', 'test', 'config', 'resultType', - gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE, - gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED, -] +IMAGEPAIR_SET_DESCRIPTIONS = ('expected image', 'actual image') -RESULTS_ALL = 'all' -RESULTS_FAILURES = 'failures' class Results(object): - """ Loads actual and expected results from all builders, supplying combined - reports as requested. + """ Loads actual and expected GM results into an ImagePairSet. + + Loads actual and expected results from all builders, except for those skipped + by _ignore_builder(). Once this object has been constructed, the results (in self._results[]) are immutable. If you want to update the results based on updated JSON @@ -94,14 +110,17 @@ class Results(object): [ { - 'builder': 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug', - 'test': 'bigmatrix', - 'config': '8888', - 'expectedHashType': 'bitmap-64bitMD5', - 'expectedHashDigest': '10894408024079689926', - 'bugs': [123, 456], - 'ignore-failure': false, - 'reviewed-by-human': true, + imagepair.KEY__EXPECTATIONS_DATA: { + KEY__EXPECTATIONS__BUGS: [123, 456], + KEY__EXPECTATIONS__IGNOREFAILURE: false, + KEY__EXPECTATIONS__REVIEWED: true, + }, + imagepair.KEY__EXTRA_COLUMN_VALUES: { + KEY__EXTRACOLUMN__BUILDER: 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug', + KEY__EXTRACOLUMN__CONFIG: '8888', + KEY__EXTRACOLUMN__TEST: 'bigmatrix', + }, + KEY__NEW_IMAGE_URL: 'bitmap-64bitMD5/bigmatrix/10894408024079689926.png', }, ... ] @@ -109,18 +128,21 @@ class Results(object): """ expected_builder_dicts = Results._read_dicts_from_root(self._expected_root) for mod in modifications: - image_name = IMAGE_FILENAME_FORMATTER % (mod['test'], mod['config']) - # TODO(epoger): assumes a single allowed digest per test - allowed_digests = [[mod['expectedHashType'], - int(mod['expectedHashDigest'])]] + image_name = IMAGE_FILENAME_FORMATTER % ( + mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__TEST], + mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__CONFIG]) + _, hash_type, hash_digest = gm_json.SplitGmRelativeUrl( + mod[KEY__NEW_IMAGE_URL]) + allowed_digests = [[hash_type, int(hash_digest)]] new_expectations = { gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS: allowed_digests, } - for field in FIELDS_PASSED_THRU_VERBATIM: - value = mod.get(field) + for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM: + value = mod[imagepair.KEY__EXPECTATIONS_DATA].get(field) if value is not None: new_expectations[field] = value - builder_dict = expected_builder_dicts[mod['builder']] + builder_dict = expected_builder_dicts[ + mod[imagepair.KEY__EXTRA_COLUMN_VALUES][KEY__EXTRACOLUMN__BUILDER]] builder_expectations = builder_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS) if not builder_expectations: builder_expectations = {} @@ -135,47 +157,7 @@ class Results(object): type: string describing which types of results to include; must be one of the RESULTS_* constants - Results are returned as a dictionary in this form: - - { - 'categories': # dictionary of categories listed in - # CATEGORIES_TO_SUMMARIZE, with the number of times - # each value appears within its category - { - 'resultType': # category name - { - 'failed': 29, # category value and total number found of that value - 'failure-ignored': 948, - 'no-comparison': 4502, - 'succeeded': 38609, - }, - 'builder': - { - 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug': 1286, - 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Release': 1134, - ... - }, - ... # other categories from CATEGORIES_TO_SUMMARIZE - }, # end of 'categories' dictionary - - 'testData': # list of test results, with a dictionary for each - [ - { - 'resultType': 'failed', - 'builder': 'Test-Mac10.6-MacMini4.1-GeForce320M-x86-Debug', - 'test': 'bigmatrix', - 'config': '8888', - 'expectedHashType': 'bitmap-64bitMD5', - 'expectedHashDigest': '10894408024079689926', - 'actualHashType': 'bitmap-64bitMD5', - 'actualHashDigest': '2409857384569', - 'bugs': [123, 456], - 'ignore-failure': false, - 'reviewed-by-human': true, - }, - ... - ], # end of 'testData' list - } + Results are returned in a dictionary as output by ImagePairSet.as_dict(). """ return self._results[type] @@ -227,6 +209,24 @@ class Results(object): return meta_dict @staticmethod + def _create_relative_url(hashtype_and_digest, test_name): + """Returns the URL for this image, relative to GM_ACTUALS_ROOT_HTTP_URL. + + If we don't have a record of this image, returns None. + + Args: + hashtype_and_digest: (hash_type, hash_digest) tuple, or None if we + don't have a record of this image + test_name: string; name of the GM test that created this image + """ + if not hashtype_and_digest: + return None + return gm_json.CreateGmRelativeUrl( + test_name=test_name, + hash_type=hashtype_and_digest[0], + hash_digest=hashtype_and_digest[1]) + + @staticmethod def _write_dicts_to_root(meta_dict, root, pattern='*.json'): """Write all per-builder dictionaries within meta_dict to files under the root path. @@ -272,36 +272,6 @@ class Results(object): 'for builders %s' % ( expected_builders_written, actual_builders_written)) - def _generate_pixel_diffs_if_needed(self, test, expected_image, actual_image): - """If expected_image and actual_image both exist but are different, - add the image pair to self._image_diff_db and generate pixel diffs. - - Args: - test: string; name of test - expected_image: (hashType, hashDigest) tuple describing the expected image - actual_image: (hashType, hashDigest) tuple describing the actual image - """ - if expected_image == actual_image: - return - - (expected_hashtype, expected_hashdigest) = expected_image - (actual_hashtype, actual_hashdigest) = actual_image - if None in [expected_hashtype, expected_hashdigest, - actual_hashtype, actual_hashdigest]: - return - - expected_url = gm_json.CreateGmActualUrl( - test_name=test, hash_type=expected_hashtype, - hash_digest=expected_hashdigest) - actual_url = gm_json.CreateGmActualUrl( - test_name=test, hash_type=actual_hashtype, - hash_digest=actual_hashdigest) - self._image_diff_db.add_image_pair( - expected_image_locator=expected_hashdigest, - expected_image_url=expected_url, - actual_image_locator=actual_hashdigest, - actual_image_url=actual_url) - def _load_actual_and_expected(self): """Loads the results of all tests, across all builders (based on the files within self._actuals_root and self._expected_root), @@ -314,25 +284,23 @@ class Results(object): self._expected_root) expected_builder_dicts = Results._read_dicts_from_root(self._expected_root) - categories_all = {} - categories_failures = {} + all_image_pairs = imagepairset.ImagePairSet(IMAGEPAIR_SET_DESCRIPTIONS) + failing_image_pairs = imagepairset.ImagePairSet(IMAGEPAIR_SET_DESCRIPTIONS) - Results._ensure_included_in_category_dict(categories_all, - 'resultType', [ - gm_json.JSONKEY_ACTUALRESULTS_FAILED, - gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED, - gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, - gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED, + all_image_pairs.ensure_extra_column_values_in_summary( + column_id=KEY__EXTRACOLUMN__RESULT_TYPE, values=[ + KEY__RESULT_TYPE__FAILED, + KEY__RESULT_TYPE__FAILUREIGNORED, + KEY__RESULT_TYPE__NOCOMPARISON, + KEY__RESULT_TYPE__SUCCEEDED, ]) - Results._ensure_included_in_category_dict(categories_failures, - 'resultType', [ - gm_json.JSONKEY_ACTUALRESULTS_FAILED, - gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED, - gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON, + failing_image_pairs.ensure_extra_column_values_in_summary( + column_id=KEY__EXTRACOLUMN__RESULT_TYPE, values=[ + KEY__RESULT_TYPE__FAILED, + KEY__RESULT_TYPE__FAILUREIGNORED, + KEY__RESULT_TYPE__NOCOMPARISON, ]) - data_all = [] - data_failures = [] builders = sorted(actual_builder_dicts.keys()) num_builders = len(builders) builder_num = 0 @@ -347,19 +315,30 @@ class Results(object): if not results_of_this_type: continue for image_name in sorted(results_of_this_type.keys()): - actual_image = results_of_this_type[image_name] + (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() + actual_image_relative_url = Results._create_relative_url( + hashtype_and_digest=results_of_this_type[image_name], + test_name=test) # Default empty expectations; overwrite these if we find any real ones expectations_per_test = None - expected_image = [None, None] + expected_image_relative_url = None + expectations_dict = None try: expectations_per_test = ( expected_builder_dicts [builder][gm_json.JSONKEY_EXPECTEDRESULTS][image_name]) - # TODO(epoger): assumes a single allowed digest per test - expected_image = ( + # TODO(epoger): assumes a single allowed digest per test, which is + # fine; see https://code.google.com/p/skia/issues/detail?id=1787 + expected_image_hashtype_and_digest = ( expectations_per_test [gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0]) + expected_image_relative_url = Results._create_relative_url( + hashtype_and_digest=expected_image_hashtype_and_digest, + test_name=test) + expectations_dict = {} + for field in EXPECTATION_FIELDS_PASSED_THRU_VERBATIM: + expectations_dict[field] = expectations_per_test.get(field) except (KeyError, TypeError): # There are several cases in which we would expect to find # no expectations for a given test: @@ -380,11 +359,11 @@ class Results(object): # Log other types, because they are rare and we should know about # them, but don't throw an exception, because we need to keep our # tools working in the meanwhile! - if result_type != gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON: + if result_type != KEY__RESULT_TYPE__NOCOMPARISON: logging.warning('No expectations found for test: %s' % { - 'builder': builder, + KEY__EXTRACOLUMN__BUILDER: builder, + KEY__EXTRACOLUMN__RESULT_TYPE: result_type, 'image_name': image_name, - 'result_type': result_type, }) # If this test was recently rebaselined, it will remain in @@ -400,122 +379,32 @@ class Results(object): # categories recorded within the gm_actuals AT ALL, and # instead evaluate the result_type ourselves based on what # we see in expectations vs actual checksum? - if expected_image == actual_image: - updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED + if expected_image_relative_url == actual_image_relative_url: + updated_result_type = KEY__RESULT_TYPE__SUCCEEDED else: updated_result_type = result_type - - (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() - self._generate_pixel_diffs_if_needed( - test=test, expected_image=expected_image, - actual_image=actual_image) - results_for_this_test = { - 'resultType': updated_result_type, - 'builder': builder, - 'test': test, - 'config': config, - 'actualHashType': actual_image[0], - 'actualHashDigest': str(actual_image[1]), - 'expectedHashType': expected_image[0], - 'expectedHashDigest': str(expected_image[1]), - - # FIELDS_PASSED_THRU_VERBATIM that may be overwritten below... - gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE: False, + extra_columns_dict = { + KEY__EXTRACOLUMN__RESULT_TYPE: updated_result_type, + KEY__EXTRACOLUMN__BUILDER: builder, + KEY__EXTRACOLUMN__TEST: test, + KEY__EXTRACOLUMN__CONFIG: config, } - if expectations_per_test: - for field in FIELDS_PASSED_THRU_VERBATIM: - results_for_this_test[field] = expectations_per_test.get(field) - - if updated_result_type == gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON: - pass # no diff record to calculate at all - elif updated_result_type == gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED: - results_for_this_test['numDifferingPixels'] = 0 - results_for_this_test['percentDifferingPixels'] = 0 - results_for_this_test['weightedDiffMeasure'] = 0 - results_for_this_test['perceptualDifference'] = 0 - results_for_this_test['maxDiffPerChannel'] = 0 - else: - try: - diff_record = self._image_diff_db.get_diff_record( - expected_image_locator=expected_image[1], - actual_image_locator=actual_image[1]) - results_for_this_test['numDifferingPixels'] = ( - diff_record.get_num_pixels_differing()) - results_for_this_test['percentDifferingPixels'] = ( - diff_record.get_percent_pixels_differing()) - results_for_this_test['weightedDiffMeasure'] = ( - diff_record.get_weighted_diff_measure()) - results_for_this_test['perceptualDifference'] = ( - diff_record.get_perceptual_difference()) - results_for_this_test['maxDiffPerChannel'] = ( - diff_record.get_max_diff_per_channel()) - except KeyError: - logging.warning('unable to find diff_record for ("%s", "%s")' % - (expected_image[1], actual_image[1])) - pass - - Results._add_to_category_dict(categories_all, results_for_this_test) - data_all.append(results_for_this_test) - - # TODO(epoger): In effect, we have a list of resultTypes that we - # include in the different result lists (data_all and data_failures). - # This same list should be used by the calls to - # Results._ensure_included_in_category_dict() earlier on. - if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED: - Results._add_to_category_dict(categories_failures, - results_for_this_test) - data_failures.append(results_for_this_test) + image_pair = imagepair.ImagePair( + image_diff_db=self._image_diff_db, + base_url=gm_json.GM_ACTUALS_ROOT_HTTP_URL, + imageA_relative_url=expected_image_relative_url, + imageB_relative_url=actual_image_relative_url, + expectations=expectations_dict, + extra_columns=extra_columns_dict) + all_image_pairs.add_image_pair(image_pair) + if updated_result_type != KEY__RESULT_TYPE__SUCCEEDED: + failing_image_pairs.add_image_pair(image_pair) self._results = { - RESULTS_ALL: - {'categories': categories_all, 'testData': data_all}, - RESULTS_FAILURES: - {'categories': categories_failures, 'testData': data_failures}, + KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(), + KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(), } - @staticmethod - def _add_to_category_dict(category_dict, test_results): - """Add test_results to the category dictionary we are building. - (See documentation of self.get_results_of_type() for the format of this - dictionary.) - - Args: - category_dict: category dict-of-dicts to add to; modify this in-place - test_results: test data with which to update category_list, in a dict: - { - 'category_name': 'category_value', - 'category_name': 'category_value', - ... - } - """ - for category in CATEGORIES_TO_SUMMARIZE: - category_value = test_results.get(category) - if not category_dict.get(category): - category_dict[category] = {} - if not category_dict[category].get(category_value): - category_dict[category][category_value] = 0 - category_dict[category][category_value] += 1 - - @staticmethod - def _ensure_included_in_category_dict(category_dict, - category_name, category_values): - """Ensure that the category name/value pairs are included in category_dict, - even if there aren't any results with that name/value pair. - (See documentation of self.get_results_of_type() for the format of this - dictionary.) - - Args: - category_dict: category dict-of-dicts to modify - category_name: category name, as a string - category_values: list of values we want to make sure are represented - for this category - """ - if not category_dict.get(category_name): - category_dict[category_name] = {} - for category_value in category_values: - if not category_dict[category_name].get(category_value): - category_dict[category_name][category_value] = 0 - def main(): logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', @@ -538,7 +427,8 @@ def main(): results = Results(actuals_root=args.actuals, expected_root=args.expectations, generated_images_root=args.workdir) - gm_json.WriteToFile(results.get_results_of_type(RESULTS_ALL), args.outfile) + gm_json.WriteToFile(results.get_results_of_type(KEY__HEADER__RESULTS_ALL), + args.outfile) if __name__ == '__main__': diff --git a/gm/rebaseline_server/results_test.py b/gm/rebaseline_server/results_test.py index 482fd9f7cd..958222c516 100755 --- a/gm/rebaseline_server/results_test.py +++ b/gm/rebaseline_server/results_test.py @@ -35,8 +35,9 @@ class ResultsTest(base_unittest.TestCase): actuals_root=os.path.join(self._input_dir, 'gm-actuals'), expected_root=os.path.join(self._input_dir, 'gm-expectations'), generated_images_root=self._temp_dir) - gm_json.WriteToFile(results_obj.get_results_of_type(results.RESULTS_ALL), - os.path.join(self._output_dir_actual, 'gm.json')) + gm_json.WriteToFile( + results_obj.get_results_of_type(results.KEY__HEADER__RESULTS_ALL), + os.path.join(self._output_dir_actual, 'gm.json')) def main(): diff --git a/gm/rebaseline_server/server.py b/gm/rebaseline_server/server.py index 0906c4e7ee..bbce2d0a7d 100755 --- a/gm/rebaseline_server/server.py +++ b/gm/rebaseline_server/server.py @@ -40,6 +40,7 @@ if TOOLS_DIRECTORY not in sys.path: import svn # Imports from local dir +import imagepairset import results ACTUALS_SVN_REPO = 'http://skia-autogen.googlecode.com/svn/gm-actual' @@ -59,6 +60,20 @@ MIME_TYPE_MAP = {'': 'application/octet-stream', 'json': 'application/json' } +# Keys that server.py uses to create the toplevel content header. +# NOTE: Keep these in sync with static/constants.js +KEY__EDITS__MODIFICATIONS = 'modifications' +KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' +KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' +KEY__HEADER = 'header' +KEY__HEADER__DATAHASH = 'dataHash' +KEY__HEADER__IS_EDITABLE = 'isEditable' +KEY__HEADER__IS_EXPORTED = 'isExported' +KEY__HEADER__IS_STILL_LOADING = 'resultsStillLoading' +KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE = 'timeNextUpdateAvailable' +KEY__HEADER__TIME_UPDATED = 'timeUpdated' +KEY__HEADER__TYPE = 'type' + DEFAULT_ACTUALS_DIR = '.gm-actuals' DEFAULT_PORT = 8888 @@ -313,10 +328,11 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: now = int(time.time()) response_dict = { - 'header': { - 'resultsStillLoading': True, - 'timeUpdated': now, - 'timeNextUpdateAvailable': now + RELOAD_INTERVAL_UNTIL_READY, + KEY__HEADER: { + KEY__HEADER__IS_STILL_LOADING: True, + KEY__HEADER__TIME_UPDATED: now, + KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: + now + RELOAD_INTERVAL_UNTIL_READY, }, } self.send_json_dict(response_dict) @@ -332,7 +348,7 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ response_dict = results_obj.get_results_of_type(type) time_updated = results_obj.get_timestamp() - response_dict['header'] = { + response_dict[KEY__HEADER] = { # Timestamps: # 1. when this data was last updated # 2. when the caller should check back for new data (if ever) @@ -340,23 +356,25 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): # We only return these timestamps if the --reload argument was passed; # otherwise, we have no idea when the expectations were last updated # (we allow the user to maintain her own expectations as she sees fit). - 'timeUpdated': time_updated if _SERVER.reload_seconds else None, - 'timeNextUpdateAvailable': ( + KEY__HEADER__TIME_UPDATED: + time_updated if _SERVER.reload_seconds else None, + KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: (time_updated+_SERVER.reload_seconds) if _SERVER.reload_seconds - else None), + else None, # The type we passed to get_results_of_type() - 'type': type, + KEY__HEADER__TYPE: type, - # Hash of testData, which the client must return with any edits-- + # Hash of dataset, which the client must return with any edits-- # this ensures that the edits were made to a particular dataset. - 'dataHash': str(hash(repr(response_dict['testData']))), + KEY__HEADER__DATAHASH: str(hash(repr( + response_dict[imagepairset.KEY__IMAGEPAIRS]))), # Whether the server will accept edits back. - 'isEditable': _SERVER.is_editable, + KEY__HEADER__IS_EDITABLE: _SERVER.is_editable, # Whether the service is accessible from other hosts. - 'isExported': _SERVER.is_exported, + KEY__HEADER__IS_EXPORTED: _SERVER.is_exported, } return response_dict @@ -406,19 +424,15 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): format: { - 'oldResultsType': 'all', # type of results that the client loaded - # and then made modifications to - 'oldResultsHash': 39850913, # hash of results when the client loaded them - # (ensures that the client and server apply - # modifications to the same base) - 'modifications': [ - { - 'builder': 'Test-Android-Nexus10-MaliT604-Arm7-Debug', - 'test': 'strokerect', - 'config': 'gpu', - 'expectedHashType': 'bitmap-64bitMD5', - 'expectedHashDigest': '1707359671708613629', - }, + KEY__EDITS__OLD_RESULTS_TYPE: 'all', # type of results that the client + # loaded and then made + # modifications to + KEY__EDITS__OLD_RESULTS_HASH: 39850913, # hash of results when the client + # loaded them (ensures that the + # client and server apply + # modifications to the same base) + KEY__EDITS__MODIFICATIONS: [ + # as needed by results.edit_expectations() ... ], } @@ -445,15 +459,15 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): # no other thread updates expectations (from the Skia repo) while we are # updating them (using the info we received from the client). with _SERVER.results_rlock: - oldResultsType = data['oldResultsType'] + oldResultsType = data[KEY__EDITS__OLD_RESULTS_TYPE] oldResults = _SERVER.results.get_results_of_type(oldResultsType) - oldResultsHash = str(hash(repr(oldResults['testData']))) - if oldResultsHash != data['oldResultsHash']: + oldResultsHash = str(hash(repr(oldResults[imagepairset.KEY__IMAGEPAIRS]))) + if oldResultsHash != data[KEY__EDITS__OLD_RESULTS_HASH]: raise Exception('results of type "%s" changed while the client was ' 'making modifications. The client should reload the ' 'results and submit the modifications again.' % oldResultsType) - _SERVER.results.edit_expectations(data['modifications']) + _SERVER.results.edit_expectations(data[KEY__EDITS__MODIFICATIONS]) # Read the updated results back from disk. # We can do this in a separate thread; we should return our success message diff --git a/gm/rebaseline_server/static/constants.js b/gm/rebaseline_server/static/constants.js new file mode 100644 index 0000000000..00c1852458 --- /dev/null +++ b/gm/rebaseline_server/static/constants.js @@ -0,0 +1,69 @@ +/* + * Constants used by our imagediff-viewing Javascript code. + */ +var module = angular.module( + 'ConstantsModule', + [] +); + +module.constant('constants', (function() { + return { + // NOTE: Keep these in sync with ../column.py + KEY__HEADER_TEXT: 'headerText', + KEY__HEADER_URL: 'headerUrl', + KEY__IS_FILTERABLE: 'isFilterable', + KEY__IS_SORTABLE: 'isSortable', + KEY__VALUES_AND_COUNTS: 'valuesAndCounts', + + // NOTE: Keep these in sync with ../imagediffdb.py + KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL: 'maxDiffPerChannel', + KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS: 'numDifferingPixels', + KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS: 'percentDifferingPixels', + KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF: 'perceptualDifference', + KEY__DIFFERENCE_DATA__WEIGHTED_DIFF: 'weightedDiffMeasure', + + // NOTE: Keep these in sync with ../imagepair.py + KEY__DIFFERENCE_DATA: 'differenceData', + KEY__EXPECTATIONS_DATA: 'expectations', + KEY__EXTRA_COLUMN_VALUES: 'extraColumns', + KEY__IMAGE_A_URL: 'imageAUrl', + KEY__IMAGE_B_URL: 'imageBUrl', + KEY__IS_DIFFERENT: 'isDifferent', + + // NOTE: Keep these in sync with ../imagepairset.py + KEY__EXTRACOLUMNHEADERS: 'extraColumnHeaders', + KEY__IMAGEPAIRS: 'imagePairs', + KEY__IMAGESETS: 'imageSets', + KEY__IMAGESETS__BASE_URL: 'baseUrl', + KEY__IMAGESETS__DESCRIPTION: 'description', + + // NOTE: Keep these in sync with ../results.py + KEY__EXPECTATIONS__BUGS: 'bugs', + KEY__EXPECTATIONS__IGNOREFAILURE: 'ignore-failure', + KEY__EXPECTATIONS__REVIEWED: 'reviewed-by-human', + KEY__EXTRACOLUMN__BUILDER: 'builder', + KEY__EXTRACOLUMN__CONFIG: 'config', + KEY__EXTRACOLUMN__RESULT_TYPE: 'resultType', + KEY__EXTRACOLUMN__TEST: 'test', + KEY__HEADER__RESULTS_ALL: 'all', + KEY__HEADER__RESULTS_FAILURES: 'failures', + KEY__NEW_IMAGE_URL: 'newImageUrl', + KEY__RESULT_TYPE__FAILED: 'failed', + KEY__RESULT_TYPE__FAILUREIGNORED: 'failure-ignored', + KEY__RESULT_TYPE__NOCOMPARISON: 'no-comparison', + KEY__RESULT_TYPE__SUCCEEDED: 'succeeded', + + // NOTE: Keep these in sync with ../server.py + KEY__EDITS__MODIFICATIONS: 'modifications', + KEY__EDITS__OLD_RESULTS_HASH: 'oldResultsHash', + KEY__EDITS__OLD_RESULTS_TYPE: 'oldResultsType', + KEY__HEADER: 'header', + KEY__HEADER__DATAHASH: 'dataHash', + KEY__HEADER__IS_EDITABLE: 'isEditable', + KEY__HEADER__IS_EXPORTED: 'isExported', + KEY__HEADER__IS_STILL_LOADING: 'resultsStillLoading', + KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: 'timeNextUpdateAvailable', + KEY__HEADER__TIME_UPDATED: 'timeUpdated', + KEY__HEADER__TYPE: 'type', + } +})()) diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js index 9ae84d613c..ab79df8189 100644 --- a/gm/rebaseline_server/static/loader.js +++ b/gm/rebaseline_server/static/loader.js @@ -1,38 +1,42 @@ /* * Loader: * Reads GM result reports written out by results.py, and imports - * them into $scope.categories and $scope.testData . + * them into $scope.extraColumnHeaders and $scope.imagePairs . */ var Loader = angular.module( 'Loader', - ['diff_viewer'] + ['ConstantsModule', 'diff_viewer'] ); - // TODO(epoger): Combine ALL of our filtering operations (including // truncation) into this one filter, so that runs most efficiently? // (We would have to make sure truncation still took place after // sorting, though.) Loader.filter( - 'removeHiddenItems', - function() { - return function(unfilteredItems, hiddenResultTypes, hiddenConfigs, + 'removeHiddenImagePairs', + function(constants) { + return function(unfilteredImagePairs, hiddenResultTypes, hiddenConfigs, builderSubstring, testSubstring, viewingTab) { - var filteredItems = []; - for (var i = 0; i < unfilteredItems.length; i++) { - var item = unfilteredItems[i]; + var filteredImagePairs = []; + for (var i = 0; i < unfilteredImagePairs.length; i++) { + var imagePair = unfilteredImagePairs[i]; + var extraColumnValues = imagePair[constants.KEY__EXTRA_COLUMN_VALUES]; // For performance, we examine the "set" objects directly rather // than calling $scope.isValueInSet(). // Besides, I don't think we have access to $scope in here... - if (!(true == hiddenResultTypes[item.resultType]) && - !(true == hiddenConfigs[item.config]) && - !(-1 == item.builder.indexOf(builderSubstring)) && - !(-1 == item.test.indexOf(testSubstring)) && - (viewingTab == item.tab)) { - filteredItems.push(item); + if (!(true == hiddenResultTypes[extraColumnValues[ + constants.KEY__EXTRACOLUMN__RESULT_TYPE]]) && + !(true == hiddenConfigs[extraColumnValues[ + constants.KEY__EXTRACOLUMN__CONFIG]]) && + !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__BUILDER] + .indexOf(builderSubstring)) && + !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__TEST] + .indexOf(testSubstring)) && + (viewingTab == imagePair.tab)) { + filteredImagePairs.push(imagePair); } } - return filteredItems; + return filteredImagePairs; }; } ); @@ -40,7 +44,8 @@ Loader.filter( Loader.controller( 'Loader.Controller', - function($scope, $http, $filter, $location, $timeout) { + function($scope, $http, $filter, $location, $timeout, constants) { + $scope.constants = constants; $scope.windowTitle = "Loading GM Results..."; $scope.resultsToLoad = $location.search().resultsToLoad; $scope.loadingMessage = "Loading results of type '" + $scope.resultsToLoad + @@ -53,26 +58,33 @@ Loader.controller( */ $http.get("/results/" + $scope.resultsToLoad).success( function(data, status, header, config) { - if (data.header.resultsStillLoading) { + var dataHeader = data[constants.KEY__HEADER]; + if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) { $scope.loadingMessage = "Server is still loading results; will retry at " + - $scope.localTimeString(data.header.timeNextUpdateAvailable); + $scope.localTimeString(dataHeader[ + constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE]); $timeout( function(){location.reload();}, - (data.header.timeNextUpdateAvailable * 1000) - new Date().getTime()); + (dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] + * 1000) - new Date().getTime()); } else { $scope.loadingMessage = "Processing data, please wait..."; - $scope.header = data.header; - $scope.categories = data.categories; - $scope.testData = data.testData; - $scope.sortColumn = 'weightedDiffMeasure'; + $scope.header = dataHeader; + $scope.extraColumnHeaders = data[constants.KEY__EXTRACOLUMNHEADERS]; + $scope.imagePairs = data[constants.KEY__IMAGEPAIRS]; + $scope.imageSets = data[constants.KEY__IMAGESETS]; + $scope.sortColumnSubdict = constants.KEY__DIFFERENCE_DATA; + $scope.sortColumnKey = constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF; $scope.showTodos = false; $scope.showSubmitAdvancedSettings = false; $scope.submitAdvancedSettings = {}; - $scope.submitAdvancedSettings['reviewed-by-human'] = true; - $scope.submitAdvancedSettings['ignore-failure'] = false; + $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__REVIEWED] = true; + $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false; $scope.submitAdvancedSettings['bug'] = ''; // Create the list of tabs (lists into which the user can file each @@ -80,7 +92,7 @@ Loader.controller( $scope.tabs = [ 'Unfiled', 'Hidden' ]; - if (data.header.isEditable) { + if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) { $scope.tabs = $scope.tabs.concat( ['Pending Approval']); } @@ -92,26 +104,32 @@ Loader.controller( for (var i = 0; i < $scope.tabs.length; i++) { $scope.numResultsPerTab[$scope.tabs[i]] = 0; } - $scope.numResultsPerTab[$scope.defaultTab] = $scope.testData.length; + $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length; // Add index and tab fields to all records. - for (var i = 0; i < $scope.testData.length; i++) { - $scope.testData[i].index = i; - $scope.testData[i].tab = $scope.defaultTab; + for (var i = 0; i < $scope.imagePairs.length; i++) { + $scope.imagePairs[i].index = i; + $scope.imagePairs[i].tab = $scope.defaultTab; } // Arrays within which the user can toggle individual elements. - $scope.selectedItems = []; + $scope.selectedImagePairs = []; // Sets within which the user can toggle individual elements. - $scope.hiddenResultTypes = { - 'failure-ignored': true, - 'no-comparison': true, - 'succeeded': true, - }; - $scope.allResultTypes = Object.keys(data.categories['resultType']); + $scope.hiddenResultTypes = {}; + $scope.hiddenResultTypes[ + constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true; + $scope.hiddenResultTypes[ + constants.KEY__RESULT_TYPE__NOCOMPARISON] = true; + $scope.hiddenResultTypes[ + constants.KEY__RESULT_TYPE__SUCCEEDED] = true; + $scope.allResultTypes = Object.keys( + $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE] + [constants.KEY__VALUES_AND_COUNTS]); $scope.hiddenConfigs = {}; - $scope.allConfigs = Object.keys(data.categories['config']); + $scope.allConfigs = Object.keys( + $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG] + [constants.KEY__VALUES_AND_COUNTS]); // Associative array of partial string matches per category. $scope.categoryValueMatch = {}; @@ -142,12 +160,12 @@ Loader.controller( /** * Select all currently showing tests. */ - $scope.selectAllItems = function() { - var numItemsShowing = $scope.limitedTestData.length; - for (var i = 0; i < numItemsShowing; i++) { - var index = $scope.limitedTestData[i].index; - if (!$scope.isValueInArray(index, $scope.selectedItems)) { - $scope.toggleValueInArray(index, $scope.selectedItems); + $scope.selectAllImagePairs = function() { + var numImagePairsShowing = $scope.limitedImagePairs.length; + for (var i = 0; i < numImagePairsShowing; i++) { + var index = $scope.limitedImagePairs[i].index; + if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) { + $scope.toggleValueInArray(index, $scope.selectedImagePairs); } } } @@ -155,12 +173,12 @@ Loader.controller( /** * Deselect all currently showing tests. */ - $scope.clearAllItems = function() { - var numItemsShowing = $scope.limitedTestData.length; - for (var i = 0; i < numItemsShowing; i++) { - var index = $scope.limitedTestData[i].index; - if ($scope.isValueInArray(index, $scope.selectedItems)) { - $scope.toggleValueInArray(index, $scope.selectedItems); + $scope.clearAllImagePairs = function() { + var numImagePairsShowing = $scope.limitedImagePairs.length; + for (var i = 0; i < numImagePairsShowing; i++) { + var index = $scope.limitedImagePairs[i].index; + if ($scope.isValueInArray(index, $scope.selectedImagePairs)) { + $scope.toggleValueInArray(index, $scope.selectedImagePairs); } } } @@ -168,11 +186,11 @@ Loader.controller( /** * Toggle selection of all currently showing tests. */ - $scope.toggleAllItems = function() { - var numItemsShowing = $scope.limitedTestData.length; - for (var i = 0; i < numItemsShowing; i++) { - var index = $scope.limitedTestData[i].index; - $scope.toggleValueInArray(index, $scope.selectedItems); + $scope.toggleAllImagePairs = function() { + var numImagePairsShowing = $scope.limitedImagePairs.length; + for (var i = 0; i < numImagePairsShowing; i++) { + var index = $scope.limitedImagePairs[i].index; + $scope.toggleValueInArray(index, $scope.selectedImagePairs); } } @@ -192,33 +210,33 @@ Loader.controller( } /** - * Move the items in $scope.selectedItems to a different tab, - * and then clear $scope.selectedItems. + * Move the imagePairs in $scope.selectedImagePairs to a different tab, + * and then clear $scope.selectedImagePairs. * * @param newTab (string): name of the tab to move the tests to */ - $scope.moveSelectedItemsToTab = function(newTab) { - $scope.moveItemsToTab($scope.selectedItems, newTab); - $scope.selectedItems = []; + $scope.moveSelectedImagePairsToTab = function(newTab) { + $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab); + $scope.selectedImagePairs = []; $scope.updateResults(); } /** - * Move a subset of $scope.testData to a different tab. + * Move a subset of $scope.imagePairs to a different tab. * - * @param itemIndices (array of ints): indices into $scope.testData + * @param imagePairIndices (array of ints): indices into $scope.imagePairs * indicating which test results to move * @param newTab (string): name of the tab to move the tests to */ - $scope.moveItemsToTab = function(itemIndices, newTab) { - var itemIndex; - var numItems = itemIndices.length; - for (var i = 0; i < numItems; i++) { - itemIndex = itemIndices[i]; - $scope.numResultsPerTab[$scope.testData[itemIndex].tab]--; - $scope.testData[itemIndex].tab = newTab; + $scope.moveImagePairsToTab = function(imagePairIndices, newTab) { + var imagePairIndex; + var numImagePairs = imagePairIndices.length; + for (var i = 0; i < numImagePairs; i++) { + imagePairIndex = imagePairIndices[i]; + $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--; + $scope.imagePairs[imagePairIndex].tab = newTab; } - $scope.numResultsPerTab[newTab] += numItems; + $scope.numResultsPerTab[newTab] += numImagePairs; } @@ -277,14 +295,16 @@ Loader.controller( 'resultsToLoad': $scope.queryParameters.copiers.simple, 'displayLimitPending': $scope.queryParameters.copiers.simple, 'imageSizePending': $scope.queryParameters.copiers.simple, - 'sortColumn': $scope.queryParameters.copiers.simple, - - 'builder': $scope.queryParameters.copiers.categoryValueMatch, - 'test': $scope.queryParameters.copiers.categoryValueMatch, + 'sortColumnSubdict': $scope.queryParameters.copiers.simple, + 'sortColumnKey': $scope.queryParameters.copiers.simple, 'hiddenResultTypes': $scope.queryParameters.copiers.set, 'hiddenConfigs': $scope.queryParameters.copiers.set, }; + $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] = + $scope.queryParameters.copiers.categoryValueMatch; + $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] = + $scope.queryParameters.copiers.categoryValueMatch; // Loads all parameters into $scope from the URL query string; // any which are not found within the URL will keep their current value. @@ -339,7 +359,7 @@ Loader.controller( $scope.displayLimit = $scope.displayLimitPending; // TODO(epoger): Every time we apply a filter, AngularJS creates // another copy of the array. Is there a way we can filter out - // the items as they are displayed, rather than storing multiple + // the imagePairs as they are displayed, rather than storing multiple // array copies? (For better performance.) if ($scope.viewingTab == $scope.defaultTab) { @@ -347,32 +367,34 @@ Loader.controller( // TODO(epoger): Until we allow the user to reverse sort order, // there are certain columns we want to sort in a different order. var doReverse = ( - ($scope.sortColumn == 'percentDifferingPixels') || - ($scope.sortColumn == 'weightedDiffMeasure')); + ($scope.sortColumnKey == + constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) || + ($scope.sortColumnKey == + constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF)); - $scope.filteredTestData = + $scope.filteredImagePairs = $filter("orderBy")( - $filter("removeHiddenItems")( - $scope.testData, + $filter("removeHiddenImagePairs")( + $scope.imagePairs, $scope.hiddenResultTypes, $scope.hiddenConfigs, $scope.categoryValueMatch.builder, $scope.categoryValueMatch.test, $scope.viewingTab ), - $scope.sortColumn, doReverse); - $scope.limitedTestData = $filter("limitTo")( - $scope.filteredTestData, $scope.displayLimit); + $scope.getSortColumnValue, doReverse); + $scope.limitedImagePairs = $filter("limitTo")( + $scope.filteredImagePairs, $scope.displayLimit); } else { - $scope.filteredTestData = + $scope.filteredImagePairs = $filter("orderBy")( $filter("filter")( - $scope.testData, + $scope.imagePairs, {tab: $scope.viewingTab}, true ), - $scope.sortColumn); - $scope.limitedTestData = $scope.filteredTestData; + $scope.getSortColumnValue); + $scope.limitedImagePairs = $scope.filteredImagePairs; } $scope.imageSize = $scope.imageSizePending; $scope.setUpdatesPending(false); @@ -382,14 +404,33 @@ Loader.controller( /** * Re-sort the displayed results. * - * @param sortColumn (string): name of the column to sort on + * @param subdict (string): which subdictionary + * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA, + * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within + * @param key (string): sort by value associated with this key in subdict */ - $scope.sortResultsBy = function(sortColumn) { - $scope.sortColumn = sortColumn; + $scope.sortResultsBy = function(subdict, key) { + $scope.sortColumnSubdict = subdict; + $scope.sortColumnKey = key; $scope.updateResults(); } /** + * For a particular ImagePair, return the value of the column we are + * sorting on (according to $scope.sortColumnSubdict and + * $scope.sortColumnKey). + * + * @param imagePair: imagePair to get a column value out of. + */ + $scope.getSortColumnValue = function(imagePair) { + if ($scope.sortColumnSubdict in imagePair) { + return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey]; + } else { + return undefined; + } + } + + /** * Set $scope.categoryValueMatch[name] = value, and update results. * * @param name @@ -456,10 +497,13 @@ Loader.controller( * Tell the server that the actual results of these particular tests * are acceptable. * - * @param testDataSubset an array of test results, most likely a subset of - * $scope.testData (perhaps with some modifications) + * TODO(epoger): This assumes that the original expectations are in + * imageSetA, and the actuals are in imageSetB. + * + * @param imagePairsSubset an array of test results, most likely a subset of + * $scope.imagePairs (perhaps with some modifications) */ - $scope.submitApprovals = function(testDataSubset) { + $scope.submitApprovals = function(imagePairsSubset) { $scope.submitPending = true; // Convert bug text field to null or 1-item array. @@ -476,30 +520,40 @@ Loader.controller( // result type, RenderModeMismatch') var encounteredComparisonConfig = false; - var newResults = []; - for (var i = 0; i < testDataSubset.length; i++) { - var actualResult = testDataSubset[i]; - var expectedResult = { - builder: actualResult['builder'], - test: actualResult['test'], - config: actualResult['config'], - expectedHashType: actualResult['actualHashType'], - expectedHashDigest: actualResult['actualHashDigest'], - }; - if (0 == expectedResult.config.indexOf('comparison-')) { + var updatedExpectations = []; + for (var i = 0; i < imagePairsSubset.length; i++) { + var imagePair = imagePairsSubset[i]; + var updatedExpectation = {}; + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = + imagePair[constants.KEY__EXPECTATIONS_DATA]; + updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] = + imagePair[constants.KEY__EXTRA_COLUMN_VALUES]; + updatedExpectation[constants.KEY__NEW_IMAGE_URL] = + imagePair[constants.KEY__IMAGE_B_URL]; + if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] + [constants.KEY__EXTRACOLUMN__CONFIG] + .indexOf('comparison-')) { encounteredComparisonConfig = true; } // Advanced settings... - expectedResult['reviewed-by-human'] = - $scope.submitAdvancedSettings['reviewed-by-human']; - if (true == $scope.submitAdvancedSettings['ignore-failure']) { + if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) { + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {}; + } + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] + [constants.KEY__EXPECTATIONS__REVIEWED] = + $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__REVIEWED]; + if (true == $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__IGNOREFAILURE]) { // if it's false, don't send it at all (just keep the default) - expectedResult['ignore-failure'] = true; + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] + [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true; } - expectedResult['bugs'] = bugs; + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] + [constants.KEY__EXPECTATIONS__BUGS] = bugs; - newResults.push(expectedResult); + updatedExpectations.push(updatedExpectation); } if (encounteredComparisonConfig) { alert("Approval failed -- you cannot approve results with config " + @@ -507,21 +561,24 @@ Loader.controller( $scope.submitPending = false; return; } + var modificationData = {}; + modificationData[constants.KEY__EDITS__MODIFICATIONS] = + updatedExpectations; + modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] = + $scope.header[constants.KEY__HEADER__DATAHASH]; + modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] = + $scope.header[constants.KEY__HEADER__TYPE]; $http({ method: "POST", url: "/edits", - data: { - oldResultsType: $scope.header.type, - oldResultsHash: $scope.header.dataHash, - modifications: newResults - } + data: modificationData }).success(function(data, status, headers, config) { - var itemIndicesToMove = []; - for (var i = 0; i < testDataSubset.length; i++) { - itemIndicesToMove.push(testDataSubset[i].index); + var imagePairIndicesToMove = []; + for (var i = 0; i < imagePairsSubset.length; i++) { + imagePairIndicesToMove.push(imagePairsSubset[i].index); } - $scope.moveItemsToTab(itemIndicesToMove, - "HackToMakeSureThisItemDisappears"); + $scope.moveImagePairsToTab(imagePairIndicesToMove, + "HackToMakeSureThisImagePairDisappears"); $scope.updateResults(); alert("New baselines submitted successfully!\n\n" + "You still need to commit the updated expectations files on " + @@ -682,5 +739,23 @@ Loader.controller( return $scope.hexColorString(v, v, v); } + /** + * Returns the last path component of image diff URL for a given ImagePair. + * + * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this + * will be relative to different base URLs. + * + * We must keep this function in sync with _get_difference_locator() in + * ../imagediffdb.py + * + * @param imagePair: ImagePair to generate image diff URL for + */ + $scope.getImageDiffRelativeUrl = function(imagePair) { + var before = + imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" + + imagePair[constants.KEY__IMAGE_B_URL]; + return before.replace(/[^\w\-]/g, "_") + ".png"; + } + } ); diff --git a/gm/rebaseline_server/static/view.html b/gm/rebaseline_server/static/view.html index 9ce3fae140..36fca750b1 100644 --- a/gm/rebaseline_server/static/view.html +++ b/gm/rebaseline_server/static/view.html @@ -5,8 +5,9 @@ <head> <title ng-bind="windowTitle"></title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script> - <script src="loader.js"></script> + <script src="constants.js"></script> <script src="diff_viewer.js"></script> + <script src="loader.js"></script> <link rel="stylesheet" href="view.css"> </head> @@ -22,16 +23,16 @@ {{loadingMessage}} </em> - <div ng-hide="!categories"><!-- everything: hide until data is loaded --> + <div ng-hide="!extraColumnHeaders"><!-- everything: hide until data is loaded --> <div class="warning-div" - ng-hide="!(header.isEditable && header.isExported)"> + ng-hide="!(header[constants.KEY__HEADER__IS_EDITABLE] && header[constants.KEY__HEADER__IS_EXPORTED])"> WARNING! These results are editable and exported, so any user who can connect to this server over the network can modify them. </div> - <div ng-hide="!(header.timeUpdated)"> - Results current as of {{localTimeString(header.timeUpdated)}} + <div ng-hide="!(header[constants.KEY__HEADER___TIME_UPDATED])"> + Results current as of {{localTimeString(header[constants.KEY__HEADER__TIME_UPDATED])}} </div> <div><!-- tabs --> @@ -60,10 +61,9 @@ </th> </tr> <tr valign="top"> - <!-- TODO(epoger): make this an ng-repeat over resultType, config, etc? --> <td> resultType<br> - <label ng-repeat="(resultType, count) in categories['resultType'] track by $index"> + <label ng-repeat="(resultType, count) in extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE][constants.KEY__VALUES_AND_COUNTS] track by $index"> <input type="checkbox" name="resultTypes" value="{{resultType}}" @@ -81,7 +81,7 @@ toggle </button> </td> - <td ng-repeat="category in ['builder', 'test']"> + <td ng-repeat="category in [constants.KEY__EXTRACOLUMN__BUILDER, constants.KEY__EXTRACOLUMN__TEST]"> {{category}} <br> <input type="text" @@ -95,7 +95,7 @@ </td> <td> config<br> - <label ng-repeat="(config, count) in categories['config'] track by $index"> + <label ng-repeat="(config, count) in extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG][constants.KEY__VALUES_AND_COUNTS] track by $index"> <input type="checkbox" name="configs" value="{{config}}" @@ -145,9 +145,9 @@ <div ng-hide="'Pending Approval' != viewingTab"> <div style="display:inline-block"> <button style="font-size:20px" - ng-click="submitApprovals(filteredTestData)" - ng-disabled="submitPending || (filteredTestData.length == 0)"> - Update these {{filteredTestData.length}} expectations on the server + ng-click="submitApprovals(filteredImagePairs)" + ng-disabled="submitPending || (filteredImagePairs.length == 0)"> + Update these {{filteredImagePairs.length}} expectations on the server </button> </div> <div style="display:inline-block"> @@ -161,7 +161,7 @@ <input type="checkbox" ng-model="showSubmitAdvancedSettings"> show <ul ng-hide="!showSubmitAdvancedSettings"> - <li ng-repeat="setting in ['reviewed-by-human', 'ignore-failure']"> + <li ng-repeat="setting in [constants.KEY__EXPECTATIONS__REVIEWED, constants.KEY__EXPECTATIONS__IGNOREFAILURE]"> {{setting}} <input type="checkbox" ng-model="submitAdvancedSettings[setting]"> </li> @@ -179,11 +179,11 @@ <table border="0" width="100%"> <!-- results header --> <tr> <td> - Found {{filteredTestData.length}} matches; - <span ng-hide="filteredTestData.length <= limitedTestData.length"> - displaying the first {{limitedTestData.length}} + Found {{filteredImagePairs.length}} matches; + <span ng-hide="filteredImagePairs.length <= limitedImagePairs.length"> + displaying the first {{limitedImagePairs.length}} </span> - <span ng-hide="filteredTestData.length > limitedTestData.length"> + <span ng-hide="filteredImagePairs.length > limitedImagePairs.length"> displaying them all </span> <br> @@ -192,21 +192,21 @@ <td align="right"> <div> all tests shown: - <button ng-click="selectAllItems()"> + <button ng-click="selectAllImagePairs()"> select </button> - <button ng-click="clearAllItems()"> + <button ng-click="clearAllImagePairs()"> clear </button> - <button ng-click="toggleAllItems()"> + <button ng-click="toggleAllImagePairs()"> toggle </button> </div> <div ng-repeat="otherTab in tabs"> - <button ng-click="moveSelectedItemsToTab(otherTab)" - ng-disabled="selectedItems.length == 0" + <button ng-click="moveSelectedImagePairsToTab(otherTab)" + ng-disabled="selectedImagePairs.length == 0" ng-hide="otherTab == viewingTab"> - move {{selectedItems.length}} selected tests to {{otherTab}} tab + move {{selectedImagePairs.length}} selected tests to {{otherTab}} tab </button> </div> </td> @@ -216,12 +216,12 @@ <table border="1" ng-app="diff_viewer"> <!-- results --> <tr> <!-- Most column headers are displayed in a common fashion... --> - <th ng-repeat="categoryName in ['resultType', 'builder', 'test', 'config']"> + <th ng-repeat="categoryName in [constants.KEY__EXTRACOLUMN__RESULT_TYPE, constants.KEY__EXTRACOLUMN__BUILDER, constants.KEY__EXTRACOLUMN__TEST, constants.KEY__EXTRACOLUMN__CONFIG]"> <input type="radio" name="sortColumnRadio" value="{{categoryName}}" - ng-checked="(sortColumn == categoryName)" - ng-click="sortResultsBy(categoryName)"> + ng-checked="(sortColumnKey == categoryName)" + ng-click="sortResultsBy(constants.KEY__EXTRA_COLUMN_VALUES, categoryName)"> {{categoryName}} </th> <!-- ... but there are a few columns where we display things differently. --> @@ -229,40 +229,30 @@ <input type="radio" name="sortColumnRadio" value="bugs" - ng-checked="(sortColumn == 'bugs')" - ng-click="sortResultsBy('bugs')"> + ng-checked="(sortColumnKey == constants.KEY__EXPECTATIONS__BUGS)" + ng-click="sortResultsBy(constants.KEY__EXPECTATIONS_DATA, constants.KEY__EXPECTATIONS__BUGS)"> bugs </th> <th width="{{imageSize}}"> - <input type="radio" - name="sortColumnRadio" - value="expectedHashDigest" - ng-checked="(sortColumn == 'expectedHashDigest')" - ng-click="sortResultsBy('expectedHashDigest')"> - expected image + {{imageSets[0][constants.KEY__IMAGESETS__DESCRIPTION]}} </th> <th width="{{imageSize}}"> - <input type="radio" - name="sortColumnRadio" - value="actualHashDigest" - ng-checked="(sortColumn == 'actualHashDigest')" - ng-click="sortResultsBy('actualHashDigest')"> - actual image + {{imageSets[1][constants.KEY__IMAGESETS__DESCRIPTION]}} </th> <th width="{{imageSize}}"> <input type="radio" name="sortColumnRadio" value="percentDifferingPixels" - ng-checked="(sortColumn == 'percentDifferingPixels')" - ng-click="sortResultsBy('percentDifferingPixels')"> + ng-checked="(sortColumnKey == constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS)" + ng-click="sortResultsBy(constants.KEY__DIFFERENCE_DATA, constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS)"> differing pixels in white </th> <th width="{{imageSize}}"> <input type="radio" name="sortColumnRadio" value="weightedDiffMeasure" - ng-checked="(sortColumn == 'weightedDiffMeasure')" - ng-click="sortResultsBy('weightedDiffMeasure')"> + ng-checked="(sortColumnKey == constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF)" + ng-click="sortResultsBy(constants.KEY__DIFFERENCE_DATA, constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF)"> perceptual difference <br> <input type="range" ng-model="pixelDiffBgColorBrightness" @@ -272,18 +262,18 @@ min="0" max="255"/> </th> <th> - <!-- item-selection checkbox column --> + <!-- imagepair-selection checkbox column --> </th> </tr> - <tr ng-repeat="result in limitedTestData" ng-controller="ImageController"> + <tr ng-repeat="imagePair in limitedImagePairs" ng-controller="ImageController"> <td> - {{result.resultType}} + {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__RESULT_TYPE]}} <br> <button class="show-only-button" ng-hide="viewingTab != defaultTab" - ng-click="showOnlyResultType(result.resultType)" - title="show only results of type '{{result.resultType}}'"> + ng-click="showOnlyResultType(imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__RESULT_TYPE])" + title="show only results of type {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__RESULT_TYPE]}}"> show only </button> <br> @@ -295,14 +285,14 @@ show all </button> </td> - <td ng-repeat="categoryName in ['builder', 'test']"> - {{result[categoryName]}} + <td ng-repeat="categoryName in [constants.KEY__EXTRACOLUMN__BUILDER, constants.KEY__EXTRACOLUMN__TEST]"> + {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName]}} <br> <button class="show-only-button" ng-hide="viewingTab != defaultTab" - ng-disabled="result[categoryName] == categoryValueMatch[categoryName]" - ng-click="setCategoryValueMatch(categoryName, result[categoryName])" - title="show only results of {{categoryName}} '{{result[categoryName]}}'"> + ng-disabled="imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName] == categoryValueMatch[categoryName]" + ng-click="setCategoryValueMatch(categoryName, imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName])" + title="show only results of {{categoryName}} {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName]}}"> show only </button> <br> @@ -315,12 +305,12 @@ </button> </td> <td> - {{result.config}} + {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__CONFIG]}} <br> <button class="show-only-button" ng-hide="viewingTab != defaultTab" - ng-click="showOnlyConfig(result.config)" - title="show only results of config '{{result.config}}'"> + ng-click="showOnlyConfig(imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__CONFIG])" + title="show only results of config {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__CONFIG]}}"> show only </button> <br> @@ -333,43 +323,41 @@ </button> </td> <td> - <a ng-repeat="bug in result['bugs']" + <a ng-repeat="bug in imagePair[constants.KEY__EXPECTATIONS_DATA][constants.KEY__EXPECTATIONS__BUGS]" href="https://code.google.com/p/skia/issues/detail?id={{bug}}" target="_blank"> {{bug}} </a> </td> - <!-- expected image --> + <!-- image A --> <td valign="bottom" width="{{imageSize}}"> - <a href="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.expectedHashType}}/{{result.test}}/{{result.expectedHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="{{imageSets[0][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_A_URL]}}" target="_blank">View Image</a><br/> <img-compare type="baseline" width="{{imageSize}}" - src="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.expectedHashType}}/{{result.test}}/{{result.expectedHashDigest}}.png" /> - + src="{{imageSets[0][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_A_URL]}}" /> </td> - <!-- actual image --> + <!-- image B --> <td valign="bottom" width="{{imageSize}}"> - <a href="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.actualHashType}}/{{result.test}}/{{result.actualHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="{{imageSets[1][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_B_URL]}}" target="_blank">View Image</a><br/> <img-compare type="test" width="{{imageSize}}" - src="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.actualHashType}}/{{result.test}}/{{result.actualHashDigest}}.png" /> - + src="{{imageSets[1][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_B_URL]}}" /> </td> <!-- whitediffs: every differing pixel shown in white --> <td valign="bottom" width="{{imageSize}}"> - <div ng-hide="result.expectedHashDigest == result.actualHashDigest" - title="{{result.numDifferingPixels | number:0}} of {{(100 * result.numDifferingPixels / result.percentDifferingPixels) | number:0}} pixels ({{result.percentDifferingPixels.toFixed(4)}}%) differ from expectation."> + <div ng-hide="!imagePair[constants.KEY__IS_DIFFERENT]" + title="{{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS] | number:0}} of {{(100 * imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS] / imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS]) | number:0}} pixels ({{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS].toFixed(4)}}%) differ from expectation."> - {{result.percentDifferingPixels.toFixed(4)}}% - ({{result.numDifferingPixels}}) + {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS].toFixed(4)}}% + ({{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS]}}) <br/> - <a href="/static/generated-images/whitediffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="/static/generated-images/whitediffs/{{getImageDiffRelativeUrl(imagePair)}}" target="_blank">View Image</a><br/> <img-compare type="differingPixelsInWhite" width="{{imageSize}}" - src="/static/generated-images/whitediffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" /> + src="/static/generated-images/whitediffs/{{getImageDiffRelativeUrl(imagePair)}}" /> </div> - <div ng-hide="result.expectedHashDigest != result.actualHashDigest" + <div ng-hide="imagePair[constants.KEY__IS_DIFFERENT]" style="text-align:center"> –none– </div> @@ -377,23 +365,23 @@ <!-- diffs: per-channel RGB deltas --> <td valign="bottom" width="{{imageSize}}"> - <div ng-hide="result.expectedHashDigest == result.actualHashDigest" - title="Perceptual difference measure is {{result.perceptualDifference.toFixed(4)}}%. Maximum difference per channel: R={{result.maxDiffPerChannel[0]}}, G={{result.maxDiffPerChannel[1]}}, B={{result.maxDiffPerChannel[2]}}"> + <div ng-hide="!imagePair[constants.KEY__IS_DIFFERENT]" + title="Perceptual difference measure is {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF].toFixed(4)}}%. Maximum difference per channel: R={{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL][0]}}, G={{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL][1]}}, B={{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL][2]}}"> - {{result.perceptualDifference.toFixed(4)}}% - {{result.maxDiffPerChannel}} + {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF].toFixed(4)}}% + {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL]}} <br/> - <a href="/static/generated-images/diffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="/static/generated-images/diffs/{{getImageDiffRelativeUrl(imagePair)}}" target="_blank">View Image</a><br/> <img-compare ng-style="{backgroundColor: pixelDiffBgColor}" type="differencePerPixel" width="{{imageSize}}" - src="/static/generated-images/diffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" + src="/static/generated-images/diffs/{{getImageDiffRelativeUrl(imagePair)}}" ng-mousedown="MagnifyDraw($event, true)" ng-mousemove="MagnifyDraw($event, false)" ng-mouseup="MagnifyEnd($event)" ng-mouseleave="MagnifyEnd($event)" /> </div> - <div ng-hide="result.expectedHashDigest != result.actualHashDigest" + <div ng-hide="imagePair[constants.KEY__IS_DIFFERENT]" style="text-align:center"> –none– </div> @@ -402,23 +390,15 @@ <td> <input type="checkbox" name="rowSelect" - value="{{result.index}}" - ng-checked="isValueInArray(result.index, selectedItems)" - ng-click="toggleValueInArray(result.index, selectedItems)"> + value="{{imagePair.index}}" + ng-checked="isValueInArray(imagePair.index, selectedImagePairs)" + ng-click="toggleValueInArray(imagePair.index, selectedImagePairs)"> </tr> - </table> <!-- results --> - </td></tr></table> <!-- table holding results header + results table --> + </table> <!-- imagePairs --> + </td></tr></table> <!-- table holding results header + imagePairs table --> </div><!-- main display area of selected tab --> </div><!-- everything: hide until data is loaded --> - <!-- TODO(epoger): Can we get the base URLs (commondatastorage and - issues list) from - https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.json ? - I tried importing the - http://skia.googlecode.com/svn/buildbot/skia_tools.js script and using - that to do so, but I got Access-Control-Allow-Origin errors. - --> - </body> </html> diff --git a/gm/rebaseline_server/testdata/outputs/actual/results_test.ResultsTest.test_gm/gm.json b/gm/rebaseline_server/testdata/outputs/actual/results_test.ResultsTest.test_gm/gm.json deleted file mode 100644 index 9aef669384..0000000000 --- a/gm/rebaseline_server/testdata/outputs/actual/results_test.ResultsTest.test_gm/gm.json +++ /dev/null @@ -1,526 +0,0 @@ -{ - "categories": { - "builder": { - "Test-Android-GalaxyNexus-SGX540-Arm7-Release": 13, - "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug": 15 - }, - "config": { - "565": 9, - "8888": 9, - "gpu": 4, - "pdf-mac": 3, - "pdf-poppler": 3 - }, - "ignore-failure": { - "null": 12, - "false": 16 - }, - "resultType": { - "failed": 1, - "failure-ignored": 4, - "no-comparison": 9, - "succeeded": 14 - }, - "reviewed-by-human": { - "null": 16, - "false": 12 - }, - "test": { - "3x3bitmaprect": 7, - "aaclip": 2, - "bigblurs": 7, - "bitmapsource": 2, - "displacement": 5, - "filterbitmap_checkerboard_192_192": 2, - "filterbitmap_checkerboard_32_2": 2, - "texdata": 1 - } - }, - "testData": [ - { - "actualHashDigest": "3695033638604474475", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "gpu", - "expectedHashDigest": "2736593828543197285", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": [ - 128, - 128, - 128 - ], - "numDifferingPixels": 120000, - "percentDifferingPixels": 75.0, - "perceptualDifference": 50.122499999999995, - "resultType": "failed", - "reviewed-by-human": null, - "test": "texdata", - "weightedDiffMeasure": 8.413046264257337 - }, - { - "actualHashDigest": "4719210487426381700", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "13270012198930365496", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 222, - 223, - 222 - ], - "numDifferingPixels": 77691, - "percentDifferingPixels": 17.593070652173914, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_192_192", - "weightedDiffMeasure": 3.661184599893761 - }, - { - "actualHashDigest": "3154864687054945306", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "14746826424040775628", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 221, - 221, - 221 - ], - "numDifferingPixels": 82952, - "percentDifferingPixels": 18.784420289855074, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_192_192", - "weightedDiffMeasure": 3.6140912497422955 - }, - { - "actualHashDigest": "15528304435129737588", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "16197252621792695154", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 222, - 223, - 222 - ], - "numDifferingPixels": 53150, - "percentDifferingPixels": 12.035778985507246, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_32_2", - "weightedDiffMeasure": 3.713243437353155 - }, - { - "actualHashDigest": "712827739969462165", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "7634650333321761866", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 221, - 221, - 221 - ], - "numDifferingPixels": 53773, - "percentDifferingPixels": 12.17685688405797, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_32_2", - "weightedDiffMeasure": 3.6723483686597684 - }, - { - "actualHashDigest": "2422083043229439955", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "17309852422285247848", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "17503582803589749280", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bitmapsource" - }, - { - "actualHashDigest": "16289727936158057543", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bitmapsource" - }, - { - "actualHashDigest": "16998423976396106083", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "16998423976396106083", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "2054956815327187963", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "2054956815327187963", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "6190901827590820995", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "6190901827590820995", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "aaclip", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "14456211900777561488", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "14456211900777561488", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "aaclip", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "4569468668668628191", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "expectedHashDigest": "4569468668668628191", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "11401048196735046263", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "expectedHashDigest": "11401048196735046263", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "5698561127291561694", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "expectedHashDigest": "5698561127291561694", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "12901125495691049846", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "expectedHashDigest": "12901125495691049846", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "16285974094717334658", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "expectedHashDigest": "16285974094717334658", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "displacement", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "14704206703218007573", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "17309852422285247848", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "1822195599289208664", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "16171608477794909861", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "6539050160610613353", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" - }, - { - "actualHashDigest": "16998423976396106083", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "expectedHashDigest": "16998423976396106083", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "2054956815327187963", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "expectedHashDigest": "2054956815327187963", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "2054956815327187963", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "expectedHashDigest": "2054956815327187963", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "8518347971308375604", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "expectedHashDigest": "8518347971308375604", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - }, - { - "actualHashDigest": "16723580409414313678", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "expectedHashDigest": "16723580409414313678", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 - } - ] -}
\ No newline at end of file diff --git a/gm/rebaseline_server/testdata/outputs/expected/results_test.ResultsTest.test_gm/gm.json b/gm/rebaseline_server/testdata/outputs/expected/results_test.ResultsTest.test_gm/gm.json index 9aef669384..38216557bc 100644 --- a/gm/rebaseline_server/testdata/outputs/expected/results_test.ResultsTest.test_gm/gm.json +++ b/gm/rebaseline_server/testdata/outputs/expected/results_test.ResultsTest.test_gm/gm.json @@ -1,526 +1,545 @@ { - "categories": { + "extraColumnHeaders": { "builder": { - "Test-Android-GalaxyNexus-SGX540-Arm7-Release": 13, - "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug": 15 + "headerText": "builder", + "isFilterable": true, + "isSortable": true, + "valuesAndCounts": { + "Test-Android-GalaxyNexus-SGX540-Arm7-Release": 13, + "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug": 15 + } }, "config": { - "565": 9, - "8888": 9, - "gpu": 4, - "pdf-mac": 3, - "pdf-poppler": 3 - }, - "ignore-failure": { - "null": 12, - "false": 16 + "headerText": "config", + "isFilterable": true, + "isSortable": true, + "valuesAndCounts": { + "565": 9, + "8888": 9, + "gpu": 4, + "pdf-mac": 3, + "pdf-poppler": 3 + } }, "resultType": { - "failed": 1, - "failure-ignored": 4, - "no-comparison": 9, - "succeeded": 14 - }, - "reviewed-by-human": { - "null": 16, - "false": 12 + "headerText": "resultType", + "isFilterable": true, + "isSortable": true, + "valuesAndCounts": { + "failed": 1, + "failure-ignored": 4, + "no-comparison": 9, + "succeeded": 14 + } }, "test": { - "3x3bitmaprect": 7, - "aaclip": 2, - "bigblurs": 7, - "bitmapsource": 2, - "displacement": 5, - "filterbitmap_checkerboard_192_192": 2, - "filterbitmap_checkerboard_32_2": 2, - "texdata": 1 + "headerText": "test", + "isFilterable": true, + "isSortable": true, + "valuesAndCounts": { + "3x3bitmaprect": 7, + "aaclip": 2, + "bigblurs": 7, + "bitmapsource": 2, + "displacement": 5, + "filterbitmap_checkerboard_192_192": 2, + "filterbitmap_checkerboard_32_2": 2, + "texdata": 1 + } } }, - "testData": [ + "imagePairs": [ + { + "differenceData": { + "maxDiffPerChannel": [ + 128, + 128, + 128 + ], + "numDifferingPixels": 120000, + "percentDifferingPixels": 75.0, + "perceptualDifference": 50.122499999999995, + "weightedDiffMeasure": 8.413046264257337 + }, + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "gpu", + "resultType": "failed", + "test": "texdata" + }, + "imageAUrl": "bitmap-64bitMD5/texdata/2736593828543197285.png", + "imageBUrl": "bitmap-64bitMD5/texdata/3695033638604474475.png", + "isDifferent": true + }, { - "actualHashDigest": "3695033638604474475", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "gpu", - "expectedHashDigest": "2736593828543197285", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": [ - 128, - 128, - 128 - ], - "numDifferingPixels": 120000, - "percentDifferingPixels": 75.0, - "perceptualDifference": 50.122499999999995, - "resultType": "failed", - "reviewed-by-human": null, - "test": "texdata", - "weightedDiffMeasure": 8.413046264257337 + "differenceData": { + "maxDiffPerChannel": [ + 222, + 223, + 222 + ], + "numDifferingPixels": 77691, + "percentDifferingPixels": 17.593070652173914, + "perceptualDifference": 100, + "weightedDiffMeasure": 3.661184599893761 + }, + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "565", + "resultType": "failure-ignored", + "test": "filterbitmap_checkerboard_192_192" + }, + "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/13270012198930365496.png", + "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/4719210487426381700.png", + "isDifferent": true }, { - "actualHashDigest": "4719210487426381700", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "13270012198930365496", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 222, - 223, - 222 - ], - "numDifferingPixels": 77691, - "percentDifferingPixels": 17.593070652173914, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_192_192", - "weightedDiffMeasure": 3.661184599893761 + "differenceData": { + "maxDiffPerChannel": [ + 221, + 221, + 221 + ], + "numDifferingPixels": 82952, + "percentDifferingPixels": 18.784420289855074, + "perceptualDifference": 100, + "weightedDiffMeasure": 3.6140912497422955 + }, + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "8888", + "resultType": "failure-ignored", + "test": "filterbitmap_checkerboard_192_192" + }, + "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/14746826424040775628.png", + "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_192_192/3154864687054945306.png", + "isDifferent": true }, { - "actualHashDigest": "3154864687054945306", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "14746826424040775628", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 221, - 221, - 221 - ], - "numDifferingPixels": 82952, - "percentDifferingPixels": 18.784420289855074, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_192_192", - "weightedDiffMeasure": 3.6140912497422955 + "differenceData": { + "maxDiffPerChannel": [ + 222, + 223, + 222 + ], + "numDifferingPixels": 53150, + "percentDifferingPixels": 12.035778985507246, + "perceptualDifference": 100, + "weightedDiffMeasure": 3.713243437353155 + }, + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "565", + "resultType": "failure-ignored", + "test": "filterbitmap_checkerboard_32_2" + }, + "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/16197252621792695154.png", + "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/15528304435129737588.png", + "isDifferent": true }, { - "actualHashDigest": "15528304435129737588", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "16197252621792695154", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 222, - 223, - 222 - ], - "numDifferingPixels": 53150, - "percentDifferingPixels": 12.035778985507246, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_32_2", - "weightedDiffMeasure": 3.713243437353155 + "differenceData": { + "maxDiffPerChannel": [ + 221, + 221, + 221 + ], + "numDifferingPixels": 53773, + "percentDifferingPixels": 12.17685688405797, + "perceptualDifference": 100, + "weightedDiffMeasure": 3.6723483686597684 + }, + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "8888", + "resultType": "failure-ignored", + "test": "filterbitmap_checkerboard_32_2" + }, + "imageAUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/7634650333321761866.png", + "imageBUrl": "bitmap-64bitMD5/filterbitmap_checkerboard_32_2/712827739969462165.png", + "isDifferent": true }, { - "actualHashDigest": "712827739969462165", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "7634650333321761866", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": [ - 221, - 221, - 221 - ], - "numDifferingPixels": 53773, - "percentDifferingPixels": 12.17685688405797, - "perceptualDifference": 100, - "resultType": "failure-ignored", - "reviewed-by-human": false, - "test": "filterbitmap_checkerboard_32_2", - "weightedDiffMeasure": 3.6723483686597684 + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "565", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/2422083043229439955.png", + "isDifferent": false }, { - "actualHashDigest": "2422083043229439955", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "8888", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", + "isDifferent": false }, { - "actualHashDigest": "17309852422285247848", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "565", + "resultType": "no-comparison", + "test": "bitmapsource" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bitmapsource/17503582803589749280.png", + "isDifferent": false }, { - "actualHashDigest": "17503582803589749280", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bitmapsource" + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "8888", + "resultType": "no-comparison", + "test": "bitmapsource" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bitmapsource/16289727936158057543.png", + "isDifferent": false }, { - "actualHashDigest": "16289727936158057543", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bitmapsource" + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "565", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", + "isDifferent": false }, { - "actualHashDigest": "16998423976396106083", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "16998423976396106083", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "8888", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", + "isDifferent": false }, { - "actualHashDigest": "2054956815327187963", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "2054956815327187963", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "565", + "resultType": "succeeded", + "test": "aaclip" + }, + "imageAUrl": "bitmap-64bitMD5/aaclip/6190901827590820995.png", + "imageBUrl": "bitmap-64bitMD5/aaclip/6190901827590820995.png", + "isDifferent": false }, { - "actualHashDigest": "6190901827590820995", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "565", - "expectedHashDigest": "6190901827590820995", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "aaclip", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", + "config": "8888", + "resultType": "succeeded", + "test": "aaclip" + }, + "imageAUrl": "bitmap-64bitMD5/aaclip/14456211900777561488.png", + "imageBUrl": "bitmap-64bitMD5/aaclip/14456211900777561488.png", + "isDifferent": false }, { - "actualHashDigest": "14456211900777561488", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Android-GalaxyNexus-SGX540-Arm7-Release", - "config": "8888", - "expectedHashDigest": "14456211900777561488", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "aaclip", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "565", + "resultType": "succeeded", + "test": "displacement" + }, + "imageAUrl": "bitmap-64bitMD5/displacement/4569468668668628191.png", + "imageBUrl": "bitmap-64bitMD5/displacement/4569468668668628191.png", + "isDifferent": false }, { - "actualHashDigest": "4569468668668628191", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "expectedHashDigest": "4569468668668628191", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "8888", + "resultType": "succeeded", + "test": "displacement" + }, + "imageAUrl": "bitmap-64bitMD5/displacement/11401048196735046263.png", + "imageBUrl": "bitmap-64bitMD5/displacement/11401048196735046263.png", + "isDifferent": false }, { - "actualHashDigest": "11401048196735046263", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "expectedHashDigest": "11401048196735046263", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "gpu", + "resultType": "succeeded", + "test": "displacement" + }, + "imageAUrl": "bitmap-64bitMD5/displacement/5698561127291561694.png", + "imageBUrl": "bitmap-64bitMD5/displacement/5698561127291561694.png", + "isDifferent": false }, { - "actualHashDigest": "5698561127291561694", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "expectedHashDigest": "5698561127291561694", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": null, + "ignore-failure": false, + "reviewed-by-human": null + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "pdf-mac", + "resultType": "succeeded", + "test": "displacement" + }, + "imageAUrl": "bitmap-64bitMD5/displacement/12901125495691049846.png", + "imageBUrl": "bitmap-64bitMD5/displacement/12901125495691049846.png", + "isDifferent": false }, { - "actualHashDigest": "12901125495691049846", - "actualHashType": "bitmap-64bitMD5", - "bugs": null, - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "expectedHashDigest": "12901125495691049846", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": false, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": null, - "test": "displacement", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "pdf-poppler", + "resultType": "succeeded", + "test": "displacement" + }, + "imageAUrl": "bitmap-64bitMD5/displacement/16285974094717334658.png", + "imageBUrl": "bitmap-64bitMD5/displacement/16285974094717334658.png", + "isDifferent": false }, { - "actualHashDigest": "16285974094717334658", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "expectedHashDigest": "16285974094717334658", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "displacement", - "weightedDiffMeasure": 0 + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "565", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/14704206703218007573.png", + "isDifferent": false }, { - "actualHashDigest": "14704206703218007573", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "8888", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/17309852422285247848.png", + "isDifferent": false }, { - "actualHashDigest": "17309852422285247848", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "gpu", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/1822195599289208664.png", + "isDifferent": false }, { - "actualHashDigest": "1822195599289208664", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "pdf-mac", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/16171608477794909861.png", + "isDifferent": false }, { - "actualHashDigest": "16171608477794909861", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "pdf-poppler", + "resultType": "no-comparison", + "test": "bigblurs" + }, + "imageAUrl": null, + "imageBUrl": "bitmap-64bitMD5/bigblurs/6539050160610613353.png", + "isDifferent": false }, { - "actualHashDigest": "6539050160610613353", - "actualHashType": "bitmap-64bitMD5", - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "expectedHashDigest": "None", - "expectedHashType": null, - "ignore-failure": false, - "resultType": "no-comparison", - "test": "bigblurs" + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "565", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/16998423976396106083.png", + "isDifferent": false }, { - "actualHashDigest": "16998423976396106083", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "565", - "expectedHashDigest": "16998423976396106083", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "8888", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", + "isDifferent": false }, { - "actualHashDigest": "2054956815327187963", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "8888", - "expectedHashDigest": "2054956815327187963", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "gpu", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/2054956815327187963.png", + "isDifferent": false }, { - "actualHashDigest": "2054956815327187963", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "gpu", - "expectedHashDigest": "2054956815327187963", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "pdf-mac", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/8518347971308375604.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/8518347971308375604.png", + "isDifferent": false }, { - "actualHashDigest": "8518347971308375604", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-mac", - "expectedHashDigest": "8518347971308375604", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "expectations": { + "bugs": [ + 1578 + ], + "ignore-failure": null, + "reviewed-by-human": false + }, + "extraColumns": { + "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", + "config": "pdf-poppler", + "resultType": "succeeded", + "test": "3x3bitmaprect" + }, + "imageAUrl": "bitmap-64bitMD5/3x3bitmaprect/16723580409414313678.png", + "imageBUrl": "bitmap-64bitMD5/3x3bitmaprect/16723580409414313678.png", + "isDifferent": false + } + ], + "imageSets": [ + { + "baseUrl": "http://chromium-skia-gm.commondatastorage.googleapis.com/gm", + "description": "expected image" }, { - "actualHashDigest": "16723580409414313678", - "actualHashType": "bitmap-64bitMD5", - "bugs": [ - 1578 - ], - "builder": "Test-Mac10.7-MacMini4.1-GeForce320M-x86_64-Debug", - "config": "pdf-poppler", - "expectedHashDigest": "16723580409414313678", - "expectedHashType": "bitmap-64bitMD5", - "ignore-failure": null, - "maxDiffPerChannel": 0, - "numDifferingPixels": 0, - "percentDifferingPixels": 0, - "perceptualDifference": 0, - "resultType": "succeeded", - "reviewed-by-human": false, - "test": "3x3bitmaprect", - "weightedDiffMeasure": 0 + "baseUrl": "http://chromium-skia-gm.commondatastorage.googleapis.com/gm", + "description": "actual image" } ] }
\ No newline at end of file |