GM: specify that currently used checksums are CityHashes of SkBitmaps
(distinct from other hash algorithms we may switch to, or hashes of
files such as PDFs that cannot be read into an SkBitmap)

Review URL: https://codereview.chromium.org/14284018

git-svn-id: http://skia.googlecode.com/svn/trunk@8877 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/gm_expectations.cpp b/gm/gm_expectations.cpp
new file mode 100644
index 0000000..1aa1ef4
--- /dev/null
+++ b/gm/gm_expectations.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm_expectations.h"
+#include "SkBitmapHasher.h"
+#include "SkImageDecoder.h"
+
+#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
+
+const static char kJsonKey_ActualResults[]   = "actual-results";
+const static char kJsonKey_ActualResults_Failed[]        = "failed";
+const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored";
+const static char kJsonKey_ActualResults_NoComparison[]  = "no-comparison";
+const static char kJsonKey_ActualResults_Succeeded[]     = "succeeded";
+const static char kJsonKey_ActualResults_AnyStatus_BitmapCityhash[]  = "bitmap-cityhash";
+
+const static char kJsonKey_ExpectedResults[] = "expected-results";
+const static char kJsonKey_ExpectedResults_AllowedBitmapCityhashes[] = "allowed-bitmap-cityhashes";
+const static char kJsonKey_ExpectedResults_IgnoreFailure[]           = "ignore-failure";
+
+namespace skiagm {
+
+    // TODO(epoger): This currently assumes that the result SkHashDigest was
+    // generated as a CityHash of an SkBitmap.  We'll need to allow for other
+    // hash types to cover non-bitmaps, MD5 instead of CityHash, etc.
+    Json::Value ActualResultAsJsonValue(const SkHashDigest& result) {
+        Json::Value jsonValue;
+        jsonValue[kJsonKey_ActualResults_AnyStatus_BitmapCityhash] = asJsonValue(result);
+        return jsonValue;
+    }
+
+    Json::Value CreateJsonTree(Json::Value expectedResults,
+                               Json::Value actualResultsFailed,
+                               Json::Value actualResultsFailureIgnored,
+                               Json::Value actualResultsNoComparison,
+                               Json::Value actualResultsSucceeded) {
+        Json::Value actualResults;
+        actualResults[kJsonKey_ActualResults_Failed] = actualResultsFailed;
+        actualResults[kJsonKey_ActualResults_FailureIgnored] = actualResultsFailureIgnored;
+        actualResults[kJsonKey_ActualResults_NoComparison] = actualResultsNoComparison;
+        actualResults[kJsonKey_ActualResults_Succeeded] = actualResultsSucceeded;
+        Json::Value root;
+        root[kJsonKey_ActualResults] = actualResults;
+        root[kJsonKey_ExpectedResults] = expectedResults;
+        return root;
+    }
+
+
+    // Expectations class...
+
+    Expectations::Expectations(bool ignoreFailure) {
+        fIgnoreFailure = ignoreFailure;
+    }
+
+    Expectations::Expectations(const SkBitmap& bitmap, bool ignoreFailure) {
+        fBitmap = bitmap;
+        fIgnoreFailure = ignoreFailure;
+        SkHashDigest digest;
+        // TODO(epoger): Better handling for error returned by ComputeDigest()?
+        // For now, we just report a digest of 0 in error cases, like before.
+        if (!SkBitmapHasher::ComputeDigest(bitmap, &digest)) {
+            digest = 0;
+        }
+        fAllowedBitmapCityhashes.push_back() = digest;
+    }
+
+    Expectations::Expectations(Json::Value jsonElement) {
+        if (jsonElement.empty()) {
+            fIgnoreFailure = kDefaultIgnoreFailure;
+        } else {
+            Json::Value ignoreFailure = jsonElement[kJsonKey_ExpectedResults_IgnoreFailure];
+            if (ignoreFailure.isNull()) {
+                fIgnoreFailure = kDefaultIgnoreFailure;
+            } else if (!ignoreFailure.isBool()) {
+                gm_fprintf(stderr, "found non-boolean json value"
+                           " for key '%s' in element '%s'\n",
+                           kJsonKey_ExpectedResults_IgnoreFailure,
+                           jsonElement.toStyledString().c_str());
+                DEBUGFAIL_SEE_STDERR;
+                fIgnoreFailure = kDefaultIgnoreFailure;
+            } else {
+                fIgnoreFailure = ignoreFailure.asBool();
+            }
+
+            Json::Value allowedChecksums =
+                jsonElement[kJsonKey_ExpectedResults_AllowedBitmapCityhashes];
+            if (allowedChecksums.isNull()) {
+                // ok, we'll just assume there aren't any expected checksums to compare against
+            } else if (!allowedChecksums.isArray()) {
+                gm_fprintf(stderr, "found non-array json value"
+                           " for key '%s' in element '%s'\n",
+                           kJsonKey_ExpectedResults_AllowedBitmapCityhashes,
+                           jsonElement.toStyledString().c_str());
+                DEBUGFAIL_SEE_STDERR;
+            } else {
+                for (Json::ArrayIndex i=0; i<allowedChecksums.size(); i++) {
+                    Json::Value checksumElement = allowedChecksums[i];
+                    if (!checksumElement.isIntegral()) {
+                        gm_fprintf(stderr, "found non-integer checksum"
+                                   " in json element '%s'\n",
+                                   jsonElement.toStyledString().c_str());
+                        DEBUGFAIL_SEE_STDERR;
+                    } else {
+                        fAllowedBitmapCityhashes.push_back() = asChecksum(checksumElement);
+                    }
+                }
+            }
+        }
+    }
+
+    bool Expectations::match(Checksum actualChecksum) const {
+        for (int i=0; i < this->fAllowedBitmapCityhashes.count(); i++) {
+            Checksum allowedChecksum = this->fAllowedBitmapCityhashes[i];
+            if (allowedChecksum == actualChecksum) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    Json::Value Expectations::asJsonValue() const {
+        Json::Value allowedChecksumArray;
+        if (!this->fAllowedBitmapCityhashes.empty()) {
+            for (int i=0; i < this->fAllowedBitmapCityhashes.count(); i++) {
+                Checksum allowedChecksum = this->fAllowedBitmapCityhashes[i];
+                allowedChecksumArray.append(Json::UInt64(allowedChecksum));
+            }
+        }
+
+        Json::Value jsonValue;
+        jsonValue[kJsonKey_ExpectedResults_AllowedBitmapCityhashes] = allowedChecksumArray;
+        jsonValue[kJsonKey_ExpectedResults_IgnoreFailure] = this->ignoreFailure();
+        return jsonValue;
+    }
+
+
+    // IndividualImageExpectationsSource class...
+
+    Expectations IndividualImageExpectationsSource::get(const char *testName) {
+        SkString path = make_filename(fRootDir.c_str(), "", testName,
+                                      "png");
+        SkBitmap referenceBitmap;
+        bool decodedReferenceBitmap =
+            SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
+                                       SkBitmap::kARGB_8888_Config,
+                                       SkImageDecoder::kDecodePixels_Mode,
+                                       NULL);
+        if (decodedReferenceBitmap) {
+            return Expectations(referenceBitmap);
+        } else {
+            return Expectations();
+        }
+    }
+
+
+    // JsonExpectationsSource class...
+
+    JsonExpectationsSource::JsonExpectationsSource(const char *jsonPath) {
+        parse(jsonPath, &fJsonRoot);
+        fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
+    }
+
+    Expectations JsonExpectationsSource::get(const char *testName) {
+        return Expectations(fJsonExpectedResults[testName]);
+    }
+
+    /*static*/ SkData* JsonExpectationsSource::readIntoSkData(SkStream &stream, size_t maxBytes) {
+        if (0 == maxBytes) {
+            return SkData::NewEmpty();
+        }
+        char* bufStart = reinterpret_cast<char *>(sk_malloc_throw(maxBytes));
+        char* bufPtr = bufStart;
+        size_t bytesRemaining = maxBytes;
+        while (bytesRemaining > 0) {
+            size_t bytesReadThisTime = stream.read(bufPtr, bytesRemaining);
+            if (0 == bytesReadThisTime) {
+                break;
+            }
+            bytesRemaining -= bytesReadThisTime;
+            bufPtr += bytesReadThisTime;
+        }
+        return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
+    }
+
+    /*static*/ bool JsonExpectationsSource::parse(const char *jsonPath, Json::Value *jsonRoot) {
+        SkFILEStream inFile(jsonPath);
+        if (!inFile.isValid()) {
+            gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
+            DEBUGFAIL_SEE_STDERR;
+            return false;
+        }
+
+        SkAutoDataUnref dataRef(readFileIntoSkData(inFile));
+        if (NULL == dataRef.get()) {
+            gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
+            DEBUGFAIL_SEE_STDERR;
+            return false;
+        }
+
+        const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
+        size_t size = dataRef.get()->size();
+        Json::Reader reader;
+        if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
+            gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath);
+            DEBUGFAIL_SEE_STDERR;
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/gm/gm_expectations.h b/gm/gm_expectations.h
index 84cb7fe..df99c6e 100644
--- a/gm/gm_expectations.h
+++ b/gm/gm_expectations.h
@@ -7,12 +7,10 @@
 #ifndef gm_expectations_DEFINED
 #define gm_expectations_DEFINED
 
-#include <stdarg.h>
 #include "gm.h"
 #include "SkBitmap.h"
 #include "SkBitmapHasher.h"
 #include "SkData.h"
-#include "SkImageDecoder.h"
 #include "SkOSFile.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
@@ -30,19 +28,6 @@
     #pragma warning(pop)
 #endif
 
-#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
-
-const static char kJsonKey_ActualResults[]   = "actual-results";
-const static char kJsonKey_ActualResults_Failed[]        = "failed";
-const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored";
-const static char kJsonKey_ActualResults_NoComparison[]  = "no-comparison";
-const static char kJsonKey_ActualResults_Succeeded[]     = "succeeded";
-const static char kJsonKey_ActualResults_AnyStatus_Checksum[]    = "checksum";
-
-const static char kJsonKey_ExpectedResults[] = "expected-results";
-const static char kJsonKey_ExpectedResults_Checksums[]     = "checksums";
-const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure";
-
 namespace skiagm {
 
     // The actual type we use to represent a checksum is hidden in here.
@@ -75,6 +60,14 @@
         return filename;
     }
 
+    Json::Value ActualResultAsJsonValue(const SkHashDigest& result);
+
+    Json::Value CreateJsonTree(Json::Value expectedResults,
+                               Json::Value actualResultsFailed,
+                               Json::Value actualResultsFailureIgnored,
+                               Json::Value actualResultsNoComparison,
+                               Json::Value actualResultsSucceeded);
+
     /**
      * Test expectations (allowed image checksums, etc.)
      */
@@ -83,25 +76,13 @@
         /**
          * No expectations at all.
          */
-        Expectations(bool ignoreFailure=kDefaultIgnoreFailure) {
-            fIgnoreFailure = ignoreFailure;
-        }
+        Expectations(bool ignoreFailure=kDefaultIgnoreFailure);
 
         /**
          * Expect exactly one image (appropriate for the case when we
          * are comparing against a single PNG file).
          */
-        Expectations(const SkBitmap& bitmap, bool ignoreFailure=kDefaultIgnoreFailure) {
-            fBitmap = bitmap;
-            fIgnoreFailure = ignoreFailure;
-            SkHashDigest digest;
-            // TODO(epoger): Better handling for error returned by ComputeDigest()?
-            // For now, we just report a digest of 0 in error cases, like before.
-            if (!SkBitmapHasher::ComputeDigest(bitmap, &digest)) {
-                digest = 0;
-            }
-            fAllowedChecksums.push_back() = digest;
-        }
+        Expectations(const SkBitmap& bitmap, bool ignoreFailure=kDefaultIgnoreFailure);
 
         /**
          * Create Expectations from a JSON element as found within the
@@ -110,48 +91,7 @@
          * It's fine if the jsonElement is null or empty; in that case, we just
          * don't have any expectations.
          */
-        Expectations(Json::Value jsonElement) {
-            if (jsonElement.empty()) {
-                fIgnoreFailure = kDefaultIgnoreFailure;
-            } else {
-                Json::Value ignoreFailure = jsonElement[kJsonKey_ExpectedResults_IgnoreFailure];
-                if (ignoreFailure.isNull()) {
-                    fIgnoreFailure = kDefaultIgnoreFailure;
-                } else if (!ignoreFailure.isBool()) {
-                    gm_fprintf(stderr, "found non-boolean json value"
-                               " for key '%s' in element '%s'\n",
-                               kJsonKey_ExpectedResults_IgnoreFailure,
-                               jsonElement.toStyledString().c_str());
-                    DEBUGFAIL_SEE_STDERR;
-                    fIgnoreFailure = kDefaultIgnoreFailure;
-                } else {
-                    fIgnoreFailure = ignoreFailure.asBool();
-                }
-
-                Json::Value allowedChecksums = jsonElement[kJsonKey_ExpectedResults_Checksums];
-                if (allowedChecksums.isNull()) {
-                    // ok, we'll just assume there aren't any expected checksums to compare against
-                } else if (!allowedChecksums.isArray()) {
-                    gm_fprintf(stderr, "found non-array json value"
-                               " for key '%s' in element '%s'\n",
-                               kJsonKey_ExpectedResults_Checksums,
-                               jsonElement.toStyledString().c_str());
-                    DEBUGFAIL_SEE_STDERR;
-                } else {
-                    for (Json::ArrayIndex i=0; i<allowedChecksums.size(); i++) {
-                        Json::Value checksumElement = allowedChecksums[i];
-                        if (!checksumElement.isIntegral()) {
-                            gm_fprintf(stderr, "found non-integer checksum"
-                                       " in json element '%s'\n",
-                                       jsonElement.toStyledString().c_str());
-                            DEBUGFAIL_SEE_STDERR;
-                        } else {
-                            fAllowedChecksums.push_back() = asChecksum(checksumElement);
-                        }
-                    }
-                }
-            }
-        }
+        Expectations(Json::Value jsonElement);
 
         /**
          * Returns true iff we want to ignore failed expectations.
@@ -161,22 +101,14 @@
         /**
          * Returns true iff there are no allowed checksums.
          */
-        bool empty() const { return this->fAllowedChecksums.empty(); }
+        bool empty() const { return this->fAllowedBitmapCityhashes.empty(); }
 
         /**
          * Returns true iff actualChecksum matches any allowedChecksum,
          * regardless of fIgnoreFailure.  (The caller can check
          * that separately.)
          */
-        bool match(Checksum actualChecksum) const {
-            for (int i=0; i < this->fAllowedChecksums.count(); i++) {
-                Checksum allowedChecksum = this->fAllowedChecksums[i];
-                if (allowedChecksum == actualChecksum) {
-                    return true;
-                }
-            }
-            return false;
-        }
+        bool match(Checksum actualChecksum) const;
 
         /**
          * If this Expectation is based on a single SkBitmap, return a
@@ -189,25 +121,14 @@
         }
 
         /**
-         * Return a JSON representation of the allowed checksums.
-         * This does NOT include any information about whether to
-         * ignore failures.
+         * Return a JSON representation of the expectations.
          */
-        Json::Value allowedChecksumsAsJson() const {
-            Json::Value allowedChecksumArray;
-            if (!this->fAllowedChecksums.empty()) {
-                for (int i=0; i < this->fAllowedChecksums.count(); i++) {
-                    Checksum allowedChecksum = this->fAllowedChecksums[i];
-                    allowedChecksumArray.append(asJsonValue(allowedChecksum));
-                }
-            }
-            return allowedChecksumArray;
-        }
+        Json::Value asJsonValue() const;
 
     private:
         const static bool kDefaultIgnoreFailure = false;
 
-        SkTArray<Checksum> fAllowedChecksums;
+        SkTArray<Checksum> fAllowedBitmapCityhashes;
         bool fIgnoreFailure;
         SkBitmap fBitmap;
     };
@@ -234,21 +155,7 @@
          */
         IndividualImageExpectationsSource(const char *rootDir) : fRootDir(rootDir) {}
 
-        Expectations get(const char *testName) SK_OVERRIDE {
-            SkString path = make_filename(fRootDir.c_str(), "", testName,
-                                          "png");
-            SkBitmap referenceBitmap;
-            bool decodedReferenceBitmap =
-                SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
-                                           SkBitmap::kARGB_8888_Config,
-                                           SkImageDecoder::kDecodePixels_Mode,
-                                           NULL);
-            if (decodedReferenceBitmap) {
-                return Expectations(referenceBitmap);
-            } else {
-                return Expectations();
-            }
-        }
+        Expectations get(const char *testName) SK_OVERRIDE ;
 
     private:
         const SkString fRootDir;
