create sk_tools::Expectation class, similar to skiagm::Expectations class
The overarching goal here is for our "gm" and "render_pictures" tools to handle
image expectations/actuals in the same way, sharing the same code, so their
results can be processed through a single pipeline.
By adding an Expectation class within tools/image_expectations.h, similar to
the Expectations class in gm/gm_expectations.h, we get one step closer to
that goal.
R=stephana@google.com
TBR=stephana
Author: epoger@google.com
Review URL: https://codereview.chromium.org/493363002
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 7325c20..ac639e5 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -297,7 +297,7 @@
SkString outputFilename;
const char *outputSubdirPtr = NULL;
if (useChecksumBasedFilenames) {
- const ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
+ ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
outputSubdirPtr = escapedInputFilename.c_str();
outputFilename.set(imageDigestPtr->getHashType());
outputFilename.append("_");
@@ -312,7 +312,7 @@
outputFilename.append(".png");
if (NULL != jsonSummaryPtr) {
- const ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
+ ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
SkString outputRelativePath;
if (outputSubdirPtr) {
outputRelativePath.set(outputSubdirPtr);
@@ -325,8 +325,8 @@
jsonSummaryPtr->add(inputFilename.c_str(), outputRelativePath.c_str(),
*imageDigestPtr, tileNumberPtr);
if (!mismatchPath.isEmpty() &&
- !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), *imageDigestPtr,
- tileNumberPtr)) {
+ !jsonSummaryPtr->getExpectation(inputFilename.c_str(),
+ tileNumberPtr).matches(*imageDigestPtr)) {
if (!write_bitmap_to_disk(bitmap, mismatchPath, outputSubdirPtr, outputFilename)) {
return false;
}
diff --git a/tools/image_expectations.cpp b/tools/image_expectations.cpp
index dfc2638..f0c9cfb 100644
--- a/tools/image_expectations.cpp
+++ b/tools/image_expectations.cpp
@@ -53,44 +53,69 @@
// ImageDigest class...
- ImageDigest::ImageDigest(const SkBitmap &bitmap) {
- if (!SkBitmapHasher::ComputeDigest(bitmap, &fHashValue)) {
- SkFAIL("unable to compute image digest");
- }
- }
+ ImageDigest::ImageDigest(const SkBitmap &bitmap) :
+ fBitmap(bitmap), fHashValue(0), fComputedHashValue(false) {}
- ImageDigest::ImageDigest(const SkString &hashType, uint64_t hashValue) {
+ ImageDigest::ImageDigest(const SkString &hashType, uint64_t hashValue) :
+ fBitmap(), fHashValue(hashValue), fComputedHashValue(true) {
if (!hashType.equals(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5)) {
- SkFAIL((SkString("unsupported hashType ")+=hashType).c_str());
- } else {
- fHashValue = hashValue;
+ SkDebugf("unsupported hashType '%s'\n", hashType.c_str());
+ SkFAIL("unsupported hashType (see above)");
}
}
- SkString ImageDigest::getHashType() const {
+ bool ImageDigest::equals(ImageDigest &other) {
+ // TODO(epoger): The current implementation assumes that this
+ // and other always have hashType kJsonKey_Hashtype_Bitmap_64bitMD5
+ return (this->getHashValue() == other.getHashValue());
+ }
+
+ SkString ImageDigest::getHashType() {
// TODO(epoger): The current implementation assumes that the
// result digest is always of type kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 .
return SkString(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5);
}
- uint64_t ImageDigest::getHashValue() const {
- return fHashValue;
+ uint64_t ImageDigest::getHashValue() {
+ if (!this->fComputedHashValue) {
+ if (!SkBitmapHasher::ComputeDigest(this->fBitmap, &this->fHashValue)) {
+ SkFAIL("unable to compute image digest");
+ }
+ this->fComputedHashValue = true;
+ }
+ return this->fHashValue;
}
// BitmapAndDigest class...
- BitmapAndDigest::BitmapAndDigest(const SkBitmap &bitmap) : fBitmap(bitmap) {
- }
+ BitmapAndDigest::BitmapAndDigest(const SkBitmap &bitmap) :
+ fBitmap(bitmap), fImageDigest(bitmap) {}
- const ImageDigest *BitmapAndDigest::getImageDigestPtr() {
- if (NULL == fImageDigestRef.get()) {
- fImageDigestRef.reset(SkNEW_ARGS(ImageDigest, (fBitmap)));
- }
- return fImageDigestRef.get();
- }
+ const SkBitmap *BitmapAndDigest::getBitmapPtr() const {return &fBitmap;}
- const SkBitmap *BitmapAndDigest::getBitmapPtr() const {
- return &fBitmap;
+ ImageDigest *BitmapAndDigest::getImageDigestPtr() {return &fImageDigest;}
+
+ // Expectation class...
+
+ // For when we need a valid ImageDigest, but we don't care what it is.
+ static const ImageDigest kDummyImageDigest(
+ SkString(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5), 0);
+
+ Expectation::Expectation(bool ignoreFailure) :
+ fIsEmpty(true), fIgnoreFailure(ignoreFailure), fImageDigest(kDummyImageDigest) {}
+
+ Expectation::Expectation(const SkString &hashType, uint64_t hashValue, bool ignoreFailure) :
+ fIsEmpty(false), fIgnoreFailure(ignoreFailure), fImageDigest(hashType, hashValue) {}
+
+ Expectation::Expectation(const SkBitmap& bitmap, bool ignoreFailure) :
+ fIsEmpty(false), fIgnoreFailure(ignoreFailure), fImageDigest(bitmap) {}
+
+ bool Expectation::ignoreFailure() const { return this->fIgnoreFailure; }
+
+ bool Expectation::empty() const { return this->fIsEmpty; }
+
+ bool Expectation::matches(ImageDigest &imageDigest) {
+ return !(this->fIsEmpty) && (this->fImageDigest.equals(imageDigest));
}
// ImageResultsAndExpectations class...
@@ -136,19 +161,11 @@
}
void ImageResultsAndExpectations::add(const char *sourceName, const char *fileName,
- const ImageDigest &digest, const int *tileNumber) {
+ ImageDigest &digest, const int *tileNumber) {
// Get expectation, if any.
- Json::Value expectedImage;
- if (!fExpectedResults.isNull()) {
- if (NULL == tileNumber) {
- expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage];
- } else {
- expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages]
- [*tileNumber];
- }
- }
+ Expectation expectation = this->getExpectation(sourceName, tileNumber);
- // Fill in info about the actual result itself.
+ // Fill in info about the actual result.
Json::Value actualChecksumAlgorithm = digest.getHashType().c_str();
Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue());
Json::Value actualImage;
@@ -157,16 +174,15 @@
actualImage[kJsonKey_Image_Filepath] = fileName;
// Compare against expectedImage to fill in comparisonResult.
- Json::Value comparisonResult = kJsonValue_Image_ComparisonResult_NoComparison;
- if (!expectedImage.isNull()) {
- if ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) &&
- (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue])) {
- comparisonResult = kJsonValue_Image_ComparisonResult_Succeeded;
- } else if (expectedImage[kJsonKey_Image_IgnoreFailure] == true) {
- comparisonResult = kJsonValue_Image_ComparisonResult_FailureIgnored;
- } else {
- comparisonResult = kJsonValue_Image_ComparisonResult_Failed;
- }
+ Json::Value comparisonResult;
+ if (expectation.empty()) {
+ comparisonResult = kJsonValue_Image_ComparisonResult_NoComparison;
+ } else if (expectation.matches(digest)) {
+ comparisonResult = kJsonValue_Image_ComparisonResult_Succeeded;
+ } else if (expectation.ignoreFailure()) {
+ comparisonResult = kJsonValue_Image_ComparisonResult_FailureIgnored;
+ } else {
+ comparisonResult = kJsonValue_Image_ComparisonResult_Failed;
}
actualImage[kJsonKey_Image_ComparisonResult] = comparisonResult;
@@ -182,11 +198,10 @@
fDescriptions[key] = value;
}
- bool ImageResultsAndExpectations::matchesExpectation(const char *sourceName,
- const ImageDigest &digest,
- const int *tileNumber) {
+ Expectation ImageResultsAndExpectations::getExpectation(const char *sourceName,
+ const int *tileNumber) {
if (fExpectedResults.isNull()) {
- return false;
+ return Expectation();
}
Json::Value expectedImage;
@@ -196,13 +211,13 @@
expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber];
}
if (expectedImage.isNull()) {
- return false;
+ return Expectation();
}
- Json::Value actualChecksumAlgorithm = digest.getHashType().c_str();
- Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue());
- return ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) &&
- (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue]));
+ bool ignoreFailure = (expectedImage[kJsonKey_Image_IgnoreFailure] == true);
+ return Expectation(SkString(expectedImage[kJsonKey_Image_ChecksumAlgorithm].asCString()),
+ expectedImage[kJsonKey_Image_ChecksumValue].asUInt64(),
+ ignoreFailure);
}
void ImageResultsAndExpectations::writeToFile(const char *filename) const {
diff --git a/tools/image_expectations.h b/tools/image_expectations.h
index ae1df19..2d58b92 100644
--- a/tools/image_expectations.h
+++ b/tools/image_expectations.h
@@ -11,7 +11,6 @@
#include "SkBitmap.h"
#include "SkJSONCPP.h"
#include "SkOSFile.h"
-#include "SkRefCnt.h"
namespace sk_tools {
@@ -20,14 +19,12 @@
*
* Currently, this is always a uint64_t hash digest of an SkBitmap.
*/
- class ImageDigest : public SkRefCnt {
+ class ImageDigest {
public:
/**
* Create an ImageDigest of a bitmap.
*
- * Note that this is an expensive operation, because it has to examine all pixels in
- * the bitmap. You may wish to consider using the BitmapAndDigest class, which will
- * compute the ImageDigest lazily.
+ * Computes the hash of the bitmap lazily, since that is an expensive operation.
*
* @param bitmap image to get the digest of
*/
@@ -43,36 +40,105 @@
explicit ImageDigest(const SkString &hashType, uint64_t hashValue);
/**
+ * Returns true iff this and other ImageDigest represent identical images.
+ */
+ bool equals(ImageDigest &other);
+
+ /**
* Returns the hash digest type as an SkString.
*
* For now, this always returns kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 .
*/
- SkString getHashType() const;
+ SkString getHashType();
/**
* Returns the hash digest value as a uint64_t.
+ *
+ * Since the hash is computed lazily, this may take some time, and it may modify
+ * some fields on this object.
*/
- uint64_t getHashValue() const;
+ uint64_t getHashValue();
private:
+ const SkBitmap fBitmap;
uint64_t fHashValue;
+ bool fComputedHashValue;
};
/**
- * Container that holds a reference to an SkBitmap and computes its ImageDigest lazily.
- *
- * Computing the ImageDigest can be expensive, so this can help you postpone (or maybe even
- * avoid) that work.
+ * Container that holds a reference to an SkBitmap and its ImageDigest.
*/
class BitmapAndDigest {
public:
explicit BitmapAndDigest(const SkBitmap &bitmap);
- const ImageDigest *getImageDigestPtr();
const SkBitmap *getBitmapPtr() const;
+
+ /**
+ * Returns a pointer to the ImageDigest.
+ *
+ * Since the hash is computed lazily within the ImageDigest object, we cannot mandate
+ * that it be held const.
+ */
+ ImageDigest *getImageDigestPtr();
private:
const SkBitmap fBitmap;
- SkAutoTUnref<ImageDigest> fImageDigestRef;
+ ImageDigest fImageDigest;
+ };
+
+ /**
+ * Expected test result: expected image (if any), and whether we want to ignore failures on
+ * this test or not.
+ *
+ * This is just an ImageDigest (or lack thereof, if there is no expectation) and a boolean
+ * telling us whether to ignore failures.
+ */
+ class Expectation {
+ public:
+ /**
+ * No expectation at all.
+ */
+ explicit Expectation(bool ignoreFailure=kDefaultIgnoreFailure);
+
+ /**
+ * Expect an image, passed as hashType/hashValue.
+ */
+ explicit Expectation(const SkString &hashType, uint64_t hashValue,
+ bool ignoreFailure=kDefaultIgnoreFailure);
+
+ /**
+ * Expect an image, passed as a bitmap.
+ */
+ explicit Expectation(const SkBitmap& bitmap,
+ bool ignoreFailure=kDefaultIgnoreFailure);
+
+ /**
+ * Returns true iff we want to ignore failed expectations.
+ */
+ bool ignoreFailure() const;
+
+ /**
+ * Returns true iff there are no allowed results.
+ */
+ bool empty() const;
+
+ /**
+ * Returns true iff we are expecting a particular image, and imageDigest matches it.
+ *
+ * If empty() returns true, this will return false.
+ *
+ * If this expectation DOES contain an image, and imageDigest doesn't match it,
+ * this method will return false regardless of what ignoreFailure() would return.
+ * (The caller can check that separately.)
+ */
+ bool matches(ImageDigest &imageDigest);
+
+ private:
+ static const bool kDefaultIgnoreFailure = false;
+
+ const bool fIsEmpty;
+ const bool fIgnoreFailure;
+ ImageDigest fImageDigest; // cannot be const, because it computes its hash lazily
};
/**
@@ -107,7 +173,7 @@
* @param digest description of the image's contents
* @param tileNumber if not NULL, pointer to tile number
*/
- void add(const char *sourceName, const char *fileName, const ImageDigest &digest,
+ void add(const char *sourceName, const char *fileName, ImageDigest &digest,
const int *tileNumber=NULL);
/**
@@ -119,15 +185,15 @@
void addDescription(const char *key, const char *value);
/**
- * Returns true if this test result matches its expectations.
- * If there are no expectations for this test result, this will return false.
+ * Returns the Expectation for this test.
*
* @param sourceName name of the source file that generated this result
- * @param digest description of the image's contents
* @param tileNumber if not NULL, pointer to tile number
+ *
+ * TODO(stephana): To make this work for GMs, we will need to add parameters for
+ * config, and maybe renderMode/builder?
*/
- bool matchesExpectation(const char *sourceName, const ImageDigest &digest,
- const int *tileNumber=NULL);
+ Expectation getExpectation(const char *sourceName, const int *tileNumber=NULL);
/**
* Writes the summary (as constructed so far) to a file.
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
index e62c3bc..595a78e 100644
--- a/tools/render_pictures_main.cpp
+++ b/tools/render_pictures_main.cpp
@@ -366,7 +366,7 @@
sk_tools::ImageDigest imageDigest(*bitmap);
jsonSummaryPtr->add(inputFilename.c_str(), outputFilename.c_str(), imageDigest);
if ((NULL != mismatchPath) && !mismatchPath->isEmpty() &&
- !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), imageDigest)) {
+ !jsonSummaryPtr->getExpectation(inputFilename.c_str()).matches(imageDigest)) {
success &= sk_tools::write_bitmap_to_disk(*bitmap, *mismatchPath, NULL,
outputFilename);
}
diff --git a/tools/tests/render_pictures_test.py b/tools/tests/render_pictures_test.py
index 1fdeb10..9ff3226 100755
--- a/tools/tests/render_pictures_test.py
+++ b/tools/tests/render_pictures_test.py
@@ -213,6 +213,43 @@
self._assert_directory_contents(
write_path_dir, ['red_skp.png', 'green_skp.png'])
+ def test_ignore_some_failures(self):
+ """test_tiled_whole_image, but ignoring some failed tests.
+ """
+ output_json_path = os.path.join(self._output_dir, 'actuals.json')
+ write_path_dir = self.create_empty_dir(
+ path=os.path.join(self._output_dir, 'writePath'))
+ self._generate_skps()
+ expectations_path = self._create_expectations(ignore_some_failures=True)
+ self._run_render_pictures([
+ '-r', self._input_skp_dir,
+ '--bbh', 'grid', '256', '256',
+ '--mode', 'tile', '256', '256',
+ '--readJsonSummaryPath', expectations_path,
+ '--writeJsonSummaryPath', output_json_path,
+ '--writePath', write_path_dir,
+ '--writeWholeImage'])
+ modified_red_tiles = copy.deepcopy(RED_TILES)
+ modified_red_tiles[5]['comparisonResult'] = 'failure-ignored'
+ expected_summary_dict = {
+ "header" : EXPECTED_HEADER_CONTENTS,
+ "descriptions" : None,
+ "actual-results" : {
+ "red.skp": {
+ "tiled-images": modified_red_tiles,
+ "whole-image": modified_dict(
+ RED_WHOLEIMAGE, {"comparisonResult" : "failure-ignored"}),
+ },
+ "green.skp": {
+ "tiled-images": GREEN_TILES,
+ "whole-image": GREEN_WHOLEIMAGE,
+ }
+ }
+ }
+ self._assert_json_contents(output_json_path, expected_summary_dict)
+ self._assert_directory_contents(
+ write_path_dir, ['red_skp.png', 'green_skp.png'])
+
def test_missing_tile_and_whole_image(self):
"""test_tiled_whole_image, but missing expectations for some images.
"""
@@ -591,12 +628,14 @@
[binary, '--config', '8888'] + args)
def _create_expectations(self, missing_some_images=False,
+ ignore_some_failures=False,
rel_path='expectations.json'):
"""Creates expectations JSON file within self._expectations_dir .
Args:
missing_some_images: (bool) whether to remove expectations for a subset
of the images
+ ignore_some_failures: (bool) whether to ignore some failing tests
rel_path: (string) relative path within self._expectations_dir to write
the expectations into
@@ -621,8 +660,13 @@
}
}
if missing_some_images:
- del expectations_dict['expected-results']['red.skp']['whole-image']
- del expectations_dict['expected-results']['red.skp']['tiled-images'][-1]
+ red_subdict = expectations_dict['expected-results']['red.skp']
+ del red_subdict['whole-image']
+ del red_subdict['tiled-images'][-1]
+ elif ignore_some_failures:
+ red_subdict = expectations_dict['expected-results']['red.skp']
+ red_subdict['whole-image']['ignoreFailure'] = True
+ red_subdict['tiled-images'][-1]['ignoreFailure'] = True
path = os.path.join(self._expectations_dir, rel_path)
with open(path, 'w') as fh:
json.dump(expectations_dict, fh)