blob: 84cb7fe25513ebefca2fffb967b2528e8e25a815 [file] [log] [blame]
epoger@google.com37269602013-01-19 04:21:27 +00001/*
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#ifndef gm_expectations_DEFINED
8#define gm_expectations_DEFINED
9
epoger@google.com5efdd0c2013-03-13 14:18:40 +000010#include <stdarg.h>
epoger@google.com37269602013-01-19 04:21:27 +000011#include "gm.h"
epoger@google.com84a18022013-02-01 20:39:15 +000012#include "SkBitmap.h"
epoger@google.com908f5832013-04-12 02:23:55 +000013#include "SkBitmapHasher.h"
epoger@google.comd271d242013-02-13 18:14:48 +000014#include "SkData.h"
epoger@google.com37269602013-01-19 04:21:27 +000015#include "SkImageDecoder.h"
16#include "SkOSFile.h"
17#include "SkRefCnt.h"
epoger@google.comd271d242013-02-13 18:14:48 +000018#include "SkStream.h"
epoger@google.com37269602013-01-19 04:21:27 +000019#include "SkTArray.h"
20
21#ifdef SK_BUILD_FOR_WIN
22 // json includes xlocale which generates warning 4530 because we're compiling without
23 // exceptions; see https://code.google.com/p/skia/issues/detail?id=1067
24 #pragma warning(push)
25 #pragma warning(disable : 4530)
26#endif
epoger@google.comd271d242013-02-13 18:14:48 +000027#include "json/reader.h"
epoger@google.com37269602013-01-19 04:21:27 +000028#include "json/value.h"
29#ifdef SK_BUILD_FOR_WIN
30 #pragma warning(pop)
31#endif
32
epoger@google.comd271d242013-02-13 18:14:48 +000033#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
34
35const static char kJsonKey_ActualResults[] = "actual-results";
36const static char kJsonKey_ActualResults_Failed[] = "failed";
37const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored";
38const static char kJsonKey_ActualResults_NoComparison[] = "no-comparison";
39const static char kJsonKey_ActualResults_Succeeded[] = "succeeded";
40const static char kJsonKey_ActualResults_AnyStatus_Checksum[] = "checksum";
41
42const static char kJsonKey_ExpectedResults[] = "expected-results";
43const static char kJsonKey_ExpectedResults_Checksums[] = "checksums";
44const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure";
45
epoger@google.com37269602013-01-19 04:21:27 +000046namespace skiagm {
47
48 // The actual type we use to represent a checksum is hidden in here.
49 typedef Json::UInt64 Checksum;
50 static inline Json::Value asJsonValue(Checksum checksum) {
51 return checksum;
52 }
epoger@google.comd271d242013-02-13 18:14:48 +000053 static inline Checksum asChecksum(Json::Value jsonValue) {
54 return jsonValue.asUInt64();
55 }
epoger@google.com37269602013-01-19 04:21:27 +000056
epoger@google.com5efdd0c2013-03-13 14:18:40 +000057 static void gm_fprintf(FILE *stream, const char format[], ...) {
58 va_list args;
59 va_start(args, format);
60 fprintf(stream, "GM: ");
61 vfprintf(stream, format, args);
62 va_end(args);
63 }
64
epoger@google.com37269602013-01-19 04:21:27 +000065 static SkString make_filename(const char path[],
66 const char renderModeDescriptor[],
67 const char *name,
68 const char suffix[]) {
69 SkString filename(path);
70 if (filename.endsWith(SkPATH_SEPARATOR)) {
71 filename.remove(filename.size() - 1, 1);
72 }
73 filename.appendf("%c%s%s.%s", SkPATH_SEPARATOR,
74 name, renderModeDescriptor, suffix);
75 return filename;
76 }
77
78 /**
79 * Test expectations (allowed image checksums, etc.)
80 */
81 class Expectations {
82 public:
83 /**
84 * No expectations at all.
epoger@google.com37269602013-01-19 04:21:27 +000085 */
epoger@google.comd271d242013-02-13 18:14:48 +000086 Expectations(bool ignoreFailure=kDefaultIgnoreFailure) {
epoger@google.com37269602013-01-19 04:21:27 +000087 fIgnoreFailure = ignoreFailure;
88 }
89
90 /**
epoger@google.com84a18022013-02-01 20:39:15 +000091 * Expect exactly one image (appropriate for the case when we
epoger@google.com37269602013-01-19 04:21:27 +000092 * are comparing against a single PNG file).
epoger@google.com37269602013-01-19 04:21:27 +000093 */
epoger@google.comd271d242013-02-13 18:14:48 +000094 Expectations(const SkBitmap& bitmap, bool ignoreFailure=kDefaultIgnoreFailure) {
epoger@google.com84a18022013-02-01 20:39:15 +000095 fBitmap = bitmap;
epoger@google.com37269602013-01-19 04:21:27 +000096 fIgnoreFailure = ignoreFailure;
epoger@google.com908f5832013-04-12 02:23:55 +000097 SkHashDigest digest;
98 // TODO(epoger): Better handling for error returned by ComputeDigest()?
99 // For now, we just report a digest of 0 in error cases, like before.
100 if (!SkBitmapHasher::ComputeDigest(bitmap, &digest)) {
101 digest = 0;
102 }
103 fAllowedChecksums.push_back() = digest;
epoger@google.com37269602013-01-19 04:21:27 +0000104 }
105
106 /**
epoger@google.comd271d242013-02-13 18:14:48 +0000107 * Create Expectations from a JSON element as found within the
108 * kJsonKey_ExpectedResults section.
109 *
110 * It's fine if the jsonElement is null or empty; in that case, we just
111 * don't have any expectations.
112 */
113 Expectations(Json::Value jsonElement) {
114 if (jsonElement.empty()) {
115 fIgnoreFailure = kDefaultIgnoreFailure;
116 } else {
117 Json::Value ignoreFailure = jsonElement[kJsonKey_ExpectedResults_IgnoreFailure];
118 if (ignoreFailure.isNull()) {
119 fIgnoreFailure = kDefaultIgnoreFailure;
120 } else if (!ignoreFailure.isBool()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000121 gm_fprintf(stderr, "found non-boolean json value"
122 " for key '%s' in element '%s'\n",
123 kJsonKey_ExpectedResults_IgnoreFailure,
124 jsonElement.toStyledString().c_str());
epoger@google.comd271d242013-02-13 18:14:48 +0000125 DEBUGFAIL_SEE_STDERR;
126 fIgnoreFailure = kDefaultIgnoreFailure;
127 } else {
128 fIgnoreFailure = ignoreFailure.asBool();
129 }
130
131 Json::Value allowedChecksums = jsonElement[kJsonKey_ExpectedResults_Checksums];
132 if (allowedChecksums.isNull()) {
133 // ok, we'll just assume there aren't any expected checksums to compare against
134 } else if (!allowedChecksums.isArray()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000135 gm_fprintf(stderr, "found non-array json value"
136 " for key '%s' in element '%s'\n",
137 kJsonKey_ExpectedResults_Checksums,
138 jsonElement.toStyledString().c_str());
epoger@google.comd271d242013-02-13 18:14:48 +0000139 DEBUGFAIL_SEE_STDERR;
140 } else {
141 for (Json::ArrayIndex i=0; i<allowedChecksums.size(); i++) {
142 Json::Value checksumElement = allowedChecksums[i];
143 if (!checksumElement.isIntegral()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000144 gm_fprintf(stderr, "found non-integer checksum"
145 " in json element '%s'\n",
146 jsonElement.toStyledString().c_str());
epoger@google.comd271d242013-02-13 18:14:48 +0000147 DEBUGFAIL_SEE_STDERR;
148 } else {
149 fAllowedChecksums.push_back() = asChecksum(checksumElement);
150 }
151 }
152 }
153 }
154 }
155
156 /**
epoger@google.com37269602013-01-19 04:21:27 +0000157 * Returns true iff we want to ignore failed expectations.
158 */
159 bool ignoreFailure() const { return this->fIgnoreFailure; }
160
161 /**
162 * Returns true iff there are no allowed checksums.
163 */
164 bool empty() const { return this->fAllowedChecksums.empty(); }
165
166 /**
167 * Returns true iff actualChecksum matches any allowedChecksum,
168 * regardless of fIgnoreFailure. (The caller can check
169 * that separately.)
170 */
171 bool match(Checksum actualChecksum) const {
172 for (int i=0; i < this->fAllowedChecksums.count(); i++) {
173 Checksum allowedChecksum = this->fAllowedChecksums[i];
174 if (allowedChecksum == actualChecksum) {
175 return true;
176 }
177 }
178 return false;
179 }
180
181 /**
epoger@google.com84a18022013-02-01 20:39:15 +0000182 * If this Expectation is based on a single SkBitmap, return a
183 * pointer to that SkBitmap. Otherwise (if the Expectation is
184 * empty, or if it was based on a list of checksums rather
185 * than a single bitmap), returns NULL.
186 */
187 const SkBitmap *asBitmap() const {
188 return (SkBitmap::kNo_Config == fBitmap.config()) ? NULL : &fBitmap;
189 }
190
191 /**
epoger@google.com37269602013-01-19 04:21:27 +0000192 * Return a JSON representation of the allowed checksums.
193 * This does NOT include any information about whether to
194 * ignore failures.
195 */
196 Json::Value allowedChecksumsAsJson() const {
197 Json::Value allowedChecksumArray;
198 if (!this->fAllowedChecksums.empty()) {
199 for (int i=0; i < this->fAllowedChecksums.count(); i++) {
200 Checksum allowedChecksum = this->fAllowedChecksums[i];
201 allowedChecksumArray.append(asJsonValue(allowedChecksum));
202 }
203 }
204 return allowedChecksumArray;
205 }
206
207 private:
epoger@google.comd271d242013-02-13 18:14:48 +0000208 const static bool kDefaultIgnoreFailure = false;
209
epoger@google.com37269602013-01-19 04:21:27 +0000210 SkTArray<Checksum> fAllowedChecksums;
211 bool fIgnoreFailure;
epoger@google.com84a18022013-02-01 20:39:15 +0000212 SkBitmap fBitmap;
epoger@google.com37269602013-01-19 04:21:27 +0000213 };
214
215 /**
216 * Abstract source of Expectations objects for individual tests.
217 */
218 class ExpectationsSource : public SkRefCnt {
219 public:
220 virtual Expectations get(const char *testName) = 0;
221 };
222
223 /**
224 * Return Expectations based on individual image files on disk.
225 */
226 class IndividualImageExpectationsSource : public ExpectationsSource {
227 public:
228 /**
229 * Create an ExpectationsSource that will return Expectations based on
230 * image files found within rootDir.
231 *
232 * rootDir: directory under which to look for image files
233 * (this string will be copied to storage within this object)
epoger@google.com37269602013-01-19 04:21:27 +0000234 */
epoger@google.comb0f8b432013-04-10 18:46:25 +0000235 IndividualImageExpectationsSource(const char *rootDir) : fRootDir(rootDir) {}
epoger@google.com37269602013-01-19 04:21:27 +0000236
237 Expectations get(const char *testName) SK_OVERRIDE {
238 SkString path = make_filename(fRootDir.c_str(), "", testName,
239 "png");
240 SkBitmap referenceBitmap;
241 bool decodedReferenceBitmap =
242 SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
243 SkBitmap::kARGB_8888_Config,
244 SkImageDecoder::kDecodePixels_Mode,
245 NULL);
246 if (decodedReferenceBitmap) {
epoger@google.com84a18022013-02-01 20:39:15 +0000247 return Expectations(referenceBitmap);
epoger@google.com37269602013-01-19 04:21:27 +0000248 } else {
epoger@google.com37269602013-01-19 04:21:27 +0000249 return Expectations();
250 }
251 }
252
253 private:
254 const SkString fRootDir;
epoger@google.com37269602013-01-19 04:21:27 +0000255 };
256
epoger@google.comd271d242013-02-13 18:14:48 +0000257 /**
258 * Return Expectations based on JSON summary file.
259 */
260 class JsonExpectationsSource : public ExpectationsSource {
261 public:
262 /**
263 * Create an ExpectationsSource that will return Expectations based on
264 * a JSON file.
265 *
266 * jsonPath: path to JSON file to read
267 */
268 JsonExpectationsSource(const char *jsonPath) {
269 parse(jsonPath, &fJsonRoot);
270 fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
271 }
272
273 Expectations get(const char *testName) SK_OVERRIDE {
274 return Expectations(fJsonExpectedResults[testName]);
275 }
276
277 private:
278
279 /**
280 * Read as many bytes as possible (up to maxBytes) from the stream into
281 * an SkData object.
282 *
283 * If the returned SkData contains fewer than maxBytes, then EOF has been
284 * reached and no more data would be available from subsequent calls.
285 * (If EOF has already been reached, then this call will return an empty
286 * SkData object immediately.)
287 *
288 * If there are fewer than maxBytes bytes available to read from the
289 * stream, but the stream has not been closed yet, this call will block
290 * until there are enough bytes to read or the stream has been closed.
291 *
292 * It is up to the caller to call unref() on the returned SkData object
293 * once the data is no longer needed, so that the underlying buffer will
294 * be freed. For example:
295 *
296 * {
297 * size_t maxBytes = 256;
298 * SkAutoDataUnref dataRef(readIntoSkData(stream, maxBytes));
299 * if (NULL != dataRef.get()) {
300 * size_t bytesActuallyRead = dataRef.get()->size();
301 * // use the data...
302 * }
303 * }
304 * // underlying buffer has been freed, thanks to auto unref
305 *
306 */
307 // TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in
308 // https://codereview.appspot.com/7300071 ?
309 // And maybe readFileIntoSkData() also?
310 static SkData* readIntoSkData(SkStream &stream, size_t maxBytes) {
311 if (0 == maxBytes) {
312 return SkData::NewEmpty();
313 }
314 char* bufStart = reinterpret_cast<char *>(sk_malloc_throw(maxBytes));
315 char* bufPtr = bufStart;
316 size_t bytesRemaining = maxBytes;
317 while (bytesRemaining > 0) {
318 size_t bytesReadThisTime = stream.read(bufPtr, bytesRemaining);
319 if (0 == bytesReadThisTime) {
320 break;
321 }
322 bytesRemaining -= bytesReadThisTime;
323 bufPtr += bytesReadThisTime;
324 }
325 return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
326 }
327
328 /**
329 * Wrapper around readIntoSkData for files: reads the entire file into
330 * an SkData object.
331 */
332 static SkData* readFileIntoSkData(SkFILEStream &stream) {
333 return readIntoSkData(stream, stream.getLength());
334 }
335
336 /**
337 * Read the file contents from jsonPath and parse them into jsonRoot.
338 *
339 * Returns true if successful.
340 */
341 static bool parse(const char *jsonPath, Json::Value *jsonRoot) {
342 SkFILEStream inFile(jsonPath);
343 if (!inFile.isValid()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000344 gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
epoger@google.comd271d242013-02-13 18:14:48 +0000345 DEBUGFAIL_SEE_STDERR;
346 return false;
347 }
348
349 SkAutoDataUnref dataRef(readFileIntoSkData(inFile));
350 if (NULL == dataRef.get()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000351 gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
epoger@google.comd271d242013-02-13 18:14:48 +0000352 DEBUGFAIL_SEE_STDERR;
353 return false;
354 }
355
356 const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
357 size_t size = dataRef.get()->size();
358 Json::Reader reader;
359 if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000360 gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath);
epoger@google.comd271d242013-02-13 18:14:48 +0000361 DEBUGFAIL_SEE_STDERR;
362 return false;
363 }
364 return true;
365 }
366
367 Json::Value fJsonRoot;
368 Json::Value fJsonExpectedResults;
369 };
370
epoger@google.com37269602013-01-19 04:21:27 +0000371}
372#endif