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