1/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "gm_expectations.h"
9#include "SkBitmapHasher.h"
10#include "SkImageDecoder.h"
11
12#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
13
14// See gm_json.py for descriptions of each of these JSON keys.
15// These constants must be kept in sync with the ones in that Python file!
16const static char kJsonKey_ActualResults[]   = "actual-results";
17const static char kJsonKey_ActualResults_Failed[]        = "failed";
18const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored";
19const static char kJsonKey_ActualResults_NoComparison[]  = "no-comparison";
20const static char kJsonKey_ActualResults_Succeeded[]     = "succeeded";
21const static char kJsonKey_ExpectedResults[] = "expected-results";
22const static char kJsonKey_ExpectedResults_AllowedDigests[] = "allowed-digests";
23const static char kJsonKey_ExpectedResults_IgnoreFailure[]  = "ignore-failure";
24
25// Types of result hashes we support in the JSON file.
26const static char kJsonKey_Hashtype_Bitmap_64bitMD5[]  = "bitmap-64bitMD5";
27
28
29namespace skiagm {
30    SK_DEFINE_INST_COUNT(ExpectationsSource)
31
32    void gm_fprintf(FILE *stream, const char format[], ...) {
33        va_list args;
34        va_start(args, format);
35        fprintf(stream, "GM: ");
36        vfprintf(stream, format, args);
37        va_end(args);
38    }
39
40    Json::Value CreateJsonTree(Json::Value expectedResults,
41                               Json::Value actualResultsFailed,
42                               Json::Value actualResultsFailureIgnored,
43                               Json::Value actualResultsNoComparison,
44                               Json::Value actualResultsSucceeded) {
45        Json::Value actualResults;
46        actualResults[kJsonKey_ActualResults_Failed] = actualResultsFailed;
47        actualResults[kJsonKey_ActualResults_FailureIgnored] = actualResultsFailureIgnored;
48        actualResults[kJsonKey_ActualResults_NoComparison] = actualResultsNoComparison;
49        actualResults[kJsonKey_ActualResults_Succeeded] = actualResultsSucceeded;
50        Json::Value root;
51        root[kJsonKey_ActualResults] = actualResults;
52        root[kJsonKey_ExpectedResults] = expectedResults;
53        return root;
54    }
55
56
57    // GmResultDigest class...
58
59    GmResultDigest::GmResultDigest(const SkBitmap &bitmap) {
60        fIsValid = SkBitmapHasher::ComputeDigest(bitmap, &fHashDigest);
61    }
62
63    GmResultDigest::GmResultDigest(const Json::Value &jsonTypeValuePair) {
64        fIsValid = false;
65        if (!jsonTypeValuePair.isArray()) {
66            gm_fprintf(stderr, "found non-array json value when parsing GmResultDigest: %s\n",
67                       jsonTypeValuePair.toStyledString().c_str());
68            DEBUGFAIL_SEE_STDERR;
69        } else if (2 != jsonTypeValuePair.size()) {
70            gm_fprintf(stderr, "found json array with wrong size when parsing GmResultDigest: %s\n",
71                       jsonTypeValuePair.toStyledString().c_str());
72            DEBUGFAIL_SEE_STDERR;
73        } else {
74            // TODO(epoger): The current implementation assumes that the
75            // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5
76            Json::Value jsonHashValue = jsonTypeValuePair[1];
77            if (!jsonHashValue.isIntegral()) {
78                gm_fprintf(stderr,
79                           "found non-integer jsonHashValue when parsing GmResultDigest: %s\n",
80                           jsonTypeValuePair.toStyledString().c_str());
81                DEBUGFAIL_SEE_STDERR;
82            } else {
83                fHashDigest = jsonHashValue.asUInt64();
84                fIsValid = true;
85            }
86        }
87    }
88
89    bool GmResultDigest::isValid() const {
90        return fIsValid;
91    }
92
93    bool GmResultDigest::equals(const GmResultDigest &other) const {
94        // TODO(epoger): The current implementation assumes that this
95        // and other are always of type kJsonKey_Hashtype_Bitmap_64bitMD5
96        return (this->fIsValid && other.fIsValid && (this->fHashDigest == other.fHashDigest));
97    }
98
99    Json::Value GmResultDigest::asJsonTypeValuePair() const {
100        // TODO(epoger): The current implementation assumes that the
101        // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5
102        Json::Value jsonTypeValuePair;
103        if (fIsValid) {
104            jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5));
105            jsonTypeValuePair.append(Json::UInt64(fHashDigest));
106        } else {
107            jsonTypeValuePair.append(Json::Value("INVALID"));
108        }
109        return jsonTypeValuePair;
110    }
111
112    SkString GmResultDigest::getHashType() const {
113        // TODO(epoger): The current implementation assumes that the
114        // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5
115        return SkString(kJsonKey_Hashtype_Bitmap_64bitMD5);
116    }
117
118    SkString GmResultDigest::getDigestValue() const {
119        // TODO(epoger): The current implementation assumes that the
120        // result digest is always of type kJsonKey_Hashtype_Bitmap_64bitMD5
121        SkString retval;
122        retval.appendU64(fHashDigest);
123        return retval;
124    }
125
126
127    // Expectations class...
128
129    Expectations::Expectations(bool ignoreFailure) {
130        fIgnoreFailure = ignoreFailure;
131    }
132
133    Expectations::Expectations(const SkBitmap& bitmap, bool ignoreFailure) {
134        fBitmap = bitmap;
135        fIgnoreFailure = ignoreFailure;
136        fAllowedResultDigests.push_back(GmResultDigest(bitmap));
137    }
138
139    Expectations::Expectations(Json::Value jsonElement) {
140        if (jsonElement.empty()) {
141            fIgnoreFailure = kDefaultIgnoreFailure;
142        } else {
143            Json::Value ignoreFailure = jsonElement[kJsonKey_ExpectedResults_IgnoreFailure];
144            if (ignoreFailure.isNull()) {
145                fIgnoreFailure = kDefaultIgnoreFailure;
146            } else if (!ignoreFailure.isBool()) {
147                gm_fprintf(stderr, "found non-boolean json value"
148                           " for key '%s' in element '%s'\n",
149                           kJsonKey_ExpectedResults_IgnoreFailure,
150                           jsonElement.toStyledString().c_str());
151                DEBUGFAIL_SEE_STDERR;
152                fIgnoreFailure = kDefaultIgnoreFailure;
153            } else {
154                fIgnoreFailure = ignoreFailure.asBool();
155            }
156
157            Json::Value allowedDigests = jsonElement[kJsonKey_ExpectedResults_AllowedDigests];
158            if (allowedDigests.isNull()) {
159                // ok, we'll just assume there aren't any AllowedDigests to compare against
160            } else if (!allowedDigests.isArray()) {
161                gm_fprintf(stderr, "found non-array json value"
162                           " for key '%s' in element '%s'\n",
163                           kJsonKey_ExpectedResults_AllowedDigests,
164                           jsonElement.toStyledString().c_str());
165                DEBUGFAIL_SEE_STDERR;
166            } else {
167                for (Json::ArrayIndex i=0; i<allowedDigests.size(); i++) {
168                    fAllowedResultDigests.push_back(GmResultDigest(allowedDigests[i]));
169                }
170            }
171        }
172    }
173
174    bool Expectations::match(GmResultDigest actualGmResultDigest) const {
175        for (int i=0; i < this->fAllowedResultDigests.count(); i++) {
176            GmResultDigest allowedResultDigest = this->fAllowedResultDigests[i];
177            if (allowedResultDigest.equals(actualGmResultDigest)) {
178                return true;
179            }
180        }
181        return false;
182    }
183
184    Json::Value Expectations::asJsonValue() const {
185        Json::Value allowedDigestArray;
186        if (!this->fAllowedResultDigests.empty()) {
187            for (int i=0; i < this->fAllowedResultDigests.count(); i++) {
188                allowedDigestArray.append(this->fAllowedResultDigests[i].asJsonTypeValuePair());
189            }
190        }
191
192        Json::Value jsonExpectations;
193        jsonExpectations[kJsonKey_ExpectedResults_AllowedDigests] = allowedDigestArray;
194        jsonExpectations[kJsonKey_ExpectedResults_IgnoreFailure]  = this->ignoreFailure();
195        return jsonExpectations;
196    }
197
198
199    // IndividualImageExpectationsSource class...
200
201    Expectations IndividualImageExpectationsSource::get(const char *testName) {
202        SkString path = SkOSPath::SkPathJoin(fRootDir.c_str(), testName);
203        SkBitmap referenceBitmap;
204        bool decodedReferenceBitmap =
205            SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
206                                       SkBitmap::kARGB_8888_Config,
207                                       SkImageDecoder::kDecodePixels_Mode,
208                                       NULL);
209        if (decodedReferenceBitmap) {
210            return Expectations(referenceBitmap);
211        } else {
212            return Expectations();
213        }
214    }
215
216
217    // JsonExpectationsSource class...
218
219    JsonExpectationsSource::JsonExpectationsSource(const char *jsonPath) {
220        Parse(jsonPath, &fJsonRoot);
221        fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
222    }
223
224    Expectations JsonExpectationsSource::get(const char *testName) {
225        return Expectations(fJsonExpectedResults[testName]);
226    }
227
228    /*static*/ SkData* JsonExpectationsSource::ReadIntoSkData(SkStream &stream, size_t maxBytes) {
229        if (0 == maxBytes) {
230            return SkData::NewEmpty();
231        }
232        char* bufStart = reinterpret_cast<char *>(sk_malloc_throw(maxBytes));
233        char* bufPtr = bufStart;
234        size_t bytesRemaining = maxBytes;
235        while (bytesRemaining > 0) {
236            size_t bytesReadThisTime = stream.read(bufPtr, bytesRemaining);
237            if (0 == bytesReadThisTime) {
238                break;
239            }
240            bytesRemaining -= bytesReadThisTime;
241            bufPtr += bytesReadThisTime;
242        }
243        return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
244    }
245
246    /*static*/ bool JsonExpectationsSource::Parse(const char *jsonPath, Json::Value *jsonRoot) {
247        SkFILEStream inFile(jsonPath);
248        if (!inFile.isValid()) {
249            gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
250            DEBUGFAIL_SEE_STDERR;
251            return false;
252        }
253
254        SkAutoDataUnref dataRef(ReadFileIntoSkData(inFile));
255        if (NULL == dataRef.get()) {
256            gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
257            DEBUGFAIL_SEE_STDERR;
258            return false;
259        }
260
261        const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
262        size_t size = dataRef.get()->size();
263        Json::Reader reader;
264        if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
265            gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath);
266            DEBUGFAIL_SEE_STDERR;
267            return false;
268        }
269        return true;
270    }
271
272}
273