Replace DM:Error with DM::Result.

Initially, Error was written with the intent that an empty string meant
Ok and anything else meant Fatal. This made things simple with implicit
constructors from strings. With the introduction of Nonfatal the state
was now tied up with an additional boolean. Now the empty string meant
Ok and causes the new boolean to be ignored, or at least that is the way
it was used since Error didn't actually enforce that itself. This leads
to GMs which return kSkip but don't set the message to not be skipped.
This could be fixed in several ways.

The first would be for the GMSrc to notice that a GM had returned kSkip
with an empty message and create the Error::Nonfatal with a non-empty
message. This has the downside of being some extra unexpected complexity
and doesn't prevent similar issues from arising in the future.

The second would be to change how DM interprets the Error, and if the
non-fatal bit is set treat that as a sign to skip, even if the message
is empty. This fixes the stated issue, but doesn't fix the issue where a
GM can return kFail but also leave the message empty. This could again
be fixed by either modifying GMSrc::draw or GM::drawContent, but this
also seems a bit brittle in not preventing this from happening again in
the future.

So this replaces Error with Result, which makes the status orthogonal to
the message. It does lose the automatic conversion from string, but by
being able to wrap the many uses of SkStringPrintf the explicit nature
doesn't add much additional noise to the more complex failure reports.

Change-Id: Ibf48b67faa09a91a4a9d792d204bd9810b441c6c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/270362
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/dm/DM.cpp b/dm/DM.cpp
index ba68db0..d03f17b 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -902,9 +902,9 @@
 
     // Try a simple Src as a canary.  If it fails, skip this sink.
     struct : public Src {
-        Error draw(SkCanvas* c) const override {
+        Result draw(SkCanvas* c) const override {
             c->drawRect(SkRect::MakeWH(1,1), SkPaint());
-            return "";
+            return Result::Ok();
         }
         SkISize size() const override { return SkISize::Make(16, 16); }
         Name name() const override { return "justOneRect"; }
@@ -913,9 +913,9 @@
     SkBitmap bitmap;
     SkDynamicMemoryWStream stream;
     SkString log;
-    Error err = sink->draw(justOneRect, &bitmap, &stream, &log);
-    if (err.isFatal()) {
-        info("Could not run %s: %s\n", config.getTag().c_str(), err.c_str());
+    Result result = sink->draw(justOneRect, &bitmap, &stream, &log);
+    if (result.isFatal()) {
+        info("Could not run %s: %s\n", config.getTag().c_str(), result.c_str());
         exit(1);
     }
 
@@ -1115,7 +1115,7 @@
             SkDynamicMemoryWStream stream;
             start(task.sink.tag.c_str(), task.src.tag.c_str(),
                   task.src.options.c_str(), name.c_str());
-            Error err = task.sink->draw(*task.src, &bitmap, &stream, &log);
+            Result result = task.sink->draw(*task.src, &bitmap, &stream, &log);
             if (!log.isEmpty()) {
                 info("%s %s %s %s:\n%s\n", task.sink.tag.c_str()
                                          , task.src.tag.c_str()
@@ -1123,19 +1123,18 @@
                                          , name.c_str()
                                          , log.c_str());
             }
-            if (!err.isEmpty()) {
-                if (err.isFatal()) {
-                    fail(SkStringPrintf("%s %s %s %s: %s",
-                                        task.sink.tag.c_str(),
-                                        task.src.tag.c_str(),
-                                        task.src.options.c_str(),
-                                        name.c_str(),
-                                        err.c_str()));
-                } else {
-                    done(task.sink.tag.c_str(), task.src.tag.c_str(),
-                         task.src.options.c_str(), name.c_str());
-                    return;
-                }
+            if (result.isSkip()) {
+                done(task.sink.tag.c_str(), task.src.tag.c_str(),
+                     task.src.options.c_str(), name.c_str());
+                return;
+            }
+            if (result.isFatal()) {
+                fail(SkStringPrintf("%s %s %s %s: %s",
+                                    task.sink.tag.c_str(),
+                                    task.src.tag.c_str(),
+                                    task.src.options.c_str(),
+                                    name.c_str(),
+                                    result.c_str()));
             }
 
             // Run verifiers if specified
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 30a27fe..947240f 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -84,14 +84,16 @@
 
 GMSrc::GMSrc(skiagm::GMFactory factory) : fFactory(factory) {}
 
-Error GMSrc::draw(SkCanvas* canvas) const {
+Result GMSrc::draw(SkCanvas* canvas) const {
     std::unique_ptr<skiagm::GM> gm(fFactory());
-    SkString errorMsg;
-    skiagm::DrawResult drawResult = gm->draw(canvas, &errorMsg);
-    if (skiagm::DrawResult::kSkip == drawResult) {
-        return Error::Nonfatal(std::move(errorMsg));  // Cause this test to be skipped.
+    SkString msg;
+    skiagm::DrawResult drawResult = gm->draw(canvas, &msg);
+    switch (drawResult) {
+        case skiagm::DrawResult::kOk  : return Result(Result::Status::Ok,    msg);
+        case skiagm::DrawResult::kFail: return Result(Result::Status::Fatal, msg);
+        case skiagm::DrawResult::kSkip: return Result(Result::Status::Skip,  msg);
+        default: SK_ABORT("");
     }
-    return errorMsg;
 }
 
 SkISize GMSrc::size() const {
@@ -147,11 +149,12 @@
     }
 }
 
-Error BRDSrc::draw(SkCanvas* canvas) const {
+Result BRDSrc::draw(SkCanvas* canvas) const {
     SkColorType colorType = canvas->imageInfo().colorType();
     if (kRGB_565_SkColorType == colorType &&
-            CodecSrc::kGetFromCanvas_DstColorType != fDstColorType) {
-        return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
+        CodecSrc::kGetFromCanvas_DstColorType != fDstColorType)
+    {
+        return Result::Skip("Testing non-565 to 565 is uninteresting.");
     }
     switch (fDstColorType) {
         case CodecSrc::kGetFromCanvas_DstColorType:
@@ -166,12 +169,12 @@
 
     std::unique_ptr<SkBitmapRegionDecoder> brd(create_brd(fPath));
     if (nullptr == brd.get()) {
-        return Error::Nonfatal(SkStringPrintf("Could not create brd for %s.", fPath.c_str()));
+        return Result::Skip("Could not create brd for %s.", fPath.c_str());
     }
 
     auto recommendedCT = brd->computeOutputColorType(colorType);
     if (kRGB_565_SkColorType == colorType && recommendedCT != colorType) {
-        return Error::Nonfatal("Skip decoding non-opaque to 565.");
+        return Result::Skip("Skip decoding non-opaque to 565.");
     }
     colorType = recommendedCT;
 
@@ -181,24 +184,24 @@
     const uint32_t height = brd->height();
     // Visually inspecting very small output images is not necessary.
     if ((width / fSampleSize <= 10 || height / fSampleSize <= 10) && 1 != fSampleSize) {
-        return Error::Nonfatal("Scaling very small images is uninteresting.");
+        return Result::Skip("Scaling very small images is uninteresting.");
     }
     switch (fMode) {
         case kFullImage_Mode: {
             SkBitmap bitmap;
             if (!brd->decodeRegion(&bitmap, nullptr, SkIRect::MakeXYWH(0, 0, width, height),
                     fSampleSize, colorType, false, colorSpace)) {
-                return "Cannot decode (full) region.";
+                return Result::Fatal("Cannot decode (full) region.");
             }
             alpha8_to_gray8(&bitmap);
 
             canvas->drawBitmap(bitmap, 0, 0);
-            return "";
+            return Result::Ok();
         }
         case kDivisor_Mode: {
             const uint32_t divisor = 2;
             if (width < divisor || height < divisor) {
-                return Error::Nonfatal("Divisor is larger than image dimension.");
+                return Result::Skip("Divisor is larger than image dimension.");
             }
 
             // Use a border to test subsets that extend outside the image.
@@ -244,7 +247,7 @@
                     if (!brd->decodeRegion(&bitmap, nullptr, SkIRect::MakeXYWH(decodeLeft,
                             decodeTop, decodeWidth, decodeHeight), fSampleSize, colorType, false,
                             colorSpace)) {
-                        return "Cannot decode region.";
+                        return Result::Fatal("Cannot decode region.");
                     }
 
                     alpha8_to_gray8(&bitmap);
@@ -259,11 +262,11 @@
                             nullptr);
                 }
             }
-            return "";
+            return Result::Ok();
         }
         default:
             SkASSERT(false);
-            return "Error: Should not be reached.";
+            return Result::Fatal("Error: Should not be reached.");
     }
 }
 
@@ -389,33 +392,33 @@
     *info = info->makeColorSpace(SkColorSpace::MakeSRGB());
 }
 
-Error CodecSrc::draw(SkCanvas* canvas) const {
+Result CodecSrc::draw(SkCanvas* canvas) const {
     sk_sp<SkData> encoded(SkData::MakeFromFileName(fPath.c_str()));
     if (!encoded) {
-        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+        return Result::Fatal("Couldn't read %s.", fPath.c_str());
     }
 
     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(encoded));
     if (nullptr == codec.get()) {
-        return SkStringPrintf("Couldn't create codec for %s.", fPath.c_str());
+        return Result::Fatal("Couldn't create codec for %s.", fPath.c_str());
     }
 
     SkImageInfo decodeInfo = codec->getInfo();
     if (!get_decode_info(&decodeInfo, canvas->imageInfo().colorType(), fDstColorType,
                          fDstAlphaType)) {
-        return Error::Nonfatal("Skipping uninteresting test.");
+        return Result::Skip("Skipping uninteresting test.");
     }
 
     // Try to scale the image if it is desired
     SkISize size = codec->getScaledDimensions(fScale);
     if (size == decodeInfo.dimensions() && 1.0f != fScale) {
-        return Error::Nonfatal("Test without scaling is uninteresting.");
+        return Result::Skip("Test without scaling is uninteresting.");
     }
 
     // Visually inspecting very small output images is not necessary.  We will
     // cover these cases in unit testing.
     if ((size.width() <= 10 || size.height() <= 10) && 1.0f != fScale) {
-        return Error::Nonfatal("Scaling very small images is uninteresting.");
+        return Result::Skip("Scaling very small images is uninteresting.");
     }
     decodeInfo = decodeInfo.makeDimensions(size);
 
@@ -441,7 +444,7 @@
         case kAnimated_Mode: {
             std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
             if (frameInfos.size() <= 1) {
-                return SkStringPrintf("%s is not an animated image.", fPath.c_str());
+                return Result::Fatal("%s is not an animated image.", fPath.c_str());
             }
 
             // As in CodecSrc::size(), compute a roughly square grid to draw the frames
@@ -494,19 +497,19 @@
                         canvas->translate(SkIntToScalar(xTranslate), SkIntToScalar(yTranslate));
                         draw_to_canvas(canvas, bitmapInfo, pixels.get(), rowBytes, fDstColorType);
                         if (result != SkCodec::kSuccess) {
-                            return "";
+                            return Result::Ok();
                         }
                         break;
                     }
                     case SkCodec::kInvalidConversion:
                         if (i > 0 && (decodeInfo.colorType() == kRGB_565_SkColorType)) {
-                            return Error::Nonfatal(SkStringPrintf(
-                                "Cannot decode frame %i to 565 (%s).", i, fPath.c_str()));
+                            return Result::Skip(
+                                "Cannot decode frame %i to 565 (%s).", i, fPath.c_str());
                         }
                         // Fall through.
                     default:
-                        return SkStringPrintf("Couldn't getPixels for frame %i in %s.",
-                                              i, fPath.c_str());
+                        return Result::Fatal(
+                            "Couldn't getPixels for frame %i in %s.", i, fPath.c_str());
                 }
             }
             break;
@@ -522,7 +525,7 @@
                     break;
                 default:
                     // Everything else is considered a failure.
-                    return SkStringPrintf("Couldn't getPixels %s.", fPath.c_str());
+                    return Result::Fatal("Couldn't getPixels %s.", fPath.c_str());
             }
 
             draw_to_canvas(canvas, bitmapInfo, pixels.get(), rowBytes, fDstColorType);
@@ -557,7 +560,7 @@
                 } else {
                     if (useIncremental) {
                         // Error: These should support incremental decode.
-                        return "Could not start incremental decode";
+                        return Result::Fatal("Could not start incremental decode");
                     }
                     // Otherwise, this is an ICO. Since incremental failed, it must contain a BMP,
                     // which should work via startScanlineDecode
@@ -567,7 +570,7 @@
 
             if (useOldScanlineMethod) {
                 if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo)) {
-                    return "Could not start scanline decoder";
+                    return Result::Fatal("Could not start scanline decoder");
                 }
 
                 // We do not need to check the return value.  On an incomplete