@@ -265,14 +172,9 @@
          *
          * jsonPath: path to JSON file to read
          */
-        JsonExpectationsSource(const char *jsonPath) {
-            parse(jsonPath, &fJsonRoot);
-            fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
-        }
+        JsonExpectationsSource(const char *jsonPath);
 
-        Expectations get(const char *testName) SK_OVERRIDE {
-            return Expectations(fJsonExpectedResults[testName]);
-        }
+        Expectations get(const char *testName) SK_OVERRIDE;
 
     private:
 
@@ -307,23 +209,7 @@
         // TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in
         // https://codereview.appspot.com/7300071 ?
         // And maybe readFileIntoSkData() also?
-        static SkData* readIntoSkData(SkStream &stream, size_t maxBytes) {
-            if (0 == maxBytes) {
-                return SkData::NewEmpty();
-            }
-            char* bufStart = reinterpret_cast<char *>(sk_malloc_throw(maxBytes));
-            char* bufPtr = bufStart;
-            size_t bytesRemaining = maxBytes;
-            while (bytesRemaining > 0) {
-                size_t bytesReadThisTime = stream.read(bufPtr, bytesRemaining);
-                if (0 == bytesReadThisTime) {
-                    break;
-                }
-                bytesRemaining -= bytesReadThisTime;
-                bufPtr += bytesReadThisTime;
-            }
-            return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
-        }
+        static SkData* readIntoSkData(SkStream &stream, size_t maxBytes);
 
         /**
          * Wrapper around readIntoSkData for files: reads the entire file into
@@ -338,31 +224,7 @@
          *
          * Returns true if successful.
          */
