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