/*
* 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;
}
}