@@ -588,7 +591,7 @@
 
             // Decode odd stripes
             if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, &options)) {
-                return "Could not start scanline decoder";
+                return Result::Fatal("Could not start scanline decoder");
             }
 
             // This mode was designed to test the new skip scanlines API in libjpeg-turbo.
@@ -614,7 +617,7 @@
             // Decode even stripes
             const SkCodec::Result startResult = codec->startScanlineDecode(decodeInfo);
             if (SkCodec::kSuccess != startResult) {
-                return "Failed to restart scanline decoder with same parameters.";
+                return Result::Fatal("Failed to restart scanline decoder with same parameters.");
             }
             for (int i = 0; i < numStripes; i += 2) {
                 // Read a stripe
@@ -645,7 +648,7 @@
                 subset = SkIRect::MakeXYWH(x, 0, std::min(tileSize, width - x), height);
                 options.fSubset = &subset;
                 if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, &options)) {
-                    return "Could not start scanline decoder.";
+                    return Result::Fatal("Could not start scanline decoder.");
                 }
 
                 codec->getScanlines(SkTAddOffset<void>(pixels.get(), x * bpp), height, rowBytes);
@@ -661,9 +664,9 @@
             const int W = codec->getInfo().width();
             const int H = codec->getInfo().height();
             if (divisor > W || divisor > H) {
-                return Error::Nonfatal(SkStringPrintf("Cannot codec subset: divisor %d is too big "
-                                                      "for %s with dimensions (%d x %d)", divisor,
-                                                      fPath.c_str(), W, H));
+                return Result::Skip("Cannot codec subset: divisor %d is too big "
+                                    "for %s with dimensions (%d x %d)", divisor,
+                                    fPath.c_str(), W, H);
             }
             // subset dimensions
             // SkWebpCodec, the only one that supports subsets, requires even top/left boundaries.
