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 
fail(ImplicitString err)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 
done(double ms,ImplicitString config,ImplicitString src,ImplicitString srcOptions,ImplicitString name,ImplicitString note,ImplicitString log)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 
start(ImplicitString config,ImplicitString src,ImplicitString srcOptions,ImplicitString name)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 {
GoldGold118     Gold() : SkString("") {}
GoldGold119     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     }
HashGold128     static uint32_t Hash(const Gold& g) { return SkGoodHash((const SkString&)g); }
129 };
130 static SkTHashSet<Gold, Gold::Hash> gGold;
131 
add_gold(JsonWriter::BitmapResult r)132 static void add_gold(JsonWriter::BitmapResult r) {
133     gGold.add(Gold(r.config, r.sourceType, r.sourceOptions, r.name, r.md5));
134 }
135 
gather_gold()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 
gather_uninteresting_hashes()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 
in_shard()174 static bool in_shard() {
175     static int N = 0;
176     return N++ % FLAGS_shards == FLAGS_shard;
177 }
178 
push_src(const char * tag,const char * options,Src * s)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 
push_codec_srcs(Path path)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 
codec_supported(const char * ext)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 
gather_srcs()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 
get_gpu_api()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 
push_sink(const char * tag,Sink * s)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 
gpu_supported()321 static bool gpu_supported() {
322 #if SK_SUPPORT_GPU
323     return FLAGS_gpu;
324 #else
325     return false;
326 #endif
327 }
328 
create_sink(const char * tag)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 
create_via(const char * tag,Sink * wrapped)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 
gather_sinks()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 
match(const char * needle,const char * haystack)421 static bool match(const char* needle, const char* haystack) {
422     return 0 == strcmp("_", needle) || NULL != strstr(haystack, needle);
423 }
424 
is_blacklisted(const char * sink,const char * src,const char * srcOptions,const char * name)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 {
TaskTask443     Task(const Tagged<Src>& src, const Tagged<Sink>& sink) : src(src), sink(sink) {}
444     const Tagged<Src>&  src;
445     const Tagged<Sink>& sink;
446 
RunTask447     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 
WriteToDiskTask523     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.
run_enclave(SkTArray<Task> * tasks)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 
gather_tests()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 
run_test(skiatest::Test * test)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.
run_enclave_and_gpu_tests(SkTArray<Task> * tasks)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.
start_keepalive()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();
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)
main(int argc,char ** argv)763 int main(int argc, char** argv) {
764     SkCommandLineFlags::Parse(argc, argv);
765     return dm_main();
766 }
767 #endif
768