OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2013 Google Inc. | 2 * Copyright 2013 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 #ifndef gm_expectations_DEFINED | 7 #ifndef gm_expectations_DEFINED |
8 #define gm_expectations_DEFINED | 8 #define gm_expectations_DEFINED |
9 | 9 |
10 #include <stdarg.h> | |
11 #include "gm.h" | 10 #include "gm.h" |
12 #include "SkBitmap.h" | 11 #include "SkBitmap.h" |
13 #include "SkBitmapHasher.h" | 12 #include "SkBitmapHasher.h" |
14 #include "SkData.h" | 13 #include "SkData.h" |
15 #include "SkImageDecoder.h" | |
16 #include "SkOSFile.h" | 14 #include "SkOSFile.h" |
17 #include "SkRefCnt.h" | 15 #include "SkRefCnt.h" |
18 #include "SkStream.h" | 16 #include "SkStream.h" |
19 #include "SkTArray.h" | 17 #include "SkTArray.h" |
20 | 18 |
21 #ifdef SK_BUILD_FOR_WIN | 19 #ifdef SK_BUILD_FOR_WIN |
22 // json includes xlocale which generates warning 4530 because we're compilin
g without | 20 // json includes xlocale which generates warning 4530 because we're compilin
g without |
23 // exceptions; see https://code.google.com/p/skia/issues/detail?id=1067 | 21 // exceptions; see https://code.google.com/p/skia/issues/detail?id=1067 |
24 #pragma warning(push) | 22 #pragma warning(push) |
25 #pragma warning(disable : 4530) | 23 #pragma warning(disable : 4530) |
26 #endif | 24 #endif |
27 #include "json/reader.h" | 25 #include "json/reader.h" |
28 #include "json/value.h" | 26 #include "json/value.h" |
29 #ifdef SK_BUILD_FOR_WIN | 27 #ifdef SK_BUILD_FOR_WIN |
30 #pragma warning(pop) | 28 #pragma warning(pop) |
31 #endif | 29 #endif |
32 | 30 |
33 #define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message") | |
34 | |
35 const static char kJsonKey_ActualResults[] = "actual-results"; | |
36 const static char kJsonKey_ActualResults_Failed[] = "failed"; | |
37 const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored"; | |
38 const static char kJsonKey_ActualResults_NoComparison[] = "no-comparison"; | |
39 const static char kJsonKey_ActualResults_Succeeded[] = "succeeded"; | |
40 const static char kJsonKey_ActualResults_AnyStatus_Checksum[] = "checksum"; | |
41 | |
42 const static char kJsonKey_ExpectedResults[] = "expected-results"; | |
43 const static char kJsonKey_ExpectedResults_Checksums[] = "checksums"; | |
44 const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure"; | |
45 | |
46 namespace skiagm { | 31 namespace skiagm { |
47 | 32 |
48 // The actual type we use to represent a checksum is hidden in here. | 33 // The actual type we use to represent a checksum is hidden in here. |
49 typedef Json::UInt64 Checksum; | 34 typedef Json::UInt64 Checksum; |
50 static inline Json::Value asJsonValue(Checksum checksum) { | 35 static inline Json::Value asJsonValue(Checksum checksum) { |
51 return checksum; | 36 return checksum; |
52 } | 37 } |
53 static inline Checksum asChecksum(Json::Value jsonValue) { | 38 static inline Checksum asChecksum(Json::Value jsonValue) { |
54 return jsonValue.asUInt64(); | 39 return jsonValue.asUInt64(); |
55 } | 40 } |
(...skipping 12 matching lines...) Expand all Loading... |
68 const char suffix[]) { | 53 const char suffix[]) { |
69 SkString filename(path); | 54 SkString filename(path); |
70 if (filename.endsWith(SkPATH_SEPARATOR)) { | 55 if (filename.endsWith(SkPATH_SEPARATOR)) { |
71 filename.remove(filename.size() - 1, 1); | 56 filename.remove(filename.size() - 1, 1); |
72 } | 57 } |
73 filename.appendf("%c%s%s.%s", SkPATH_SEPARATOR, | 58 filename.appendf("%c%s%s.%s", SkPATH_SEPARATOR, |
74 name, renderModeDescriptor, suffix); | 59 name, renderModeDescriptor, suffix); |
75 return filename; | 60 return filename; |
76 } | 61 } |
77 | 62 |
| 63 Json::Value ActualResultAsJsonValue(const SkHashDigest& result); |
| 64 |
| 65 Json::Value CreateJsonTree(Json::Value expectedResults, |
| 66 Json::Value actualResultsFailed, |
| 67 Json::Value actualResultsFailureIgnored, |
| 68 Json::Value actualResultsNoComparison, |
| 69 Json::Value actualResultsSucceeded); |
| 70 |
78 /** | 71 /** |
79 * Test expectations (allowed image checksums, etc.) | 72 * Test expectations (allowed image checksums, etc.) |
80 */ | 73 */ |
81 class Expectations { | 74 class Expectations { |
82 public: | 75 public: |
83 /** | 76 /** |
84 * No expectations at all. | 77 * No expectations at all. |
85 */ | 78 */ |
86 Expectations(bool ignoreFailure=kDefaultIgnoreFailure) { | 79 Expectations(bool ignoreFailure=kDefaultIgnoreFailure); |
87 fIgnoreFailure = ignoreFailure; | |
88 } | |
89 | 80 |
90 /** | 81 /** |
91 * Expect exactly one image (appropriate for the case when we | 82 * Expect exactly one image (appropriate for the case when we |
92 * are comparing against a single PNG file). | 83 * are comparing against a single PNG file). |
93 */ | 84 */ |
94 Expectations(const SkBitmap& bitmap, bool ignoreFailure=kDefaultIgnoreFa
ilure) { | 85 Expectations(const SkBitmap& bitmap, bool ignoreFailure=kDefaultIgnoreFa
ilure); |
95 fBitmap = bitmap; | |
96 fIgnoreFailure = ignoreFailure; | |
97 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; | |
104 } | |
105 | 86 |
106 /** | 87 /** |
107 * Create Expectations from a JSON element as found within the | 88 * Create Expectations from a JSON element as found within the |
108 * kJsonKey_ExpectedResults section. | 89 * kJsonKey_ExpectedResults section. |
109 * | 90 * |
110 * It's fine if the jsonElement is null or empty; in that case, we just | 91 * It's fine if the jsonElement is null or empty; in that case, we just |
111 * don't have any expectations. | 92 * don't have any expectations. |
112 */ | 93 */ |
113 Expectations(Json::Value jsonElement) { | 94 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()) { | |
121 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()); | |
125 DEBUGFAIL_SEE_STDERR; | |
126 fIgnoreFailure = kDefaultIgnoreFailure; | |
127 } else { | |
128 fIgnoreFailure = ignoreFailure.asBool(); | |
129 } | |
130 | |
131 Json::Value allowedChecksums = jsonElement[kJsonKey_ExpectedResu
lts_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()) { | |
135 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()); | |
139 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()) { | |
144 gm_fprintf(stderr, "found non-integer checksum" | |
145 " in json element '%s'\n", | |
146 jsonElement.toStyledString().c_str()); | |
147 DEBUGFAIL_SEE_STDERR; | |
148 } else { | |
149 fAllowedChecksums.push_back() = asChecksum(checksumE
lement); | |
150 } | |
151 } | |
152 } | |
153 } | |
154 } | |
155 | 95 |
156 /** | 96 /** |
157 * Returns true iff we want to ignore failed expectations. | 97 * Returns true iff we want to ignore failed expectations. |
158 */ | 98 */ |
159 bool ignoreFailure() const { return this->fIgnoreFailure; } | 99 bool ignoreFailure() const { return this->fIgnoreFailure; } |
160 | 100 |
161 /** | 101 /** |
162 * Returns true iff there are no allowed checksums. | 102 * Returns true iff there are no allowed checksums. |
163 */ | 103 */ |
164 bool empty() const { return this->fAllowedChecksums.empty(); } | 104 bool empty() const { return this->fAllowedBitmapCityhashes.empty(); } |
165 | 105 |
166 /** | 106 /** |
167 * Returns true iff actualChecksum matches any allowedChecksum, | 107 * Returns true iff actualChecksum matches any allowedChecksum, |
168 * regardless of fIgnoreFailure. (The caller can check | 108 * regardless of fIgnoreFailure. (The caller can check |
169 * that separately.) | 109 * that separately.) |
170 */ | 110 */ |
171 bool match(Checksum actualChecksum) const { | 111 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 | 112 |
181 /** | 113 /** |
182 * If this Expectation is based on a single SkBitmap, return a | 114 * If this Expectation is based on a single SkBitmap, return a |
183 * pointer to that SkBitmap. Otherwise (if the Expectation is | 115 * pointer to that SkBitmap. Otherwise (if the Expectation is |
184 * empty, or if it was based on a list of checksums rather | 116 * empty, or if it was based on a list of checksums rather |
185 * than a single bitmap), returns NULL. | 117 * than a single bitmap), returns NULL. |
186 */ | 118 */ |
187 const SkBitmap *asBitmap() const { | 119 const SkBitmap *asBitmap() const { |
188 return (SkBitmap::kNo_Config == fBitmap.config()) ? NULL : &fBitmap; | 120 return (SkBitmap::kNo_Config == fBitmap.config()) ? NULL : &fBitmap; |
189 } | 121 } |
190 | 122 |
191 /** | 123 /** |
192 * Return a JSON representation of the allowed checksums. | 124 * Return a JSON representation of the expectations. |
193 * This does NOT include any information about whether to | |
194 * ignore failures. | |
195 */ | 125 */ |
196 Json::Value allowedChecksumsAsJson() const { | 126 Json::Value asJsonValue() 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 | 127 |
207 private: | 128 private: |
208 const static bool kDefaultIgnoreFailure = false; | 129 const static bool kDefaultIgnoreFailure = false; |
209 | 130 |
210 SkTArray<Checksum> fAllowedChecksums; | 131 SkTArray<Checksum> fAllowedBitmapCityhashes; |
211 bool fIgnoreFailure; | 132 bool fIgnoreFailure; |
212 SkBitmap fBitmap; | 133 SkBitmap fBitmap; |
213 }; | 134 }; |
214 | 135 |
215 /** | 136 /** |
216 * Abstract source of Expectations objects for individual tests. | 137 * Abstract source of Expectations objects for individual tests. |
217 */ | 138 */ |
218 class ExpectationsSource : public SkRefCnt { | 139 class ExpectationsSource : public SkRefCnt { |
219 public: | 140 public: |
220 virtual Expectations get(const char *testName) = 0; | 141 virtual Expectations get(const char *testName) = 0; |
221 }; | 142 }; |
222 | 143 |
223 /** | 144 /** |
224 * Return Expectations based on individual image files on disk. | 145 * Return Expectations based on individual image files on disk. |
225 */ | 146 */ |
226 class IndividualImageExpectationsSource : public ExpectationsSource { | 147 class IndividualImageExpectationsSource : public ExpectationsSource { |
227 public: | 148 public: |
228 /** | 149 /** |
229 * Create an ExpectationsSource that will return Expectations based on | 150 * Create an ExpectationsSource that will return Expectations based on |
230 * image files found within rootDir. | 151 * image files found within rootDir. |
231 * | 152 * |
232 * rootDir: directory under which to look for image files | 153 * rootDir: directory under which to look for image files |
233 * (this string will be copied to storage within this object) | 154 * (this string will be copied to storage within this object) |
234 */ | 155 */ |
235 IndividualImageExpectationsSource(const char *rootDir) : fRootDir(rootDi
r) {} | 156 IndividualImageExpectationsSource(const char *rootDir) : fRootDir(rootDi
r) {} |
236 | 157 |
237 Expectations get(const char *testName) SK_OVERRIDE { | 158 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) { | |
247 return Expectations(referenceBitmap); | |
248 } else { | |
249 return Expectations(); | |
250 } | |
251 } | |
252 | 159 |
253 private: | 160 private: |
254 const SkString fRootDir; | 161 const SkString fRootDir; |
255 }; | 162 }; |
256 | 163 |
257 /** | 164 /** |
258 * Return Expectations based on JSON summary file. | 165 * Return Expectations based on JSON summary file. |
259 */ | 166 */ |
260 class JsonExpectationsSource : public ExpectationsSource { | 167 class JsonExpectationsSource : public ExpectationsSource { |
261 public: | 168 public: |
262 /** | 169 /** |
263 * Create an ExpectationsSource that will return Expectations based on | 170 * Create an ExpectationsSource that will return Expectations based on |
264 * a JSON file. | 171 * a JSON file. |
265 * | 172 * |
266 * jsonPath: path to JSON file to read | 173 * jsonPath: path to JSON file to read |
267 */ | 174 */ |
268 JsonExpectationsSource(const char *jsonPath) { | 175 JsonExpectationsSource(const char *jsonPath); |
269 parse(jsonPath, &fJsonRoot); | |
270 fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults]; | |
271 } | |
272 | 176 |
273 Expectations get(const char *testName) SK_OVERRIDE { | 177 Expectations get(const char *testName) SK_OVERRIDE; |
274 return Expectations(fJsonExpectedResults[testName]); | |
275 } | |
276 | 178 |
277 private: | 179 private: |
278 | 180 |
279 /** | 181 /** |
280 * Read as many bytes as possible (up to maxBytes) from the stream into | 182 * Read as many bytes as possible (up to maxBytes) from the stream into |
281 * an SkData object. | 183 * an SkData object. |
282 * | 184 * |
283 * If the returned SkData contains fewer than maxBytes, then EOF has bee
n | 185 * If the returned SkData contains fewer than maxBytes, then EOF has bee
n |
284 * reached and no more data would be available from subsequent calls. | 186 * 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 | 187 * (If EOF has already been reached, then this call will return an empty |
(...skipping 14 matching lines...) Expand all Loading... |
300 * size_t bytesActuallyRead = dataRef.get()->size(); | 202 * size_t bytesActuallyRead = dataRef.get()->size(); |
301 * // use the data... | 203 * // use the data... |
302 * } | 204 * } |
303 * } | 205 * } |
304 * // underlying buffer has been freed, thanks to auto unref | 206 * // underlying buffer has been freed, thanks to auto unref |
305 * | 207 * |
306 */ | 208 */ |
307 // TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in | 209 // TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in |
308 // https://codereview.appspot.com/7300071 ? | 210 // https://codereview.appspot.com/7300071 ? |
309 // And maybe readFileIntoSkData() also? | 211 // And maybe readFileIntoSkData() also? |
310 static SkData* readIntoSkData(SkStream &stream, size_t maxBytes) { | 212 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 | 213 |
328 /** | 214 /** |
329 * Wrapper around readIntoSkData for files: reads the entire file into | 215 * Wrapper around readIntoSkData for files: reads the entire file into |
330 * an SkData object. | 216 * an SkData object. |
331 */ | 217 */ |
332 static SkData* readFileIntoSkData(SkFILEStream &stream) { | 218 static SkData* readFileIntoSkData(SkFILEStream &stream) { |
333 return readIntoSkData(stream, stream.getLength()); | 219 return readIntoSkData(stream, stream.getLength()); |
334 } | 220 } |
335 | 221 |
336 /** | 222 /** |
337 * Read the file contents from jsonPath and parse them into jsonRoot. | 223 * Read the file contents from jsonPath and parse them into jsonRoot. |
338 * | 224 * |
339 * Returns true if successful. | 225 * Returns true if successful. |
340 */ | 226 */ |
341 static bool parse(const char *jsonPath, Json::Value *jsonRoot) { | 227 static bool parse(const char *jsonPath, Json::Value *jsonRoot); |
342 SkFILEStream inFile(jsonPath); | |
343 if (!inFile.isValid()) { | |
344 gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath); | |
345 DEBUGFAIL_SEE_STDERR; | |
346 return false; | |
347 } | |
348 | |
349 SkAutoDataUnref dataRef(readFileIntoSkData(inFile)); | |
350 if (NULL == dataRef.get()) { | |
351 gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath); | |
352 DEBUGFAIL_SEE_STDERR; | |
353 return false; | |
354 } | |
355 | |
356 const char *bytes = reinterpret_cast<const char *>(dataRef.get()->da
ta()); | |
357 size_t size = dataRef.get()->size(); | |
358 Json::Reader reader; | |
359 if (!reader.parse(bytes, bytes+size, *jsonRoot)) { | |
360 gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath); | |
361 DEBUGFAIL_SEE_STDERR; | |
362 return false; | |
363 } | |
364 return true; | |
365 } | |
366 | 228 |
367 Json::Value fJsonRoot; | 229 Json::Value fJsonRoot; |
368 Json::Value fJsonExpectedResults; | 230 Json::Value fJsonExpectedResults; |
369 }; | 231 }; |
370 | 232 |
371 } | 233 } |
372 #endif | 234 #endif |
OLD | NEW |