@@ -701,10 +704,10 @@
                         case SkCodec::kIncompleteInput:
                             break;
                         default:
-                            return SkStringPrintf("subset codec failed to decode (%d, %d, %d, %d) "
-                                                  "from %s with dimensions (%d x %d)\t error %d",
-                                                  x, y, decodeInfo.width(), decodeInfo.height(),
-                                                  fPath.c_str(), W, H, result);
+                            return Result::Fatal("subset codec failed to decode (%d, %d, %d, %d) "
+                                                 "from %s with dimensions (%d x %d)\t error %d",
+                                                 x, y, decodeInfo.width(), decodeInfo.height(),
+                                                 fPath.c_str(), W, H, result);
                     }
                     draw_to_canvas(canvas, subsetBitmapInfo, dst, subsetRowBytes, fDstColorType,
                                    SkIntToScalar(left), SkIntToScalar(top));
@@ -715,13 +718,13 @@
                 // translate by the scaled width.
                 left += decodeInfo.width();
             }
-            return "";
+            return Result::Ok();
         }
         default:
             SkASSERT(false);
-            return "Invalid fMode";
+            return Result::Fatal("Invalid fMode");
     }
-    return "";
+    return Result::Ok();
 }
 
 SkISize CodecSrc::size() const {
@@ -774,20 +777,20 @@
         || flags.approach != SinkFlags::kDirect;
 }
 
-Error AndroidCodecSrc::draw(SkCanvas* canvas) const {
+Result AndroidCodecSrc::draw(SkCanvas* canvas) const {
     sk_sp<SkData> encoded(SkData::MakeFromFileName(fPath.c_str()));
     if (!encoded) {
-        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+        return Result::Fatal("Couldn't read %s.", fPath.c_str());
     }
     std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromData(encoded));
     if (nullptr == codec) {
-        return SkStringPrintf("Couldn't create android codec for %s.", fPath.c_str());
+        return Result::Fatal("Couldn't create android codec for %s.", fPath.c_str());
     }
 
     SkImageInfo decodeInfo = codec->getInfo();
     if (!get_decode_info(&decodeInfo, canvas->imageInfo().colorType(), fDstColorType,
                          fDstAlphaType)) {
-        return Error::Nonfatal("Skipping uninteresting test.");
+        return Result::Skip("Skipping uninteresting test.");
     }
 
     // Scale the image if it is desired.
@@ -796,7 +799,7 @@
     // Visually inspecting very small output images is not necessary.  We will
     // cover these cases in unit testing.
     if ((size.width() <= 10 || size.height() <= 10) && 1 != fSampleSize) {
-        return Error::Nonfatal("Scaling very small images is uninteresting.");
+        return Result::Skip("Scaling very small images is uninteresting.");
     }
     decodeInfo = decodeInfo.makeDimensions(size);
 
@@ -822,10 +825,10 @@
         case SkCodec::kIncompleteInput:
             break;
         default:
-            return SkStringPrintf("Couldn't getPixels %s.", fPath.c_str());
+            return Result::Fatal("Couldn't getPixels %s.", fPath.c_str());
     }
     draw_to_canvas(canvas, bitmapInfo, pixels.get(), rowBytes, fDstColorType);
-    return "";
+    return Result::Ok();
 }
 
 SkISize AndroidCodecSrc::size() const {
@@ -866,21 +869,21 @@
     return flags.type != SinkFlags::kRaster || flags.approach != SinkFlags::kDirect;
 }
 
-Error ImageGenSrc::draw(SkCanvas* canvas) const {
+Result ImageGenSrc::draw(SkCanvas* canvas) const {
     if (kRGB_565_SkColorType == canvas->imageInfo().colorType()) {
-        return Error::Nonfatal("Uninteresting to test image generator to 565.");
+        return Result::Skip("Uninteresting to test image generator to 565.");
     }
 
     sk_sp<SkData> encoded(SkData::MakeFromFileName(fPath.c_str()));
     if (!encoded) {
-        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+        return Result::Fatal("Couldn't read %s.", fPath.c_str());
     }
 
 #if defined(SK_BUILD_FOR_WIN)
     // Initialize COM in order to test with WIC.
     SkAutoCoInitialize com;
     if (!com.succeeded()) {
-        return "Could not initialize COM.";
+        return Result::Fatal("Could not initialize COM.");
     }
 #endif
 
@@ -889,7 +892,7 @@
         case kCodec_Mode:
             gen = SkCodecImageGenerator::MakeFromEncodedCodec(encoded);
             if (!gen) {
-                return "Could not create codec image generator.";
+                return Result::Fatal("Could not create codec image generator.");
             }
             break;
         case kPlatform_Mode: {
@@ -899,23 +902,23 @@
             gen = SkImageGeneratorWIC::MakeFromEncodedWIC(encoded);
 #endif
             if (!gen) {
-                return "Could not create platform image generator.";
+                return Result::Fatal("Could not create platform image generator.");
             }
             break;
         }
         default:
             SkASSERT(false);
-            return "Invalid image generator mode";
+            return Result::Fatal("Invalid image generator mode");
     }
 
     // Test deferred decoding path on GPU
     if (fIsGpu) {
         sk_sp<SkImage> image(SkImage::MakeFromGenerator(std::move(gen), nullptr));
         if (!image) {
-            return "Could not create image from codec image generator.";
+            return Result::Fatal("Could not create image from codec image generator.");
         }
         canvas->drawImage(image, 0, 0);
-        return "";
+        return Result::Ok();
     }
 
     // Test various color and alpha types on CPU
@@ -925,23 +928,20 @@
     size_t rowBytes = decodeInfo.width() * bpp;
     SkAutoMalloc pixels(decodeInfo.height() * rowBytes);
     if (!gen->getPixels(decodeInfo, pixels.get(), rowBytes)) {
-        SkString err =
-                SkStringPrintf("Image generator could not getPixels() for %s\n", fPath.c_str());
-
+        Result::Status status = Result::Status::Fatal;
 #if defined(SK_BUILD_FOR_WIN)
         if (kPlatform_Mode == fMode) {
             // Do not issue a fatal error for WIC flakiness.
-            return Error::Nonfatal(err);
+            status = Result::Status::Skip;
         }
 #endif
-
-        return err;
+        return Result(status, "Image generator could not getPixels() for %s\n", fPath.c_str());
     }
 
     set_bitmap_color_space(&decodeInfo);
     draw_to_canvas(canvas, decodeInfo, pixels.get(), rowBytes,
                    CodecSrc::kGetFromCanvas_DstColorType);
-    return "";
+    return Result::Ok();
 }
 
 SkISize ImageGenSrc::size() const {
@@ -967,15 +967,15 @@
     return flags.type != SinkFlags::kRaster || flags.approach != SinkFlags::kDirect;
 }
 
