Let DM run unit tests.
  - refactor GYPs and a few flags
  - make GPU tests grab a thread-local GrContextFactory when needed as we do in DM for GMs
  - add a few more UI features to make DM more like tests

I believe this makes the program 'tests' obsolete.

It should be somewhat faster to run the two sets together than running the old binaries serially:
  - serial: tests 20s (3m18s CPU), dm 21s (3m01s CPU)
  - together: 27s (6m21s CPU)

Next up is to incorporate benches.  I'm only planning there on a single-pass sanity check, so that won't obsolete the program 'bench' just yet.

Tested: out/Debug/tests && out/Debug/dm && echo ok
BUG=skia:

Committed: http://code.google.com/p/skia/source/detail?r=13586

R=reed@google.com, bsalomon@google.com, mtklein@google.com, tfarina@chromium.org

Author: mtklein@chromium.org

Review URL: https://codereview.chromium.org/178273002

git-svn-id: http://skia.googlecode.com/svn/trunk@13592 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 8a4a382..ee53e9d 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -7,26 +7,29 @@
 #include "SkForceLinking.h"
 #include "SkGraphics.h"
 #include "SkString.h"
+#include "Test.h"
 #include "gm.h"
 
+#include "DMCpuTask.h"
+#include "DMGpuTask.h"
 #include "DMReporter.h"
 #include "DMTask.h"
 #include "DMTaskRunner.h"
-#include "DMCpuTask.h"
-#include "DMGpuTask.h"
+#include "DMTestTask.h"
 #include "DMWriteTask.h"
 
 #include <string.h>
 
 using skiagm::GM;
 using skiagm::GMRegistry;
+using skiatest::Test;
+using skiatest::TestRegistry;
 
-DEFINE_int32(cpuThreads, -1, "Threads for CPU work. Default NUM_CPUS.");
-DEFINE_int32(gpuThreads, 1, "Threads for GPU work.");
+DEFINE_int32(threads, -1, "Threads for CPU work. Default NUM_CPUS.");
 DEFINE_string2(expectations, r, "",
                "If a directory, compare generated images against images under this path. "
                "If a file, compare generated images against JSON expectations at this path.");
-DEFINE_string(resources, "resources", "Path to resources directory.");
+DEFINE_string2(resources, i, "resources", "Path to resources directory.");
 DEFINE_string(match, "",  "[~][^]substring[$] [...] of GM name to run.\n"
                           "Multiple matches may be separated by spaces.\n"
                           "~ causes a matching GM to always be skipped\n"
@@ -37,6 +40,10 @@
                           "it is skipped unless some list entry starts with ~");
 DEFINE_string(config, "565 8888 gpu",
         "Options: 565 8888 gpu msaa4 msaa16 gpunull gpudebug angle mesa"); // TODO(mtklein): pdf
+DEFINE_bool(leaks, false, "Print leaked instance-counted objects at exit?");
+
+DEFINE_bool(gms, true, "Run GMs?");
+DEFINE_bool(tests, true, "Run tests?");
 
 __SK_FORCE_IMAGE_DECODER_LINKING;
 
@@ -48,11 +55,11 @@
     return s;
 }
 
