Home | History | Annotate | Download | only in dm
      1 /*
      2  * Copyright 2013 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "CrashHandler.h"
      9 #include "DMJsonWriter.h"
     10 #include "DMSrcSink.h"
     11 #include "DMSrcSinkAndroid.h"
     12 #include "OverwriteLine.h"
     13 #include "ProcStats.h"
     14 #include "SkBBHFactory.h"
     15 #include "SkChecksum.h"
     16 #include "SkCommonFlags.h"
     17 #include "SkForceLinking.h"
     18 #include "SkGraphics.h"
     19 #include "SkInstCnt.h"
     20 #include "SkMD5.h"
     21 #include "SkOSFile.h"
     22 #include "SkTHash.h"
     23 #include "SkTaskGroup.h"
     24 #include "SkThreadUtils.h"
     25 #include "Test.h"
     26 #include "Timer.h"
     27 
     28 DEFINE_string(src, "tests gm skp image", "Source types to test.");
     29 DEFINE_bool(nameByHash, false,
     30             "If true, write to FLAGS_writePath[0]/<hash>.png instead of "
     31             "to FLAGS_writePath[0]/<config>/<sourceType>/<sourceOptions>/<name>.png");
     32 DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
     33 DEFINE_string(matrix, "1 0 0 1",
     34               "2x2 scale+skew matrix to apply or upright when using "
     35               "'matrix' or 'upright' in config.");
     36 DEFINE_bool(gpu_threading, false, "Allow GPU work to run on multiple threads?");
     37 
     38 DEFINE_string(blacklist, "",
     39         "Space-separated config/src/srcOptions/name quadruples to blacklist.  '_' matches anything.  E.g. \n"
     40         "'--blacklist gpu skp _ _' will blacklist all SKPs drawn into the gpu config.\n"
     41         "'--blacklist gpu skp _ _ 8888 gm _ aarects' will also blacklist the aarects GM on 8888.");
     42 
     43 DEFINE_string2(readPath, r, "", "If set check for equality with golden results in this directory.");
     44 
     45 DEFINE_string(uninterestingHashesFile, "",
     46         "File containing a list of uninteresting hashes. If a result hashes to something in "
     47         "this list, no image is written for that result.");
     48 
     49 DEFINE_int32(shards, 1, "We're splitting source data into this many shards.");
     50 DEFINE_int32(shard,  0, "Which shard do I run?");
     51 
     52 __SK_FORCE_IMAGE_DECODER_LINKING;
     53 using namespace DM;
     54 
     55 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
     56 
     57 SK_DECLARE_STATIC_MUTEX(gFailuresMutex);
     58 static SkTArray<SkString> gFailures;
     59 
     60 static void fail(ImplicitString err) {
     61     SkAutoMutexAcquire lock(gFailuresMutex);
     62     SkDebugf("\n\nFAILURE: %s\n\n", err.c_str());
     63     gFailures.push_back(err);
     64 }
     65 
     66 static int32_t gPending = 0;  // Atomic.  Total number of running and queued tasks.
     67 
     68 SK_DECLARE_STATIC_MUTEX(gRunningMutex);
     69 static SkTArray<SkString> gRunning;
     70 
     71 static void done(double ms,
     72                  ImplicitString config, ImplicitString src, ImplicitString srcOptions,
     73                  ImplicitString name, ImplicitString note, ImplicitString log) {
     74     SkString id = SkStringPrintf("%s %s %s %s", config.c_str(), src.c_str(),
     75                                                 srcOptions.c_str(), name.c_str());
     76     {
     77         SkAutoMutexAcquire lock(gRunningMutex);
     78         for (int i = 0; i < gRunning.count(); i++) {
     79             if (gRunning[i] == id) {
     80                 gRunning.removeShuffle(i);
     81                 break;
     82             }
     83         }
     84     }
     85     if (!FLAGS_verbose) {
     86         note = "";
     87     }
     88     if (!log.isEmpty()) {
     89         log.prepend("\n");
     90     }
     91     auto pending = sk_atomic_dec(&gPending)-1;
     92     SkDebugf("%s(%4d/%-4dMB %5d) %s\t%s%s%s", FLAGS_verbose ? "\n" : kSkOverwriteLine
     93                                        , sk_tools::getCurrResidentSetSizeMB()
     94                                        , sk_tools::getMaxResidentSetSizeMB()
     95                                        , pending
     96                                        , HumanizeMs(ms).c_str()
     97                                        , id.c_str()
     98                                        , note.c_str()
     99                                        , log.c_str());
    100     // We write our dm.json file every once in a while in case we crash.
    101     // Notice this also handles the final dm.json when pending == 0.
    102     if (pending % 500 == 0) {
    103         JsonWriter::DumpJson();
    104     }
    105 }
    106 
    107 static void start(ImplicitString config, ImplicitString src,
    108                   ImplicitString srcOptions, ImplicitString name) {
    109     SkString id = SkStringPrintf("%s %s %s %s", config.c_str(), src.c_str(),
    110                                                 srcOptions.c_str(), name.c_str());
    111     SkAutoMutexAcquire lock(gRunningMutex);
    112     gRunning.push_back(id);
    113 }
    114 
    115 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    116 
    117 struct Gold : public SkString {
    118     Gold() : SkString("") {}
    119     Gold(ImplicitString sink, ImplicitString src, ImplicitString srcOptions,
    120          ImplicitString name, ImplicitString md5)
    121         : SkString("") {
    122         this->append(sink);
    123         this->append(src);
    124         this->append(srcOptions);
    125         this->append(name);
    126         this->append(md5);
    127     }
    128     static uint32_t Hash(const Gold& g) { return SkGoodHash((const SkString&)g); }
    129 };
    130 static SkTHashSet<Gold, Gold::Hash> gGold;
    131 
    132 static void add_gold(JsonWriter::BitmapResult r) {
    133     gGold.add(Gold(r.config, r.sourceType, r.sourceOptions, r.name, r.md5));
    134 }
    135 
    136 static void gather_gold() {
    137     if (!FLAGS_readPath.isEmpty()) {
    138         SkString path(FLAGS_readPath[0]);
    139         path.append("/dm.json");
    140         if (!JsonWriter::ReadJson(path.c_str(), add_gold)) {
    141             fail(SkStringPrintf("Couldn't read %s for golden results.", path.c_str()));
    142         }
    143     }
    144 }
    145 
    146 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    147 
    148 static SkTHashSet<SkString> gUninterestingHashes;
    149 
    150 static void gather_uninteresting_hashes() {
    151     if (!FLAGS_uninterestingHashesFile.isEmpty()) {
    152         SkAutoTUnref<SkData> data(SkData::NewFromFileName(FLAGS_uninterestingHashesFile[0]));
    153         SkTArray<SkString> hashes;
    154         SkStrSplit((const char*)data->data(), "\n", &hashes);
    155         for (const SkString& hash : hashes) {
    156             gUninterestingHashes.add(hash);
    157         }
    158     }
    159 }
    160 
    161 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    162 
    163 template <typename T>
    164 struct Tagged : public SkAutoTDelete<T> {
    165   const char* tag;
    166   const char* options;
    167 };
    168 
    169 static const bool kMemcpyOK = true;
    170 
    171 static SkTArray<Tagged<Src>,  kMemcpyOK>  gSrcs;
    172 static SkTArray<Tagged<Sink>, kMemcpyOK> gSinks;
    173 
    174 static bool in_shard() {
    175     static int N = 0;
    176     return N++ % FLAGS_shards == FLAGS_shard;
    177 }
    178 
    179 static void push_src(const char* tag, const char* options, Src* s) {
    180     SkAutoTDelete<Src> src(s);
    181     if (in_shard() &&
    182         FLAGS_src.contains(tag) &&
    183         !SkCommandLineFlags::ShouldSkip(FLAGS_match, src->name().c_str())) {
    184         Tagged<Src>& s = gSrcs.push_back();
    185         s.reset(src.detach());
    186         s.tag = tag;
    187         s.options = options;
    188     }
    189 }
    190 
    191 static void push_codec_srcs(Path path) {
    192     SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(path.c_str()));
    193     if (!encoded) {
    194         SkDebugf("Couldn't read %s.", path.c_str());
    195         return;
    196     }
    197     SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(encoded));
    198     if (NULL == codec.get()) {
    199         SkDebugf("Couldn't create codec for %s.", path.c_str());
    200         return;
    201     }
    202 
    203     // Build additional test cases for images that decode natively to non-canvas types
    204     switch(codec->getInfo().colorType()) {
    205         case kGray_8_SkColorType:
    206             push_src("image", "codec_kGray8", new CodecSrc(path, CodecSrc::kNormal_Mode,
    207                     CodecSrc::kGrayscale_Always_DstColorType));
    208             push_src("image", "scanline_kGray8", new CodecSrc(path, CodecSrc::kScanline_Mode,
    209                     CodecSrc::kGrayscale_Always_DstColorType));
    210             // Intentional fall through
    211             // FIXME: Is this a long term solution for testing wbmps decodes to kIndex8?
    212             // Further discussion on this topic is at skbug.com/3683
    213       case kIndex_8_SkColorType:
    214           push_src("image", "codec_kIndex8", new CodecSrc(path, CodecSrc::kNormal_Mode,
    215                   CodecSrc::kIndex8_Always_DstColorType));
    216           push_src("image", "scanline_kIndex8", new CodecSrc(path, CodecSrc::kScanline_Mode,
    217                   CodecSrc::kIndex8_Always_DstColorType));
    218         break;
    219       default:
    220         // Do nothing
    221         break;
    222     }
    223 
    224     // Decode all images to the canvas color type
    225     push_src("image", "codec", new CodecSrc(path, CodecSrc::kNormal_Mode,
    226             CodecSrc::kGetFromCanvas_DstColorType));
    227     push_src("image", "scanline", new CodecSrc(path, CodecSrc::kScanline_Mode,
    228             CodecSrc::kGetFromCanvas_DstColorType));
    229 }
    230 
    231 static bool codec_supported(const char* ext) {
    232     // FIXME: Once other versions of SkCodec are available, we can add them to this
    233     // list (and eventually we can remove this check once they are all supported).
    234     static const char* const exts[] = {
    235         "bmp", "gif", "jpg", "jpeg", "png", "ico", "wbmp",
    236         "BMP", "GIF", "JPG", "JPEG", "PNG", "ICO", "WBMP"
    237     };
    238 
    239     for (uint32_t i = 0; i < SK_ARRAY_COUNT(exts); i++) {
    240         if (0 == strcmp(exts[i], ext)) {
    241             return true;
    242         }
    243     }
    244     return false;
    245 }
    246 
    247 static void gather_srcs() {
    248     for (const skiagm::GMRegistry* r = skiagm::GMRegistry::Head(); r; r = r->next()) {
    249         push_src("gm", "", new GMSrc(r->factory()));
    250     }
    251     for (int i = 0; i < FLAGS_skps.count(); i++) {
    252         const char* path = FLAGS_skps[i];
    253         if (sk_isdir(path)) {
    254             SkOSFile::Iter it(path, "skp");
    255             for (SkString file; it.next(&file); ) {
    256                 push_src("skp", "", new SKPSrc(SkOSPath::Join(path, file.c_str())));
    257             }
    258         } else {
    259             push_src("skp", "", new SKPSrc(path));
    260         }
    261     }
    262     static const char* const exts[] = {
    263         "bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico",
    264         "BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO",
    265     };
    266     for (int i = 0; i < FLAGS_images.count(); i++) {
    267         const char* flag = FLAGS_images[i];
    268         if (sk_isdir(flag)) {
    269             for (size_t j = 0; j < SK_ARRAY_COUNT(exts); j++) {
    270                 SkOSFile::Iter it(flag, exts[j]);
    271                 for (SkString file; it.next(&file); ) {
    272                     SkString path = SkOSPath::Join(flag, file.c_str());
    273                     push_src("image", "decode", new ImageSrc(path)); // Decode entire image
    274                     push_src("image", "subset", new ImageSrc(path, 2)); // Decode into 2x2 subsets
    275                     if (codec_supported(exts[j])) {
    276                         push_codec_srcs(path);
    277                     }
    278                 }
    279             }
    280         } else if (sk_exists(flag)) {
    281             // assume that FLAGS_images[i] is a valid image if it is a file.
    282             push_src("image", "decode", new ImageSrc(flag)); // Decode entire image.
    283             push_src("image", "subset", new ImageSrc(flag, 2)); // Decode into 2 x 2 subsets
    284             push_codec_srcs(flag);
    285         }
    286     }
    287 }
    288 
    289 static GrGLStandard get_gpu_api() {
    290     if (FLAGS_gpuAPI.contains("gl"))   { return kGL_GrGLStandard; }
    291     if (FLAGS_gpuAPI.contains("gles")) { return kGLES_GrGLStandard; }
    292     return kNone_GrGLStandard;
    293 }
    294 
    295 static void push_sink(const char* tag, Sink* s) {
    296     SkAutoTDelete<Sink> sink(s);
    297     if (!FLAGS_config.contains(tag)) {
    298         return;
    299     }
    300     // Try a noop Src as a canary.  If it fails, skip this sink.
    301     struct : public Src {
    302         Error draw(SkCanvas*) const override { return ""; }
    303         SkISize size() const override { return SkISize::Make(16, 16); }
    304         Name name() const override { return "noop"; }
    305     } noop;
    306 
    307     SkBitmap bitmap;
    308     SkDynamicMemoryWStream stream;
    309     SkString log;
    310     Error err = sink->draw(noop, &bitmap, &stream, &log);
    311     if (err.isFatal()) {
    312         SkDebugf("Could not run %s: %s\n", tag, err.c_str());
    313         exit(1);
    314     }
    315 
    316     Tagged<Sink>& ts = gSinks.push_back();
    317     ts.reset(sink.detach());
    318     ts.tag = tag;
    319 }
    320 
    321 static bool gpu_supported() {
    322 #if SK_SUPPORT_GPU
    323     return FLAGS_gpu;
    324 #else
    325     return false;
    326 #endif
    327 }
    328 
    329 static Sink* create_sink(const char* tag) {
    330 #define SINK(t, sink, ...) if (0 == strcmp(t, tag)) { return new sink(__VA_ARGS__); }
    331     if (gpu_supported()) {
    332         typedef GrContextFactory Gr;
    333         const GrGLStandard api = get_gpu_api();
    334         SINK("gpunull",    GPUSink, Gr::kNull_GLContextType,   api,  0, false, FLAGS_gpu_threading);
    335         SINK("gpudebug",   GPUSink, Gr::kDebug_GLContextType,  api,  0, false, FLAGS_gpu_threading);
    336         SINK("gpu",        GPUSink, Gr::kNative_GLContextType, api,  0, false, FLAGS_gpu_threading);
    337         SINK("gpudft",     GPUSink, Gr::kNative_GLContextType, api,  0,  true, FLAGS_gpu_threading);
    338         SINK("msaa4",      GPUSink, Gr::kNative_GLContextType, api,  4, false, FLAGS_gpu_threading);
    339         SINK("msaa16",     GPUSink, Gr::kNative_GLContextType, api, 16, false, FLAGS_gpu_threading);
    340         SINK("nvprmsaa4",  GPUSink, Gr::kNVPR_GLContextType,   api,  4, false, FLAGS_gpu_threading);
    341         SINK("nvprmsaa16", GPUSink, Gr::kNVPR_GLContextType,   api, 16, false, FLAGS_gpu_threading);
    342     #if SK_ANGLE
    343         SINK("angle",      GPUSink, Gr::kANGLE_GLContextType,  api,  0, false, FLAGS_gpu_threading);
    344     #endif
    345     #if SK_MESA
    346         SINK("mesa",       GPUSink, Gr::kMESA_GLContextType,   api,  0, false, FLAGS_gpu_threading);
    347     #endif
    348     }
    349 
    350 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
    351     SINK("hwui",           HWUISink);
    352 #endif
    353 
    354     if (FLAGS_cpu) {
    355         SINK("565",  RasterSink, kRGB_565_SkColorType);
    356         SINK("8888", RasterSink, kN32_SkColorType);
    357         SINK("pdf",  PDFSink);
    358         SINK("skp",  SKPSink);
    359         SINK("svg",  SVGSink);
    360         SINK("null", NullSink);
    361         SINK("xps",  XPSSink);
    362     }
    363 #undef SINK
    364     return NULL;
    365 }
    366 
    367 static Sink* create_via(const char* tag, Sink* wrapped) {
    368 #define VIA(t, via, ...) if (0 == strcmp(t, tag)) { return new via(__VA_ARGS__); }
    369     VIA("twice",     ViaTwice,         wrapped);
    370     VIA("pipe",      ViaPipe,          wrapped);
    371     VIA("serialize", ViaSerialization, wrapped);
    372     VIA("deferred",  ViaDeferred,      wrapped);
    373     VIA("2ndpic",    ViaSecondPicture, wrapped);
    374     VIA("sp",        ViaSingletonPictures, wrapped);
    375     VIA("tiles",     ViaTiles, 256, 256,               NULL, wrapped);
    376     VIA("tiles_rt",  ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
    377 
    378     if (FLAGS_matrix.count() == 4) {
    379         SkMatrix m;
    380         m.reset();
    381         m.setScaleX((SkScalar)atof(FLAGS_matrix[0]));
    382         m.setSkewX ((SkScalar)atof(FLAGS_matrix[1]));
    383         m.setSkewY ((SkScalar)atof(FLAGS_matrix[2]));
    384         m.setScaleY((SkScalar)atof(FLAGS_matrix[3]));
    385         VIA("matrix",  ViaMatrix,  m, wrapped);
    386         VIA("upright", ViaUpright, m, wrapped);
    387     }
    388 
    389 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
    390     VIA("androidsdk", ViaAndroidSDK, wrapped);
    391 #endif
    392 
    393 #undef VIA
    394     return NULL;
    395 }
    396 
    397 static void gather_sinks() {
    398     for (int i = 0; i < FLAGS_config.count(); i++) {
    399         const char* config = FLAGS_config[i];
    400         SkTArray<SkString> parts;
    401         SkStrSplit(config, "-", &parts);
    402 
    403         Sink* sink = NULL;
    404         for (int i = parts.count(); i-- > 0;) {
    405             const char* part = parts[i].c_str();
    406             Sink* next = (sink == NULL) ? create_sink(part) : create_via(part, sink);
    407             if (next == NULL) {
    408                 SkDebugf("Skipping %s: Don't understand '%s'.\n", config, part);
    409                 delete sink;
    410                 sink = NULL;
    411                 break;
    412             }
    413             sink = next;
    414         }
    415         if (sink) {
    416             push_sink(config, sink);
    417         }
    418     }
    419 }
    420 
    421 static bool match(const char* needle, const char* haystack) {
    422     return 0 == strcmp("_", needle) || NULL != strstr(haystack, needle);
    423 }
    424 
    425 static ImplicitString is_blacklisted(const char* sink, const char* src,
    426                                      const char* srcOptions, const char* name) {
    427     for (int i = 0; i < FLAGS_blacklist.count() - 3; i += 4) {
    428         if (match(FLAGS_blacklist[i+0], sink) &&
    429             match(FLAGS_blacklist[i+1], src) &&
    430             match(FLAGS_blacklist[i+2], srcOptions) &&
    431             match(FLAGS_blacklist[i+3], name)) {
    432             return SkStringPrintf("%s %s %s %s",
    433                                   FLAGS_blacklist[i+0], FLAGS_blacklist[i+1],
    434                                   FLAGS_blacklist[i+2], FLAGS_blacklist[i+3]);
    435         }
    436     }
    437     return "";
    438 }
    439 
    440 // The finest-grained unit of work we can run: draw a single Src into a single Sink,
    441 // report any errors, and perhaps write out the output: a .png of the bitmap, or a raw stream.
    442 struct Task {
    443     Task(const Tagged<Src>& src, const Tagged<Sink>& sink) : src(src), sink(sink) {}
    444     const Tagged<Src>&  src;
    445     const Tagged<Sink>& sink;
    446 
    447     static void Run(Task* task) {
    448         SkString name = task->src->name();
    449         SkString note;
    450         SkString whyBlacklisted = is_blacklisted(task->sink.tag, task->src.tag,
    451                                                  task->src.options, name.c_str());
    452         if (!whyBlacklisted.isEmpty()) {
    453             note.appendf(" (--blacklist %s)", whyBlacklisted.c_str());
    454         }
    455         SkString log;
    456         WallTimer timer;
    457         timer.start();
    458         if (!FLAGS_dryRun && whyBlacklisted.isEmpty()) {
    459             SkBitmap bitmap;
    460             SkDynamicMemoryWStream stream;
    461             start(task->sink.tag, task->src.tag, task->src.options, name.c_str());
    462             Error err = task->sink->draw(*task->src, &bitmap, &stream, &log);
    463             if (!err.isEmpty()) {
    464                 timer.end();
    465                 if (err.isFatal()) {
    466                     fail(SkStringPrintf("%s %s %s %s: %s",
    467                                         task->sink.tag,
    468                                         task->src.tag,
    469                                         task->src.options,
    470                                         name.c_str(),
    471                                         err.c_str()));
    472                 } else {
    473                     note.appendf(" (skipped: %s)", err.c_str());
    474                 }
    475                 done(timer.fWall, task->sink.tag, task->src.tag, task->src.options,
    476                      name, note, log);
    477                 return;
    478             }
    479             SkAutoTDelete<SkStreamAsset> data(stream.detachAsStream());
    480 
    481             SkString md5;
    482             if (!FLAGS_writePath.isEmpty() || !FLAGS_readPath.isEmpty()) {
    483                 SkMD5 hash;
    484                 if (data->getLength()) {
    485                     hash.writeStream(data, data->getLength());
    486                     data->rewind();
    487                 } else {
    488                     hash.write(bitmap.getPixels(), bitmap.getSize());
    489                 }
    490                 SkMD5::Digest digest;
    491                 hash.finish(digest);
    492                 for (int i = 0; i < 16; i++) {
    493                     md5.appendf("%02x", digest.data[i]);
    494                 }
    495             }
    496 
    497             if (!FLAGS_readPath.isEmpty() &&
    498                 !gGold.contains(Gold(task->sink.tag, task->src.tag,
    499                                      task->src.options, name, md5))) {
    500                 fail(SkStringPrintf("%s not found for %s %s %s %s in %s",
    501                                     md5.c_str(),
    502                                     task->sink.tag,
    503                                     task->src.tag,
    504                                     task->src.options,
    505                                     name.c_str(),
    506                                     FLAGS_readPath[0]));
    507             }
    508 
    509             if (!FLAGS_writePath.isEmpty()) {
    510                 const char* ext = task->sink->fileExtension();
    511                 if (data->getLength()) {
    512                     WriteToDisk(*task, md5, ext, data, data->getLength(), NULL);
    513                     SkASSERT(bitmap.drawsNothing());
    514                 } else if (!bitmap.drawsNothing()) {
    515                     WriteToDisk(*task, md5, ext, NULL, 0, &bitmap);
    516                 }
    517             }
    518         }
    519         timer.end();
    520         done(timer.fWall, task->sink.tag, task->src.tag, task->src.options, name, note, log);
    521     }
    522 
    523     static void WriteToDisk(const Task& task,
    524                             SkString md5,
    525                             const char* ext,
    526                             SkStream* data, size_t len,
    527                             const SkBitmap* bitmap) {
    528         JsonWriter::BitmapResult result;
    529         result.name          = task.src->name();
    530         result.config        = task.sink.tag;
    531         result.sourceType    = task.src.tag;
    532         result.sourceOptions = task.src.options;
    533         result.ext           = ext;
    534         result.md5           = md5;
    535         JsonWriter::AddBitmapResult(result);
    536 
    537         // If an MD5 is uninteresting, we want it noted in the JSON file,
    538         // but don't want to dump it out as a .png (or whatever ext is).
    539         if (gUninterestingHashes.contains(md5)) {
    540             return;
    541         }
    542 
    543         const char* dir = FLAGS_writePath[0];
    544         if (0 == strcmp(dir, "@")) {  // Needed for iOS.
    545             dir = FLAGS_resourcePath[0];
    546         }
    547         sk_mkdir(dir);
    548 
    549         SkString path;
    550         if (FLAGS_nameByHash) {
    551             path = SkOSPath::Join(dir, result.md5.c_str());
    552             path.append(".");
    553             path.append(ext);
    554             if (sk_exists(path.c_str())) {
    555                 return;  // Content-addressed.  If it exists already, we're done.
    556             }
    557         } else {
    558             path = SkOSPath::Join(dir, task.sink.tag);
    559             sk_mkdir(path.c_str());
    560             path = SkOSPath::Join(path.c_str(), task.src.tag);
    561             sk_mkdir(path.c_str());
    562             if (strcmp(task.src.options, "") != 0) {
    563               path = SkOSPath::Join(path.c_str(), task.src.options);
    564               sk_mkdir(path.c_str());
    565             }
    566             path = SkOSPath::Join(path.c_str(), task.src->name().c_str());
    567             path.append(".");
    568             path.append(ext);
    569         }
    570 
    571         SkFILEWStream file(path.c_str());
    572         if (!file.isValid()) {
    573             fail(SkStringPrintf("Can't open %s for writing.\n", path.c_str()));
    574             return;
    575         }
    576 
    577         if (bitmap) {
    578             // We can't encode A8 bitmaps as PNGs.  Convert them to 8888 first.
    579             SkBitmap converted;
    580             if (bitmap->info().colorType() == kAlpha_8_SkColorType) {
    581                 if (!bitmap->copyTo(&converted, kN32_SkColorType)) {
    582                     fail("Can't convert A8 to 8888.\n");
    583                     return;
    584                 }
    585                 bitmap = &converted;
    586             }
    587             if (!SkImageEncoder::EncodeStream(&file, *bitmap, SkImageEncoder::kPNG_Type, 100)) {
    588                 fail(SkStringPrintf("Can't encode PNG to %s.\n", path.c_str()));
    589                 return;
    590             }
    591         } else {
    592             if (!file.writeStream(data, len)) {
    593                 fail(SkStringPrintf("Can't write to %s.\n", path.c_str()));
    594                 return;
    595             }
    596         }
    597     }
    598 };
    599 
    600 // Run all tasks in the same enclave serially on the same thread.
    601 // They can't possibly run concurrently with each other.
    602 static void run_enclave(SkTArray<Task>* tasks) {
    603     for (int i = 0; i < tasks->count(); i++) {
    604         Task::Run(tasks->begin() + i);
    605     }
    606 }
    607 
    608 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    609 
    610 // Unit tests don't fit so well into the Src/Sink model, so we give them special treatment.
    611 
    612 static SkTDArray<skiatest::Test> gThreadedTests, gGPUTests;
    613 
    614 static void gather_tests() {
    615     if (!FLAGS_src.contains("tests")) {
    616         return;
    617     }
    618     for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r = r->next()) {
    619         if (!in_shard()) {
    620             continue;
    621         }
    622         // Despite its name, factory() is returning a reference to
    623         // link-time static const POD data.
    624         const skiatest::Test& test = r->factory();
    625         if (SkCommandLineFlags::ShouldSkip(FLAGS_match, test.name)) {
    626             continue;
    627         }
    628         if (test.needsGpu && gpu_supported()) {
    629             (FLAGS_gpu_threading ? gThreadedTests : gGPUTests).push(test);
    630         } else if (!test.needsGpu && FLAGS_cpu) {
    631             gThreadedTests.push(test);
    632         }
    633     }
    634 }
    635 
    636 static void run_test(skiatest::Test* test) {
    637     struct : public skiatest::Reporter {
    638         void reportFailed(const skiatest::Failure& failure) override {
    639             fail(failure.toString());
    640             JsonWriter::AddTestFailure(failure);
    641         }
    642         bool allowExtendedTest() const override {
    643             return FLAGS_pathOpsExtended;
    644         }
    645         bool verbose() const override { return FLAGS_veryVerbose; }
    646     } reporter;
    647     WallTimer timer;
    648     timer.start();
    649     if (!FLAGS_dryRun) {
    650         start("unit", "test", "", test->name);
    651         GrContextFactory factory;
    652         test->proc(&reporter, &factory);
    653     }
    654     timer.end();
    655     done(timer.fWall, "unit", "test", "", test->name, "", "");
    656 }
    657 
    658 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    659 
    660 // If we're isolating all GPU-bound work to one thread (the default), this function runs all that.
    661 static void run_enclave_and_gpu_tests(SkTArray<Task>* tasks) {
    662     run_enclave(tasks);
    663     for (int i = 0; i < gGPUTests.count(); i++) {
    664         run_test(&gGPUTests[i]);
    665     }
    666 }
    667 
    668 // Some runs (mostly, Valgrind) are so slow that the bot framework thinks we've hung.
    669 // This prints something every once in a while so that it knows we're still working.
    670 static void start_keepalive() {
    671     struct Loop {
    672         static void forever(void*) {
    673             for (;;) {
    674                 static const int kSec = 300;
    675             #if defined(SK_BUILD_FOR_WIN)
    676                 Sleep(kSec * 1000);
    677             #else
    678                 sleep(kSec);
    679             #endif
    680                 SkString running;
    681                 {
    682                     SkAutoMutexAcquire lock(gRunningMutex);
    683                     for (int i = 0; i < gRunning.count(); i++) {
    684                         running.appendf("\n\t%s", gRunning[i].c_str());
    685                     }
    686                 }
    687                 SkDebugf("\nCurrently running:%s\n", running.c_str());
    688             }
    689         }
    690     };
    691     static SkThread* intentionallyLeaked = new SkThread(Loop::forever);
    692     intentionallyLeaked->start();
    693 }
    694 
    695 int dm_main();
    696 int dm_main() {
    697     SetupCrashHandler();
    698     SkAutoGraphics ag;
    699     SkTaskGroup::Enabler enabled(FLAGS_threads);
    700     if (FLAGS_leaks) {
    701         SkInstCountPrintLeaksOnExit();
    702     }
    703 
    704     start_keepalive();
    705 
    706     gather_gold();
    707     gather_uninteresting_hashes();
    708 
    709     gather_srcs();
    710     gather_sinks();
    711     gather_tests();
    712 
    713     gPending = gSrcs.count() * gSinks.count() + gThreadedTests.count() + gGPUTests.count();
    714     SkDebugf("%d srcs * %d sinks + %d tests == %d tasks\n",
    715              gSrcs.count(), gSinks.count(), gThreadedTests.count() + gGPUTests.count(), gPending);
    716 
    717     // We try to exploit as much parallelism as is safe.  Most Src/Sink pairs run on any thread,
    718     // but Sinks that identify as part of a particular enclave run serially on a single thread.
    719     // CPU tests run on any thread.  GPU tests depend on --gpu_threading.
    720     SkTArray<Task> enclaves[kNumEnclaves];
    721     for (int j = 0; j < gSinks.count(); j++) {
    722         SkTArray<Task>& tasks = enclaves[gSinks[j]->enclave()];
    723         for (int i = 0; i < gSrcs.count(); i++) {
    724             tasks.push_back(Task(gSrcs[i], gSinks[j]));
    725         }
    726     }
    727 
    728     SkTaskGroup tg;
    729     tg.batch(run_test, gThreadedTests.begin(), gThreadedTests.count());
    730     for (int i = 0; i < kNumEnclaves; i++) {
    731         switch(i) {
    732             case kAnyThread_Enclave:
    733                 tg.batch(Task::Run, enclaves[i].begin(), enclaves[i].count());
    734                 break;
    735             case kGPU_Enclave:
    736                 tg.add(run_enclave_and_gpu_tests, &enclaves[i]);
    737                 break;
    738             default:
    739                 tg.add(run_enclave, &enclaves[i]);
    740                 break;
    741         }
    742     }
    743     tg.wait();
    744     // At this point we're back in single-threaded land.
    745 
    746     SkDebugf("\n");
    747     if (gFailures.count() > 0) {
    748         SkDebugf("Failures:\n");
    749         for (int i = 0; i < gFailures.count(); i++) {
    750             SkDebugf("\t%s\n", gFailures[i].c_str());
    751         }
    752         SkDebugf("%d failures\n", gFailures.count());
    753         return 1;
    754     }
    755     if (gPending > 0) {
    756         SkDebugf("Hrm, we didn't seem to run everything we intended to!  Please file a bug.\n");
    757         return 1;
    758     }
    759     return 0;
    760 }
    761 
    762 #if !defined(SK_BUILD_FOR_IOS)
    763 int main(int argc, char** argv) {
    764     SkCommandLineFlags::Parse(argc, argv);
    765     return dm_main();
    766 }
    767 #endif
    768