-Error ColorCodecSrc::draw(SkCanvas* canvas) const {
+Result ColorCodecSrc::draw(SkCanvas* canvas) const {
     sk_sp<SkData> encoded(SkData::MakeFromFileName(fPath.c_str()));
     if (!encoded) {
-        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+        return Result::Fatal("Couldn't read %s.", fPath.c_str());
     }
 
     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(encoded));
     if (nullptr == codec) {
-        return SkStringPrintf("Couldn't create codec for %s.", fPath.c_str());
+        return Result::Fatal("Couldn't create codec for %s.", fPath.c_str());
     }
 
     SkImageInfo info = codec->getInfo();
@@ -987,15 +987,15 @@
             // that doesn't mean they are wrong. We have a test verifying that
             // passing a null SkColorSpace skips conversion, so skip this
             // misleading test.
-            return Error::Nonfatal("Skipping decoding without color transform.");
+            return Result::Skip("Skipping decoding without color transform.");
         }
         info = canvasInfo.makeDimensions(info.dimensions());
     }
 
     SkBitmap bitmap;
     if (!bitmap.tryAllocPixels(info)) {
-        return SkStringPrintf("Image(%s) is too large (%d x %d)",
-                              fPath.c_str(), info.width(), info.height());
+        return Result::Fatal("Image(%s) is too large (%d x %d)",
+                             fPath.c_str(), info.width(), info.height());
     }
 
     switch (auto r = codec->getPixels(info, bitmap.getPixels(), bitmap.rowBytes())) {
@@ -1003,12 +1003,12 @@
         case SkCodec::kErrorInInput:
         case SkCodec::kIncompleteInput:
             canvas->drawBitmap(bitmap, 0,0);
-            return "";
+            return Result::Ok();
         case SkCodec::kInvalidConversion:
             // TODO(mtklein): why are there formats we can't decode to?
-            return Error::Nonfatal("SkCodec can't decode to this format.");
+            return Result::Skip("SkCodec can't decode to this format.");
         default:
-            return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
+            return Result::Fatal("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
     }
 }
 
@@ -1032,19 +1032,19 @@
 
 SKPSrc::SKPSrc(Path path) : fPath(path) { }
 
