skia2/gm/gm_expectations.cpp
scroggo@google.com 5187c4313c More work to integrate skimage with rebaseline tools.
tools/skimage_main.cpp:
Add the ability to write the results to checksum based filenames,
much like GM uses. This will allow using the skpdiff server to
rebaseline images.
Write the keys in the JSON file as <original image>_<pref config>.png,
so it matches gm_json.IMAGE_FILENAME_PATTERN. Also replace '_' with
'-' in the original file name, to avoid confusing the pattern matcher.
The '_' to '-' replacement also happens on the output filename.
Read the keys in a similar manner.
In make_outname, no longer remove a suffix. This fixes a bug where
subset decoding writes multiple subsets to the same file.

tools/rebaseline.py:
Since the filenames written to json files now match
gm_json.IMAGE_FILENAME_PATTERN, enable the option to match based
on configs/tests when rebaselining skimage.

test json files:
Update to match the new format of output.

gm/gm_expectations:
Add a constructor that takes a BitmapAndDigest as input.

tools/tests/skimage_self_test.py:
Test that reading the expectations file just created by skimage with
the same file actually compares to the original file (rather than just
succeeding because expectations were missing).

Change the expectations files to match the new format.

Will require a buildbot change to use the new flag: https://codereview.chromium.org/27389002/

BUG=1466
R=epoger@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@11902 2bbb7eff-a529-9590-31e7-b0007b416f81
2013-10-22 00:42:46 +00:00

284 lines
11 KiB
C++

/*
* 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")
// 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 {
SK_DEFINE_INST_COUNT(ExpectationsSource)
void gm_fprintf(FILE *stream, const char format[], ...) {
va_list args;
va_start(args, format);
fprintf(stream, "GM: ");
vfprintf(stream, format, args);
#ifdef SK_BUILD_FOR_WIN
if (stderr == stream || stdout == stream) {
fflush(stream);
}
#endif
va_end(args);
}
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()) {
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;
}
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()) {
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 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()) {
gm_fprintf(stderr, "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,
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) const {
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;
}
}