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