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