2013-04-26 15:06:44 +00:00
|
|
|
/*
|
|
|
|
* 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")
|
|
|
|
|
2013-05-14 18:58:12 +00:00
|
|
|
// These constants must be kept in sync with the JSONKEY_ constants in
|
2013-05-28 15:25:38 +00:00
|
|
|
// gm_json.py !
|
2013-04-26 15:06:44 +00:00
|
|
|
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";
|
2013-05-24 14:33:28 +00:00
|
|
|
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";
|
|
|
|
|
2013-04-26 15:06:44 +00:00
|
|
|
|
|
|
|
namespace skiagm {
|
|
|
|
|
2013-05-08 19:14:23 +00:00
|
|
|
void gm_fprintf(FILE *stream, const char format[], ...) {
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
fprintf(stream, "GM: ");
|
|
|
|
vfprintf(stream, format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
2013-05-24 18:28:57 +00:00
|
|
|
SkString SkPathJoin(const char *rootPath, const char *relativePath) {
|
|
|
|
SkString result(rootPath);
|
|
|
|
if (!result.endsWith(SkPATH_SEPARATOR)) {
|
|
|
|
result.appendUnichar(SkPATH_SEPARATOR);
|
|
|
|
}
|
|
|
|
result.append(relativePath);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2013-04-26 15:06:44 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-24 14:33:28 +00:00
|
|
|
// GmResultDigest class...
|
|
|
|
|
|
|
|
GmResultDigest::GmResultDigest(const SkBitmap &bitmap) {
|
|
|
|
fIsValid = SkBitmapHasher::ComputeDigest(bitmap, &fHashDigest);
|
|
|
|
}
|
|
|
|
|
|
|
|
GmResultDigest::GmResultDigest(const Json::Value &jsonTypeValuePair) {
|
|
|
|
fIsValid = false;
|
|
|
|
if (!jsonTypeValuePair.isArray()) {
|
|
|
|
gm_fprintf(stderr, "found non-array json value when parsing GmResultDigest: %s\n",
|
|
|
|
jsonTypeValuePair.toStyledString().c_str());
|
|
|
|
DEBUGFAIL_SEE_STDERR;
|
|
|
|
} else if (2 != jsonTypeValuePair.size()) {
|
|
|
|
gm_fprintf(stderr, "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()) {
|
|
|
|
gm_fprintf(stderr,
|
|
|
|
"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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-04-26 15:06:44 +00:00
|
|
|
// Expectations class...
|
|
|
|
|
|
|
|
Expectations::Expectations(bool ignoreFailure) {
|
|
|
|
fIgnoreFailure = ignoreFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
Expectations::Expectations(const SkBitmap& bitmap, bool ignoreFailure) {
|
|
|
|
fBitmap = bitmap;
|
|
|
|
fIgnoreFailure = ignoreFailure;
|
2013-05-24 14:33:28 +00:00
|
|
|
fAllowedResultDigests.push_back(GmResultDigest(bitmap));
|
2013-04-26 15:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2013-05-24 14:33:28 +00:00
|
|
|
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()) {
|
2013-04-26 15:06:44 +00:00
|
|
|
gm_fprintf(stderr, "found non-array json value"
|
|
|
|
" for key '%s' in element '%s'\n",
|
2013-05-24 14:33:28 +00:00
|
|
|
kJsonKey_ExpectedResults_AllowedDigests,
|
2013-04-26 15:06:44 +00:00
|
|
|
jsonElement.toStyledString().c_str());
|
|
|
|
DEBUGFAIL_SEE_STDERR;
|
|
|
|
} else {
|
2013-05-24 14:33:28 +00:00
|
|
|
for (Json::ArrayIndex i=0; i<allowedDigests.size(); i++) {
|
|
|
|
fAllowedResultDigests.push_back(GmResultDigest(allowedDigests[i]));
|
2013-04-26 15:06:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-24 14:33:28 +00:00
|
|
|
bool Expectations::match(GmResultDigest actualGmResultDigest) const {
|
|
|
|
for (int i=0; i < this->fAllowedResultDigests.count(); i++) {
|
|
|
|
GmResultDigest allowedResultDigest = this->fAllowedResultDigests[i];
|
|
|
|
if (allowedResultDigest.equals(actualGmResultDigest)) {
|
2013-04-26 15:06:44 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Json::Value Expectations::asJsonValue() const {
|
2013-05-24 14:33:28 +00:00
|
|
|
Json::Value allowedDigestArray;
|
|
|
|
if (!this->fAllowedResultDigests.empty()) {
|
|
|
|
for (int i=0; i < this->fAllowedResultDigests.count(); i++) {
|
|
|
|
allowedDigestArray.append(this->fAllowedResultDigests[i].asJsonTypeValuePair());
|
2013-04-26 15:06:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-24 14:33:28 +00:00
|
|
|
Json::Value jsonExpectations;
|
|
|
|
jsonExpectations[kJsonKey_ExpectedResults_AllowedDigests] = allowedDigestArray;
|
|
|
|
jsonExpectations[kJsonKey_ExpectedResults_IgnoreFailure] = this->ignoreFailure();
|
|
|
|
return jsonExpectations;
|
2013-04-26 15:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IndividualImageExpectationsSource class...
|
|
|
|
|
|
|
|
Expectations IndividualImageExpectationsSource::get(const char *testName) {
|
2013-05-24 18:28:57 +00:00
|
|
|
SkString path = SkPathJoin(fRootDir.c_str(), testName);
|
2013-04-26 15:06:44 +00:00
|
|
|
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) {
|
2013-05-08 19:14:23 +00:00
|
|
|
Parse(jsonPath, &fJsonRoot);
|
2013-04-26 15:06:44 +00:00
|
|
|
fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
|
|
|
|
}
|
|
|
|
|
|
|
|
Expectations JsonExpectationsSource::get(const char *testName) {
|
|
|
|
return Expectations(fJsonExpectedResults[testName]);
|
|
|
|
}
|
|
|
|
|
2013-05-08 19:14:23 +00:00
|
|
|
/*static*/ SkData* JsonExpectationsSource::ReadIntoSkData(SkStream &stream, size_t maxBytes) {
|
2013-04-26 15:06:44 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-05-08 19:14:23 +00:00
|
|
|
/*static*/ bool JsonExpectationsSource::Parse(const char *jsonPath, Json::Value *jsonRoot) {
|
2013-04-26 15:06:44 +00:00
|
|
|
SkFILEStream inFile(jsonPath);
|
|
|
|
if (!inFile.isValid()) {
|
|
|
|
gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
|
|
|
|
DEBUGFAIL_SEE_STDERR;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-05-08 19:14:23 +00:00
|
|
|
SkAutoDataUnref dataRef(ReadFileIntoSkData(inFile));
|
2013-04-26 15:06:44 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|