diff --git a/src/tools/cppunit/SafetyLock.cpp b/src/tools/cppunit/SafetyLock.cpp new file mode 100644 index 0000000000..8f451c9ac1 --- /dev/null +++ b/src/tools/cppunit/SafetyLock.cpp @@ -0,0 +1,14 @@ +#include +#include + +SafetyLock::SafetyLock(BLocker *locker) + : fLocker(locker) +{ + if (fLocker) + fLocker->Lock(); +} + +SafetyLock::~SafetyLock() { + if (fLocker) + fLocker->Unlock(); +} \ No newline at end of file diff --git a/src/tools/cppunit/TestCase.cpp b/src/tools/cppunit/TestCase.cpp index 0c00ff0f2f..72ffca4bcd 100644 --- a/src/tools/cppunit/TestCase.cpp +++ b/src/tools/cppunit/TestCase.cpp @@ -1,33 +1,43 @@ #include #include +#include -TestCase::TestCase() - : CppUnit::TestCase() - , fValidCWD(false) -{ -} - -TestCase::TestCase(std::string name) +BTestCase::BTestCase(std::string name) : CppUnit::TestCase(name) , fValidCWD(false) + , fSubTestNum(0) { } -// Saves the location of the current working directory. To return to the -// last saved working directory, all \ref RestorCWD(). void -TestCase::SaveCWD() { +BTestCase::tearDown() { + NextSubTestBlock(); +} + +void +BTestCase::NextSubTest() { + printf("[%ld]", fSubTestNum++); + fflush(stdout); +} + +void +BTestCase::NextSubTestBlock() { +// printf("\n"); +} + +/*! To return to the last saved working directory, call RestoreCWD(). */ +void +BTestCase::SaveCWD() { fValidCWD = getcwd(fCurrentWorkingDir, B_PATH_NAME_LENGTH); } -/* Restores the current working directory to last directory saved by a - call to SaveCWD(). If SaveCWD() has not been called and an alternate +/* If SaveCWD() has not been called and an alternate directory is specified by alternate, the current working directory is changed to alternate. If alternate is null, the current working directory is not modified. */ void -TestCase::RestoreCWD(const char *alternate) { +BTestCase::RestoreCWD(const char *alternate) { if (fValidCWD) chdir(fCurrentWorkingDir); else if (alternate != NULL) diff --git a/src/tools/cppunit/TestListener.cpp b/src/tools/cppunit/TestListener.cpp new file mode 100644 index 0000000000..a3b5a0e6e6 --- /dev/null +++ b/src/tools/cppunit/TestListener.cpp @@ -0,0 +1,33 @@ +#include + +#include +#include +#include +#include + +void +BTestListener::startTest( CppUnit::Test *test ) { + fOkay = true; + cout << test->getName() << endl; +} + +void +BTestListener::addFailure( const CppUnit::TestFailure &failure ) { + fOkay = false; + cout << " - "; + cout << (failure.isError() ? "ERROR" : "FAILURE"); + cout << " -- "; + cout << (failure.thrownException() != NULL + ? failure.thrownException()->what() + : "(unknown error)"); + cout << endl; +} + +void +BTestListener::endTest( CppUnit::Test *test ) { + if (fOkay) + cout << " + PASSED" << endl; + else + cout << " - FAILED" << endl; + cout << endl; +} diff --git a/src/tools/cppunit/TestShell.cpp b/src/tools/cppunit/TestShell.cpp index 27772cee44..3f2268f735 100644 --- a/src/tools/cppunit/TestShell.cpp +++ b/src/tools/cppunit/TestShell.cpp @@ -1,36 +1,209 @@ #include -#include -#include -#include -TestShell::TestShell(const std::string &description, SyncObject *syncObject) - : CppUnitShell(description, syncObject), - fTestDir(NULL) -{ -} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -TestShell::~TestShell() +BTestShell::BTestShell(const std::string &description, SyncObject *syncObject) + : fVerbosityLevel(v2) + , fDescription(description) + , fTestResults(syncObject) { - delete fTestDir; +}; + +void +BTestShell::AddSuite(const std::string &name, const SuiteFunction suite) { + if (suite != NULL) + fTests[name] = suite; } int -TestShell::Run(int argc, char *argv[]) -{ - // Let's hope BPath does work. ;-) - BPath path(argv[0]); - if (path.InitCheck() == B_OK) { - fTestDir = new BPath(); - if (path.GetParent(fTestDir) != B_OK) - printf("Couldn't get test dir.\n"); - } else - printf("Couldn't find the path to the test app.\n"); - return CppUnitShell::Run(argc, argv); +BTestShell::Run(int argc, char *argv[]) { + // Parse the command line args + if (!ProcessArguments(argc, argv)) + return 0; + + // Add the proper tests to our suite (or exit if there + // are no tests installed). + CppUnit::TestSuite suite; + if (fTests.empty()) { + + // No installed tests whatsoever, so bail + cout << "ERROR: No installed tests to run!" << endl; + return 0; + + } else if (fTestsToRun.empty()) { + + // None specified, so run them all + std::map::iterator i; + for (i = fTests.begin(); i != fTests.end(); ++i) + suite.addTest( i->second() ); + + } else { + + // One or more specified, so only run those + std::set::const_iterator i; + for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) + suite.addTest( fTests[*i]() ); + + } + + // Run all the tests + InitOutput(); + suite.run(&fTestResults); + PrintResults(); + + return 0; } -const char* -TestShell::TestDir() const -{ - return (fTestDir ? fTestDir->Path() : NULL); +BTestShell::VerbosityLevel +BTestShell::Verbosity() const { + return fVerbosityLevel; +} + +void +BTestShell::PrintDescription(int argc, char *argv[]) { + cout << endl << fDescription; +} + +void +BTestShell::PrintHelp() { + const char indent[] = " "; + cout << endl; + cout << "VALID ARGUMENTS: " << endl; + cout << indent << "--help Displays this help text plus some other garbage" << endl; + cout << indent << "--list Lists the names of classes with installed tests" << endl; + cout << indent << "-v0 Sets verbosity level to 0 (concise summary only)" << endl; + cout << indent << "-v1 Sets verbosity level to 1 (complete summary only)" << endl; + cout << indent << "-v2 Sets verbosity level to 2 (*default* -- per-test results plus" << endl; + cout << indent << " complete summary)" << endl; + cout << indent << "-v3 Sets verbosity level to 3 (per-test results and timing info" << endl; + cout << indent << " plus complete summary)" << endl; + cout << indent << "CLASSNAME Instructs the program to run the test for the given class; if" << endl; + cout << indent << " no classes are specified, all tests are run" << endl; + cout << endl; + +} + +bool +BTestShell::ProcessArguments(int argc, char *argv[]) { + // If we're given no parameters, the default settings + // will do just fine + if (argc < 2) + return true; + + // Handle each command line argument (skipping the first + // which is just the app name) + for (int i = 1; i < argc; i++) { + std::string str(argv[i]); + + if (str == "--help") { + PrintDescription(argc, argv); + PrintHelp(); + return false; + } + else if (str == "--list") { + // Print out the list of installed tests + cout << "------------------------------------------------------------------------------" << endl; + cout << "Available Tests:" << endl; + cout << "------------------------------------------------------------------------------" << endl; + map::const_iterator i; + for (i = fTests.begin(); i != fTests.end(); ++i) + cout << i->first << endl; + cout << endl; + return false; + } + else if (str == "-v0") { + fVerbosityLevel = v0; + } + else if (str == "-v1") { + fVerbosityLevel = v1; + } + else if (str == "-v2") { + fVerbosityLevel = v2; + } + else if (fTests.find(str) != fTests.end()) { + fTestsToRun.insert(str); + } + else { + cout << endl << "ERROR: Invalid argument \"" << str << "\"" << endl; + PrintHelp(); + return false; + } + + } + + return true; +} + +void +BTestShell::InitOutput() { + // For vebosity level 2, we output info about each test + // as we go. This involves a custom CppUnit::TestListener + // class. + if (fVerbosityLevel == v2) { + cout << "------------------------------------------------------------------------------" << endl; + cout << "Tests" << endl; + cout << "------------------------------------------------------------------------------" << endl; + fTestResults.addListener(new BTestListener); + fTestResults.addListener(&fResultsCollector); + } +} + +void +BTestShell::PrintResults() { + + if (fVerbosityLevel > v0) { + // Print out detailed results for verbosity levels > 0 + cout << "------------------------------------------------------------------------------" << endl; + cout << "Results " << endl; + cout << "------------------------------------------------------------------------------" << endl; + + // Print failures and errors if there are any, otherwise just say "PASSED" + ::CppUnit::TestResultCollector::TestFailures::const_iterator iFailure; + if (fResultsCollector.testFailuresTotal() > 0) { + if (fResultsCollector.testFailures() > 0) { + cout << "- FAILURES: " << fResultsCollector.testFailures() << endl; + for (iFailure = fResultsCollector.failures().begin(); + iFailure != fResultsCollector.failures().end(); + ++iFailure) + { + if (!(*iFailure)->isError()) + cout << " " << (*iFailure)->toString() << endl; + } + } + if (fResultsCollector.testErrors() > 0) { + cout << "- ERRORS: " << fResultsCollector.testErrors() << endl; + for (iFailure = fResultsCollector.failures().begin(); + iFailure != fResultsCollector.failures().end(); + ++iFailure) + { + if ((*iFailure)->isError()) + cout << " " << (*iFailure)->toString() << endl; + } + } + + } + else + cout << "+ PASSED" << endl; + + cout << endl; + + } + else { + // Print out concise results for verbosity level == 0 + if (fResultsCollector.testFailuresTotal() > 0) + cout << "- FAILED" << endl; + else + cout << "+ PASSED" << endl; + } + } diff --git a/src/tools/cppunit/ThreadedTestCase.cpp b/src/tools/cppunit/ThreadedTestCase.cpp new file mode 100644 index 0000000000..0bf371c218 --- /dev/null +++ b/src/tools/cppunit/ThreadedTestCase.cpp @@ -0,0 +1,59 @@ +#include +#include + +BThreadedTestCase::BThreadedTestCase(std::string name, std::string progressSeparator) + : BTestCase(name) + , fProgressSeparator(progressSeparator) + , fNumberMapLock(new BLocker()) +{ +} + +BThreadedTestCase::~BThreadedTestCase() { + // Kill our locker + delete fNumberMapLock; + + // Clean up + for (std::map::iterator i = fNumberMap.begin(); + i != fNumberMap.end(); + i++) + { + delete i->second; + } +} + +void +BThreadedTestCase::NextSubTest() { + // Find out what thread we're in + thread_id id = find_thread(NULL); + + { + // Lock the number map + SafetyLock lock(fNumberMapLock); + std::map::iterator i = fNumberMap.find(id); + if (i != fNumberMap.end() && i->second) { + // Handle multi-threaded case + ThreadSubTestInfo *info = i->second; + cout << "[" << info->subTestNum++ << fProgressSeparator << info->name << "]"; + return; + } + } + + // Handle single-threaded case + BTestCase::NextSubTest(); +} + +void +BThreadedTestCase::InitThreadInfo(thread_id id, std::string threadName) { + SafetyLock lock(fNumberMapLock); // Lock the number map + std::map::iterator i = fNumberMap.find(id); + if (i != fNumberMap.end() && i->second) { + i->second->name = threadName; + i->second->subTestNum = 0; + } else { + // New addition + ThreadSubTestInfo *info = new ThreadSubTestInfo(); + info->name = threadName; + info->subTestNum = 0; + fNumberMap[id] = info; + } +}