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