blob: 3d3f2b55fa7e0e018bc0827f0ad0d55f63d7e906 [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.com37269602013-01-19 04:21:27 +000013#include "SkBitmapChecksummer.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.com84a18022013-02-01 20:39:15 +000097 fAllowedChecksums.push_back() = SkBitmapChecksummer::Compute64(bitmap);
epoger@google.com37269602013-01-19 04:21:27 +000098 }
99
100 /**
epoger@google.comd271d242013-02-13 18:14:48 +0000101 * Create Expectations from a JSON element as found within the
102 * kJsonKey_ExpectedResults section.
103 *
104 * It's fine if the jsonElement is null or empty; in that case, we just
105 * don't have any expectations.
106 */
107 Expectations(Json::Value jsonElement) {
108 if (jsonElement.empty()) {
109 fIgnoreFailure = kDefaultIgnoreFailure;
110 } else {
111 Json::Value ignoreFailure = jsonElement[kJsonKey_ExpectedResults_IgnoreFailure];
112 if (ignoreFailure.isNull()) {
113 fIgnoreFailure = kDefaultIgnoreFailure;
114 } else if (!ignoreFailure.isBool()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000115 gm_fprintf(stderr, "found non-boolean json value"
116 " for key '%s' in element '%s'\n",
117 kJsonKey_ExpectedResults_IgnoreFailure,
118 jsonElement.toStyledString().c_str());
epoger@google.comd271d242013-02-13 18:14:48 +0000119 DEBUGFAIL_SEE_STDERR;
120 fIgnoreFailure = kDefaultIgnoreFailure;
121 } else {
122 fIgnoreFailure = ignoreFailure.asBool();
123 }
124
125 Json::Value allowedChecksums = jsonElement[kJsonKey_ExpectedResults_Checksums];
126 if (allowedChecksums.isNull()) {
127 // ok, we'll just assume there aren't any expected checksums to compare against
128 } else if (!allowedChecksums.isArray()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000129 gm_fprintf(stderr, "found non-array json value"
130 " for key '%s' in element '%s'\n",
131 kJsonKey_ExpectedResults_Checksums,
132 jsonElement.toStyledString().c_str());
epoger@google.comd271d242013-02-13 18:14:48 +0000133 DEBUGFAIL_SEE_STDERR;
134 } else {
135 for (Json::ArrayIndex i=0; i<allowedChecksums.size(); i++) {
136 Json::Value checksumElement = allowedChecksums[i];
137 if (!checksumElement.isIntegral()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000138 gm_fprintf(stderr, "found non-integer checksum"
139 " in json element '%s'\n",
140 jsonElement.toStyledString().c_str());
epoger@google.comd271d242013-02-13 18:14:48 +0000141 DEBUGFAIL_SEE_STDERR;
142 } else {
143 fAllowedChecksums.push_back() = asChecksum(checksumElement);
144 }
145 }
146 }
147 }
148 }
149
150 /**
epoger@google.com37269602013-01-19 04:21:27 +0000151 * Returns true iff we want to ignore failed expectations.
152 */
153 bool ignoreFailure() const { return this->fIgnoreFailure; }
154
155 /**
156 * Returns true iff there are no allowed checksums.
157 */
158 bool empty() const { return this->fAllowedChecksums.empty(); }
159
160 /**
161 * Returns true iff actualChecksum matches any allowedChecksum,
162 * regardless of fIgnoreFailure. (The caller can check
163 * that separately.)
164 */
165 bool match(Checksum actualChecksum) const {
166 for (int i=0; i < this->fAllowedChecksums.count(); i++) {
167 Checksum allowedChecksum = this->fAllowedChecksums[i];
168 if (allowedChecksum == actualChecksum) {
169 return true;
170 }
171 }
172 return false;
173 }
174
175 /**
epoger@google.com84a18022013-02-01 20:39:15 +0000176 * If this Expectation is based on a single SkBitmap, return a
177 * pointer to that SkBitmap. Otherwise (if the Expectation is
178 * empty, or if it was based on a list of checksums rather
179 * than a single bitmap), returns NULL.
180 */
181 const SkBitmap *asBitmap() const {
182 return (SkBitmap::kNo_Config == fBitmap.config()) ? NULL : &fBitmap;
183 }
184
185 /**
epoger@google.com37269602013-01-19 04:21:27 +0000186 * Return a JSON representation of the allowed checksums.
187 * This does NOT include any information about whether to
188 * ignore failures.
189 */
190 Json::Value allowedChecksumsAsJson() const {
191 Json::Value allowedChecksumArray;
192 if (!this->fAllowedChecksums.empty()) {
193 for (int i=0; i < this->fAllowedChecksums.count(); i++) {
194 Checksum allowedChecksum = this->fAllowedChecksums[i];
195 allowedChecksumArray.append(asJsonValue(allowedChecksum));
196 }
197 }
198 return allowedChecksumArray;
199 }
200
201 private:
epoger@google.comd271d242013-02-13 18:14:48 +0000202 const static bool kDefaultIgnoreFailure = false;
203
epoger@google.com37269602013-01-19 04:21:27 +0000204 SkTArray<Checksum> fAllowedChecksums;
205 bool fIgnoreFailure;
epoger@google.com84a18022013-02-01 20:39:15 +0000206 SkBitmap fBitmap;
epoger@google.com37269602013-01-19 04:21:27 +0000207 };
208
209 /**
210 * Abstract source of Expectations objects for individual tests.
211 */
212 class ExpectationsSource : public SkRefCnt {
213 public:
214 virtual Expectations get(const char *testName) = 0;
215 };
216
217 /**
218 * Return Expectations based on individual image files on disk.
219 */
220 class IndividualImageExpectationsSource : public ExpectationsSource {
221 public:
222 /**
223 * Create an ExpectationsSource that will return Expectations based on
224 * image files found within rootDir.
225 *
226 * rootDir: directory under which to look for image files
227 * (this string will be copied to storage within this object)
epoger@google.com37269602013-01-19 04:21:27 +0000228 */
epoger@google.comb0f8b432013-04-10 18:46:25 +0000229 IndividualImageExpectationsSource(const char *rootDir) : fRootDir(rootDir) {}
epoger@google.com37269602013-01-19 04:21:27 +0000230
231 Expectations get(const char *testName) SK_OVERRIDE {
232 SkString path = make_filename(fRootDir.c_str(), "", testName,
233 "png");
234 SkBitmap referenceBitmap;
235 bool decodedReferenceBitmap =
236 SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
237 SkBitmap::kARGB_8888_Config,
238 SkImageDecoder::kDecodePixels_Mode,
239 NULL);
240 if (decodedReferenceBitmap) {
epoger@google.com84a18022013-02-01 20:39:15 +0000241 return Expectations(referenceBitmap);
epoger@google.com37269602013-01-19 04:21:27 +0000242 } else {
epoger@google.com37269602013-01-19 04:21:27 +0000243 return Expectations();
244 }
245 }
246
247 private:
248 const SkString fRootDir;
epoger@google.com37269602013-01-19 04:21:27 +0000249 };
250
epoger@google.comd271d242013-02-13 18:14:48 +0000251 /**
252 * Return Expectations based on JSON summary file.
253 */
254 class JsonExpectationsSource : public ExpectationsSource {
255 public:
256 /**
257 * Create an ExpectationsSource that will return Expectations based on
258 * a JSON file.
259 *
260 * jsonPath: path to JSON file to read
261 */
262 JsonExpectationsSource(const char *jsonPath) {
263 parse(jsonPath, &fJsonRoot);
264 fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
265 }
266
267 Expectations get(const char *testName) SK_OVERRIDE {
268 return Expectations(fJsonExpectedResults[testName]);
269 }
270
271 private:
272
273 /**
274 * Read as many bytes as possible (up to maxBytes) from the stream into
275 * an SkData object.
276 *
277 * If the returned SkData contains fewer than maxBytes, then EOF has been
278 * reached and no more data would be available from subsequent calls.
279 * (If EOF has already been reached, then this call will return an empty
280 * SkData object immediately.)
281 *
282 * If there are fewer than maxBytes bytes available to read from the
283 * stream, but the stream has not been closed yet, this call will block
284 * until there are enough bytes to read or the stream has been closed.
285 *
286 * It is up to the caller to call unref() on the returned SkData object
287 * once the data is no longer needed, so that the underlying buffer will
288 * be freed. For example:
289 *
290 * {
291 * size_t maxBytes = 256;
292 * SkAutoDataUnref dataRef(readIntoSkData(stream, maxBytes));
293 * if (NULL != dataRef.get()) {
294 * size_t bytesActuallyRead = dataRef.get()->size();
295 * // use the data...
296 * }
297 * }
298 * // underlying buffer has been freed, thanks to auto unref
299 *
300 */
301 // TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in
302 // https://codereview.appspot.com/7300071 ?
303 // And maybe readFileIntoSkData() also?
304 static SkData* readIntoSkData(SkStream &stream, size_t maxBytes) {
305 if (0 == maxBytes) {
306 return SkData::NewEmpty();
307 }
308 char* bufStart = reinterpret_cast<char *>(sk_malloc_throw(maxBytes));
309 char* bufPtr = bufStart;
310 size_t bytesRemaining = maxBytes;
311 while (bytesRemaining > 0) {
312 size_t bytesReadThisTime = stream.read(bufPtr, bytesRemaining);
313 if (0 == bytesReadThisTime) {
314 break;
315 }
316 bytesRemaining -= bytesReadThisTime;
317 bufPtr += bytesReadThisTime;
318 }
319 return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
320 }
321
322 /**
323 * Wrapper around readIntoSkData for files: reads the entire file into
324 * an SkData object.
325 */
326 static SkData* readFileIntoSkData(SkFILEStream &stream) {
327 return readIntoSkData(stream, stream.getLength());
328 }
329
330 /**
331 * Read the file contents from jsonPath and parse them into jsonRoot.
332 *
333 * Returns true if successful.
334 */
335 static bool parse(const char *jsonPath, Json::Value *jsonRoot) {
336 SkFILEStream inFile(jsonPath);
337 if (!inFile.isValid()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000338 gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
epoger@google.comd271d242013-02-13 18:14:48 +0000339 DEBUGFAIL_SEE_STDERR;
340 return false;
341 }
342
343 SkAutoDataUnref dataRef(readFileIntoSkData(inFile));
344 if (NULL == dataRef.get()) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000345 gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
epoger@google.comd271d242013-02-13 18:14:48 +0000346 DEBUGFAIL_SEE_STDERR;
347 return false;
348 }
349
350 const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
351 size_t size = dataRef.get()->size();
352 Json::Reader reader;
353 if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
epoger@google.com5efdd0c2013-03-13 14:18:40 +0000354 gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath);
epoger@google.comd271d242013-02-13 18:14:48 +0000355 DEBUGFAIL_SEE_STDERR;
356 return false;
357 }
358 return true;
359 }
360
361 Json::Value fJsonRoot;
362 Json::Value fJsonExpectedResults;
363 };
364
epoger@google.com37269602013-01-19 04:21:27 +0000365}
366#endif