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 "OverwriteLine.h"
10 #include "Resources.h"
11 #include "SkBitmap.h"
12 #include "SkCanvas.h"
13 #include "SkColor.h"
14 #include "SkColorPriv.h"
15 #include "SkCommandLineFlags.h"
16 #include "SkDevice.h"
17 #include "SkForceLinking.h"
18 #include "SkGraphics.h"
19 #include "SkImageEncoder.h"
20 #include "SkOSFile.h"
21 #include "SkPathOpsDebug.h"
22 #include "SkPicture.h"
23 #include "SkRTConf.h"
24 #include "SkTSort.h"
25 #include "SkStream.h"
26 #include "SkString.h"
27 #include "SkTArray.h"
28 #include "SkTDArray.h"
29 #include "SkTaskGroup.h"
30 #include "SkTemplates.h"
31 #include "SkTime.h"
32 
33 #include <stdlib.h>
34 
35 /* add local exceptions here */
36 /* TODO : add command flag interface */
37 const struct SkipOverTest {
38     int directory;
39     const char* filename;
40     bool blamePathOps;
41 } skipOver[] = {
42     { 2, "http___www_groupon_sg_.skp", false},  // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
43     { 6, "http___www_googleventures_com_.skp", true},  // addTCoincident SkASSERT(test->fT < 1);
44     { 7, "http___www_foxsports_nl_.skp", true},  // (no repro on mac) addT SkASSERT(this != other || fVerb == SkPath::kCubic_Verb)
45     {13, "http___www_modernqigong_com_.skp", false},  // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
46     {14, "http___www_devbridge_com_.skp", true},  // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple);
47     {16, "http___www_1023world_net_.skp", false},  // bitmap decode assert (corrupt skp?)
48     {19, "http___www_alamdi_com_.skp", true},  // cubic/quad intersection
49     {26, "http___www_liveencounters_net_.skp", true},  // (no repro on mac) checkSmall addT:549 (line, expects cubic)
50     {28, "http___www_encros_fr_.skp", false},  // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
51     {37, "http___www_familysurvivalprotocol_wordpress_com_.skp", true},  // bumpSpan SkASSERT(span->fOppValue >= 0);
52     {39, "http___sufeinet_com_.skp", false}, // bitmap decode assert (corrupt skp?)
53     {41, "http___www_rano360_com_.skp", true}, // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple);
54     {44, "http___www_firstunitedbank_com_.skp", true},  // addTCancel SkASSERT(oIndex > 0);
55     {46, "http___www_shinydemos_com_.skp", true},  // addSimpleAngle SkASSERT(index == count() - 2);
56     {48, "http___www_familysurvivalprotocol_com_.skp", true},  // bumpSpan SkASSERT "span->fOppValue >= 0"
57     {57, "http___www_lptemp_com_.skp", true}, // addTCoincident oPeek = &other->fTs[++oPeekIndex];
58     {71, "http___www_1milyonkahraman_org_.skp", true},  // addTCoincident SkASSERT(test->fT < 1);
59     {88, "http___www_apuntesdelechuza_wordpress_com_.skp", true},  // bumpSpan SkASSERT "span->fOppValue >= 0"
60     {89, "http___www_mobilizedconsulting_com_.skp", true}, // addTCancel SkASSERT(oIndex > 0);
61     {93, "http___www_simple_living_in_suffolk_co_uk_.skp", true},  // bumpSpan SkASSERT "span->fOppValue >= 0"
62 };
63 
64 size_t skipOverCount = sizeof(skipOver) / sizeof(skipOver[0]);
65 
66 
67 /* customize file in/out here */
68 /* TODO : add command flag interface */
69 #define CHROME_VERSION "1e5dfa4-4a995df"
70 #define SUMMARY_RUN 1
71 
72 #ifdef SK_BUILD_FOR_WIN
73     #define DRIVE_SPEC "D:"
74     #define PATH_SLASH "\\"
75 #else
76     #define DRIVE_SPEC ""
77     #define PATH_SLASH "/"
78 #endif
79 
80 #define IN_DIR_PRE  DRIVE_SPEC PATH_SLASH "skps"   PATH_SLASH "slave"
81 #define OUT_DIR_PRE DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "slave"
82 #define OUT_DIR_SUM DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "summary"
83 #define DIR_POST               PATH_SLASH "All"    PATH_SLASH CHROME_VERSION
84 
85 static const char outOpDir[]     = "opClip";
86 static const char outOldDir[]    = "oldClip";
87 static const char outStatusDir[] = "statusTest";
88 
get_in_path(int dirNo,const char * filename)89 static SkString get_in_path(int dirNo, const char* filename) {
90     SkString path;
91     SkASSERT(dirNo);
92     path.appendf("%s%d%s", IN_DIR_PRE, dirNo, DIR_POST);
93     if (!sk_exists(path.c_str())) {
94         SkDebugf("could not read %s\n", path.c_str());
95         return SkString();
96     }
97     if (filename) {
98         path.appendf("%s%s", PATH_SLASH, filename);
99         if (!sk_exists(path.c_str())) {
100             SkDebugf("could not read %s\n", path.c_str());
101             return SkString();
102         }
103     }
104     return path;
105 }
106 
make_recursive_dir(const SkString & path)107 static void make_recursive_dir(const SkString& path) {
108     if (sk_exists(path.c_str())) {
109         return;
110     }
111     const char* pathStr = path.c_str();
112     int last = (int) path.size();
113     do {
114         while (last > 0 && pathStr[--last] != PATH_SLASH[0])
115             ;
116         SkASSERT(last > 0);
117         SkString shorter(pathStr, last);
118         if (sk_mkdir(shorter.c_str())) {
119             break;
120         }
121     } while (true);
122     do {
123         while (last < (int) path.size() && pathStr[++last] != PATH_SLASH[0])
124             ;
125         SkString shorter(pathStr, last);
126         SkAssertResult(sk_mkdir(shorter.c_str()));
127     } while (last < (int) path.size());
128 }
129 
get_out_path(int dirNo,const char * dirName)130 static SkString get_out_path(int dirNo, const char* dirName) {
131     SkString path;
132     SkASSERT(dirNo);
133     SkASSERT(dirName);
134     path.appendf("%s%d%s%s%s", OUT_DIR_PRE, dirNo, DIR_POST, PATH_SLASH, dirName);
135     make_recursive_dir(path);
136     return path;
137 }
138 
get_sum_path(const char * dirName)139 static SkString get_sum_path(const char* dirName) {
140     SkString path;
141     SkASSERT(dirName);
142     path.appendf("%s%d%s%s", OUT_DIR_SUM, SUMMARY_RUN, PATH_SLASH, dirName);
143     SkDebugf("%s\n", path.c_str());
144     make_recursive_dir(path);
145     return path;
146 }
147 
make_png_name(const char * filename)148 static SkString make_png_name(const char* filename) {
149     SkString pngName = SkString(filename);
150     pngName.remove(pngName.size() - 3, 3);
151     pngName.append("png");
152     return pngName;
153 }
154 
155 ////////////////////////////////////////////////////////
156 
157 enum TestStep {
158     kCompareBits,
159     kEncodeFiles,
160 };
161 
162 enum {
163     kMaxLength = 256,
164     kMaxFiles = 128,
165     kSmallLimit = 1000,
166 };
167 
168 struct TestResult {
initTestResult169     void init(int dirNo) {
170         fDirNo = dirNo;
171         sk_bzero(fFilename, sizeof(fFilename));
172         fTestStep = kCompareBits;
173         fScale = 1;
174     }
175 
initTestResult176     void init(int dirNo, const SkString& filename) {
177         fDirNo = dirNo;
178         strcpy(fFilename, filename.c_str());
179         fTestStep = kCompareBits;
180         fScale = 1;
181     }
182 
statusTestResult183     SkString status() {
184         SkString outStr;
185         outStr.printf("%s %d %d\n", fFilename, fPixelError, fTime);
186         return outStr;
187     }
188 
progressTestResult189     SkString progress() {
190         SkString outStr;
191         outStr.printf("dir=%d %s ", fDirNo, fFilename);
192         if (fPixelError) {
193             outStr.appendf(" err=%d", fPixelError);
194         }
195         if (fTime) {
196             outStr.appendf(" time=%d", fTime);
197         }
198         if (fScale != 1) {
199             outStr.appendf(" scale=%d", fScale);
200         }
201         outStr.appendf("\n");
202         return outStr;
203 
204     }
205 
testTestResult206     void test(int dirNo, const SkString& filename) {
207         init(dirNo);
208         strcpy(fFilename, filename.c_str());
209         testOne();
210     }
211 
212     void testOne();
213 
214     char fFilename[kMaxLength];
215     TestStep fTestStep;
216     int fDirNo;
217     int fPixelError;
218     int fTime;
219     int fScale;
220 };
221 
222 class SortByPixel : public TestResult {
223 public:
operator <(const SortByPixel & rh) const224     bool operator<(const SortByPixel& rh) const {
225         return fPixelError < rh.fPixelError;
226     }
227 };
228 
229 class SortByTime : public TestResult {
230 public:
operator <(const SortByTime & rh) const231     bool operator<(const SortByTime& rh) const {
232         return fTime < rh.fTime;
233     }
234 };
235 
236 class SortByName : public TestResult {
237 public:
operator <(const SortByName & rh) const238     bool operator<(const SortByName& rh) const {
239         return strcmp(fFilename, rh.fFilename) < 0;
240     }
241 };
242 
243 struct TestState {
initTestState244     void init(int dirNo) {
245         fResult.init(dirNo);
246     }
247 
248     SkTDArray<SortByPixel> fPixelWorst;
249     SkTDArray<SortByTime> fSlowest;
250     TestResult fResult;
251 };
252 
253 struct TestRunner {
254     ~TestRunner();
255     void render();
256     SkTDArray<class TestRunnable*> fRunnables;
257 };
258 
259 class TestRunnable {
260 public:
operator ()()261     void operator()() {
262         SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
263         (*fTestFun)(&fState);
264     }
265 
266     TestState fState;
267     void (*fTestFun)(TestState*);
268 };
269 
270 
271 class TestRunnableDir : public TestRunnable {
272 public:
TestRunnableDir(void (* testFun)(TestState *),int dirNo,TestRunner * runner)273     TestRunnableDir(void (*testFun)(TestState*), int dirNo, TestRunner* runner) {
274         fState.init(dirNo);
275         fTestFun = testFun;
276     }
277 
278 };
279 
280 class TestRunnableFile : public TestRunnable {
281 public:
TestRunnableFile(void (* testFun)(TestState *),int dirNo,const char * name,TestRunner * runner)282     TestRunnableFile(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) {
283         fState.init(dirNo);
284         strcpy(fState.fResult.fFilename, name);
285         fTestFun = testFun;
286     }
287 };
288 
289 class TestRunnableEncode : public TestRunnableFile {
290 public:
TestRunnableEncode(void (* testFun)(TestState *),int dirNo,const char * name,TestRunner * runner)291     TestRunnableEncode(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner)
292         : TestRunnableFile(testFun, dirNo, name, runner) {
293         fState.fResult.fTestStep = kEncodeFiles;
294     }
295 };
296 
~TestRunner()297 TestRunner::~TestRunner() {
298     for (int index = 0; index < fRunnables.count(); index++) {
299         delete fRunnables[index];
300     }
301 }
302 
render()303 void TestRunner::render() {
304     SkTaskGroup().batch(fRunnables.count(), [&](int i) {
305         (*fRunnables[i])();
306     });
307 }
308 
309 ////////////////////////////////////////////////
310 
311 
similarBits(const SkBitmap & gr,const SkBitmap & sk)312 static int similarBits(const SkBitmap& gr, const SkBitmap& sk) {
313     const int kRowCount = 3;
314     const int kThreshold = 3;
315     int width = SkTMin(gr.width(), sk.width());
316     if (width < kRowCount) {
317         return true;
318     }
319     int height = SkTMin(gr.height(), sk.height());
320     if (height < kRowCount) {
321         return true;
322     }
323     int errorTotal = 0;
324     SkTArray<int, true> errorRows;
325     errorRows.push_back_n(width * kRowCount);
326     SkAutoLockPixels autoGr(gr);
327     SkAutoLockPixels autoSk(sk);
328     for (int y = 0; y < height; ++y) {
329         SkPMColor* grRow = gr.getAddr32(0, y);
330         SkPMColor* skRow = sk.getAddr32(0, y);
331         int* base = &errorRows[0];
332         int* cOut = &errorRows[y % kRowCount];
333         for (int x = 0; x < width; ++x) {
334             SkPMColor grColor = grRow[x];
335             SkPMColor skColor = skRow[x];
336             int dr = SkGetPackedR32(grColor) - SkGetPackedR32(skColor);
337             int dg = SkGetPackedG32(grColor) - SkGetPackedG32(skColor);
338             int db = SkGetPackedB32(grColor) - SkGetPackedB32(skColor);
339             int error = cOut[x] = SkTMax(SkAbs32(dr), SkTMax(SkAbs32(dg), SkAbs32(db)));
340             if (error < kThreshold || x < 2) {
341                 continue;
342             }
343             if (base[x - 2] < kThreshold
344                     || base[width + x - 2] < kThreshold
345                     || base[width * 2 + x - 2] < kThreshold
346                     || base[x - 1] < kThreshold
347                     || base[width + x - 1] < kThreshold
348                     || base[width * 2 + x - 1] < kThreshold
349                     || base[x] < kThreshold
350                     || base[width + x] < kThreshold
351                     || base[width * 2 + x] < kThreshold) {
352                 continue;
353             }
354             errorTotal += error;
355         }
356     }
357     return errorTotal;
358 }
359 
addError(TestState * data,const TestResult & testResult)360 static bool addError(TestState* data, const TestResult& testResult) {
361     if (testResult.fPixelError <= 0 && testResult.fTime <= 0) {
362         return false;
363     }
364     int worstCount = data->fPixelWorst.count();
365     int pixelError = testResult.fPixelError;
366     if (pixelError > 0) {
367         for (int index = 0; index < worstCount; ++index) {
368             if (pixelError > data->fPixelWorst[index].fPixelError) {
369                 data->fPixelWorst[index] = *(SortByPixel*) &testResult;
370                 return true;
371             }
372         }
373     }
374     int slowCount = data->fSlowest.count();
375     int time = testResult.fTime;
376     if (time > 0) {
377         for (int index = 0; index < slowCount; ++index) {
378             if (time > data->fSlowest[index].fTime) {
379                 data->fSlowest[index] = *(SortByTime*) &testResult;
380                 return true;
381             }
382         }
383     }
384     if (pixelError > 0 && worstCount < kMaxFiles) {
385         *data->fPixelWorst.append() = *(SortByPixel*) &testResult;
386         return true;
387     }
388     if (time > 0 && slowCount < kMaxFiles) {
389         *data->fSlowest.append() = *(SortByTime*) &testResult;
390         return true;
391     }
392     return false;
393 }
394 
timePict(SkPicture * pic,SkCanvas * canvas)395 static SkMSec timePict(SkPicture* pic, SkCanvas* canvas) {
396     canvas->save();
397     SkScalar pWidth = pic->cullRect().width();
398     SkScalar pHeight = pic->cullRect().height();
399     const SkScalar maxDimension = 1000.0f;
400     const int slices = 3;
401     SkScalar xInterval = SkTMax(pWidth - maxDimension, 0.0f) / (slices - 1);
402     SkScalar yInterval = SkTMax(pHeight - maxDimension, 0.0f) / (slices - 1);
403     SkRect rect = {0, 0, SkTMin(maxDimension, pWidth), SkTMin(maxDimension, pHeight) };
404     canvas->clipRect(rect);
405     SkMSec start = SkTime::GetMSecs();
406     for (int x = 0; x < slices; ++x) {
407         for (int y = 0; y < slices; ++y) {
408             pic->playback(canvas);
409             canvas->translate(0, yInterval);
410         }
411         canvas->translate(xInterval, -yInterval * slices);
412     }
413     SkMSec end = SkTime::GetMSecs();
414     canvas->restore();
415     return end - start;
416 }
417 
drawPict(SkPicture * pic,SkCanvas * canvas,int scale)418 static void drawPict(SkPicture* pic, SkCanvas* canvas, int scale) {
419     canvas->clear(SK_ColorWHITE);
420     if (scale != 1) {
421         canvas->save();
422         canvas->scale(1.0f / scale, 1.0f / scale);
423     }
424     pic->playback(canvas);
425     if (scale != 1) {
426         canvas->restore();
427     }
428 }
429 
writePict(const SkBitmap & bitmap,const char * outDir,const char * pngName)430 static void writePict(const SkBitmap& bitmap, const char* outDir, const char* pngName) {
431     SkString outFile = get_sum_path(outDir);
432     outFile.appendf("%s%s", PATH_SLASH, pngName);
433     if (!SkImageEncoder::EncodeFile(outFile.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100)) {
434         SkDebugf("unable to encode gr %s (width=%d height=%d)\n", pngName,
435                     bitmap.width(), bitmap.height());
436     }
437 }
438 
testOne()439 void TestResult::testOne() {
440     SkPicture* pic = nullptr;
441     {
442     #if DEBUG_SHOW_TEST_NAME
443         if (fTestStep == kCompareBits) {
444             SkString testName(fFilename);
445             const char http[] = "http";
446             if (testName.startsWith(http)) {
447                 testName.remove(0, sizeof(http) - 1);
448             }
449             while (testName.startsWith("_")) {
450                 testName.remove(0, 1);
451             }
452             const char dotSkp[] = ".skp";
453             if (testName.endsWith(dotSkp)) {
454                 size_t len = testName.size();
455                 testName.remove(len - (sizeof(dotSkp) - 1), sizeof(dotSkp) - 1);
456             }
457             testName.prepend("skp");
458             testName.append("1");
459             strncpy(DEBUG_FILENAME_STRING, testName.c_str(), DEBUG_FILENAME_STRING_LENGTH);
460         } else if (fTestStep == kEncodeFiles) {
461             strncpy(DEBUG_FILENAME_STRING, "", DEBUG_FILENAME_STRING_LENGTH);
462         }
463     #endif
464         SkString path = get_in_path(fDirNo, fFilename);
465         SkFILEStream stream(path.c_str());
466         if (!stream.isValid()) {
467             SkDebugf("invalid stream %s\n", path.c_str());
468             goto finish;
469         }
470         pic = SkPicture::CreateFromStream(&stream);
471         if (!pic) {
472             SkDebugf("unable to decode %s\n", fFilename);
473             goto finish;
474         }
475         SkScalar width = pic->cullRect().width();
476         SkScalar height = pic->cullRect().height();
477         SkBitmap oldBitmap, opBitmap;
478         fScale = 1;
479         while (width / fScale > 32767 || height / fScale > 32767) {
480             ++fScale;
481         }
482         do {
483             int dimX = SkScalarCeilToInt(width / fScale);
484             int dimY = SkScalarCeilToInt(height / fScale);
485             if (oldBitmap.tryAllocN32Pixels(dimX, dimY) && opBitmap.tryAllocN32Pixels(dimX, dimY)) {
486                 break;
487             }
488             SkDebugf("-%d-", fScale);
489         } while (++fScale < 256);
490         if (fScale >= 256) {
491             SkDebugf("unable to allocate bitmap for %s (w=%f h=%f)\n", fFilename,
492                     width, height);
493             goto finish;
494         }
495         oldBitmap.eraseColor(SK_ColorWHITE);
496         SkCanvas oldCanvas(oldBitmap);
497         oldCanvas.setAllowSimplifyClip(false);
498         opBitmap.eraseColor(SK_ColorWHITE);
499         SkCanvas opCanvas(opBitmap);
500         opCanvas.setAllowSimplifyClip(true);
501         drawPict(pic, &oldCanvas, fScale);
502         drawPict(pic, &opCanvas, fScale);
503         if (fTestStep == kCompareBits) {
504             fPixelError = similarBits(oldBitmap, opBitmap);
505             int oldTime = timePict(pic, &oldCanvas);
506             int opTime = timePict(pic, &opCanvas);
507             fTime = SkTMax(0, oldTime - opTime);
508         } else if (fTestStep == kEncodeFiles) {
509             SkString pngStr = make_png_name(fFilename);
510             const char* pngName = pngStr.c_str();
511             writePict(oldBitmap, outOldDir, pngName);
512             writePict(opBitmap, outOpDir, pngName);
513         }
514     }
515 finish:
516     if (pic) {
517         pic->unref();
518     }
519 }
520 
521 DEFINE_string2(match, m, "PathOpsSkpClipThreaded",
522         "[~][^]substring[$] [...] of test name to run.\n"
523         "Multiple matches may be separated by spaces.\n"
524         "~ causes a matching test to always be skipped\n"
525         "^ requires the start of the test to match\n"
526         "$ requires the end of the test to match\n"
527         "^ and $ requires an exact match\n"
528         "If a test does not match any list entry,\n"
529         "it is skipped unless some list entry starts with ~");
530 DEFINE_string2(dir, d, nullptr, "range of directories (e.g., 1-100)");
531 DEFINE_string2(skp, s, nullptr, "skp to test");
532 DEFINE_bool2(single, z, false, "run tests on a single thread internally.");
533 DEFINE_int32(testIndex, 0, "override local test index (PathOpsSkpClipOneOff only).");
534 DEFINE_bool2(verbose, v, false, "enable verbose output.");
535 
verbose()536 static bool verbose() {
537     return FLAGS_verbose;
538 }
539 
540 class Dirs {
541 public:
Dirs()542     Dirs() {
543         reset();
544         sk_bzero(fRun, sizeof(fRun));
545         fSet = false;
546     }
547 
first() const548     int first() const {
549         int index = 0;
550         while (++index < kMaxDir) {
551             if (fRun[index]) {
552                 return index;
553             }
554         }
555         SkASSERT(0);
556         return -1;
557     }
558 
last() const559     int last() const {
560         int index = kMaxDir;
561         while (--index > 0 && !fRun[index])
562             ;
563         return index;
564     }
565 
next()566     int next() {
567         while (++fIndex < kMaxDir) {
568             if (fRun[fIndex]) {
569                 return fIndex;
570             }
571         }
572         return -1;
573     }
574 
reset()575     void reset() {
576         fIndex = -1;
577     }
578 
set(int start,int end)579     void set(int start, int end) {
580         while (start < end) {
581             fRun[start++] = 1;
582         }
583         fSet = true;
584     }
585 
setDefault()586     void setDefault() {
587         if (!fSet) {
588             set(1, 100);
589         }
590     }
591 
592 private:
593     enum {
594          kMaxDir = 101
595     };
596     char fRun[kMaxDir];
597     int fIndex;
598     bool fSet;
599 } gDirs;
600 
601 class Filenames {
602 public:
Filenames()603     Filenames()
604         : fIndex(-1) {
605     }
606 
next()607     const char* next() {
608         while (fNames && ++fIndex < fNames->count()) {
609             return (*fNames)[fIndex];
610         }
611         return nullptr;
612     }
613 
set(const SkCommandLineFlags::StringArray & names)614     void set(const SkCommandLineFlags::StringArray& names) {
615         fNames = &names;
616     }
617 
618 private:
619     int fIndex;
620     const SkCommandLineFlags::StringArray* fNames;
621 } gNames;
622 
buildTestDir(int dirNo,int firstDirNo,SkTDArray<TestResult> * tests,SkTDArray<SortByName * > * sorted)623 static bool buildTestDir(int dirNo, int firstDirNo,
624         SkTDArray<TestResult>* tests, SkTDArray<SortByName*>* sorted) {
625     SkString dirName = get_out_path(dirNo, outStatusDir);
626     if (!dirName.size()) {
627         return false;
628     }
629     SkOSFile::Iter iter(dirName.c_str(), "skp");
630     SkString filename;
631     while (iter.next(&filename)) {
632         TestResult test;
633         test.init(dirNo);
634         SkString spaceFile(filename);
635         char* spaces = spaceFile.writable_str();
636         int spaceSize = (int) spaceFile.size();
637         for (int index = 0; index < spaceSize; ++index) {
638             if (spaces[index] == '.') {
639                 spaces[index] = ' ';
640             }
641         }
642         int success = sscanf(spaces, "%s %d %d skp", test.fFilename,
643                 &test.fPixelError, &test.fTime);
644         if (success < 3) {
645             SkDebugf("failed to scan %s matched=%d\n", filename.c_str(), success);
646             return false;
647         }
648         *tests[dirNo - firstDirNo].append() = test;
649     }
650     if (!sorted) {
651         return true;
652     }
653     SkTDArray<TestResult>& testSet = tests[dirNo - firstDirNo];
654     int count = testSet.count();
655     for (int index = 0; index < count; ++index) {
656         *sorted[dirNo - firstDirNo].append() = (SortByName*) &testSet[index];
657     }
658     if (sorted[dirNo - firstDirNo].count()) {
659         SkTQSort<SortByName>(sorted[dirNo - firstDirNo].begin(),
660                 sorted[dirNo - firstDirNo].end() - 1);
661         if (verbose()) {
662             SkDebugf("+");
663         }
664     }
665     return true;
666 }
667 
testSkpClip(TestState * data)668 static void testSkpClip(TestState* data) {
669     data->fResult.testOne();
670     SkString statName(data->fResult.fFilename);
671     SkASSERT(statName.endsWith(".skp"));
672     statName.remove(statName.size() - 4, 4);
673     statName.appendf(".%d.%d.skp", data->fResult.fPixelError, data->fResult.fTime);
674     SkString statusFile = get_out_path(data->fResult.fDirNo, outStatusDir);
675     if (!statusFile.size()) {
676         SkDebugf("failed to create %s", statusFile.c_str());
677         return;
678     }
679     statusFile.appendf("%s%s", PATH_SLASH, statName.c_str());
680     FILE* file = sk_fopen(statusFile.c_str(), kWrite_SkFILE_Flag);
681     if (!file) {
682             SkDebugf("failed to create %s", statusFile.c_str());
683             return;
684     }
685     sk_fclose(file);
686     if (verbose()) {
687         if (data->fResult.fPixelError || data->fResult.fTime) {
688             SkDebugf("%s", data->fResult.progress().c_str());
689         } else {
690             SkDebugf(".");
691         }
692     }
693 }
694 
695 bool Less(const SortByName& a, const SortByName& b);
Less(const SortByName & a,const SortByName & b)696 bool Less(const SortByName& a, const SortByName& b) {
697     return a < b;
698 }
699 
doOneDir(TestState * state,bool threaded)700 static bool doOneDir(TestState* state, bool threaded) {
701     int dirNo = state->fResult.fDirNo;
702     SkString dirName = get_in_path(dirNo, nullptr);
703     if (!dirName.size()) {
704         return false;
705     }
706     SkTDArray<TestResult> tests[1];
707     SkTDArray<SortByName*> sorted[1];
708     if (!buildTestDir(dirNo, dirNo, tests, sorted)) {
709         return false;
710     }
711     SkOSFile::Iter iter(dirName.c_str(), "skp");
712     SkString filename;
713     while (iter.next(&filename)) {
714         for (size_t index = 0; index < skipOverCount; ++index) {
715             if (skipOver[index].directory == dirNo
716                     && strcmp(filename.c_str(), skipOver[index].filename) == 0) {
717                 goto checkEarlyExit;
718             }
719         }
720         {
721             SortByName name;
722             name.init(dirNo);
723             strncpy(name.fFilename, filename.c_str(), filename.size() - 4);  // drop .skp
724             int count = sorted[0].count();
725             int idx = SkTSearch<SortByName, Less>(sorted[0].begin(), count, &name, sizeof(&name));
726             if (idx >= 0) {
727                 SortByName* found = sorted[0][idx];
728                 (void) addError(state, *found);
729                 continue;
730             }
731             TestResult test;
732             test.init(dirNo, filename);
733             state->fResult = test;
734             testSkpClip(state);
735 #if 0 // artificially limit to a few while debugging code
736             static int debugLimit = 0;
737             if (++debugLimit == 5) {
738                 return true;
739             }
740 #endif
741         }
742 checkEarlyExit:
743         ;
744     }
745     return true;
746 }
747 
initTest()748 static void initTest() {
749 #if !defined SK_BUILD_FOR_WIN && !defined SK_BUILD_FOR_MAC
750     SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true);
751     SK_CONF_SET("images.png.suppressDecoderWarnings", true);
752 #endif
753 }
754 
testSkpClipEncode(TestState * data)755 static void testSkpClipEncode(TestState* data) {
756     data->fResult.testOne();
757     if (verbose()) {
758         SkDebugf("+");
759     }
760 }
761 
encodeFound(TestState & state)762 static void encodeFound(TestState& state) {
763     if (verbose()) {
764         if (state.fPixelWorst.count()) {
765             SkTDArray<SortByPixel*> worst;
766             for (int index = 0; index < state.fPixelWorst.count(); ++index) {
767                 *worst.append() = &state.fPixelWorst[index];
768             }
769             SkTQSort<SortByPixel>(worst.begin(), worst.end() - 1);
770             for (int index = 0; index < state.fPixelWorst.count(); ++index) {
771                 const TestResult& result = *worst[index];
772                 SkDebugf("%d %s pixelError=%d\n", result.fDirNo, result.fFilename, result.fPixelError);
773             }
774         }
775         if (state.fSlowest.count()) {
776             SkTDArray<SortByTime*> slowest;
777             for (int index = 0; index < state.fSlowest.count(); ++index) {
778                 *slowest.append() = &state.fSlowest[index];
779             }
780             if (slowest.count() > 0) {
781                 SkTQSort<SortByTime>(slowest.begin(), slowest.end() - 1);
782                 for (int index = 0; index < slowest.count(); ++index) {
783                     const TestResult& result = *slowest[index];
784                     SkDebugf("%d %s time=%d\n", result.fDirNo, result.fFilename, result.fTime);
785                 }
786             }
787         }
788     }
789     TestRunner testRunner;
790     for (int index = 0; index < state.fPixelWorst.count(); ++index) {
791         const TestResult& result = state.fPixelWorst[index];
792         SkString filename(result.fFilename);
793         if (!filename.endsWith(".skp")) {
794             filename.append(".skp");
795         }
796         *testRunner.fRunnables.append() = new TestRunnableEncode(&testSkpClipEncode, result.fDirNo,
797                                                                  filename.c_str(), &testRunner);
798     }
799     testRunner.render();
800 }
801 
802 class Test {
803 public:
Test()804     Test() {}
~Test()805     virtual ~Test() {}
806 
getName()807     const char* getName() { onGetName(&fName); return fName.c_str(); }
run()808     void run() { onRun(); }
809 
810 protected:
811     virtual void onGetName(SkString*) = 0;
812     virtual void onRun() = 0;
813 
814 private:
815     SkString    fName;
816 };
817 
818 typedef SkTRegistry<Test*(*)(void*)> TestRegistry;
819 
820 #define DEF_TEST(name)                                                \
821     static void test_##name();                                        \
822     class name##Class : public Test {                                 \
823     public:                                                           \
824         static Test* Factory(void*) { return new name##Class; }       \
825                                                                       \
826     protected:                                                        \
827         void onGetName(SkString* name) override { name->set(#name); } \
828         void onRun() override { test_##name(); }                      \
829     };                                                                \
830     static TestRegistry gReg_##name##Class(name##Class::Factory);     \
831     static void test_##name()
832 
DEF_TEST(PathOpsSkpClip)833 DEF_TEST(PathOpsSkpClip) {
834     gDirs.setDefault();
835     initTest();
836     SkTArray<TestResult, true> errors;
837     TestState state;
838     state.init(0);
839     int dirNo;
840     gDirs.reset();
841     while ((dirNo = gDirs.next()) > 0) {
842         if (verbose()) {
843             SkDebugf("dirNo=%d\n", dirNo);
844         }
845         state.fResult.fDirNo = dirNo;
846         if (!doOneDir(&state, false)) {
847             break;
848         }
849     }
850     encodeFound(state);
851 }
852 
testSkpClipMain(TestState * data)853 static void testSkpClipMain(TestState* data) {
854         (void) doOneDir(data, true);
855 }
856 
DEF_TEST(PathOpsSkpClipThreaded)857 DEF_TEST(PathOpsSkpClipThreaded) {
858     gDirs.setDefault();
859     initTest();
860     TestRunner testRunner;
861     int dirNo;
862     gDirs.reset();
863     while ((dirNo = gDirs.next()) > 0) {
864         *testRunner.fRunnables.append() = new TestRunnableDir(&testSkpClipMain, dirNo, &testRunner);
865     }
866     testRunner.render();
867     TestState state;
868     state.init(0);
869     gDirs.reset();
870     while ((dirNo = gDirs.next()) > 0) {
871         TestState& testState = testRunner.fRunnables[dirNo - 1]->fState;
872         SkASSERT(testState.fResult.fDirNo == dirNo);
873         for (int inner = 0; inner < testState.fPixelWorst.count(); ++inner) {
874             addError(&state, testState.fPixelWorst[inner]);
875         }
876         for (int inner = 0; inner < testState.fSlowest.count(); ++inner) {
877             addError(&state, testState.fSlowest[inner]);
878         }
879     }
880     encodeFound(state);
881 }
882 
buildTests(SkTDArray<TestResult> * tests,SkTDArray<SortByName * > * sorted)883 static bool buildTests(SkTDArray<TestResult>* tests, SkTDArray<SortByName*>* sorted) {
884     int firstDirNo = gDirs.first();
885     int dirNo;
886     while ((dirNo = gDirs.next()) > 0) {
887         if (!buildTestDir(dirNo, firstDirNo, tests, sorted)) {
888             return false;
889         }
890     }
891     return true;
892 }
893 
DEF_TEST(PathOpsSkpClipUberThreaded)894 DEF_TEST(PathOpsSkpClipUberThreaded) {
895     gDirs.setDefault();
896     const int firstDirNo = gDirs.next();
897     const int lastDirNo = gDirs.last();
898     initTest();
899     int dirCount = lastDirNo - firstDirNo + 1;
900     SkAutoTDeleteArray<SkTDArray<TestResult> > tests(new SkTDArray<TestResult>[dirCount]);
901     SkAutoTDeleteArray<SkTDArray<SortByName*> > sorted(new SkTDArray<SortByName*>[dirCount]);
902     if (!buildTests(tests.get(), sorted.get())) {
903         return;
904     }
905     TestRunner testRunner;
906     int dirNo;
907     gDirs.reset();
908     while ((dirNo = gDirs.next()) > 0) {
909         SkString dirName = get_in_path(dirNo, nullptr);
910         if (!dirName.size()) {
911             continue;
912         }
913         SkOSFile::Iter iter(dirName.c_str(), "skp");
914         SkString filename;
915         while (iter.next(&filename)) {
916             for (size_t index = 0; index < skipOverCount; ++index) {
917                 if (skipOver[index].directory == dirNo
918                         && strcmp(filename.c_str(), skipOver[index].filename) == 0) {
919                     goto checkEarlyExit;
920                 }
921             }
922             {
923                 SortByName name;
924                 name.init(dirNo);
925                 strncpy(name.fFilename, filename.c_str(), filename.size() - 4);  // drop .skp
926                 int count = sorted.get()[dirNo - firstDirNo].count();
927                 if (SkTSearch<SortByName, Less>(sorted.get()[dirNo - firstDirNo].begin(),
928                         count, &name, sizeof(&name)) < 0) {
929                     *testRunner.fRunnables.append() = new TestRunnableFile(
930                             &testSkpClip, dirNo, filename.c_str(), &testRunner);
931                 }
932             }
933     checkEarlyExit:
934             ;
935         }
936 
937     }
938     testRunner.render();
939     SkAutoTDeleteArray<SkTDArray<TestResult> > results(new SkTDArray<TestResult>[dirCount]);
940     if (!buildTests(results.get(), nullptr)) {
941         return;
942     }
943     SkTDArray<TestResult> allResults;
944     for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) {
945         SkTDArray<TestResult>& array = results.get()[dirNo - firstDirNo];
946         allResults.append(array.count(), array.begin());
947     }
948     int allCount = allResults.count();
949     SkTDArray<SortByPixel*> pixels;
950     SkTDArray<SortByTime*> times;
951     for (int index = 0; index < allCount; ++index) {
952         *pixels.append() = (SortByPixel*) &allResults[index];
953         *times.append() = (SortByTime*) &allResults[index];
954     }
955     TestState state;
956     if (pixels.count()) {
957         SkTQSort<SortByPixel>(pixels.begin(), pixels.end() - 1);
958         for (int inner = 0; inner < kMaxFiles; ++inner) {
959             *state.fPixelWorst.append() = *pixels[allCount - inner - 1];
960         }
961     }
962     if (times.count()) {
963         SkTQSort<SortByTime>(times.begin(), times.end() - 1);
964         for (int inner = 0; inner < kMaxFiles; ++inner) {
965             *state.fSlowest.append() = *times[allCount - inner - 1];
966         }
967     }
968     encodeFound(state);
969 }
970 
DEF_TEST(PathOpsSkpClipOneOff)971 DEF_TEST(PathOpsSkpClipOneOff) {
972     const int testIndex = FLAGS_testIndex;
973     int dirNo = gDirs.next();
974     if (dirNo < 0) {
975         dirNo = skipOver[testIndex].directory;
976     }
977     const char* skp = gNames.next();
978     if (!skp) {
979         skp = skipOver[testIndex].filename;
980     }
981     initTest();
982     SkAssertResult(get_in_path(dirNo, skp).size());
983     SkString filename(skp);
984     TestResult state;
985     state.test(dirNo, filename);
986     if (verbose()) {
987         SkDebugf("%s", state.status().c_str());
988     }
989     state.fTestStep = kEncodeFiles;
990     state.testOne();
991 }
992 
DEF_TEST(PathOpsTestSkipped)993 DEF_TEST(PathOpsTestSkipped) {
994     for (size_t index = 0; index < skipOverCount; ++index) {
995         const SkipOverTest& skip = skipOver[index];
996         if (!skip.blamePathOps) {
997             continue;
998         }
999         int dirNo = skip.directory;
1000         const char* skp = skip.filename;
1001         initTest();
1002         SkAssertResult(get_in_path(dirNo, skp).size());
1003         SkString filename(skp);
1004         TestResult state;
1005         state.test(dirNo, filename);
1006         if (verbose()) {
1007             SkDebugf("%s", state.status().c_str());
1008         }
1009         state.fTestStep = kEncodeFiles;
1010         state.testOne();
1011     }
1012 }
1013 
DEF_TEST(PathOpsCopyFails)1014 DEF_TEST(PathOpsCopyFails) {
1015     FLAGS_verbose = true;
1016     for (size_t index = 0; index < skipOverCount; ++index) {
1017         int dirNo = skipOver[index].directory;
1018         SkDebugf("mkdir -p " IN_DIR_PRE "%d" DIR_POST "\n", dirNo);
1019     }
1020     for (size_t index = 0; index < skipOverCount; ++index) {
1021         int dirNo = skipOver[index].directory;
1022         const char* filename = skipOver[index].filename;
1023         SkDebugf("rsync -av cary-linux.cnc:/tera" PATH_SLASH "skps" PATH_SLASH "slave"
1024             "%d" DIR_POST "/%s " IN_DIR_PRE "%d" DIR_POST "\n", dirNo, filename, dirNo);
1025     }
1026 }
1027 
1028 template TestRegistry* TestRegistry::gHead;
1029 
1030 class Iter {
1031 public:
Iter()1032     Iter() { this->reset(); }
reset()1033     void reset() { fReg = TestRegistry::Head(); }
1034 
next()1035     Test* next() {
1036         if (fReg) {
1037             TestRegistry::Factory fact = fReg->factory();
1038             fReg = fReg->next();
1039             Test* test = fact(nullptr);
1040             return test;
1041         }
1042         return nullptr;
1043     }
1044 
1045 private:
1046     const TestRegistry* fReg;
1047 };
1048 
1049 int tool_main(int argc, char** argv);
tool_main(int argc,char ** argv)1050 int tool_main(int argc, char** argv) {
1051     SetupCrashHandler();
1052     SkCommandLineFlags::SetUsage("");
1053     SkCommandLineFlags::Parse(argc, argv);
1054     SkGraphics::Init();
1055     SkString header("PathOps SkpClip:");
1056     if (!FLAGS_match.isEmpty()) {
1057         header.appendf(" --match");
1058         for (int index = 0; index < FLAGS_match.count(); ++index) {
1059             header.appendf(" %s", FLAGS_match[index]);
1060         }
1061     }
1062     if (!FLAGS_dir.isEmpty()) {
1063         int count = FLAGS_dir.count();
1064         for (int i = 0; i < count; ++i) {
1065             const char* range = FLAGS_dir[i];
1066             const char* dash = strchr(range, '-');
1067             if (!dash) {
1068                 dash = strchr(range, ',');
1069             }
1070             int first = atoi(range);
1071             int last = dash ? atoi(dash + 1) : first;
1072             if (!first || !last) {
1073                 SkDebugf("couldn't parse --dir %s\n", range);
1074                 return 1;
1075             }
1076             gDirs.set(first, last);
1077         }
1078     }
1079     if (!FLAGS_skp.isEmpty()) {
1080         gNames.set(FLAGS_skp);
1081     }
1082 #ifdef SK_DEBUG
1083     header.append(" SK_DEBUG");
1084 #else
1085     header.append(" SK_RELEASE");
1086 #endif
1087     if (FLAGS_verbose) {
1088         header.appendf("\n");
1089     }
1090     SkDebugf("%s", header.c_str());
1091     Iter iter;
1092     Test* test;
1093     while ((test = iter.next()) != nullptr) {
1094         SkAutoTDelete<Test> owned(test);
1095         if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, test->getName())) {
1096             test->run();
1097         }
1098     }
1099     return 0;
1100 }
1101 
1102 #if !defined(SK_BUILD_FOR_IOS)
main(int argc,char * const argv[])1103 int main(int argc, char * const argv[]) {
1104     return tool_main(argc, (char**) argv);
1105 }
1106 #endif
1107