-static void kick_off_tasks(const SkTDArray<GMRegistry::Factory>& gms,
-                           const SkTArray<SkString>& configs,
-                           const DM::Expectations& expectations,
-                           DM::Reporter* reporter,
-                           DM::TaskRunner* tasks) {
+static void kick_off_gms(const SkTDArray<GMRegistry::Factory>& gms,
+                         const SkTArray<SkString>& configs,
+                         const DM::Expectations& expectations,
+                         DM::Reporter* reporter,
+                         DM::TaskRunner* tasks) {
     const SkColorType _565 = kRGB_565_SkColorType;
     const SkColorType _8888 = kPMColor_SkColorType;
     const GrContextFactory::GLContextType native = GrContextFactory::kNative_GLContextType;
@@ -93,6 +100,14 @@
 #undef START
 }
 
+static void kick_off_tests(const SkTDArray<TestRegistry::Factory>& tests,
+                           DM::Reporter* reporter,
+                           DM::TaskRunner* tasks) {
+    for (int i = 0; i < tests.count(); i++) {
+        tasks->add(SkNEW_ARGS(DM::TestTask, (reporter, tasks, tests[i])));
+    }
+}
+
 static void report_failures(const DM::Reporter& reporter) {
     SkTArray<SkString> failures;
     reporter.getFailures(&failures);
@@ -109,40 +124,57 @@
 
 int tool_main(int argc, char** argv);
 int tool_main(int argc, char** argv) {
+#if SK_ENABLE_INST_COUNT
+    gPrintInstCount = FLAGS_leaks;
+#endif
     SkGraphics::Init();
-
     SkCommandLineFlags::Parse(argc, argv);
     GM::SetResourcePath(FLAGS_resources[0]);
+    Test::SetResourcePath(FLAGS_resources[0]);
+
     SkTArray<SkString> configs;
-    for (int i = 0; i < FLAGS_config.count(); i++) {
-        SkStrSplit(FLAGS_config[i], ", ", &configs);
-    }
-
     SkTDArray<GMRegistry::Factory> gms;
-    for (const GMRegistry* reg = GMRegistry::Head(); reg != NULL; reg = reg->next()) {
-        SkAutoTDelete<GM> gmForName(reg->factory()(NULL));
-        if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gmForName->shortName())) {
-            *gms.append() = reg->factory();
-        }
-    }
-    SkDebugf("%d GMs x %d configs\n", gms.count(), configs.count());
-
     SkAutoTDelete<DM::Expectations> expectations(SkNEW(DM::NoExpectations));
-    if (FLAGS_expectations.count() > 0) {
-        const char* path = FLAGS_expectations[0];
-        if (sk_isdir(path)) {
-            expectations.reset(SkNEW_ARGS(DM::WriteTask::Expectations, (path)));
-        } else {
-            expectations.reset(SkNEW_ARGS(DM::JsonExpectations, (path)));
+
+    if (FLAGS_gms) {
+        for (int i = 0; i < FLAGS_config.count(); i++) {
+            SkStrSplit(FLAGS_config[i], ", ", &configs);
+        }
+
+        for (const GMRegistry* reg = GMRegistry::Head(); reg != NULL; reg = reg->next()) {
+            SkAutoTDelete<GM> gmForName(reg->factory()(NULL));
+            if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gmForName->shortName())) {
+                *gms.append() = reg->factory();
+            }
+        }
+
+        if (FLAGS_expectations.count() > 0) {
+            const char* path = FLAGS_expectations[0];
+            if (sk_isdir(path)) {
+                expectations.reset(SkNEW_ARGS(DM::WriteTask::Expectations, (path)));
+            } else {
+                expectations.reset(SkNEW_ARGS(DM::JsonExpectations, (path)));
+            }
         }
     }
 
+    SkTDArray<TestRegistry::Factory> tests;
+    if (FLAGS_tests) {
+        for (const TestRegistry* reg = TestRegistry::Head(); reg != NULL; reg = reg->next()) {
+            SkAutoTDelete<Test> testForName(reg->factory()(NULL));
+            if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, testForName->getName())) {
+                *tests.append() = reg->factory();
+            }
+        }
+    }
+
+    SkDebugf("%d GMs x %d configs, %d tests\n", gms.count(), configs.count(), tests.count());
     DM::Reporter reporter;
-    DM::TaskRunner tasks(FLAGS_cpuThreads, FLAGS_gpuThreads);
-    kick_off_tasks(gms, configs, *expectations, &reporter, &tasks);
+    DM::TaskRunner tasks(FLAGS_threads);
+    kick_off_gms(gms, configs, *expectations, &reporter, &tasks);
+    kick_off_tests(tests, &reporter, &tasks);
     tasks.wait();
 
-    reporter.updateStatusLine();
     SkDebugf("\n");
     report_failures(reporter);
 
diff --git a/dm/DMGpuTask.cpp b/dm/DMGpuTask.cpp
index f787e25..3a4708b 100644
--- a/dm/DMGpuTask.cpp
+++ b/dm/DMGpuTask.cpp
@@ -18,6 +18,7 @@
                  GrContextFactory::GLContextType contextType,
                  int sampleCount)
     : Task(reporter, taskRunner)