-Error SKPSrc::draw(SkCanvas* canvas) const {
+Result SKPSrc::draw(SkCanvas* canvas) const {
     std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(fPath.c_str());
     if (!stream) {
-        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+        return Result::Fatal("Couldn't read %s.", fPath.c_str());
     }
     sk_sp<SkPicture> pic(SkPicture::MakeFromStream(stream.get()));
     if (!pic) {
-        return SkStringPrintf("Couldn't parse file %s.", fPath.c_str());
+        return Result::Fatal("Couldn't parse file %s.", fPath.c_str());
     }
     stream = nullptr;  // Might as well drop this when we're done with it.
     canvas->clipRect(SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize));
     canvas->drawPicture(pic);
-    return "";
+    return Result::Ok();
 }
 
 static SkRect get_cull_rect_for_skp(const char* path) {
@@ -1074,7 +1074,7 @@
 
 BisectSrc::BisectSrc(Path path, const char* trail) : INHERITED(path), fTrail(trail) {}
 
-Error BisectSrc::draw(SkCanvas* canvas) const {
+Result BisectSrc::draw(SkCanvas* canvas) const {
     struct FoundPath {
         SkPath fPath;
         SkPaint fPaint;
@@ -1097,9 +1097,9 @@
 
     PathFindingCanvas pathFinder(canvas->getBaseLayerSize().width(),
                                  canvas->getBaseLayerSize().height());
-    Error err = this->INHERITED::draw(&pathFinder);
-    if (!err.isEmpty()) {
-        return err;
+    Result result = this->INHERITED::draw(&pathFinder);
+    if (!result.isOk()) {
+        return result;
     }
 
     int start = 0, end = pathFinder.foundPaths().count();
@@ -1119,7 +1119,7 @@
         canvas->drawPath(path.fPath, path.fPaint);
     }
 
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@@ -1127,7 +1127,7 @@
 #if defined(SK_ENABLE_SKOTTIE)
 SkottieSrc::SkottieSrc(Path path) : fPath(std::move(path)) {}
 
-Error SkottieSrc::draw(SkCanvas* canvas) const {
+Result SkottieSrc::draw(SkCanvas* canvas) const {
     auto animation = skottie::Animation::Builder()
         .setResourceProvider(
                 skresources::DataURIResourceProviderProxy::Make(
@@ -1136,7 +1136,7 @@
                     /*predecode=*/true))
         .makeFromFile(fPath.c_str());
     if (!animation) {
-        return SkStringPrintf("Unable to parse file: %s", fPath.c_str());
+        return Result::Fatal("Unable to parse file: %s", fPath.c_str());
     }
 
     canvas->drawColor(SK_ColorWHITE);
@@ -1169,7 +1169,7 @@
         }
     }
 
-    return "";
+    return Result::Ok();
 }
 
 SkISize SkottieSrc::size() const {
@@ -1221,16 +1221,16 @@
     }
 }
 
-Error SVGSrc::draw(SkCanvas* canvas) const {
+Result SVGSrc::draw(SkCanvas* canvas) const {
     if (!fDom) {
-        return SkStringPrintf("Unable to parse file: %s", fName.c_str());
+        return Result::Fatal("Unable to parse file: %s", fName.c_str());
     }
 
     SkAutoCanvasRestore acr(canvas, true);
     canvas->scale(fScale, fScale);
     fDom->render(canvas);
 
-    return "";
+    return Result::Ok();
 }
 
 SkISize SVGSrc::size() const {
@@ -1272,45 +1272,45 @@
     return i >= 0 && i < fPages.count() ? fPages[i].fSize.toCeil() : SkISize{0, 0};
 }
 
-Error MSKPSrc::draw(SkCanvas* c) const { return this->draw(0, c); }
-Error MSKPSrc::draw(int i, SkCanvas* canvas) const {
+Result MSKPSrc::draw(SkCanvas* c) const { return this->draw(0, c); }
+Result MSKPSrc::draw(int i, SkCanvas* canvas) const {
     if (this->pageCount() == 0) {
-        return SkStringPrintf("Unable to parse MultiPictureDocument file: %s", fPath.c_str());
+        return Result::Fatal("Unable to parse MultiPictureDocument file: %s", fPath.c_str());
     }
     if (i >= fPages.count() || i < 0) {
-        return SkStringPrintf("MultiPictureDocument page number out of range: %d", i);
+        return Result::Fatal("MultiPictureDocument page number out of range: %d", i);
     }
     SkPicture* page = fPages[i].fPicture.get();
     if (!page) {
         std::unique_ptr<SkStreamAsset> stream = SkStream::MakeFromFile(fPath.c_str());
         if (!stream) {
-            return SkStringPrintf("Unable to open file: %s", fPath.c_str());
+            return Result::Fatal("Unable to open file: %s", fPath.c_str());
         }
         if (!SkMultiPictureDocumentRead(stream.get(), &fPages[0], fPages.count())) {
-            return SkStringPrintf("SkMultiPictureDocument reader failed on page %d: %s", i,
-                                  fPath.c_str());
+            return Result::Fatal("SkMultiPictureDocument reader failed on page %d: %s", i,
+                                 fPath.c_str());
         }
         page = fPages[i].fPicture.get();
     }
     canvas->drawPicture(page);
-    return "";
+    return Result::Ok();
 }
 
 Name MSKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-Error NullSink::draw(const Src& src, SkBitmap*, SkWStream*, SkString*) const {
+Result NullSink::draw(const Src& src, SkBitmap*, SkWStream*, SkString*) const {
     return src.draw(SkMakeNullCanvas().get());
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-static Error compare_bitmaps(const SkBitmap& reference, const SkBitmap& bitmap) {
+static Result compare_bitmaps(const SkBitmap& reference, const SkBitmap& bitmap) {
     // The dimensions are a property of the Src only, and so should be identical.
     SkASSERT(reference.computeByteSize() == bitmap.computeByteSize());
     if (reference.computeByteSize() != bitmap.computeByteSize()) {
-        return "Dimensions don't match reference";
+        return Result::Fatal("Dimensions don't match reference");
     }
     // All SkBitmaps in DM are tight, so this comparison is easy.
     if (0 != memcmp(reference.getPixels(), bitmap.getPixels(), reference.computeByteSize())) {
@@ -1330,9 +1330,9 @@
             errString.append("\nActual image failed to encode: ");
             errString.append(encoded);
         }
-        return errString;
+        return Result::Fatal(errString);
     }
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@@ -1364,13 +1364,13 @@
     }
 }
 
-Error GPUSink::draw(const Src& src, SkBitmap* dst, SkWStream* dstStream, SkString* log) const {
+Result GPUSink::draw(const Src& src, SkBitmap* dst, SkWStream* dstStream, SkString* log) const {
     return this->onDraw(src, dst, dstStream, log, fBaseContextOptions);
 }
 
-Error GPUSink::onDraw(const Src& src, SkBitmap* dst, SkWStream*, SkString* log,
-                      const GrContextOptions& baseOptions,
-                      std::function<void(GrContext*)> initContext) const {
+Result GPUSink::onDraw(const Src& src, SkBitmap* dst, SkWStream*, SkString* log,
+                       const GrContextOptions& baseOptions,
+                       std::function<void(GrContext*)> initContext) const {
     GrContextOptions grOptions = baseOptions;
 
     // We don't expect the src to mess with the persistent cache or the executor.
@@ -1390,7 +1390,7 @@
     }
     const int maxDimension = context->priv().caps()->maxTextureSize();
     if (maxDimension < std::max(size.width(), size.height())) {
-        return Error::Nonfatal("Src too large to create a texture.\n");
+        return Result::Skip("Src too large to create a texture.\n");
     }
     uint32_t flags = fUseDIText ? SkSurfaceProps::kUseDeviceIndependentFonts_Flag : 0;
     SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
@@ -1422,15 +1422,15 @@
     }
 
     if (!surface) {
-        return "Could not create a surface.";
+        return Result::Fatal("Could not create a surface.");
     }
     if (FLAGS_preAbandonGpuContext) {
         factory.abandonContexts();
     }
     SkCanvas* canvas = surface->getCanvas();
-    Error err = src.draw(canvas);
-    if (!err.isEmpty()) {
-        return err;
+    Result result = src.draw(canvas);
+    if (!result.isOk()) {
+        return result;
     }
     surface->flush();
     if (FLAGS_gpuStats) {
@@ -1462,7 +1462,7 @@
     if (grOptions.fPersistentCache) {
         context->storeVkPipelineCacheData();
     }
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@@ -1474,7 +1474,7 @@
     SkASSERT(fExecutor);
 }
 
-Error GPUThreadTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
+Result GPUThreadTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
                                  SkString* log) const {
     // Draw twice, once with worker threads, and once without. Verify that we get the same result.
     // Also, force us to only use the software path renderer, so we really stress-test the threaded
@@ -1483,18 +1483,18 @@
     contextOptions.fGpuPathRenderers = GpuPathRenderers::kNone;
     contextOptions.fExecutor = fExecutor.get();
 
-    Error err = this->onDraw(src, dst, wStream, log, contextOptions);
-    if (!err.isEmpty() || !dst) {
-        return err;
+    Result result = this->onDraw(src, dst, wStream, log, contextOptions);
+    if (!result.isOk() || !dst) {
+        return result;
     }
 
     SkBitmap reference;
     SkString refLog;
     SkDynamicMemoryWStream refStream;
     contextOptions.fExecutor = nullptr;
-    Error refErr = this->onDraw(src, &reference, &refStream, &refLog, contextOptions);
-    if (!refErr.isEmpty()) {
-        return refErr;
+    Result refResult = this->onDraw(src, &reference, &refStream, &refLog, contextOptions);
+    if (!refResult.isOk()) {
+        return refResult;
     }
 
     return compare_bitmaps(reference, *dst);
@@ -1507,8 +1507,8 @@
     : INHERITED(config, grCtxOptions)
     , fCacheType(config->getTestPersistentCache()) {}
 
-Error GPUPersistentCacheTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
-                                          SkString* log) const {
+Result GPUPersistentCacheTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
+                                           SkString* log) const {
     // Draw twice, once with a cold cache, and again with a warm cache. Verify that we get the same
     // result.
     sk_gpu_test::MemoryCache memoryCache;
@@ -1521,18 +1521,18 @@
     contextOptions.fGpuPathRenderers =
             contextOptions.fGpuPathRenderers & ~GpuPathRenderers::kStencilAndCover;
 
-    Error err = this->onDraw(src, dst, wStream, log, contextOptions);
-    if (!err.isEmpty() || !dst) {
-        return err;
+    Result result = this->onDraw(src, dst, wStream, log, contextOptions);
+    if (!result.isOk() || !dst) {
+        return result;
     }
 
     SkBitmap reference;
     SkString refLog;
     SkDynamicMemoryWStream refStream;
     memoryCache.resetNumCacheMisses();
-    Error refErr = this->onDraw(src, &reference, &refStream, &refLog, contextOptions);
-    if (!refErr.isEmpty()) {
-        return refErr;
+    Result refResult = this->onDraw(src, &reference, &refStream, &refLog, contextOptions);
+    if (!refResult.isOk()) {
+        return refResult;
     }
     SkASSERT(!memoryCache.numCacheMisses());
 
@@ -1546,8 +1546,8 @@
                                                    const GrContextOptions& grCtxOptions)
     : INHERITED(config, grCtxOptions) {}
 
-Error GPUPrecompileTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
-                                     SkString* log) const {
+Result GPUPrecompileTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
+                                      SkString* log) const {
     // Three step process:
     // 1) Draw once with an SkSL cache, and store off the shader blobs.
     // 2) For the second context, pre-compile the shaders to warm the cache.
@@ -1561,9 +1561,9 @@
     contextOptions.fGpuPathRenderers =
             contextOptions.fGpuPathRenderers & ~GpuPathRenderers::kStencilAndCover;
 
-    Error err = this->onDraw(src, dst, wStream, log, contextOptions);
-    if (!err.isEmpty() || !dst) {
-        return err;
+    Result result = this->onDraw(src, dst, wStream, log, contextOptions);
+    if (!result.isOk() || !dst) {
+        return result;
     }
 
     auto precompileShaders = [&memoryCache](GrContext* context) {
@@ -1584,10 +1584,10 @@
     SkBitmap reference;
     SkString refLog;
     SkDynamicMemoryWStream refStream;
-    Error refErr = this->onDraw(src, &reference, &refStream, &refLog, replayOptions,
-                                precompileShaders);
-    if (!refErr.isEmpty()) {
-        return refErr;
+    Result refResult = this->onDraw(src, &reference, &refStream, &refLog, replayOptions,
+                                    precompileShaders);
+    if (!refResult.isOk()) {
+        return refResult;
     }
     SkASSERT(!replayCache.numCacheMisses());
 
@@ -1595,9 +1595,9 @@
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-static Error draw_skdocument(const Src& src, SkDocument* doc, SkWStream* dst) {
+static Result draw_skdocument(const Src& src, SkDocument* doc, SkWStream* dst) {
     if (src.size().isEmpty()) {
-        return "Source has empty dimensions";
+        return Result::Fatal("Source has empty dimensions");
     }
     SkASSERT(doc);
     int pageCount = src.pageCount();
@@ -1606,20 +1606,20 @@
         SkCanvas* canvas =
                 doc->beginPage(SkIntToScalar(width), SkIntToScalar(height));
         if (!canvas) {
-            return "SkDocument::beginPage(w,h) returned nullptr";
+            return Result::Fatal("SkDocument::beginPage(w,h) returned nullptr");
         }
-        Error err = src.draw(i, canvas);
-        if (!err.isEmpty()) {
-            return err;
+        Result result = src.draw(i, canvas);
+        if (!result.isOk()) {
+            return result;
         }
         doc->endPage();
     }
     doc->close();
     dst->flush();
-    return "";
+    return Result::Ok();
 }
 
-Error PDFSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
+Result PDFSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
     SkPDF::Metadata metadata;
     metadata.fTitle = src.name();
     metadata.fSubject = "rendering correctness test";
@@ -1632,7 +1632,7 @@
 #endif
     auto doc = SkPDF::MakeDocument(dst, metadata);
     if (!doc) {
-        return "SkPDF::MakeDocument() returned nullptr";
+        return Result::Fatal("SkPDF::MakeDocument() returned nullptr");
     }
     return draw_skdocument(src, doc.get(), dst);
 }
@@ -1651,24 +1651,24 @@
     return SkTScopedComPtr<IXpsOMObjectFactory>(factory);
 }
 
-Error XPSSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
+Result XPSSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
     SkAutoCoInitialize com;
     if (!com.succeeded()) {
-        return "Could not initialize COM.";
+        return Result::Fatal("Could not initialize COM.");
     }
     SkTScopedComPtr<IXpsOMObjectFactory> factory = make_xps_factory();
     if (!factory) {
-        return "Failed to create XPS Factory.";
+        return Result::Fatal("Failed to create XPS Factory.");
     }
     auto doc = SkXPS::MakeDocument(dst, factory.get());
     if (!doc) {
-        return "SkXPS::MakeDocument() returned nullptr";
+        return Result::Fatal("SkXPS::MakeDocument() returned nullptr");
     }
     return draw_skdocument(src, doc.get(), dst);
 }
 #else
-Error XPSSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
-    return "XPS not supported on this platform.";
+Result XPSSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
+    return Result::Fatal("XPS not supported on this platform.");
 }
 #endif
 
@@ -1676,24 +1676,24 @@
 
 SKPSink::SKPSink() {}
 
-Error SKPSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
+Result SKPSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
     auto size = SkSize::Make(src.size());
     SkPictureRecorder recorder;
-    Error err = src.draw(recorder.beginRecording(size.width(), size.height()));
-    if (!err.isEmpty()) {
-        return err;
+    Result result = src.draw(recorder.beginRecording(size.width(), size.height()));
+    if (!result.isOk()) {
+        return result;
     }
     recorder.finishRecordingAsPicture()->serialize(dst);
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-Error DebugSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
+Result DebugSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
     DebugCanvas debugCanvas(src.size().width(), src.size().height());
-    Error err = src.draw(&debugCanvas);
-    if (!err.isEmpty()) {
-        return err;
+    Result result = src.draw(&debugCanvas);
+    if (!result.isOk()) {
+        return result;
     }
     std::unique_ptr<SkCanvas> nullCanvas = SkMakeNullCanvas();
     UrlDataManager dataManager(SkString("data"));
@@ -1702,20 +1702,20 @@
     debugCanvas.toJSON(writer, dataManager, nullCanvas.get());
     writer.endObject(); // root
     writer.flush();
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
 SVGSink::SVGSink(int pageIndex) : fPageIndex(pageIndex) {}
 
-Error SVGSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
+Result SVGSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
 #if defined(SK_XML)
     if (src.pageCount() > 1) {
         int pageCount = src.pageCount();
         if (fPageIndex > pageCount - 1) {
-            return Error(SkStringPrintf("Page index %d too high for document with only %d pages.",
-                                        fPageIndex, pageCount));
+            return Result::Fatal("Page index %d too high for document with only %d pages.",
+                                 fPageIndex, pageCount);
         }
     }
     return src.draw(fPageIndex,
@@ -1725,7 +1725,7 @@
                             .get());
 #else
     (void)fPageIndex;
-    return Error("SVG sink is disabled.");
+    return Result::Fatal("SVG sink is disabled.");
 #endif // SK_XML
 }
 
@@ -1735,7 +1735,7 @@
     : fColorType(colorType)
     , fColorSpace(std::move(colorSpace)) {}
 
-Error RasterSink::draw(const Src& src, SkBitmap* dst, SkWStream*, SkString*) const {
+Result RasterSink::draw(const Src& src, SkBitmap* dst, SkWStream*, SkString*) const {
     const SkISize size = src.size();
     // If there's an appropriate alpha type for this color type, use it, otherwise use premul.
     SkAlphaType alphaType = kPremul_SkAlphaType;
@@ -1755,12 +1755,12 @@
 // Several examples below.
 
 template <typename Fn>
-static Error draw_to_canvas(Sink* sink, SkBitmap* bitmap, SkWStream* stream, SkString* log,
+static Result draw_to_canvas(Sink* sink, SkBitmap* bitmap, SkWStream* stream, SkString* log,
                             SkISize size, const Fn& draw) {
     class ProxySrc : public Src {
     public:
         ProxySrc(SkISize size, const Fn& draw) : fSize(size), fDraw(draw) {}
-        Error   draw(SkCanvas* canvas) const override { return fDraw(canvas); }
+        Result  draw(SkCanvas* canvas) const override { return fDraw(canvas); }
         Name    name() const override { return "ProxySrc"; }
         SkISize size() const override { return fSize; }
     private:
@@ -1775,22 +1775,22 @@
 static DEFINE_bool(check, true, "If true, have most Via- modes fail if they affect the output.");
 
 // Is *bitmap identical to what you get drawing src into sink?
-static Error check_against_reference(const SkBitmap* bitmap, const Src& src, Sink* sink) {
+static Result check_against_reference(const SkBitmap* bitmap, const Src& src, Sink* sink) {
     // We can only check raster outputs.
     // (Non-raster outputs like .pdf, .skp, .svg may differ but still draw identically.)
     if (FLAGS_check && bitmap) {
         SkBitmap reference;
         SkString log;
         SkDynamicMemoryWStream wStream;
-        Error err = sink->draw(src, &reference, &wStream, &log);
+        Result result = sink->draw(src, &reference, &wStream, &log);
         // If we can draw into this Sink via some pipeline, we should be able to draw directly.
-        SkASSERT(err.isEmpty());
-        if (!err.isEmpty()) {
-            return err;
+        SkASSERT(result.isOk());
+        if (!result.isOk()) {
+            return result;
         }
         return compare_bitmaps(reference, *bitmap);
     }
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@@ -1804,7 +1804,7 @@
 
 ViaMatrix::ViaMatrix(SkMatrix matrix, Sink* sink) : Via(sink), fMatrix(matrix) {}
 
-Error ViaMatrix::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
+Result ViaMatrix::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     SkMatrix matrix = fMatrix;
     SkISize size = auto_compute_translate(&matrix, src.size().width(), src.size().height());
     return draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) {
@@ -1817,15 +1817,15 @@
 // This should be pixel-preserving.
 ViaUpright::ViaUpright(SkMatrix matrix, Sink* sink) : Via(sink), fMatrix(matrix) {}
 
-Error ViaUpright::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
-    Error err = fSink->draw(src, bitmap, stream, log);
-    if (!err.isEmpty()) {
-        return err;
+Result ViaUpright::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
+    Result result = fSink->draw(src, bitmap, stream, log);
+    if (!result.isOk()) {
+        return result;
     }
 
     SkMatrix inverse;
     if (!fMatrix.rectStaysRect() || !fMatrix.invert(&inverse)) {
-        return "Cannot upright --matrix.";
+        return Result::Fatal("Cannot upright --matrix.");
     }
     SkMatrix upright = SkMatrix::I();
     upright.setScaleX(SkScalarSignAsScalar(inverse.getScaleX()));
@@ -1844,32 +1844,32 @@
     canvas.drawBitmap(*bitmap, 0, 0, &paint);
 
     *bitmap = uprighted;
-    return "";
+    return Result::Ok();
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-Error ViaSerialization::draw(
+Result ViaSerialization::draw(
         const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     // Record our Src into a picture.
     auto size = src.size();
     SkPictureRecorder recorder;
-    Error err = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
-                                                 SkIntToScalar(size.height())));
-    if (!err.isEmpty()) {
-        return err;
+    Result result = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
+                                                     SkIntToScalar(size.height())));
+    if (!result.isOk()) {
+        return result;
     }
     sk_sp<SkPicture> pic(recorder.finishRecordingAsPicture());
 
     // Serialize it and then deserialize it.
     sk_sp<SkPicture> deserialized(SkPicture::MakeFromData(pic->serialize().get()));
 
-    err = draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) {
+    result = draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) {
         canvas->drawPicture(deserialized);
-        return "";
+        return Result::Ok();
     });
-    if (!err.isEmpty()) {
-        return err;
+    if (!result.isOk()) {
+        return result;
     }
 
     return check_against_reference(bitmap, src, fSink.get());
@@ -1880,13 +1880,13 @@
 ViaDDL::ViaDDL(int numReplays, int numDivisions, Sink* sink)
         : Via(sink), fNumReplays(numReplays), fNumDivisions(numDivisions) {}
 
-Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
+Result ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     auto size = src.size();
     SkPictureRecorder recorder;
-    Error err = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
-                                                 SkIntToScalar(size.height())));
-    if (!err.isEmpty()) {
-        return err;
+    Result result = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
+                                                     SkIntToScalar(size.height())));
+    if (!result.isOk()) {
+        return result;
     }
     sk_sp<SkPicture> inputPicture(recorder.finishRecordingAsPicture());
 
@@ -1896,12 +1896,12 @@
     DDLPromiseImageHelper promiseImageHelper;
     sk_sp<SkData> compressedPictureData = promiseImageHelper.deflateSKP(inputPicture.get());
     if (!compressedPictureData) {
-        return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
+        return Result::Fatal("ViaDDL: Couldn't deflate SkPicture");
     }
-    auto draw = [&](SkCanvas* canvas) -> Error {
+    auto draw = [&](SkCanvas* canvas) -> Result {
         GrContext* context = canvas->getGrContext();
         if (!context || !context->priv().getGpu()) {
-            return SkStringPrintf("DDLs are GPU only");
+            return Result::Fatal("DDLs are GPU only");
         }
 
         // This is here bc this is the first point where we have access to the context
@@ -1941,29 +1941,29 @@
             tiles.composeAllTiles(canvas);
             context->flush();
         }
-        return "";
+        return Result::Ok();
     };
     return draw_to_canvas(fSink.get(), bitmap, stream, log, size, draw);
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-Error ViaPicture::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
+Result ViaPicture::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     auto size = src.size();
-    Error err = draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) {
+    Result result = draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) {
         SkPictureRecorder recorder;
         sk_sp<SkPicture> pic;
-        Error err = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
-                                                     SkIntToScalar(size.height())));
-        if (!err.isEmpty()) {
-            return err;
+        Result result = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
+                                                         SkIntToScalar(size.height())));
+        if (!result.isOk()) {
+            return result;
         }
         pic = recorder.finishRecordingAsPicture();
         canvas->drawPicture(pic);
-        return err;
+        return result;
     });
-    if (!err.isEmpty()) {
-        return err;
+    if (!result.isOk()) {
+        return result;
     }
 
     return check_against_reference(bitmap, src, fSink.get());
@@ -1976,14 +1976,14 @@
 #include "include/svg/SkSVGCanvas.h"
 #include "src/xml/SkXMLWriter.h"
 
-Error ViaSVG::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
+Result ViaSVG::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     auto size = src.size();
-    return draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) -> Error {
+    return draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) -> Result {
         SkDynamicMemoryWStream wstream;
         SkXMLStreamWriter writer(&wstream);
-        Error err = src.draw(SkSVGCanvas::Make(SkRect::Make(size), &writer).get());
-        if (!err.isEmpty()) {
-            return err;
+        Result result = src.draw(SkSVGCanvas::Make(SkRect::Make(size), &writer).get());
+        if (!result.isOk()) {
+            return result;
         }
         std::unique_ptr<SkStream> rstream(wstream.detachAsStream());
         auto dom = SkSVGDOM::MakeFromStream(*rstream);
@@ -1991,7 +1991,7 @@
             dom->setContainerSize(SkSize::Make(size));
             dom->render(canvas);
         }
-        return "";
+        return Result::Ok();
     });
 }
 #endif
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index d07b151..ae05d9f 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -40,28 +40,41 @@
 typedef ImplicitString Name;
 typedef ImplicitString Path;
 
-class Error {
+class Result {
 public:
-    Error(const SkString& s) : fMsg(s), fFatal(!this->isEmpty()) {}
-    Error(const char* s)     : fMsg(s), fFatal(!this->isEmpty()) {}
+    enum class Status : int { Ok, Fatal, Skip };
+    Result(Status status, const SkString& s) : fMsg(s), fStatus(status) {}
+    Result(Status status, const char* s) : fMsg(s), fStatus(status) {}
+    template <typename... Args> Result (Status status, const char* s, Args... args)
+        : fMsg(SkStringPrintf(s, args...)), fStatus(status) {}
 
-    Error(const Error&)            = default;
-    Error& operator=(const Error&) = default;
+    Result(const Result&)            = default;
+    Result& operator=(const Result&) = default;
 
-    static Error Nonfatal(const SkString& s) { return Nonfatal(s.c_str()); }
-    static Error Nonfatal(const char* s) {
-        Error e(s);
-        e.fFatal = false;
-        return e;
+    static Result Ok() { return Result(Status::Ok, nullptr); }
+
+    static Result Fatal(const SkString& s) { return Result(Status::Fatal, s); }
+    static Result Fatal(const char* s) { return Result(Status::Fatal, s); }
+    template <typename... Args> static Result Fatal(const char* s, Args... args) {
+        return Result(Status::Fatal, s, args...);
     }
 
+    static Result Skip(const SkString& s) { return Result(Status::Skip, s); }
+    static Result Skip(const char* s) { return Result(Status::Skip, s); }
+    template <typename... Args> static Result Skip(const char* s, Args... args) {
+        return Result(Status::Skip, s, args...);
+    }
+
+    bool isOk() { return fStatus == Status::Ok; }
+    bool isFatal() { return fStatus == Status::Fatal; }
+    bool isSkip() { return fStatus == Status::Skip; }
+
     const char* c_str() const { return fMsg.c_str(); }
-    bool isEmpty() const { return fMsg.isEmpty(); }
-    bool isFatal() const { return fFatal; }
+    Status status() const { return fStatus; }
 
 private:
     SkString fMsg;
-    bool     fFatal;
+    Status   fStatus;
 };
 
 struct SinkFlags {
@@ -74,14 +87,14 @@
 
 struct Src {
     virtual ~Src() {}
-    virtual Error SK_WARN_UNUSED_RESULT draw(SkCanvas*) const = 0;
+    virtual Result SK_WARN_UNUSED_RESULT draw(SkCanvas*) const = 0;
     virtual SkISize size() const = 0;
     virtual Name name() const = 0;
     virtual void modifyGrContextOptions(GrContextOptions* options) const {}
     virtual bool veto(SinkFlags) const { return false; }
 
     virtual int pageCount() const { return 1; }
-    virtual Error SK_WARN_UNUSED_RESULT draw(int, SkCanvas* canvas) const {
+    virtual Result SK_WARN_UNUSED_RESULT draw(int, SkCanvas* canvas) const {
         return this->draw(canvas);
     }
     virtual SkISize size(int) const { return this->size(); }
@@ -97,7 +110,7 @@
 struct Sink {
     virtual ~Sink() {}
     // You may write to either the bitmap or stream.  If you write to log, we'll print that out.
-    virtual Error SK_WARN_UNUSED_RESULT draw(const Src&, SkBitmap*, SkWStream*, SkString* log)
+    virtual Result SK_WARN_UNUSED_RESULT draw(const Src&, SkBitmap*, SkWStream*, SkString* log)
         const = 0;
 
     // Force Tasks using this Sink to run on the main thread?
@@ -118,7 +131,7 @@
 public:
     explicit GMSrc(skiagm::GMFactory);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     void modifyGrContextOptions(GrContextOptions* options) const override;
@@ -150,7 +163,7 @@
     };
     CodecSrc(Path, Mode, DstColorType, SkAlphaType, float);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -168,7 +181,7 @@
 public:
     AndroidCodecSrc(Path, CodecSrc::DstColorType, SkAlphaType, int sampleSize);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -196,7 +209,7 @@
 
     BRDSrc(Path, Mode, CodecSrc::DstColorType, uint32_t);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -215,7 +228,7 @@
     };
     ImageGenSrc(Path, Mode, SkAlphaType, bool);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -232,7 +245,7 @@
 public:
     ColorCodecSrc(Path, bool decode_to_dst);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -245,7 +258,7 @@
 public:
     explicit SKPSrc(Path path);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
 private:
@@ -259,7 +272,7 @@
 public:
     explicit BisectSrc(Path path, const char* trail);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
 
 private:
     SkString fTrail;
@@ -273,7 +286,7 @@
 public:
     explicit SkottieSrc(Path path);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -301,7 +314,7 @@
 public:
     explicit SVGSrc(Path path);
 
-    Error draw(SkCanvas*) const override;
+    Result draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
@@ -321,8 +334,8 @@
     explicit MSKPSrc(Path path);
 
     int pageCount() const override;
-    Error draw(SkCanvas* c) const override;
-    Error draw(int, SkCanvas*) const override;
+    Result draw(SkCanvas* c) const override;
+    Result draw(int, SkCanvas*) const override;
     SkISize size() const override;
     SkISize size(int) const override;
     Name name() const override;
@@ -338,7 +351,7 @@
 public:
     NullSink() {}
 
-    Error draw(const Src& src, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src& src, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return ""; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kNull, SinkFlags::kDirect }; }
 };
@@ -347,10 +360,10 @@
 public:
     GPUSink(const SkCommandLineConfigGpu*, const GrContextOptions&);
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
-    Error onDraw(const Src&, SkBitmap*, SkWStream*, SkString*,
-                 const GrContextOptions& baseOptions,
-                 std::function<void(GrContext*)> initContext = nullptr) const;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result onDraw(const Src&, SkBitmap*, SkWStream*, SkString*,
+                  const GrContextOptions& baseOptions,
+                  std::function<void(GrContext*)> initContext = nullptr) const;
 
     sk_gpu_test::GrContextFactory::ContextType contextType() const { return fContextType; }
     const sk_gpu_test::GrContextFactory::ContextOverrides& contextOverrides() {
@@ -387,7 +400,7 @@
 public:
     GPUThreadTestingSink(const SkCommandLineConfigGpu*, const GrContextOptions&);
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 
     const char* fileExtension() const override {
         // Suppress writing out results from this config - we just want to do our matching test
@@ -404,7 +417,7 @@
 public:
     GPUPersistentCacheTestingSink(const SkCommandLineConfigGpu*, const GrContextOptions&);
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 
     const char* fileExtension() const override {
         // Suppress writing out results from this config - we just want to do our matching test
@@ -421,7 +434,7 @@
 public:
     GPUPrecompileTestingSink(const SkCommandLineConfigGpu*, const GrContextOptions&);
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 
     const char* fileExtension() const override {
         // Suppress writing out results from this config - we just want to do our matching test
@@ -435,7 +448,7 @@
 class PDFSink : public Sink {
 public:
     PDFSink(bool pdfa, SkScalar rasterDpi) : fPDFA(pdfa), fRasterDpi(rasterDpi) {}
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return "pdf"; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; }
     bool fPDFA;
@@ -446,7 +459,7 @@
 public:
     XPSSink();
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return "xps"; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; }
 };
@@ -455,7 +468,7 @@
 public:
     explicit RasterSink(SkColorType, sk_sp<SkColorSpace> = nullptr);
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return "png"; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kRaster, SinkFlags::kDirect }; }
 
@@ -467,21 +480,21 @@
 class ThreadedSink : public RasterSink {
 public:
     explicit ThreadedSink(SkColorType, sk_sp<SkColorSpace> = nullptr);
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 };
 
 class SKPSink : public Sink {
 public:
     SKPSink();
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return "skp"; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; }
 };
 
 class DebugSink : public Sink {
 public:
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return "json"; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; }
 };
@@ -490,7 +503,7 @@
 public:
     SVGSink(int pageIndex = 0);
 
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
     const char* fileExtension() const override { return "svg"; }
     SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; }
 
@@ -518,7 +531,7 @@
 class ViaMatrix : public Via {
 public:
     ViaMatrix(SkMatrix, Sink*);
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 private:
     const SkMatrix fMatrix;
 };
@@ -526,7 +539,7 @@
 class ViaUpright : public Via {
 public:
     ViaUpright(SkMatrix, Sink*);
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 private:
     const SkMatrix fMatrix;
 };
@@ -534,19 +547,19 @@
 class ViaSerialization : public Via {
 public:
     explicit ViaSerialization(Sink* sink) : Via(sink) {}
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 };
 
 class ViaPicture : public Via {
 public:
     explicit ViaPicture(Sink* sink) : Via(sink) {}
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 };
 
 class ViaDDL : public Via {
 public:
     ViaDDL(int numReplays, int numDivisions, Sink* sink);
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 private:
     const int fNumReplays;
     const int fNumDivisions;
@@ -555,7 +568,7 @@
 class ViaSVG : public Via {
 public:
     explicit ViaSVG(Sink* sink) : Via(sink) {}
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+    Result draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 };
 
 }  // namespace DM