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