/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "CrashHandler.h" // #include "OverwriteLine.h" #include "Resources.h" #include "SkBitmap.h" #include "SkCanvas.h" #include "SkColor.h" #include "SkColorPriv.h" #include "SkCommandLineFlags.h" #include "SkDevice.h" #include "SkForceLinking.h" #include "SkGraphics.h" #include "SkImageDecoder.h" #include "SkImageEncoder.h" #include "SkOSFile.h" #include "SkPathOpsDebug.h" #include "SkPicture.h" #include "SkRTConf.h" #include "SkRunnable.h" #include "SkTSort.h" #include "SkStream.h" #include "SkString.h" #include "SkTArray.h" #include "SkTDArray.h" #include "SkTaskGroup.h" #include "SkTemplates.h" #include "SkTime.h" #include __SK_FORCE_IMAGE_DECODER_LINKING; /* add local exceptions here */ /* TODO : add command flag interface */ const struct SkipOverTest { int directory; const char* filename; bool blamePathOps; } skipOver[] = { { 2, "http___www_groupon_sg_.skp", false}, // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y)); { 6, "http___www_googleventures_com_.skp", true}, // addTCoincident SkASSERT(test->fT < 1); { 7, "http___www_foxsports_nl_.skp", true}, // (no repro on mac) addT SkASSERT(this != other || fVerb == SkPath::kCubic_Verb) {13, "http___www_modernqigong_com_.skp", false}, // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y)); {14, "http___www_devbridge_com_.skp", true}, // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple); {16, "http___www_1023world_net_.skp", false}, // bitmap decode assert (corrupt skp?) {19, "http___www_alamdi_com_.skp", true}, // cubic/quad intersection {26, "http___www_liveencounters_net_.skp", true}, // (no repro on mac) checkSmall addT:549 (line, expects cubic) {28, "http___www_encros_fr_.skp", false}, // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y)); {37, "http___www_familysurvivalprotocol_wordpress_com_.skp", true}, // bumpSpan SkASSERT(span->fOppValue >= 0); {39, "http___sufeinet_com_.skp", false}, // bitmap decode assert (corrupt skp?) {41, "http___www_rano360_com_.skp", true}, // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple); {44, "http___www_firstunitedbank_com_.skp", true}, // addTCancel SkASSERT(oIndex > 0); {46, "http___www_shinydemos_com_.skp", true}, // addSimpleAngle SkASSERT(index == count() - 2); {48, "http___www_familysurvivalprotocol_com_.skp", true}, // bumpSpan SkASSERT "span->fOppValue >= 0" {57, "http___www_lptemp_com_.skp", true}, // addTCoincident oPeek = &other->fTs[++oPeekIndex]; {71, "http___www_1milyonkahraman_org_.skp", true}, // addTCoincident SkASSERT(test->fT < 1); {88, "http___www_apuntesdelechuza_wordpress_com_.skp", true}, // bumpSpan SkASSERT "span->fOppValue >= 0" {89, "http___www_mobilizedconsulting_com_.skp", true}, // addTCancel SkASSERT(oIndex > 0); {93, "http___www_simple_living_in_suffolk_co_uk_.skp", true}, // bumpSpan SkASSERT "span->fOppValue >= 0" }; size_t skipOverCount = sizeof(skipOver) / sizeof(skipOver[0]); /* customize file in/out here */ /* TODO : add command flag interface */ #define CHROME_VERSION "1e5dfa4-4a995df" #define SUMMARY_RUN 1 #ifdef SK_BUILD_FOR_WIN #define DRIVE_SPEC "D:" #define PATH_SLASH "\\" #else #define DRIVE_SPEC "" #define PATH_SLASH "/" #endif #define IN_DIR_PRE DRIVE_SPEC PATH_SLASH "skps" PATH_SLASH "slave" #define OUT_DIR_PRE DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "slave" #define OUT_DIR_SUM DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "summary" #define DIR_POST PATH_SLASH "All" PATH_SLASH CHROME_VERSION static const char outOpDir[] = "opClip"; static const char outOldDir[] = "oldClip"; static const char outStatusDir[] = "statusTest"; static SkString get_in_path(int dirNo, const char* filename) { SkString path; SkASSERT(dirNo); path.appendf("%s%d%s", IN_DIR_PRE, dirNo, DIR_POST); if (!sk_exists(path.c_str())) { SkDebugf("could not read %s\n", path.c_str()); return SkString(); } if (filename) { path.appendf("%s%s", PATH_SLASH, filename); if (!sk_exists(path.c_str())) { SkDebugf("could not read %s\n", path.c_str()); return SkString(); } } return path; } static void make_recursive_dir(const SkString& path) { if (sk_exists(path.c_str())) { return; } const char* pathStr = path.c_str(); int last = (int) path.size(); do { while (last > 0 && pathStr[--last] != PATH_SLASH[0]) ; SkASSERT(last > 0); SkString shorter(pathStr, last); if (sk_mkdir(shorter.c_str())) { break; } } while (true); do { while (last < (int) path.size() && pathStr[++last] != PATH_SLASH[0]) ; SkString shorter(pathStr, last); SkAssertResult(sk_mkdir(shorter.c_str())); } while (last < (int) path.size()); } static SkString get_out_path(int dirNo, const char* dirName) { SkString path; SkASSERT(dirNo); SkASSERT(dirName); path.appendf("%s%d%s%s%s", OUT_DIR_PRE, dirNo, DIR_POST, PATH_SLASH, dirName); make_recursive_dir(path); return path; } static SkString get_sum_path(const char* dirName) { SkString path; SkASSERT(dirName); path.appendf("%s%d%s%s", OUT_DIR_SUM, SUMMARY_RUN, PATH_SLASH, dirName); SkDebugf("%s\n", path.c_str()); make_recursive_dir(path); return path; } static SkString make_png_name(const char* filename) { SkString pngName = SkString(filename); pngName.remove(pngName.size() - 3, 3); pngName.append("png"); return pngName; } //////////////////////////////////////////////////////// enum TestStep { kCompareBits, kEncodeFiles, }; enum { kMaxLength = 256, kMaxFiles = 128, kSmallLimit = 1000, }; struct TestResult { void init(int dirNo) { fDirNo = dirNo; sk_bzero(fFilename, sizeof(fFilename)); fTestStep = kCompareBits; fScale = 1; } void init(int dirNo, const SkString& filename) { fDirNo = dirNo; strcpy(fFilename, filename.c_str()); fTestStep = kCompareBits; fScale = 1; } SkString status() { SkString outStr; outStr.printf("%s %d %d\n", fFilename, fPixelError, fTime); return outStr; } SkString progress() { SkString outStr; outStr.printf("dir=%d %s ", fDirNo, fFilename); if (fPixelError) { outStr.appendf(" err=%d", fPixelError); } if (fTime) { outStr.appendf(" time=%d", fTime); } if (fScale != 1) { outStr.appendf(" scale=%d", fScale); } outStr.appendf("\n"); return outStr; } void test(int dirNo, const SkString& filename) { init(dirNo); strcpy(fFilename, filename.c_str()); testOne(); } void testOne(); char fFilename[kMaxLength]; TestStep fTestStep; int fDirNo; int fPixelError; int fTime; int fScale; }; class SortByPixel : public TestResult { public: bool operator<(const SortByPixel& rh) const { return fPixelError < rh.fPixelError; } }; class SortByTime : public TestResult { public: bool operator<(const SortByTime& rh) const { return fTime < rh.fTime; } }; class SortByName : public TestResult { public: bool operator<(const SortByName& rh) const { return strcmp(fFilename, rh.fFilename) < 0; } }; struct TestState { void init(int dirNo) { fResult.init(dirNo); } SkTDArray fPixelWorst; SkTDArray fSlowest; TestResult fResult; }; struct TestRunner { ~TestRunner(); void render(); SkTDArray fRunnables; }; class TestRunnable : public SkRunnable { public: void run() override { SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024); (*fTestFun)(&fState); } TestState fState; void (*fTestFun)(TestState*); }; class TestRunnableDir : public TestRunnable { public: TestRunnableDir(void (*testFun)(TestState*), int dirNo, TestRunner* runner) { fState.init(dirNo); fTestFun = testFun; } }; class TestRunnableFile : public TestRunnable { public: TestRunnableFile(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) { fState.init(dirNo); strcpy(fState.fResult.fFilename, name); fTestFun = testFun; } }; class TestRunnableEncode : public TestRunnableFile { public: TestRunnableEncode(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) : TestRunnableFile(testFun, dirNo, name, runner) { fState.fResult.fTestStep = kEncodeFiles; } }; TestRunner::~TestRunner() { for (int index = 0; index < fRunnables.count(); index++) { delete fRunnables[index]; } } void TestRunner::render() { // TODO: this doesn't really need to use SkRunnables any more. // We can just write the code to run in the for-loop directly. sk_parallel_for(fRunnables.count(), [&](int i) { fRunnables[i]->run(); }); } //////////////////////////////////////////////// static int similarBits(const SkBitmap& gr, const SkBitmap& sk) { const int kRowCount = 3; const int kThreshold = 3; int width = SkTMin(gr.width(), sk.width()); if (width < kRowCount) { return true; } int height = SkTMin(gr.height(), sk.height()); if (height < kRowCount) { return true; } int errorTotal = 0; SkTArray errorRows; errorRows.push_back_n(width * kRowCount); SkAutoLockPixels autoGr(gr); SkAutoLockPixels autoSk(sk); for (int y = 0; y < height; ++y) { SkPMColor* grRow = gr.getAddr32(0, y); SkPMColor* skRow = sk.getAddr32(0, y); int* base = &errorRows[0]; int* cOut = &errorRows[y % kRowCount]; for (int x = 0; x < width; ++x) { SkPMColor grColor = grRow[x]; SkPMColor skColor = skRow[x]; int dr = SkGetPackedR32(grColor) - SkGetPackedR32(skColor); int dg = SkGetPackedG32(grColor) - SkGetPackedG32(skColor); int db = SkGetPackedB32(grColor) - SkGetPackedB32(skColor); int error = cOut[x] = SkTMax(SkAbs32(dr), SkTMax(SkAbs32(dg), SkAbs32(db))); if (error < kThreshold || x < 2) { continue; } if (base[x - 2] < kThreshold || base[width + x - 2] < kThreshold || base[width * 2 + x - 2] < kThreshold || base[x - 1] < kThreshold || base[width + x - 1] < kThreshold || base[width * 2 + x - 1] < kThreshold || base[x] < kThreshold || base[width + x] < kThreshold || base[width * 2 + x] < kThreshold) { continue; } errorTotal += error; } } return errorTotal; } static bool addError(TestState* data, const TestResult& testResult) { if (testResult.fPixelError <= 0 && testResult.fTime <= 0) { return false; } int worstCount = data->fPixelWorst.count(); int pixelError = testResult.fPixelError; if (pixelError > 0) { for (int index = 0; index < worstCount; ++index) { if (pixelError > data->fPixelWorst[index].fPixelError) { data->fPixelWorst[index] = *(SortByPixel*) &testResult; return true; } } } int slowCount = data->fSlowest.count(); int time = testResult.fTime; if (time > 0) { for (int index = 0; index < slowCount; ++index) { if (time > data->fSlowest[index].fTime) { data->fSlowest[index] = *(SortByTime*) &testResult; return true; } } } if (pixelError > 0 && worstCount < kMaxFiles) { *data->fPixelWorst.append() = *(SortByPixel*) &testResult; return true; } if (time > 0 && slowCount < kMaxFiles) { *data->fSlowest.append() = *(SortByTime*) &testResult; return true; } return false; } static SkMSec timePict(SkPicture* pic, SkCanvas* canvas) { canvas->save(); SkScalar pWidth = pic->cullRect().width(); SkScalar pHeight = pic->cullRect().height(); const SkScalar maxDimension = 1000.0f; const int slices = 3; SkScalar xInterval = SkTMax(pWidth - maxDimension, 0.0f) / (slices - 1); SkScalar yInterval = SkTMax(pHeight - maxDimension, 0.0f) / (slices - 1); SkRect rect = {0, 0, SkTMin(maxDimension, pWidth), SkTMin(maxDimension, pHeight) }; canvas->clipRect(rect); SkMSec start = SkTime::GetMSecs(); for (int x = 0; x < slices; ++x) { for (int y = 0; y < slices; ++y) { pic->playback(canvas); canvas->translate(0, yInterval); } canvas->translate(xInterval, -yInterval * slices); } SkMSec end = SkTime::GetMSecs(); canvas->restore(); return end - start; } static void drawPict(SkPicture* pic, SkCanvas* canvas, int scale) { canvas->clear(SK_ColorWHITE); if (scale != 1) { canvas->save(); canvas->scale(1.0f / scale, 1.0f / scale); } pic->playback(canvas); if (scale != 1) { canvas->restore(); } } static void writePict(const SkBitmap& bitmap, const char* outDir, const char* pngName) { SkString outFile = get_sum_path(outDir); outFile.appendf("%s%s", PATH_SLASH, pngName); if (!SkImageEncoder::EncodeFile(outFile.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100)) { SkDebugf("unable to encode gr %s (width=%d height=%d)\n", pngName, bitmap.width(), bitmap.height()); } } void TestResult::testOne() { SkPicture* pic = nullptr; { #if DEBUG_SHOW_TEST_NAME if (fTestStep == kCompareBits) { SkString testName(fFilename); const char http[] = "http"; if (testName.startsWith(http)) { testName.remove(0, sizeof(http) - 1); } while (testName.startsWith("_")) { testName.remove(0, 1); } const char dotSkp[] = ".skp"; if (testName.endsWith(dotSkp)) { size_t len = testName.size(); testName.remove(len - (sizeof(dotSkp) - 1), sizeof(dotSkp) - 1); } testName.prepend("skp"); testName.append("1"); strncpy(DEBUG_FILENAME_STRING, testName.c_str(), DEBUG_FILENAME_STRING_LENGTH); } else if (fTestStep == kEncodeFiles) { strncpy(DEBUG_FILENAME_STRING, "", DEBUG_FILENAME_STRING_LENGTH); } #endif SkString path = get_in_path(fDirNo, fFilename); SkFILEStream stream(path.c_str()); if (!stream.isValid()) { SkDebugf("invalid stream %s\n", path.c_str()); goto finish; } pic = SkPicture::CreateFromStream(&stream, &SkImageDecoder::DecodeMemory); if (!pic) { SkDebugf("unable to decode %s\n", fFilename); goto finish; } SkScalar width = pic->cullRect().width(); SkScalar height = pic->cullRect().height(); SkBitmap oldBitmap, opBitmap; fScale = 1; while (width / fScale > 32767 || height / fScale > 32767) { ++fScale; } do { int dimX = SkScalarCeilToInt(width / fScale); int dimY = SkScalarCeilToInt(height / fScale); if (oldBitmap.tryAllocN32Pixels(dimX, dimY) && opBitmap.tryAllocN32Pixels(dimX, dimY)) { break; } SkDebugf("-%d-", fScale); } while (++fScale < 256); if (fScale >= 256) { SkDebugf("unable to allocate bitmap for %s (w=%f h=%f)\n", fFilename, width, height); goto finish; } oldBitmap.eraseColor(SK_ColorWHITE); SkCanvas oldCanvas(oldBitmap); oldCanvas.setAllowSimplifyClip(false); opBitmap.eraseColor(SK_ColorWHITE); SkCanvas opCanvas(opBitmap); opCanvas.setAllowSimplifyClip(true); drawPict(pic, &oldCanvas, fScale); drawPict(pic, &opCanvas, fScale); if (fTestStep == kCompareBits) { fPixelError = similarBits(oldBitmap, opBitmap); int oldTime = timePict(pic, &oldCanvas); int opTime = timePict(pic, &opCanvas); fTime = SkTMax(0, oldTime - opTime); } else if (fTestStep == kEncodeFiles) { SkString pngStr = make_png_name(fFilename); const char* pngName = pngStr.c_str(); writePict(oldBitmap, outOldDir, pngName); writePict(opBitmap, outOpDir, pngName); } } finish: if (pic) { pic->unref(); } } DEFINE_string2(match, m, "PathOpsSkpClipThreaded", "[~][^]substring[$] [...] of test name to run.\n" "Multiple matches may be separated by spaces.\n" "~ causes a matching test to always be skipped\n" "^ requires the start of the test to match\n" "$ requires the end of the test to match\n" "^ and $ requires an exact match\n" "If a test does not match any list entry,\n" "it is skipped unless some list entry starts with ~"); DEFINE_string2(dir, d, nullptr, "range of directories (e.g., 1-100)"); DEFINE_string2(skp, s, nullptr, "skp to test"); DEFINE_bool2(single, z, false, "run tests on a single thread internally."); DEFINE_int32(testIndex, 0, "override local test index (PathOpsSkpClipOneOff only)."); DEFINE_bool2(verbose, v, false, "enable verbose output."); static bool verbose() { return FLAGS_verbose; } class Dirs { public: Dirs() { reset(); sk_bzero(fRun, sizeof(fRun)); fSet = false; } int first() const { int index = 0; while (++index < kMaxDir) { if (fRun[index]) { return index; } } SkASSERT(0); return -1; } int last() const { int index = kMaxDir; while (--index > 0 && !fRun[index]) ; return index; } int next() { while (++fIndex < kMaxDir) { if (fRun[fIndex]) { return fIndex; } } return -1; } void reset() { fIndex = -1; } void set(int start, int end) { while (start < end) { fRun[start++] = 1; } fSet = true; } void setDefault() { if (!fSet) { set(1, 100); } } private: enum { kMaxDir = 101 }; char fRun[kMaxDir]; int fIndex; bool fSet; } gDirs; class Filenames { public: Filenames() : fIndex(-1) { } const char* next() { while (fNames && ++fIndex < fNames->count()) { return (*fNames)[fIndex]; } return nullptr; } void set(const SkCommandLineFlags::StringArray& names) { fNames = &names; } private: int fIndex; const SkCommandLineFlags::StringArray* fNames; } gNames; static bool buildTestDir(int dirNo, int firstDirNo, SkTDArray* tests, SkTDArray* sorted) { SkString dirName = get_out_path(dirNo, outStatusDir); if (!dirName.size()) { return false; } SkOSFile::Iter iter(dirName.c_str(), "skp"); SkString filename; while (iter.next(&filename)) { TestResult test; test.init(dirNo); SkString spaceFile(filename); char* spaces = spaceFile.writable_str(); int spaceSize = (int) spaceFile.size(); for (int index = 0; index < spaceSize; ++index) { if (spaces[index] == '.') { spaces[index] = ' '; } } int success = sscanf(spaces, "%s %d %d skp", test.fFilename, &test.fPixelError, &test.fTime); if (success < 3) { SkDebugf("failed to scan %s matched=%d\n", filename.c_str(), success); return false; } *tests[dirNo - firstDirNo].append() = test; } if (!sorted) { return true; } SkTDArray& testSet = tests[dirNo - firstDirNo]; int count = testSet.count(); for (int index = 0; index < count; ++index) { *sorted[dirNo - firstDirNo].append() = (SortByName*) &testSet[index]; } if (sorted[dirNo - firstDirNo].count()) { SkTQSort(sorted[dirNo - firstDirNo].begin(), sorted[dirNo - firstDirNo].end() - 1); if (verbose()) { SkDebugf("+"); } } return true; } static void testSkpClip(TestState* data) { data->fResult.testOne(); SkString statName(data->fResult.fFilename); SkASSERT(statName.endsWith(".skp")); statName.remove(statName.size() - 4, 4); statName.appendf(".%d.%d.skp", data->fResult.fPixelError, data->fResult.fTime); SkString statusFile = get_out_path(data->fResult.fDirNo, outStatusDir); if (!statusFile.size()) { SkDebugf("failed to create %s", statusFile.c_str()); return; } statusFile.appendf("%s%s", PATH_SLASH, statName.c_str()); SkFILE* file = sk_fopen(statusFile.c_str(), kWrite_SkFILE_Flag); if (!file) { SkDebugf("failed to create %s", statusFile.c_str()); return; } sk_fclose(file); if (verbose()) { if (data->fResult.fPixelError || data->fResult.fTime) { SkDebugf("%s", data->fResult.progress().c_str()); } else { SkDebugf("."); } } } bool Less(const SortByName& a, const SortByName& b); bool Less(const SortByName& a, const SortByName& b) { return a < b; } static bool doOneDir(TestState* state, bool threaded) { int dirNo = state->fResult.fDirNo; SkString dirName = get_in_path(dirNo, nullptr); if (!dirName.size()) { return false; } SkTDArray tests[1]; SkTDArray sorted[1]; if (!buildTestDir(dirNo, dirNo, tests, sorted)) { return false; } SkOSFile::Iter iter(dirName.c_str(), "skp"); SkString filename; while (iter.next(&filename)) { for (size_t index = 0; index < skipOverCount; ++index) { if (skipOver[index].directory == dirNo && strcmp(filename.c_str(), skipOver[index].filename) == 0) { goto checkEarlyExit; } } { SortByName name; name.init(dirNo); strncpy(name.fFilename, filename.c_str(), filename.size() - 4); // drop .skp int count = sorted[0].count(); int idx = SkTSearch(sorted[0].begin(), count, &name, sizeof(&name)); if (idx >= 0) { SortByName* found = sorted[0][idx]; (void) addError(state, *found); continue; } TestResult test; test.init(dirNo, filename); state->fResult = test; testSkpClip(state); #if 0 // artificially limit to a few while debugging code static int debugLimit = 0; if (++debugLimit == 5) { return true; } #endif } checkEarlyExit: ; } return true; } static void initTest() { #if !defined SK_BUILD_FOR_WIN && !defined SK_BUILD_FOR_MAC SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true); SK_CONF_SET("images.png.suppressDecoderWarnings", true); #endif } static void testSkpClipEncode(TestState* data) { data->fResult.testOne(); if (verbose()) { SkDebugf("+"); } } static void encodeFound(TestState& state) { if (verbose()) { if (state.fPixelWorst.count()) { SkTDArray worst; for (int index = 0; index < state.fPixelWorst.count(); ++index) { *worst.append() = &state.fPixelWorst[index]; } SkTQSort(worst.begin(), worst.end() - 1); for (int index = 0; index < state.fPixelWorst.count(); ++index) { const TestResult& result = *worst[index]; SkDebugf("%d %s pixelError=%d\n", result.fDirNo, result.fFilename, result.fPixelError); } } if (state.fSlowest.count()) { SkTDArray slowest; for (int index = 0; index < state.fSlowest.count(); ++index) { *slowest.append() = &state.fSlowest[index]; } if (slowest.count() > 0) { SkTQSort(slowest.begin(), slowest.end() - 1); for (int index = 0; index < slowest.count(); ++index) { const TestResult& result = *slowest[index]; SkDebugf("%d %s time=%d\n", result.fDirNo, result.fFilename, result.fTime); } } } } TestRunner testRunner; for (int index = 0; index < state.fPixelWorst.count(); ++index) { const TestResult& result = state.fPixelWorst[index]; SkString filename(result.fFilename); if (!filename.endsWith(".skp")) { filename.append(".skp"); } *testRunner.fRunnables.append() = new TestRunnableEncode(&testSkpClipEncode, result.fDirNo, filename.c_str(), &testRunner); } testRunner.render(); } class Test { public: Test() {} virtual ~Test() {} const char* getName() { onGetName(&fName); return fName.c_str(); } void run() { onRun(); } protected: virtual void onGetName(SkString*) = 0; virtual void onRun() = 0; private: SkString fName; }; typedef SkTRegistry TestRegistry; #define DEF_TEST(name) \ static void test_##name(); \ class name##Class : public Test { \ public: \ static Test* Factory(void*) { return new name##Class; } \ \ protected: \ void onGetName(SkString* name) override { name->set(#name); } \ void onRun() override { test_##name(); } \ }; \ static TestRegistry gReg_##name##Class(name##Class::Factory); \ static void test_##name() DEF_TEST(PathOpsSkpClip) { gDirs.setDefault(); initTest(); SkTArray errors; TestState state; state.init(0); int dirNo; gDirs.reset(); while ((dirNo = gDirs.next()) > 0) { if (verbose()) { SkDebugf("dirNo=%d\n", dirNo); } state.fResult.fDirNo = dirNo; if (!doOneDir(&state, false)) { break; } } encodeFound(state); } static void testSkpClipMain(TestState* data) { (void) doOneDir(data, true); } DEF_TEST(PathOpsSkpClipThreaded) { gDirs.setDefault(); initTest(); TestRunner testRunner; int dirNo; gDirs.reset(); while ((dirNo = gDirs.next()) > 0) { *testRunner.fRunnables.append() = new TestRunnableDir(&testSkpClipMain, dirNo, &testRunner); } testRunner.render(); TestState state; state.init(0); gDirs.reset(); while ((dirNo = gDirs.next()) > 0) { TestState& testState = testRunner.fRunnables[dirNo - 1]->fState; SkASSERT(testState.fResult.fDirNo == dirNo); for (int inner = 0; inner < testState.fPixelWorst.count(); ++inner) { addError(&state, testState.fPixelWorst[inner]); } for (int inner = 0; inner < testState.fSlowest.count(); ++inner) { addError(&state, testState.fSlowest[inner]); } } encodeFound(state); } static bool buildTests(SkTDArray* tests, SkTDArray* sorted) { int firstDirNo = gDirs.first(); int dirNo; while ((dirNo = gDirs.next()) > 0) { if (!buildTestDir(dirNo, firstDirNo, tests, sorted)) { return false; } } return true; } DEF_TEST(PathOpsSkpClipUberThreaded) { gDirs.setDefault(); const int firstDirNo = gDirs.next(); const int lastDirNo = gDirs.last(); initTest(); int dirCount = lastDirNo - firstDirNo + 1; SkAutoTDeleteArray > tests(new SkTDArray[dirCount]); SkAutoTDeleteArray > sorted(new SkTDArray[dirCount]); if (!buildTests(tests.get(), sorted.get())) { return; } TestRunner testRunner; int dirNo; gDirs.reset(); while ((dirNo = gDirs.next()) > 0) { SkString dirName = get_in_path(dirNo, nullptr); if (!dirName.size()) { continue; } SkOSFile::Iter iter(dirName.c_str(), "skp"); SkString filename; while (iter.next(&filename)) { for (size_t index = 0; index < skipOverCount; ++index) { if (skipOver[index].directory == dirNo && strcmp(filename.c_str(), skipOver[index].filename) == 0) { goto checkEarlyExit; } } { SortByName name; name.init(dirNo); strncpy(name.fFilename, filename.c_str(), filename.size() - 4); // drop .skp int count = sorted.get()[dirNo - firstDirNo].count(); if (SkTSearch(sorted.get()[dirNo - firstDirNo].begin(), count, &name, sizeof(&name)) < 0) { *testRunner.fRunnables.append() = new TestRunnableFile( &testSkpClip, dirNo, filename.c_str(), &testRunner); } } checkEarlyExit: ; } } testRunner.render(); SkAutoTDeleteArray > results(new SkTDArray[dirCount]); if (!buildTests(results.get(), nullptr)) { return; } SkTDArray allResults; for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) { SkTDArray& array = results.get()[dirNo - firstDirNo]; allResults.append(array.count(), array.begin()); } int allCount = allResults.count(); SkTDArray pixels; SkTDArray times; for (int index = 0; index < allCount; ++index) { *pixels.append() = (SortByPixel*) &allResults[index]; *times.append() = (SortByTime*) &allResults[index]; } TestState state; if (pixels.count()) { SkTQSort(pixels.begin(), pixels.end() - 1); for (int inner = 0; inner < kMaxFiles; ++inner) { *state.fPixelWorst.append() = *pixels[allCount - inner - 1]; } } if (times.count()) { SkTQSort(times.begin(), times.end() - 1); for (int inner = 0; inner < kMaxFiles; ++inner) { *state.fSlowest.append() = *times[allCount - inner - 1]; } } encodeFound(state); } DEF_TEST(PathOpsSkpClipOneOff) { const int testIndex = FLAGS_testIndex; int dirNo = gDirs.next(); if (dirNo < 0) { dirNo = skipOver[testIndex].directory; } const char* skp = gNames.next(); if (!skp) { skp = skipOver[testIndex].filename; } initTest(); SkAssertResult(get_in_path(dirNo, skp).size()); SkString filename(skp); TestResult state; state.test(dirNo, filename); if (verbose()) { SkDebugf("%s", state.status().c_str()); } state.fTestStep = kEncodeFiles; state.testOne(); } DEF_TEST(PathOpsTestSkipped) { for (size_t index = 0; index < skipOverCount; ++index) { const SkipOverTest& skip = skipOver[index]; if (!skip.blamePathOps) { continue; } int dirNo = skip.directory; const char* skp = skip.filename; initTest(); SkAssertResult(get_in_path(dirNo, skp).size()); SkString filename(skp); TestResult state; state.test(dirNo, filename); if (verbose()) { SkDebugf("%s", state.status().c_str()); } state.fTestStep = kEncodeFiles; state.testOne(); } } DEF_TEST(PathOpsCopyFails) { FLAGS_verbose = true; for (size_t index = 0; index < skipOverCount; ++index) { int dirNo = skipOver[index].directory; SkDebugf("mkdir -p " IN_DIR_PRE "%d" DIR_POST "\n", dirNo); } for (size_t index = 0; index < skipOverCount; ++index) { int dirNo = skipOver[index].directory; const char* filename = skipOver[index].filename; SkDebugf("rsync -av cary-linux.cnc:/tera" PATH_SLASH "skps" PATH_SLASH "slave" "%d" DIR_POST "/%s " IN_DIR_PRE "%d" DIR_POST "\n", dirNo, filename, dirNo); } } template TestRegistry* TestRegistry::gHead; class Iter { public: Iter() { this->reset(); } void reset() { fReg = TestRegistry::Head(); } Test* next() { if (fReg) { TestRegistry::Factory fact = fReg->factory(); fReg = fReg->next(); Test* test = fact(nullptr); return test; } return nullptr; } private: const TestRegistry* fReg; }; int tool_main(int argc, char** argv); int tool_main(int argc, char** argv) { SetupCrashHandler(); SkCommandLineFlags::SetUsage(""); SkCommandLineFlags::Parse(argc, argv); SkGraphics::Init(); SkString header("PathOps SkpClip:"); if (!FLAGS_match.isEmpty()) { header.appendf(" --match"); for (int index = 0; index < FLAGS_match.count(); ++index) { header.appendf(" %s", FLAGS_match[index]); } } if (!FLAGS_dir.isEmpty()) { int count = FLAGS_dir.count(); for (int i = 0; i < count; ++i) { const char* range = FLAGS_dir[i]; const char* dash = strchr(range, '-'); if (!dash) { dash = strchr(range, ','); } int first = atoi(range); int last = dash ? atoi(dash + 1) : first; if (!first || !last) { SkDebugf("couldn't parse --dir %s\n", range); return 1; } gDirs.set(first, last); } } if (!FLAGS_skp.isEmpty()) { gNames.set(FLAGS_skp); } #ifdef SK_DEBUG header.append(" SK_DEBUG"); #else header.append(" SK_RELEASE"); #endif if (FLAGS_verbose) { header.appendf("\n"); } SkDebugf("%s", header.c_str()); Iter iter; Test* test; while ((test = iter.next()) != nullptr) { SkAutoTDelete owned(test); if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, test->getName())) { test->run(); } } SkGraphics::Term(); return 0; } #if !defined(SK_BUILD_FOR_IOS) int main(int argc, char * const argv[]) { return tool_main(argc, (char**) argv); } #endif