aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gm/gm_json.py6
-rwxr-xr-xtools/jsondiff.py175
-rw-r--r--tools/tests/jsondiff/input/new.json22
-rw-r--r--tools/tests/jsondiff/input/old.json22
-rw-r--r--tools/tests/jsondiff/output/old-vs-new/output-expected/command_line1
-rw-r--r--tools/tests/jsondiff/output/old-vs-new/output-expected/return_value1
-rw-r--r--tools/tests/jsondiff/output/old-vs-new/output-expected/stdout14
-rwxr-xr-xtools/tests/rebaseline.sh2
-rwxr-xr-xtools/tests/run.sh30
9 files changed, 272 insertions, 1 deletions
diff --git a/gm/gm_json.py b/gm/gm_json.py
index 6ba3a261a5..cd4198415c 100644
--- a/gm/gm_json.py
+++ b/gm/gm_json.py
@@ -23,6 +23,12 @@ JSONKEY_ACTUALRESULTS_FAILUREIGNORED = 'failure-ignored'
JSONKEY_ACTUALRESULTS_NOCOMPARISON = 'no-comparison'
JSONKEY_ACTUALRESULTS_SUCCEEDED = 'succeeded'
+JSONKEY_EXPECTEDRESULTS = 'expected-results'
+JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS = 'allowed-digests'
+JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE = 'ignore-failure'
+
+JSONKEY_HASHTYPE_BITMAP_64BITMD5 = 'bitmap-64bitMD5'
+
def LoadFromString(file_contents):
"""Loads the JSON summary written out by the GM tool.
Returns a dictionary keyed by the values listed as JSONKEY_ constants
diff --git a/tools/jsondiff.py b/tools/jsondiff.py
new file mode 100755
index 0000000000..a1ca257572
--- /dev/null
+++ b/tools/jsondiff.py
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+
+'''
+Copyright 2013 Google Inc.
+
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+'''
+
+'''
+Gathers diffs between 2 JSON expectations files, or between actual and
+expected results within a single JSON actual-results file,
+and generates an old-vs-new diff dictionary.
+'''
+
+# System-level imports
+import argparse
+import json
+import os
+import sys
+import urllib2
+
+# Imports from within Skia
+#
+# We need to add the 'gm' directory, so that we can import gm_json.py within
+# that directory. That script allows us to parse the actual-results.json file
+# written out by the GM tool.
+# Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
+# so any dirs that are already in the PYTHONPATH will be preferred.
+#
+# This assumes that the 'gm' directory has been checked out as a sibling of
+# the 'tools' directory containing this script, which will be the case if
+# 'trunk' was checked out as a single unit.
+GM_DIRECTORY = os.path.realpath(
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm'))
+if GM_DIRECTORY not in sys.path:
+ sys.path.append(GM_DIRECTORY)
+import gm_json
+
+
+# Object that generates diffs between two JSON gm result files.
+class GMDiffer(object):
+
+ def __init__(self):
+ pass
+
+ def _GetFileContentsAsString(self, filepath):
+ """Returns the full contents of a file, as a single string.
+ If the filename looks like a URL, download its contents..."""
+ if filepath.startswith('http:') or filepath.startswith('https:'):
+ return urllib2.urlopen(filepath).read()
+ else:
+ return open(filepath, 'r').read()
+
+ def _GetExpectedResults(self, filepath):
+ """Returns the dictionary of expected results from a JSON file,
+ in this form:
+
+ {
+ 'test1' : 14760033689012826769,
+ 'test2' : 9151974350149210736,
+ ...
+ }
+
+ We make these simplifying assumptions:
+ 1. Each test has either 0 or 1 allowed results.
+ 2. All expectations are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5.
+
+ Any tests which violate those assumptions will cause an exception to
+ be raised.
+
+ Any tests for which we have no expectations will be left out of the
+ returned dictionary.
+ """
+ result_dict = {}
+ contents = self._GetFileContentsAsString(filepath)
+ json_dict = gm_json.LoadFromString(contents)
+ all_expectations = json_dict[gm_json.JSONKEY_EXPECTEDRESULTS]
+ for test_name in all_expectations.keys():
+ test_expectations = all_expectations[test_name]
+ allowed_digests = test_expectations[
+ gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]
+ if allowed_digests:
+ num_allowed_digests = len(allowed_digests)
+ if num_allowed_digests > 1:
+ raise ValueError(
+ 'test %s in file %s has %d allowed digests' % (
+ test_name, filepath, num_allowed_digests))
+ digest_pair = allowed_digests[0]
+ if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5:
+ raise ValueError(
+ 'test %s in file %s has unsupported hashtype %s' % (
+ test_name, filepath, digest_pair[0]))
+ result_dict[test_name] = digest_pair[1]
+ return result_dict
+
+ def _GetActualResults(self, filepath):
+ """Returns the dictionary of actual results from a JSON file,
+ in this form:
+
+ {
+ 'test1' : 14760033689012826769,
+ 'test2' : 9151974350149210736,
+ ...
+ }
+
+ We make these simplifying assumptions:
+ 1. All results are of type JSONKEY_HASHTYPE_BITMAP_64BITMD5.
+
+ Any tests which violate those assumptions will cause an exception to
+ be raised.
+
+ Any tests for which we have no actual results will be left out of the
+ returned dictionary.
+ """
+ result_dict = {}
+ contents = self._GetFileContentsAsString(filepath)
+ json_dict = gm_json.LoadFromString(contents)
+ all_result_types = json_dict[gm_json.JSONKEY_ACTUALRESULTS]
+ for result_type in all_result_types.keys():
+ results_of_this_type = all_result_types[result_type]
+ if results_of_this_type:
+ for test_name in results_of_this_type.keys():
+ digest_pair = results_of_this_type[test_name]
+ if digest_pair[0] != gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5:
+ raise ValueError(
+ 'test %s in file %s has unsupported hashtype %s' % (
+ test_name, filepath, digest_pair[0]))
+ result_dict[test_name] = digest_pair[1]
+ return result_dict
+
+ def _DictionaryDiff(self, old_dict, new_dict):
+ """Generate a dictionary showing the diffs between old_dict and new_dict.
+ Any entries which are identical across them will be left out."""
+ diff_dict = {}
+ all_keys = set(old_dict.keys() + new_dict.keys())
+ for key in all_keys:
+ if old_dict.get(key) != new_dict.get(key):
+ new_entry = {}
+ new_entry['old'] = old_dict.get(key)
+ new_entry['new'] = new_dict.get(key)
+ diff_dict[key] = new_entry
+ return diff_dict
+
+ def GenerateDiffDict(self, oldfile, newfile=None):
+ """Generate a dictionary showing the diffs:
+ old = expectations within oldfile
+ new = expectations within newfile
+
+ If newfile is not specified, then 'new' is the actual results within
+ oldfile.
+ """
+ old_results = self._GetExpectedResults(oldfile)
+ if newfile:
+ new_results = self._GetExpectedResults(newfile)
+ else:
+ new_results = self._GetActualResults(oldfile)
+ return self._DictionaryDiff(old_results, new_results)
+
+
+# main...
+parser = argparse.ArgumentParser()
+parser.add_argument('old',
+ help='Path to JSON file whose expectations to display on ' +
+ 'the "old" side of the diff. This can be a filepath on ' +
+ 'local storage, or a URL.')
+parser.add_argument('new', nargs='?',
+ help='Path to JSON file whose expectations to display on ' +
+ 'the "new" side of the diff; if not specified, uses the ' +
+ 'ACTUAL results from the "old" JSON file. This can be a ' +
+ 'filepath on local storage, or a URL.')
+args = parser.parse_args()
+differ = GMDiffer()
+diffs = differ.GenerateDiffDict(oldfile=args.old, newfile=args.new)
+json.dump(diffs, sys.stdout, sort_keys=True, indent=2)
diff --git a/tools/tests/jsondiff/input/new.json b/tools/tests/jsondiff/input/new.json
new file mode 100644
index 0000000000..0bf352f5f7
--- /dev/null
+++ b/tools/tests/jsondiff/input/new.json
@@ -0,0 +1,22 @@
+{
+ "expected-results" : {
+ "identical.png" : {
+ "allowed-digests" : [
+ [ "bitmap-64bitMD5", 1111111 ]
+ ],
+ "ignore-failure" : false
+ },
+ "differing.png" : {
+ "allowed-digests" : [
+ [ "bitmap-64bitMD5", 777777777 ]
+ ],
+ "ignore-failure" : false
+ },
+ "missing-from-old.png" : {
+ "allowed-digests" : [
+ [ "bitmap-64bitMD5", 3333333 ]
+ ],
+ "ignore-failure" : false
+ }
+ }
+}
diff --git a/tools/tests/jsondiff/input/old.json b/tools/tests/jsondiff/input/old.json
new file mode 100644
index 0000000000..b599b99fec
--- /dev/null
+++ b/tools/tests/jsondiff/input/old.json
@@ -0,0 +1,22 @@
+{
+ "expected-results" : {
+ "identical.png" : {
+ "allowed-digests" : [
+ [ "bitmap-64bitMD5", 1111111 ]
+ ],
+ "ignore-failure" : false
+ },
+ "differing.png" : {
+ "allowed-digests" : [
+ [ "bitmap-64bitMD5", 888888888 ]
+ ],
+ "ignore-failure" : false
+ },
+ "missing-from-new.png" : {
+ "allowed-digests" : [
+ [ "bitmap-64bitMD5", 44444444 ]
+ ],
+ "ignore-failure" : false
+ }
+ }
+}
diff --git a/tools/tests/jsondiff/output/old-vs-new/output-expected/command_line b/tools/tests/jsondiff/output/old-vs-new/output-expected/command_line
new file mode 100644
index 0000000000..4d5c36bde7
--- /dev/null
+++ b/tools/tests/jsondiff/output/old-vs-new/output-expected/command_line
@@ -0,0 +1 @@
+python tools/jsondiff.py tools/tests/jsondiff/input/old.json tools/tests/jsondiff/input/new.json
diff --git a/tools/tests/jsondiff/output/old-vs-new/output-expected/return_value b/tools/tests/jsondiff/output/old-vs-new/output-expected/return_value
new file mode 100644
index 0000000000..573541ac97
--- /dev/null
+++ b/tools/tests/jsondiff/output/old-vs-new/output-expected/return_value
@@ -0,0 +1 @@
+0
diff --git a/tools/tests/jsondiff/output/old-vs-new/output-expected/stdout b/tools/tests/jsondiff/output/old-vs-new/output-expected/stdout
new file mode 100644
index 0000000000..212c305bf1
--- /dev/null
+++ b/tools/tests/jsondiff/output/old-vs-new/output-expected/stdout
@@ -0,0 +1,14 @@
+{
+ "differing.png": {
+ "new": 777777777,
+ "old": 888888888
+ },
+ "missing-from-new.png": {
+ "new": null,
+ "old": 44444444
+ },
+ "missing-from-old.png": {
+ "new": 3333333,
+ "old": null
+ }
+} \ No newline at end of file
diff --git a/tools/tests/rebaseline.sh b/tools/tests/rebaseline.sh
index 38f19499ed..4fe044dbbe 100755
--- a/tools/tests/rebaseline.sh
+++ b/tools/tests/rebaseline.sh
@@ -80,7 +80,7 @@ cd $(dirname $0)
./run.sh
SELFTEST_RESULT=$?
-SUBDIRS="skdiff benchgraphs rebaseline/output"
+SUBDIRS="skdiff benchgraphs rebaseline/output jsondiff/output"
echo
if [ "$SELFTEST_RESULT" != "0" ]; then
for SUBDIR in $SUBDIRS; do
diff --git a/tools/tests/run.sh b/tools/tests/run.sh
index 2b919cb884..3f76de1b58 100755
--- a/tools/tests/run.sh
+++ b/tools/tests/run.sh
@@ -150,6 +150,27 @@ function rebaseline_test {
compare_directories $EXPECTED_OUTPUT_DIR $ACTUAL_OUTPUT_DIR
}
+# Run jsondiff.py with arguments in $1, recording its output.
+# Then compare that output to the content of $2/output-expected.
+function jsondiff_test {
+ if [ $# != 2 ]; then
+ echo "jsondiff_test requires exactly 2 parameters, got $#"
+ exit 1
+ fi
+ ARGS="$1"
+ ACTUAL_OUTPUT_DIR="$2/output-actual"
+ EXPECTED_OUTPUT_DIR="$2/output-expected"
+
+ rm -rf $ACTUAL_OUTPUT_DIR
+ mkdir -p $ACTUAL_OUTPUT_DIR
+ COMMAND="python tools/jsondiff.py $ARGS"
+ echo "$COMMAND" >$ACTUAL_OUTPUT_DIR/command_line
+ $COMMAND &>$ACTUAL_OUTPUT_DIR/stdout
+ echo $? >$ACTUAL_OUTPUT_DIR/return_value
+
+ compare_directories $EXPECTED_OUTPUT_DIR $ACTUAL_OUTPUT_DIR
+}
+
#
@@ -215,4 +236,13 @@ rebaseline_test "--json-base-url file:$REBASELINE_INPUT/json1 --subdirs base-and
rebaseline_test "--json-base-url file:$REBASELINE_INPUT/json1 --subdirs base-android-galaxy-nexus base-shuttle-win7-intel-float --add-new" "$REBASELINE_OUTPUT/using-json1-add-new"
rebaseline_test "--json-base-url file:$REBASELINE_INPUT/json1 --subdirs base-android-galaxy-nexus base-shuttle-win7-intel-float --expectations-root $REBASELINE_INPUT/json1" "$REBASELINE_OUTPUT/using-json1-expectations"
+#
+# Test jsondiff.py ...
+#
+
+JSONDIFF_INPUT=tools/tests/jsondiff/input
+JSONDIFF_OUTPUT=tools/tests/jsondiff/output
+jsondiff_test "$JSONDIFF_INPUT/old.json $JSONDIFF_INPUT/new.json" "$JSONDIFF_OUTPUT/old-vs-new"
+
+
echo "All tests passed."