+    , fTaskRunner(taskRunner)
     , fGM(gmFactory(NULL))
     , fName(UnderJoin(fGM->shortName(), name))
     , fExpectations(expectations)
@@ -26,24 +27,12 @@
     , fSampleCount(sampleCount)
     {}
 
-static void* new_gr_context_factory() {
-    return SkNEW(GrContextFactory);
-}
-
-static void delete_gr_context_factory(void* factory) {
-    SkDELETE((GrContextFactory*) factory);
-}
-
-static GrContextFactory* get_gr_factory() {
-    return reinterpret_cast<GrContextFactory*>(SkTLS::Get(&new_gr_context_factory,
-                                                          &delete_gr_context_factory));
-}
-
 void GpuTask::draw() {
-    GrContext* gr = get_gr_factory()->get(fContextType);  // Will be owned by device.
     SkImageInfo info = SkImageInfo::Make(SkScalarCeilToInt(fGM->width()),
                                          SkScalarCeilToInt(fGM->height()),
-                                         fColorType, kPremul_SkAlphaType);
+                                         fColorType,
+                                         kPremul_SkAlphaType);
+    GrContext* gr = fTaskRunner->getGrContextFactory()->get(fContextType);  // Owned by surface.
     SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTarget(gr, info, fSampleCount));
     SkCanvas* canvas = surface->getCanvas();
 
diff --git a/dm/DMGpuTask.h b/dm/DMGpuTask.h
index a3fe52b..1380e44 100644
--- a/dm/DMGpuTask.h
+++ b/dm/DMGpuTask.h
@@ -32,6 +32,7 @@
     virtual SkString name() const SK_OVERRIDE { return fName; }
 
 private:
+    TaskRunner* fTaskRunner;
     SkAutoTDelete<skiagm::GM> fGM;
     const SkString fName;
     const Expectations& fExpectations;
diff --git a/dm/DMReporter.cpp b/dm/DMReporter.cpp
index 0fd83e5..1ff64c5 100644
--- a/dm/DMReporter.cpp
+++ b/dm/DMReporter.cpp
@@ -3,21 +3,27 @@
 #include "SkCommandLineFlags.h"
 #include "OverwriteLine.h"
 
