/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * TODO(epoger): Combine this with tools/image_expectations.cpp, or eliminate one of the two. */ #include "gm_expectations.h" #include "SkBitmapHasher.h" #include "SkData.h" #include "SkImageDecoder.h" #define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message") // See gm_json.py for descriptions of each of these JSON keys. // These constants must be kept in sync with the ones in that Python file! 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_ExpectedResults[] = "expected-results"; const static char kJsonKey_ExpectedResults_AllowedDigests[] = "allowed-digests"; const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure"; // Types of result hashes we support in the JSON file. const static char kJsonKey_Hashtype_Bitmap_64bitMD5[] = "bitmap-64bitMD5"; namespace skiagm { 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; } // GmResultDigest class... GmResultDigest::GmResultDigest(const SkBitmap &bitmap) { fIsValid = SkBitmapHasher::ComputeDigest(bitmap, &fHashDigest); } GmResultDigest::GmResultDigest(const Json::Value &jsonTypeValuePair) { fIsValid = false; if (!jsonTypeValuePair.isArray()) { SkDebugf("found non-array json value when parsing GmResultDigest: %s\n", jsonTypeValuePair.toStyledString().c_str()); DEBUGFAIL_SEE_STDERR; } else if (2 != jsonTypeValuePair.size()) { SkDebugf("found json array with wrong size when parsing GmResultDigest: %s\n", jsonTypeValuePair.toStyledString().c_str()); DEBUGFAIL_SEE_STDERR; } else { // TODO(epoger): The current implementation assumes that the // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5 Json::Value jsonHashValue = jsonTypeValuePair[1]; if (!jsonHashValue.isIntegral()) { SkDebugf("found non-integer jsonHashValue when parsing GmResultDigest: %s\n", jsonTypeValuePair.toStyledString().c_str()); DEBUGFAIL_SEE_STDERR; } else { fHashDigest = jsonHashValue.asUInt64(); fIsValid = true; } } } bool GmResultDigest::isValid() const { return fIsValid; } bool GmResultDigest::equals(const GmResultDigest &other) const { // TODO(epoger): The current implementation assumes that this // and other are always of type kJsonKey_Hashtype_Bitmap_64bitMD5 return (this->fIsValid && other.fIsValid && (this->fHashDigest == other.fHashDigest)); } Json::Value GmResultDigest::asJsonTypeValuePair() const { // TODO(epoger): The current implementation assumes that the // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5 Json::Value jsonTypeValuePair; if (fIsValid) { jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5)); jsonTypeValuePair.append(Json::UInt64(fHashDigest)); } else { jsonTypeValuePair.append(Json::Value("INVALID")); } return jsonTypeValuePair; } SkString GmResultDigest::getHashType() const { // TODO(epoger): The current implementation assumes that the // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5 return SkString(kJsonKey_Hashtype_Bitmap_64bitMD5); } SkString GmResultDigest::getDigestValue() const { // TODO(epoger): The current implementation assumes that the // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5 SkString retval; retval.appendU64(fHashDigest); return retval; } // Expectations class... Expectations::Expectations(bool ignoreFailure) { fIgnoreFailure = ignoreFailure; } Expectations::Expectations(const SkBitmap& bitmap, bool ignoreFailure) { fBitmap = bitmap; fIgnoreFailure = ignoreFailure; fAllowedResultDigests.push_back(GmResultDigest(bitmap)); } Expectations::Expectations(const BitmapAndDigest& bitmapAndDigest) { fBitmap = bitmapAndDigest.fBitmap; fIgnoreFailure = false; fAllowedResultDigests.push_back(bitmapAndDigest.fDigest); } 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()) { SkDebugf("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 allowedDigests = jsonElement[kJsonKey_ExpectedResults_AllowedDigests]; if (allowedDigests.isNull()) { // ok, we'll just assume there aren't any AllowedDigests to compare against } else if (!allowedDigests.isArray()) { SkDebugf("found non-array json value for key '%s' in element '%s'\n", kJsonKey_ExpectedResults_AllowedDigests, jsonElement.toStyledString().c_str()); DEBUGFAIL_SEE_STDERR; } else { for (Json::ArrayIndex i=0; i<allowedDigests.size(); i++) { fAllowedResultDigests.push_back(GmResultDigest(allowedDigests[i])); } } } } bool Expectations::match(GmResultDigest actualGmResultDigest) const { for (int i=0; i < this->fAllowedResultDigests.count(); i++) { GmResultDigest allowedResultDigest = this->fAllowedResultDigests[i]; if (allowedResultDigest.equals(actualGmResultDigest)) { return true; } } return false; } Json::Value Expectations::asJsonValue() const { Json::Value allowedDigestArray; if (!this->fAllowedResultDigests.empty()) { for (int i=0; i < this->fAllowedResultDigests.count(); i++) { allowedDigestArray.append(this->fAllowedResultDigests[i].asJsonTypeValuePair()); } } Json::Value jsonExpectations; jsonExpectations[kJsonKey_ExpectedResults_AllowedDigests] = allowedDigestArray; jsonExpectations[kJsonKey_ExpectedResults_IgnoreFailure] = this->ignoreFailure(); return jsonExpectations; } // IndividualImageExpectationsSource class... Expectations IndividualImageExpectationsSource::get(const char *testName) const { SkString path = SkOSPath::SkPathJoin(fRootDir.c_str(), testName); SkBitmap referenceBitmap; bool decodedReferenceBitmap = SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap, kN32_SkColorType, 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) const { return Expectations(fJsonExpectedResults[testName]); } /*static*/ bool JsonExpectationsSource::Parse(const char *jsonPath, Json::Value *jsonRoot) { SkAutoDataUnref dataRef(SkData::NewFromFileName(jsonPath)); if (NULL == dataRef.get()) { SkDebugf("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)) { SkDebugf("error parsing JSON file %s\n", jsonPath); DEBUGFAIL_SEE_STDERR; return false; } return true; } }