| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <atomic> |
| #include "include/core/SkGraphics.h" |
| #include "include/core/SkTime.h" |
| #include "include/private/SkTArray.h" |
| #include "include/private/SkTemplates.h" |
| #include "src/core/SkOSFile.h" |
| #include "src/core/SkTaskGroup.h" |
| #include "src/pathops/SkPathOpsDebug.h" |
| #include "tests/PathOpsDebug.h" |
| #include "tests/Test.h" |
| #include "tools/CrashHandler.h" |
| #include "tools/OverwriteLine.h" |
| #include "tools/Resources.h" |
| #include "tools/flags/CommandLineFlags.h" |
| #include "tools/gpu/GrContextFactory.h" |
| |
| using namespace skiatest; |
| using namespace sk_gpu_test; |
| |
| static DEFINE_bool2(dumpOp, d, false, "dump the pathOps to a file to recover mid-crash."); |
| static DEFINE_bool2(extendedTest, x, false, "run extended tests for pathOps."); |
| static DEFINE_bool2(runFail, f, false, "check for success on tests known to fail."); |
| static DEFINE_bool2(verifyOp, y, false, "compare the pathOps result against a region."); |
| static DEFINE_string2(json, J, "", "write json version of tests."); |
| static DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver."); |
| static DEFINE_bool2(veryVerbose, V, false, "tell individual tests to be verbose."); |
| static DEFINE_bool(cpu, true, "Run CPU-bound work?"); |
| static DEFINE_bool(gpu, true, "Run GPU-bound work?"); |
| static DEFINE_bool(graphite, true, "Run Graphite work?"); |
| |
| static DEFINE_string2(match, m, nullptr, |
| "[~][^]substring[$] [...] of name to run.\n" |
| "Multiple matches may be separated by spaces.\n" |
| "~ causes a matching name to always be skipped\n" |
| "^ requires the start of the name to match\n" |
| "$ requires the end of the name to match\n" |
| "^ and $ requires an exact match\n" |
| "If a name does not match any list entry,\n" |
| "it is skipped unless some list entry starts with ~"); |
| |
| static DEFINE_int_2(threads, j, -1, |
| "Run threadsafe tests on a threadpool with this many extra threads, " |
| "defaulting to one extra thread per core."); |
| |
| #if DEBUG_COIN |
| static DEFINE_bool2(coinTest, c, false, "detect unused coincidence algorithms."); |
| #endif |
| |
| // need to explicitly declare this, or we get some weird infinite loop llist |
| template TestRegistry* TestRegistry::gHead; |
| void (*gVerboseFinalize)() = nullptr; |
| |
| // The threads report back to this object when they are done. |
| class Status { |
| public: |
| explicit Status(int total) |
| : fDone(0), fTestCount(0), fFailCount(0), fTotal(total) {} |
| // Threadsafe. |
| void endTest(const char* testName, |
| bool success, |
| SkMSec elapsed, |
| int testCount) { |
| const int done = ++fDone; |
| fTestCount += testCount; |
| if (!success) { |
| SkDebugf("\n---- %s FAILED", testName); |
| } |
| |
| SkString prefix(kSkOverwriteLine); |
| SkString time; |
| if (FLAGS_verbose) { |
| prefix.printf("\n"); |
| time.printf("%5dms ", elapsed); |
| } |
| SkDebugf("%s[%3d/%3d] %s%s", prefix.c_str(), done, fTotal, time.c_str(), |
| testName); |
| } |
| |
| void reportFailure() { fFailCount++; } |
| |
| int32_t testCount() { return fTestCount; } |
| int32_t failCount() { return fFailCount; } |
| |
| private: |
| std::atomic<int32_t> fDone; |
| std::atomic<int32_t> fTestCount; |
| std::atomic<int32_t> fFailCount; |
| const int fTotal; |
| }; |
| |
| class SkTestRunnable { |
| public: |
| SkTestRunnable(const Test& test, Status* status) : fTest(test), fStatus(status) {} |
| |
| void operator()() { |
| struct TestReporter : public skiatest::Reporter { |
| public: |
| TestReporter() : fStats(nullptr), fError(false), fTestCount(0) {} |
| void bumpTestCount() override { ++fTestCount; } |
| bool allowExtendedTest() const override { return FLAGS_extendedTest; } |
| bool verbose() const override { return FLAGS_veryVerbose; } |
| void reportFailed(const skiatest::Failure& failure) override { |
| SkDebugf("\nFAILED: %s", failure.toString().c_str()); |
| fError = true; |
| } |
| void* stats() const override { return fStats; } |
| void* fStats; |
| bool fError; |
| int fTestCount; |
| } reporter; |
| |
| const Timer timer; |
| fTest.fProc(&reporter, GrContextOptions()); |
| SkMSec elapsed = timer.elapsedMsInt(); |
| if (reporter.fError) { |
| fStatus->reportFailure(); |
| } |
| fStatus->endTest(fTest.fName, !reporter.fError, elapsed, reporter.fTestCount); |
| } |
| |
| private: |
| Test fTest; |
| Status* fStatus; |
| }; |
| |
| static bool should_run(const char* testName, bool isGPUTest, bool isGraphiteTest) { |
| if (CommandLineFlags::ShouldSkip(FLAGS_match, testName)) { |
| return false; |
| } |
| if (!FLAGS_cpu && !isGPUTest && !isGraphiteTest) { |
| return false; |
| } |
| if (!FLAGS_gpu && isGPUTest) { |
| return false; |
| } |
| if (!FLAGS_graphite && isGraphiteTest) { |
| return false; |
| } |
| return true; |
| } |
| |
| int main(int argc, char** argv) { |
| CommandLineFlags::Parse(argc, argv); |
| #if DEBUG_DUMP_VERIFY |
| SkPathOpsDebug::gDumpOp = FLAGS_dumpOp; |
| SkPathOpsDebug::gVerifyOp = FLAGS_verifyOp; |
| #endif |
| SkPathOpsDebug::gRunFail = FLAGS_runFail; |
| SkPathOpsDebug::gVeryVerbose = FLAGS_veryVerbose; |
| PathOpsDebug::gOutFirst = true; |
| PathOpsDebug::gCheckForDuplicateNames = false; |
| PathOpsDebug::gOutputSVG = false; |
| if ((PathOpsDebug::gJson = !FLAGS_json.isEmpty())) { |
| PathOpsDebug::gOut = fopen(FLAGS_json[0], "wb"); |
| fprintf(PathOpsDebug::gOut, "{\n"); |
| FLAGS_threads = 0; |
| PathOpsDebug::gMarkJsonFlaky = false; |
| } |
| SetupCrashHandler(); |
| |
| SkAutoGraphics ag; |
| |
| { |
| SkString header("Skia UnitTests:"); |
| if (!FLAGS_match.isEmpty()) { |
| header.appendf(" --match"); |
| for (int index = 0; index < FLAGS_match.count(); ++index) { |
| header.appendf(" %s", FLAGS_match[index]); |
| } |
| } |
| SkString tmpDir = skiatest::GetTmpDir(); |
| if (!tmpDir.isEmpty()) { |
| header.appendf(" --tmpDir %s", tmpDir.c_str()); |
| } |
| SkString resourcePath = GetResourcePath(); |
| if (!resourcePath.isEmpty()) { |
| header.appendf(" --resourcePath %s", resourcePath.c_str()); |
| } |
| #if DEBUG_COIN |
| if (FLAGS_coinTest) { |
| header.appendf(" -c"); |
| } |
| #endif |
| if (FLAGS_dumpOp) { |
| header.appendf(" -d"); |
| } |
| #ifdef SK_DEBUG |
| if (FLAGS_runFail) { |
| header.appendf(" -f"); |
| } |
| #endif |
| if (FLAGS_verbose) { |
| header.appendf(" -v"); |
| } |
| if (FLAGS_veryVerbose) { |
| header.appendf(" -V"); |
| } |
| if (FLAGS_extendedTest) { |
| header.appendf(" -x"); |
| } |
| if (FLAGS_verifyOp) { |
| header.appendf(" -y"); |
| } |
| #ifdef SK_DEBUG |
| header.append(" SK_DEBUG"); |
| #else |
| header.append(" SK_RELEASE"); |
| #endif |
| if (FLAGS_veryVerbose) { |
| header.appendf("\n"); |
| } |
| SkDebugf("%s", header.c_str()); |
| } |
| |
| |
| // Count tests first. |
| int total = 0; |
| int toRun = 0; |
| |
| for (const Test& test : TestRegistry::Range()) { |
| if (should_run(test.fName, test.fNeedsGpu, test.fNeedsGraphite)) { |
| toRun++; |
| } |
| total++; |
| } |
| |
| // Now run them. |
| int skipCount = 0; |
| |
| SkTaskGroup::Enabler enabled(FLAGS_threads); |
| SkTaskGroup cpuTests; |
| SkTArray<const Test*> gpuTests; |
| |
| Status status(toRun); |
| |
| for (const Test& test : TestRegistry::Range()) { |
| if (!should_run(test.fName, test.fNeedsGpu, test.fNeedsGraphite)) { |
| ++skipCount; |
| } else if (test.fNeedsGpu || test.fNeedsGraphite) { |
| gpuTests.push_back(&test); |
| } else { |
| cpuTests.add(SkTestRunnable(test, &status)); |
| } |
| } |
| |
| // Run GPU tests on this thread. |
| for (int i = 0; i < gpuTests.count(); i++) { |
| SkTestRunnable(*gpuTests[i], &status)(); |
| } |
| |
| // Block until threaded tests finish. |
| cpuTests.wait(); |
| |
| if (FLAGS_verbose) { |
| SkDebugf( |
| "\nFinished %d tests, %d failures, %d skipped. " |
| "(%d internal tests)", |
| toRun, status.failCount(), skipCount, status.testCount()); |
| if (gVerboseFinalize) { |
| (*gVerboseFinalize)(); |
| } |
| } |
| |
| SkDebugf("\n"); |
| #if DEBUG_COIN |
| if (FLAGS_coinTest) { |
| SkPathOpsDebug::DumpCoinDict(); |
| } |
| #endif |
| if (PathOpsDebug::gJson) { |
| fprintf(PathOpsDebug::gOut, "\n}\n"); |
| fclose(PathOpsDebug::gOut); |
| } |
| return (status.failCount() == 0) ? 0 : 1; |
| } |