skia2/tests/skia_test.cpp
caryclark@google.com 8d0a524a48 harden and speed up path op unit tests
PathOps tests internal routines direcctly. Check to make sure that
test points, lines, quads, curves, triangles, and bounds read from
arrays are valid (i.e., don't contain NaN) before calling the
test function.

Repurpose the test flags.
- make 'v' verbose test region output against path output
- make 'z' single threaded (before it made it multithreaded)

The latter change speeds up tests run by the buildbot by 2x to 3x.

BUG=

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

git-svn-id: http://skia.googlecode.com/svn/trunk@10107 2bbb7eff-a529-9590-31e7-b0007b416f81
2013-07-16 16:11:16 +00:00

309 lines
8.8 KiB
C++

/*
* 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 "SkCommandLineFlags.h"
#include "SkGraphics.h"
#include "SkOSFile.h"
#include "SkRunnable.h"
#include "SkTArray.h"
#include "SkTemplates.h"
#include "SkThreadPool.h"
#include "SkTime.h"
#include "Test.h"
#if SK_SUPPORT_GPU
#include "GrContext.h"
#endif
using namespace skiatest;
// need to explicitly declare this, or we get some weird infinite loop llist
template TestRegistry* TestRegistry::gHead;
class Iter {
public:
Iter(Reporter* r) : fReporter(r) {
r->ref();
this->reset();
}
void reset() {
fReg = TestRegistry::Head();
}
~Iter() {
fReporter->unref();
}
Test* next() {
if (fReg) {
TestRegistry::Factory fact = fReg->factory();
fReg = fReg->next();
Test* test = fact(NULL);
test->setReporter(fReporter);
return test;
}
return NULL;
}
private:
Reporter* fReporter;
const TestRegistry* fReg;
};
class DebugfReporter : public Reporter {
public:
DebugfReporter(bool allowExtendedTest, bool allowThreaded, bool verbose)
: fNextIndex(0)
, fPending(0)
, fTotal(0)
, fAllowExtendedTest(allowExtendedTest)
, fAllowThreaded(allowThreaded)
, fVerbose(verbose) {
}
void setTotal(int total) {
fTotal = total;
}
virtual bool allowExtendedTest() const SK_OVERRIDE {
return fAllowExtendedTest;
}
virtual bool allowThreaded() const SK_OVERRIDE {
return fAllowThreaded;
}
virtual bool verbose() const SK_OVERRIDE {
return fVerbose;
}
protected:
virtual void onStart(Test* test) {
const int index = sk_atomic_inc(&fNextIndex);
sk_atomic_inc(&fPending);
SkDebugf("[%3d/%3d] (%d) %s\n", index+1, fTotal, fPending, test->getName());
}
virtual void onReportFailed(const SkString& desc) {
SkDebugf("\tFAILED: %s\n", desc.c_str());
}
virtual void onEnd(Test* test) {
if (!test->passed()) {
SkDebugf("---- %s FAILED\n", test->getName());
}
sk_atomic_dec(&fPending);
if (fNextIndex == fTotal) {
// Just waiting on straggler tests. Shame them by printing their name and runtime.
SkDebugf(" (%d) %5.1fs %s\n",
fPending, test->elapsedMs() / 1e3, test->getName());
}
}
private:
int32_t fNextIndex;
int32_t fPending;
int fTotal;
bool fAllowExtendedTest;
bool fAllowThreaded;
bool fVerbose;
};
DEFINE_string2(match, m, NULL, "[~][^]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(tmpDir, t, NULL, "tmp directory for tests to use.");
DEFINE_string2(resourcePath, i, NULL, "directory for test resources.");
DEFINE_bool2(extendedTest, x, false, "run extended tests for pathOps.");
DEFINE_bool2(single, z, false, "run tests on a single thread internally.");
DEFINE_bool2(verbose, v, false, "enable verbose output.");
DEFINE_int32(threads, SkThreadPool::kThreadPerCore,
"Run threadsafe tests on a threadpool with this many threads.");
SkString Test::GetTmpDir() {
const char* tmpDir = FLAGS_tmpDir.isEmpty() ? NULL : FLAGS_tmpDir[0];
return SkString(tmpDir);
}
SkString Test::GetResourcePath() {
const char* resourcePath = FLAGS_resourcePath.isEmpty() ? NULL : FLAGS_resourcePath[0];
return SkString(resourcePath);
}
// Deletes self when run.
class SkTestRunnable : public SkRunnable {
public:
// Takes ownership of test.
SkTestRunnable(Test* test, int32_t* failCount) : fTest(test), fFailCount(failCount) {}
virtual void run() {
fTest->run();
if(!fTest->passed()) {
sk_atomic_inc(fFailCount);
}
SkDELETE(this);
}
private:
SkAutoTDelete<Test> fTest;
int32_t* fFailCount;
};
/* Takes a list of the form [~][^]match[$]
~ causes a matching test to always be skipped
^ requires the start of the test to match
$ requires the end of the test to match
^ and $ requires an exact match
If a test does not match any list entry, it is skipped unless some list entry starts with ~
*/
static bool shouldSkip(const char* testName) {
int count = FLAGS_match.count();
size_t testLen = strlen(testName);
bool anyExclude = count == 0;
for (int index = 0; index < count; ++index) {
const char* matchName = FLAGS_match[index];
size_t matchLen = strlen(matchName);
bool matchExclude, matchStart, matchEnd;
if ((matchExclude = matchName[0] == '~')) {
anyExclude = true;
matchName++;
matchLen--;
}
if ((matchStart = matchName[0] == '^')) {
matchName++;
matchLen--;
}
if ((matchEnd = matchName[matchLen - 1] == '$')) {
matchLen--;
}
if (matchStart ? (!matchEnd || matchLen == testLen)
&& strncmp(testName, matchName, matchLen) == 0
: matchEnd ? matchLen <= testLen
&& strncmp(testName + testLen - matchLen, matchName, matchLen) == 0
: strstr(testName, matchName) != 0) {
return matchExclude;
}
}
return !anyExclude;
}
int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
SkCommandLineFlags::SetUsage("");
SkCommandLineFlags::Parse(argc, argv);
#if SK_ENABLE_INST_COUNT
gPrintInstCount = true;
#endif
SkGraphics::Init();
{
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 = Test::GetTmpDir();
if (!tmpDir.isEmpty()) {
header.appendf(" --tmpDir %s", tmpDir.c_str());
}
SkString resourcePath = Test::GetResourcePath();
if (!resourcePath.isEmpty()) {
header.appendf(" --resourcePath %s", resourcePath.c_str());
}
#ifdef SK_DEBUG
header.append(" SK_DEBUG");
#else
header.append(" SK_RELEASE");
#endif
#ifdef SK_SCALAR_IS_FIXED
header.append(" SK_SCALAR_IS_FIXED");
#else
header.append(" SK_SCALAR_IS_FLOAT");
#endif
SkDebugf("%s\n", header.c_str());
}
DebugfReporter reporter(FLAGS_extendedTest, !FLAGS_single, FLAGS_verbose);
Iter iter(&reporter);
// Count tests first.
int total = 0;
int toRun = 0;
Test* test;
while ((test = iter.next()) != NULL) {
SkAutoTDelete<Test> owned(test);
if(!shouldSkip(test->getName())) {
toRun++;
}
total++;
}
reporter.setTotal(toRun);
// Now run them.
iter.reset();
int32_t failCount = 0;
int skipCount = 0;
SkAutoTDelete<SkThreadPool> threadpool(SkNEW_ARGS(SkThreadPool, (FLAGS_threads)));
SkTArray<Test*> unsafeTests; // Always passes ownership to an SkTestRunnable
for (int i = 0; i < total; i++) {
SkAutoTDelete<Test> test(iter.next());
if (shouldSkip(test->getName())) {
++skipCount;
} else if (!test->isThreadsafe()) {
unsafeTests.push_back() = test.detach();
} else {
threadpool->add(SkNEW_ARGS(SkTestRunnable, (test.detach(), &failCount)));
}
}
// Run the tests that aren't threadsafe.
for (int i = 0; i < unsafeTests.count(); i++) {
SkNEW_ARGS(SkTestRunnable, (unsafeTests[i], &failCount))->run();
}
// Blocks until threaded tests finish.
threadpool.free();
SkDebugf("Finished %d tests, %d failures, %d skipped.\n",
toRun, failCount, skipCount);
const int testCount = reporter.countTests();
if (FLAGS_verbose && testCount > 0) {
SkDebugf("Ran %d Internal tests.\n", testCount);
}
#if SK_SUPPORT_GPU
#if GR_CACHE_STATS
GrContext *gr = GpuTest::GetContext();
gr->printCacheStats();
#endif
#endif
SkGraphics::Term();
GpuTest::DestroyContexts();
return (failCount == 0) ? 0 : 1;
}
#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
#endif