diff options
author | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-02-26 16:31:22 +0000 |
---|---|---|
committer | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-02-26 16:31:22 +0000 |
commit | 0dc5bd149a8b69e8dc6d3b4713b827659c9b0a6b (patch) | |
tree | 53fdc1e7747c8ed33c275deb6f24802bc41d365a /dm | |
parent | 1e762d39bb6383d33d8e1f04a296f164aa2a6beb (diff) |
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
Diffstat (limited to 'dm')
-rw-r--r-- | dm/DM.cpp | 92 | ||||
-rw-r--r-- | dm/DMGpuTask.cpp | 19 | ||||
-rw-r--r-- | dm/DMGpuTask.h | 1 | ||||
-rw-r--r-- | dm/DMReporter.cpp | 16 | ||||
-rw-r--r-- | dm/DMReporter.h | 7 | ||||
-rw-r--r-- | dm/DMTask.cpp | 11 | ||||
-rw-r--r-- | dm/DMTask.h | 2 | ||||
-rw-r--r-- | dm/DMTaskRunner.cpp | 28 | ||||
-rw-r--r-- | dm/DMTaskRunner.h | 12 | ||||
-rw-r--r-- | dm/DMTestTask.cpp | 34 | ||||
-rw-r--r-- | dm/DMTestTask.h | 50 |
11 files changed, 205 insertions, 67 deletions
@@ -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 @@ DEFINE_string(match, "", "[~][^]substring[$] [...] of GM name to run.\n" "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 @@ static SkString lowercase(SkString s) { 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 @@ static void kick_off_tasks(const SkTDArray<GMRegistry::Factory>& gms, #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 @@ static void report_failures(const DM::Reporter& reporter) { 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]); - SkTArray<SkString> configs; - for (int i = 0; i < FLAGS_config.count(); i++) { - SkStrSplit(FLAGS_config[i], ", ", &configs); - } + Test::SetResourcePath(FLAGS_resources[0]); + SkTArray<SkString> 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(); + SkAutoTDelete<DM::Expectations> expectations(SkNEW(DM::NoExpectations)); + + 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))); + } } } - 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))); + 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 f787e2544f..3a4708b8db 100644 --- a/dm/DMGpuTask.cpp +++ b/dm/DMGpuTask.cpp @@ -18,6 +18,7 @@ GpuTask::GpuTask(const char* name, GrContextFactory::GLContextType contextType, int sampleCount) : Task(reporter, taskRunner) + , fTaskRunner(taskRunner) , fGM(gmFactory(NULL)) , fName(UnderJoin(fGM->shortName(), name)) , fExpectations(expectations) @@ -26,24 +27,12 @@ GpuTask::GpuTask(const char* name, , 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 a3fe52b751..1380e443d4 100644 --- a/dm/DMGpuTask.h +++ b/dm/DMGpuTask.h @@ -32,6 +32,7 @@ public: 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 0fd83e5bb6..1ff64c57cc 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 @@ int32_t Reporter::failed() const { 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 4f4ad432d5..2bb4702178 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 @@ public: 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 a5c75f0f8a..2f009f0670 100644 --- a/dm/DMTask.cpp +++ b/dm/DMTask.cpp @@ -26,8 +26,7 @@ void Task::run() { if (!this->shouldSkip()) { this->draw(); } - fReporter->finish(); - fReporter->updateStatusLine(); + fReporter->finish(this->name()); delete this; } @@ -39,8 +38,12 @@ void Task::spawnChild(Task* task) { } } -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 0d3ef74cc3..638a939304 100644 --- a/dm/DMTask.h +++ b/dm/DMTask.h @@ -34,7 +34,7 @@ public: 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 22269a4d70..bd53ce615a 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::add(Task* task) { } 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 5d7b320d6c..8af1b63719 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 Task; 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 0000000000..7a5f8cf9f9 --- /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 0000000000..a4903ee717 --- /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 |