-        static bool parse(const char *jsonPath, Json::Value *jsonRoot) {
-            SkFILEStream inFile(jsonPath);
-            if (!inFile.isValid()) {
-                gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
-                DEBUGFAIL_SEE_STDERR;
-                return false;
-            }
-
-            SkAutoDataUnref dataRef(readFileIntoSkData(inFile));
-            if (NULL == dataRef.get()) {
-                gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
-                DEBUGFAIL_SEE_STDERR;
-                return false;
-            }
-
-            const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
-            size_t size = dataRef.get()->size();
-            Json::Reader reader;
-            if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
-                gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath);
-                DEBUGFAIL_SEE_STDERR;
-                return false;
-            }
-            return true;
-        }
+        static bool parse(const char *jsonPath, Json::Value *jsonRoot);
 
         Json::Value fJsonRoot;
         Json::Value fJsonExpectedResults;
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index e68ec81..420b684 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -63,6 +63,8 @@
 typedef int GLContextType;
 #endif
 
+#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
+
 extern bool gSkSuppressFontCachePurgeSpew;
 
 #ifdef SK_SUPPORT_PDF
@@ -768,17 +770,15 @@
 
     /**
      * Add this result to the appropriate JSON collection of actual results,
-     * depending on status.
+     * depending on errors encountered.
      */
     void add_actual_results_to_json_summary(const char testName[],
-                                            const SkHashDigest& actualBitmapHash,
-                                            ErrorCombination result,
+                                            const SkHashDigest& actualResult,
+                                            ErrorCombination errors,
                                             bool ignoreFailure) {
-        Json::Value actualResults;
-        actualResults[kJsonKey_ActualResults_AnyStatus_Checksum] =
-            asJsonValue(actualBitmapHash);
-        if (result.isEmpty()) {
-            this->fJsonActualResults_Succeeded[testName] = actualResults;
+        Json::Value jsonActualResults = ActualResultAsJsonValue(actualResult);
+        if (errors.isEmpty()) {
+            this->fJsonActualResults_Succeeded[testName] = jsonActualResults;
         } else {
             if (ignoreFailure) {
                 // TODO: Once we have added the ability to compare
@@ -788,9 +788,9 @@
                 // failures (both for kMissingExpectations_ErrorType
                 // and kExpectationsMismatch_ErrorType).
                 this->fJsonActualResults_FailureIgnored[testName] =
-                    actualResults;
+                    jsonActualResults;
             } else {
-                if (result.includes(kMissingExpectations_ErrorType)) {
+                if (errors.includes(kMissingExpectations_ErrorType)) {
                     // TODO: What about the case where there IS an
                     // expected image hash digest, but that gm test
                     // doesn't actually run?  For now, those cases
@@ -803,11 +803,11 @@
                     // (and add a test case for which an expectation
                     // is given but the test is never run).
                     this->fJsonActualResults_NoComparison[testName] =
-                        actualResults;
+                        jsonActualResults;
                 }
-                if (result.includes(kExpectationsMismatch_ErrorType) ||
-                    result.includes(kRenderModeMismatch_ErrorType)) {
-                    this->fJsonActualResults_Failed[testName] = actualResults;
+                if (errors.includes(kExpectationsMismatch_ErrorType) ||
+                    errors.includes(kRenderModeMismatch_ErrorType)) {
+                    this->fJsonActualResults_Failed[testName] = jsonActualResults;
                 }
             }
         }
@@ -818,15 +818,7 @@
      */
     void add_expected_results_to_json_summary(const char testName[],
                                               Expectations expectations) {
-        // For now, we assume that this collection starts out empty and we
-        // just fill it in as we go; once gm accepts a JSON file as input,
-        // we'll have to change that.
-        Json::Value expectedResults;
-        expectedResults[kJsonKey_ExpectedResults_Checksums] =
-            expectations.allowedChecksumsAsJson();
-        expectedResults[kJsonKey_ExpectedResults_IgnoreFailure] =
-            expectations.ignoreFailure();
-        this->fJsonExpectedResults[testName] = expectedResults;
+        this->fJsonExpectedResults[testName] = expectations.asJsonValue();
     }
 
     /**
@@ -1959,18 +1951,10 @@
 #endif
 
     if (FLAGS_writeJsonSummaryPath.count() == 1) {
-        Json::Value actualResults;
-        actualResults[kJsonKey_ActualResults_Failed] =
-            gmmain.fJsonActualResults_Failed;
-        actualResults[kJsonKey_ActualResults_FailureIgnored] =
-            gmmain.fJsonActualResults_FailureIgnored;
-        actualResults[kJsonKey_ActualResults_NoComparison] =
-            gmmain.fJsonActualResults_NoComparison;
-        actualResults[kJsonKey_ActualResults_Succeeded] =
-            gmmain.fJsonActualResults_Succeeded;
-        Json::Value root;
-        root[kJsonKey_ActualResults] = actualResults;
-        root[kJsonKey_ExpectedResults] = gmmain.fJsonExpectedResults;
+        Json::Value root = CreateJsonTree(
+            gmmain.fJsonExpectedResults,
+            gmmain.fJsonActualResults_Failed, gmmain.fJsonActualResults_FailureIgnored,
+            gmmain.fJsonActualResults_NoComparison, gmmain.fJsonActualResults_Succeeded);
         std::string jsonStdString = root.toStyledString();
         SkFILEWStream stream(FLAGS_writeJsonSummaryPath[0]);
         stream.write(jsonStdString.c_str(), jsonStdString.length());
diff --git a/gm/tests/outputs/compared-against-different-pixels-images/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-different-pixels-images/output-expected/json-summary.txt
index 4cfe151..5214f7a 100644
--- a/gm/tests/outputs/compared-against-different-pixels-images/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-different-pixels-images/output-expected/json-summary.txt
@@ -2,10 +2,10 @@
    "actual-results" : {
       "failed" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       },
       "failure-ignored" : null,
@@ -14,11 +14,11 @@
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 11071285354315388429 ],
+         "allowed-bitmap-cityhashes" : [ 11071285354315388429 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 16527650414256125612 ],
+         "allowed-bitmap-cityhashes" : [ 16527650414256125612 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/compared-against-different-pixels-json/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-different-pixels-json/output-expected/json-summary.txt
index 4cfe151..5214f7a 100644
--- a/gm/tests/outputs/compared-against-different-pixels-json/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-different-pixels-json/output-expected/json-summary.txt
@@ -2,10 +2,10 @@
    "actual-results" : {
       "failed" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       },
       "failure-ignored" : null,
@@ -14,11 +14,11 @@
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 11071285354315388429 ],
+         "allowed-bitmap-cityhashes" : [ 11071285354315388429 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 16527650414256125612 ],
+         "allowed-bitmap-cityhashes" : [ 16527650414256125612 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt
index 69ef127..31892c9 100644
--- a/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt
@@ -4,21 +4,21 @@
       "failure-ignored" : null,
       "no-comparison" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       },
       "succeeded" : null
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : null,
+         "allowed-bitmap-cityhashes" : null,
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : null,
+         "allowed-bitmap-cityhashes" : null,
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/compared-against-identical-bytes-images/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-identical-bytes-images/output-expected/json-summary.txt
index cfaf455..417e3d8 100644
--- a/gm/tests/outputs/compared-against-identical-bytes-images/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-identical-bytes-images/output-expected/json-summary.txt
@@ -5,20 +5,20 @@
       "no-comparison" : null,
       "succeeded" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       }
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 9512553915271796906 ],
+         "allowed-bitmap-cityhashes" : [ 9512553915271796906 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 14022967492765711532 ],
+         "allowed-bitmap-cityhashes" : [ 14022967492765711532 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/compared-against-identical-bytes-json/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-identical-bytes-json/output-expected/json-summary.txt
index cfaf455..417e3d8 100644
--- a/gm/tests/outputs/compared-against-identical-bytes-json/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-identical-bytes-json/output-expected/json-summary.txt
@@ -5,20 +5,20 @@
       "no-comparison" : null,
       "succeeded" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       }
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 9512553915271796906 ],
+         "allowed-bitmap-cityhashes" : [ 9512553915271796906 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 14022967492765711532 ],
+         "allowed-bitmap-cityhashes" : [ 14022967492765711532 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/compared-against-identical-pixels-images/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-identical-pixels-images/output-expected/json-summary.txt
index cfaf455..417e3d8 100644
--- a/gm/tests/outputs/compared-against-identical-pixels-images/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-identical-pixels-images/output-expected/json-summary.txt
@@ -5,20 +5,20 @@
       "no-comparison" : null,
       "succeeded" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       }
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 9512553915271796906 ],
+         "allowed-bitmap-cityhashes" : [ 9512553915271796906 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 14022967492765711532 ],
+         "allowed-bitmap-cityhashes" : [ 14022967492765711532 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/compared-against-identical-pixels-json/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-identical-pixels-json/output-expected/json-summary.txt
index cfaf455..417e3d8 100644
--- a/gm/tests/outputs/compared-against-identical-pixels-json/output-expected/json-summary.txt
+++ b/gm/tests/outputs/compared-against-identical-pixels-json/output-expected/json-summary.txt
@@ -5,20 +5,20 @@
       "no-comparison" : null,
       "succeeded" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       }
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 9512553915271796906 ],
+         "allowed-bitmap-cityhashes" : [ 9512553915271796906 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 14022967492765711532 ],
+         "allowed-bitmap-cityhashes" : [ 14022967492765711532 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/ignore-expectations-mismatch/output-expected/json-summary.txt b/gm/tests/outputs/ignore-expectations-mismatch/output-expected/json-summary.txt
index 4cfe151..5214f7a 100644
--- a/gm/tests/outputs/ignore-expectations-mismatch/output-expected/json-summary.txt
+++ b/gm/tests/outputs/ignore-expectations-mismatch/output-expected/json-summary.txt
@@ -2,10 +2,10 @@
    "actual-results" : {
       "failed" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       },
       "failure-ignored" : null,
@@ -14,11 +14,11 @@
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 11071285354315388429 ],
+         "allowed-bitmap-cityhashes" : [ 11071285354315388429 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 16527650414256125612 ],
+         "allowed-bitmap-cityhashes" : [ 16527650414256125612 ],
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/intentionally-skipped-tests/output-expected/json-summary.txt b/gm/tests/outputs/intentionally-skipped-tests/output-expected/json-summary.txt
index a3ac39f..1ee2207 100644
--- a/gm/tests/outputs/intentionally-skipped-tests/output-expected/json-summary.txt
+++ b/gm/tests/outputs/intentionally-skipped-tests/output-expected/json-summary.txt
@@ -4,16 +4,16 @@
       "failure-ignored" : null,
       "no-comparison" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "565/selftest2" : {
-            "checksum" : 11071285354315388429
+            "bitmap-cityhash" : 11071285354315388429
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          },
          "8888/selftest2" : {
-            "checksum" : 16527650414256125612
+            "bitmap-cityhash" : 16527650414256125612
          }
       },
       "succeeded" : null
diff --git a/gm/tests/outputs/no-readpath/output-expected/json-summary.txt b/gm/tests/outputs/no-readpath/output-expected/json-summary.txt
index 0e19ec1..c2325d0 100644
--- a/gm/tests/outputs/no-readpath/output-expected/json-summary.txt
+++ b/gm/tests/outputs/no-readpath/output-expected/json-summary.txt
@@ -4,10 +4,10 @@
       "failure-ignored" : null,
       "no-comparison" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       },
       "succeeded" : null
diff --git a/gm/tests/outputs/nonverbose/output-expected/json-summary.txt b/gm/tests/outputs/nonverbose/output-expected/json-summary.txt
index 69ef127..31892c9 100644
--- a/gm/tests/outputs/nonverbose/output-expected/json-summary.txt
+++ b/gm/tests/outputs/nonverbose/output-expected/json-summary.txt
@@ -4,21 +4,21 @@
       "failure-ignored" : null,
       "no-comparison" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       },
       "succeeded" : null
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : null,
+         "allowed-bitmap-cityhashes" : null,
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : null,
+         "allowed-bitmap-cityhashes" : null,
          "ignore-failure" : false
       }
    }
diff --git a/gm/tests/outputs/pipe-playback-failure/output-expected/json-summary.txt b/gm/tests/outputs/pipe-playback-failure/output-expected/json-summary.txt
index 249fe09..54e0bde 100644
--- a/gm/tests/outputs/pipe-playback-failure/output-expected/json-summary.txt
+++ b/gm/tests/outputs/pipe-playback-failure/output-expected/json-summary.txt
@@ -2,31 +2,31 @@
    "actual-results" : {
       "failed" : {
          "comparison/selftest1-pipe" : {
-            "checksum" : 4259036727585789440
+            "bitmap-cityhash" : 4259036727585789440
          }
       },
       "failure-ignored" : null,
       "no-comparison" : null,
       "succeeded" : {
          "565/selftest1" : {
-            "checksum" : 9512553915271796906
+            "bitmap-cityhash" : 9512553915271796906
          },
          "8888/selftest1" : {
-            "checksum" : 14022967492765711532
+            "bitmap-cityhash" : 14022967492765711532
          }
       }
    },
    "expected-results" : {
       "565/selftest1" : {
-         "checksums" : [ 9512553915271796906 ],
+         "allowed-bitmap-cityhashes" : [ 9512553915271796906 ],
          "ignore-failure" : false
       },
       "8888/selftest1" : {
-         "checksums" : [ 14022967492765711532 ],
+         "allowed-bitmap-cityhashes" : [ 14022967492765711532 ],
          "ignore-failure" : false
       },
       "comparison/selftest1-pipe" : {
-         "checksums" : [ 14022967492765711532 ],
+         "allowed-bitmap-cityhashes" : [ 14022967492765711532 ],
          "ignore-failure" : false
       }
    }