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