-DEFINE_bool(quiet, false, "If true, don't print status updates.");
+DEFINE_bool2(quiet, q, false, "If true, don't print status updates.");
+DEFINE_bool2(verbose, v, false, "If true, print status updates one-per-line.");
 
 namespace DM {
 
-void Reporter::updateStatusLine() const {
+void Reporter::finish(SkString name) {
+    sk_atomic_inc(&fFinished);
+
     if (FLAGS_quiet) {
         return;
     }
 
     SkString status;
-    status.printf("%s%d tasks left", kSkOverwriteLine, this->started() - this->finished());
+    status.printf("%s%d tasks left",
+                  FLAGS_verbose ? "\n" : kSkOverwriteLine,
+                  this->started() - this->finished());
     const int failed = this->failed();
     if (failed > 0) {
         status.appendf(", %d failed", failed);
     }
+    status.appendf("\t[%s done]", name.c_str());
     SkDebugf(status.c_str());
 }
 
@@ -26,9 +32,9 @@
     return fFailures.count();
 }
 
-void Reporter::fail(SkString name) {
+void Reporter::fail(SkString msg) {
     SkAutoMutexAcquire writer(&fMutex);
-    fFailures.push_back(name);
+    fFailures.push_back(msg);
 }
 
 void Reporter::getFailures(SkTArray<SkString>* failures) const {
diff --git a/dm/DMReporter.h b/dm/DMReporter.h
index 4f4ad43..2bb4702 100644
--- a/dm/DMReporter.h
+++ b/dm/DMReporter.h
@@ -7,7 +7,6 @@
 #include "SkTypes.h"
 
 // Used to report status changes including failures.  All public methods are threadsafe.
-
 namespace DM {
 
 class Reporter : SkNoncopyable {
@@ -15,15 +14,13 @@
     Reporter() : fStarted(0), fFinished(0) {}
 
     void start()  { sk_atomic_inc(&fStarted); }
-    void finish() { sk_atomic_inc(&fFinished); }
-    void fail(SkString name);
+    void finish(SkString name);
+    void fail(SkString msg);
 
     int32_t started()  const { return fStarted; }
     int32_t finished() const { return fFinished; }
     int32_t failed()   const;
 
-    void updateStatusLine() const;
-
     void getFailures(SkTArray<SkString>*) const;
 
 private:
diff --git a/dm/DMTask.cpp b/dm/DMTask.cpp
index a5c75f0..2f009f0 100644
--- a/dm/DMTask.cpp
+++ b/dm/DMTask.cpp
@@ -26,8 +26,7 @@
     if (!this->shouldSkip()) {
         this->draw();
     }
-    fReporter->finish();
-    fReporter->updateStatusLine();
+    fReporter->finish(this->name());
     delete this;
 }
 
@@ -39,8 +38,12 @@
     }
 }
 
-void Task::fail() {
-    fReporter->fail(this->name());
+void Task::fail(const char* msg) {
+    SkString failure(this->name());
+    if (msg) {
+        failure.appendf(": %s", msg);
+    }
+    fReporter->fail(failure);
 }
 
 }  // namespace DM
diff --git a/dm/DMTask.h b/dm/DMTask.h
index 0d3ef74..638a939 100644
--- a/dm/DMTask.h
+++ b/dm/DMTask.h
@@ -34,7 +34,7 @@
 
 protected:
     void spawnChild(Task* task);
-    void fail();
+    void fail(const char* msg = NULL);
 
 private:
     // Both unowned.
diff --git a/dm/DMTaskRunner.cpp b/dm/DMTaskRunner.cpp
index 22269a4..bd53ce6 100644
--- a/dm/DMTaskRunner.cpp
+++ b/dm/DMTaskRunner.cpp
@@ -3,10 +3,21 @@
 
 namespace DM {
 
-TaskRunner::TaskRunner(int cputhreads, int gpuThreads)
+
+TaskRunner::TaskRunner(int cputhreads)
     : fMain(cputhreads)
-    , fGpu(gpuThreads)
-    {}
+    , fGpu(1) {
+    // Enqueue a task on the GPU thread to create a GrContextFactory.
+    struct Create : public SkRunnable {
+        Create(GrContextFactory** ptr) : fPtr(ptr) {}
+        void run() SK_OVERRIDE {
+            *fPtr = SkNEW(GrContextFactory);
+            delete this;
+        }
+        GrContextFactory** fPtr;
+    };
+    fGpu.add(SkNEW_ARGS(Create, (&fGrContextFactory)));
+}
 
 void TaskRunner::add(Task* task) {
     if (task->usesGpu()) {
@@ -17,6 +28,17 @@
 }
 
 void TaskRunner::wait() {
+    // Enqueue a task on the GPU thread to destroy the GrContextFactory.
+    struct Delete : public SkRunnable {
+        Delete(GrContextFactory* ptr) : fPtr(ptr) {}
+        void run() SK_OVERRIDE {
+            delete fPtr;
+            delete this;
+        }
+        GrContextFactory* fPtr;
+    };
+    fGpu.add(SkNEW_ARGS(Delete, (fGrContextFactory)));
+
     // These wait calls block until the threadpool is done.  We don't allow
     // children to spawn new GPU tasks so we can wait for that first knowing
     // we'll never try to add to it later.  Same can't be said of fMain: fGpu
diff --git a/dm/DMTaskRunner.h b/dm/DMTaskRunner.h
index 5d7b320..8af1b63 100644
--- a/dm/DMTaskRunner.h
+++ b/dm/DMTaskRunner.h
@@ -1,12 +1,12 @@
 #ifndef DMTaskRunner_DEFINED
 #define DMTaskRunner_DEFINED
 
+#include "GrContextFactory.h"
 #include "SkThreadPool.h"
 #include "SkTypes.h"
 
-// TaskRunner runs Tasks on one of two threadpools depending on the Task's usesGpu() method.
-// This lets us drive the GPU with a small number of threads (e.g. 2 or 4 can be faster than 1)
-// while not swamping it with requests from the full fleet of threads that CPU-bound tasks run on.
+// TaskRunner runs Tasks on one of two threadpools depending on the Task's usesGpu() method.  This
+// lets us drive the GPU from a single thread while parallelizing CPU-bound work.
 
 namespace DM {
 
@@ -14,13 +14,17 @@
 
 class TaskRunner : SkNoncopyable {
 public:
-    TaskRunner(int cputhreads, int gpuThreads);
+    explicit TaskRunner(int cputhreads);
 
     void add(Task* task);
     void wait();
 
+    // This can only be safely called from a GPU task's draw() method.
+    GrContextFactory* getGrContextFactory() const { return fGrContextFactory; }
+
 private:
     SkThreadPool fMain, fGpu;
+    GrContextFactory* fGrContextFactory;  // Created and destroyed on fGpu threadpool.
 };
 
 }  // namespace DM
diff --git a/dm/DMTestTask.cpp b/dm/DMTestTask.cpp
new file mode 100644
index 0000000..7a5f8cf
--- /dev/null
+++ b/dm/DMTestTask.cpp
@@ -0,0 +1,34 @@
+#include "DMTestTask.h"
+#include "DMUtil.h"
+#include "SkCommandLineFlags.h"
+
+DEFINE_bool2(pathOpsExtended,     x, false, "Run extended pathOps tests.");
+DEFINE_bool2(pathOpsSingleThread, z, false, "Disallow pathOps tests from using threads.");
+DEFINE_bool2(pathOpsVerbose,      V, false, "Tell pathOps tests to be verbose.");
+
+namespace DM {
+
+TestTask::TestTask(Reporter* reporter,
+                   TaskRunner* taskRunner,
+                   skiatest::TestRegistry::Factory factory)
+    : Task(reporter, taskRunner)
+    , fTaskRunner(taskRunner)
+    , fTest(factory(NULL))
+    , fName(UnderJoin("test", fTest->getName())) {}
+
+void TestTask::draw() {
+    if (this->usesGpu()) {
+        fTest->setGrContextFactory(fTaskRunner->getGrContextFactory());
+    }
+    fTest->setReporter(&fTestReporter);
+    fTest->run();
+    if (!fTest->passed()) {
+        this->fail(fTestReporter.failure());
+    }
+}
+
+bool TestTask::TestReporter::allowExtendedTest() const { return FLAGS_pathOpsExtended; }
+bool TestTask::TestReporter::allowThreaded()     const { return !FLAGS_pathOpsSingleThread; }
+bool TestTask::TestReporter::verbose()           const { return FLAGS_pathOpsVerbose; }
+
+}  // namespace DM
diff --git a/dm/DMTestTask.h b/dm/DMTestTask.h
new file mode 100644
index 0000000..a4903ee
--- /dev/null
+++ b/dm/DMTestTask.h
@@ -0,0 +1,50 @@
+#ifndef DMTestTask_DEFINED
+#define DMTestTask_DEFINED
+
+#include "DMReporter.h"
+#include "DMTask.h"
+#include "DMTaskRunner.h"
+#include "SkString.h"
+#include "SkTemplates.h"
+#include "Test.h"
+
+// Runs a unit test.
+namespace DM {
+
+class TestTask : public Task {
+public:
+    TestTask(Reporter*, TaskRunner*, skiatest::TestRegistry::Factory);
+
+    virtual void draw() SK_OVERRIDE;
+    virtual bool usesGpu() const SK_OVERRIDE { return fTest->isGPUTest(); }
+    virtual bool shouldSkip() const SK_OVERRIDE { return false; }
+    virtual SkString name() const SK_OVERRIDE { return fName; }
+
+private:
+    class TestReporter : public skiatest::Reporter {
+    public:
+      TestReporter() {}
+
+      const char* failure() const { return fFailure.c_str(); }
+
+    private:
+      virtual bool allowExtendedTest() const SK_OVERRIDE;
+      virtual bool allowThreaded()     const SK_OVERRIDE;
+      virtual bool verbose()           const SK_OVERRIDE;
+
+      virtual void onReportFailed(const SkString& desc) SK_OVERRIDE {
+          fFailure = desc;
+      }
+
+      SkString fFailure;
+    };
+
+    TaskRunner* fTaskRunner;
+    TestReporter fTestReporter;
+    SkAutoTDelete<skiatest::Test> fTest;
+    const SkString fName;
+};
+
+}  // namespace DM
+
+#endif // DMTestTask_DEFINED