| 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 |