diff --git a/headers/tools/cppunit/LockerSyncObject.h b/headers/tools/cppunit/LockerSyncObject.h index 523b3105de..b3367d546e 100644 --- a/headers/tools/cppunit/LockerSyncObject.h +++ b/headers/tools/cppunit/LockerSyncObject.h @@ -4,25 +4,28 @@ #include #include -/*! \brief BLocker based implementation of CppUnit::SynchronizedObject::SynchronizationObject +//! BLocker based implementation of CppUnit::SynchronizedObject::SynchronizationObject +/*! This class is used to serialize access to a TestResult object. You should + not need to explicitly use it anywhere in your testing code. */ class LockerSyncObject : public CppUnit::SynchronizedObject::SynchronizationObject { public: -/* LockerSyncObject() : fLock(new BLocker()) {} - virtual ~LockerSyncObject() { cout << 1 << endl; delete fLock; cout << 2 << endl; } - - virtual void lock() { fLock->Lock(); } - virtual void unlock() { fLock->Unlock(); } -protected: - BLocker *fLock; -*/ LockerSyncObject() {} virtual ~LockerSyncObject() {} virtual void lock() { fLock.Lock(); } virtual void unlock() { fLock.Unlock(); } + protected: BLocker fLock; + +private: + //! Prevents the use of the copy constructor. + LockerSyncObject( const LockerSyncObject © ); + + //! Prevents the use of the copy operator. + void operator =( const LockerSyncObject © ); + }; #endif // _beos_synchronization_object_h_ diff --git a/headers/tools/cppunit/SafetyLock.h b/headers/tools/cppunit/SafetyLock.h new file mode 100644 index 0000000000..5883d78cf1 --- /dev/null +++ b/headers/tools/cppunit/SafetyLock.h @@ -0,0 +1,30 @@ +#ifndef _beos_safety_lock_ +#define _beos_safety_lock_ + +class BLocker; + +// +// The SafetyLock class is a utility class for use in actual tests +// of the BLocker interfaces. It is used to make sure that if the +// test fails and an exception is thrown with the lock held, that +// lock will be released. Without this SafetyLock, there could be +// deadlocks if one thread in a test has a failure while holding the +// lock. It should be used like so: +// +// template void myTestClass::myTestFunc(void) +// { +// SafetyLock mySafetyLock(theLocker); +// ...perform tests without worrying about holding the lock on assert... +// +class SafetyLock { +public: + SafetyLock(BLocker *locker); + virtual ~SafetyLock(); + +private: + BLocker *fLocker; + +}; + +#endif // _beos_safety_lock_ + diff --git a/headers/tools/cppunit/TestCase.h b/headers/tools/cppunit/TestCase.h index 558dc0f9d3..64a778c36b 100644 --- a/headers/tools/cppunit/TestCase.h +++ b/headers/tools/cppunit/TestCase.h @@ -3,16 +3,30 @@ #include #include +#include -class TestCase : public CppUnit::TestCase { +//! Base class for single threaded unit tests +class BTestCase : public CppUnit::TestCase { public: - TestCase(); - TestCase(std::string Name); + BTestCase(std::string Name = ""); + + //! Displays the next sub test progress indicator (i.e. [0][1][2][3]...). + virtual void NextSubTest(); + + //! Starts a new sub test block (i.e. prints a newline :-) + virtual void NextSubTestBlock(); + + //! Saves the location of the current working directory. void SaveCWD(); + + virtual void tearDown(); + + //! Restores the current working directory to last directory saved by a call to SaveCWD(). void RestoreCWD(const char *alternate = NULL); protected: bool fValidCWD; - char fCurrentWorkingDir[B_PATH_NAME_LENGTH+1]; + char fCurrentWorkingDir[B_PATH_NAME_LENGTH+1]; + int32 fSubTestNum; }; #endif // _beos_test_case_h_ \ No newline at end of file diff --git a/headers/tools/cppunit/TestListener.h b/headers/tools/cppunit/TestListener.h new file mode 100644 index 0000000000..7bc38e84de --- /dev/null +++ b/headers/tools/cppunit/TestListener.h @@ -0,0 +1,27 @@ +#ifndef _beos_test_listener_h_ +#define _beos_test_listener_h_ + +#include + +class CppUnit::Test; +class CppUnit::TestFailure; +class CppUnit::Exception; + +//! Handles printing of test information +/*! Receives notification of the beginning and end of each test, + and notification of all failures and errors. Prints out said + information in a standard format to standard output. + + You should not need to explicitly use this class in any + of your tests. +*/ +class BTestListener : public CppUnit::TestListener { +public: + virtual void startTest( CppUnit::Test *test ); + virtual void addFailure( const CppUnit::TestFailure &failure ); + virtual void endTest( CppUnit::Test *test ); +protected: + bool fOkay; +}; + +#endif // _beos_test_listener_h_ diff --git a/headers/tools/cppunit/TestShell.h b/headers/tools/cppunit/TestShell.h index c6dd3994f3..de71dd4515 100644 --- a/headers/tools/cppunit/TestShell.h +++ b/headers/tools/cppunit/TestShell.h @@ -1,17 +1,93 @@ #ifndef _beos_test_shell_h_ #define _beos_test_shell_h_ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -class TestShell : public CppUnitShell { +// Defines SuiteFunction to be a pointer to a function that +// takes no arguments and returns a pointer to a CppUnit::Test +typedef CppUnit::Test* (*SuiteFunction)(void); + +// This is just absurd to have to type... +typedef CppUnit::SynchronizedObject::SynchronizationObject SyncObject; + +//! BeOS savvy command line interface for the CppUnit testing framework. +/*! This class provides a fully functional command-line testing interface + built on top of the CppUnit testing library. You add named test suites + via AddSuite(), and then call Run(), which does all the dirty work. The + user can get a list of each test installed via AddSuite(), and optionally + can opt to run only a specified set of them. +*/ +class BTestShell { public: - TestShell(const std::string &description = "", SyncObject *syncObject = 0); - ~TestShell(); + BTestShell(const std::string &description = "", SyncObject *syncObject = 0); + + // This function is used to add test suites to the list of available + // tests. A SuiteFunction is just a function that takes no parameters + // and returns a pointer to a CppUnit::Test object. Return NULL at + // your own risk :-). The name given is the name that will be presented + // when the program is run with "--list" as an argument. Usually the + // given suite would be a test suite for an entire class, but that's + // not a requirement. + void AddSuite(const std::string &name, const SuiteFunction suite); + + // This is the function you call after you've added all your test + // suites with calls to AddSuite(). It runs the test, or displays + // help, or lists installed tests, or whatever, depending on the + // command-line arguments passed in. int Run(int argc, char *argv[]); -// virtual void PrintDescription(int argc, char *argv[]); - const char* TestDir() const; + + // Verbosity Level enumeration and accessor function + enum VerbosityLevel { v0, v1, v2, v3 }; + VerbosityLevel Verbosity() const; + + // Returns true if verbosity is high enough that individual tests are + // allowed to make noise. + bool BeVerbose() const { return Verbosity() >= v2; }; + + +protected: + VerbosityLevel fVerbosityLevel; + std::set fTestsToRun; + std::map fTests; + CppUnit::TestResult fTestResults; + CppUnit::TestResultCollector fResultsCollector; + std::string fDescription; + + // Prints a brief description of the program and a guess as to + // which Storage Kit library the app was linked with based on + // the filename of the app + virtual void PrintDescription(int argc, char *argv[]); + + // Prints out command line argument instructions + void PrintHelp(); + + // Handles command line arguments; returns true if everything goes + // okay, false if not (or if the program just needs to terminate without + // running any tests). Modifies settings in "settings" as necessary. + bool ProcessArguments(int argc, char *argv[]); + + // Makes any necessary pre-test preparations + void InitOutput(); + + // Prints out the test results in the proper format per + // the specified verbosity level. + void PrintResults(); + private: - BPath *fTestDir; -}; + //! Prevents the use of the copy constructor. + BTestShell( const BTestShell © ); + + //! Prevents the use of the copy operator. + void operator =( const BTestShell © ); + +}; // class BTestShell #endif // _beos_test_shell_h_ \ No newline at end of file diff --git a/headers/tools/cppunit/TestUtils.h b/headers/tools/cppunit/TestUtils.h new file mode 100644 index 0000000000..d5a8807c70 --- /dev/null +++ b/headers/tools/cppunit/TestUtils.h @@ -0,0 +1,5 @@ +#include + +#define CHK CPPUNIT_ASSERT +#define RES DecodeResult + diff --git a/headers/tools/cppunit/ThreadManager.h b/headers/tools/cppunit/ThreadManager.h new file mode 100644 index 0000000000..7a58a3664f --- /dev/null +++ b/headers/tools/cppunit/ThreadManager.h @@ -0,0 +1,192 @@ +#ifndef _beos_thread_manager_h_ +#define _beos_thread_manager_h_ + +#include +#include +#include +#include +#include + +//class ThreadedTestCase; +class CppUnit::TestResult; + +// Pointer to a function that takes no parameters and +// returns no result. All threads must be implemented +// in a function of this type. + +// Helper class to handle thread management +template +class BThreadManager { +public: + typedef void (TestClass::*ThreadMethod)(); + + BThreadManager(std::string threadName, TestClass *object, ThreadMethod method); + ~BThreadManager(); + + status_t LaunchThread(CppUnit::TestResult *result); + + // Thread management methods + int32 Stop(); + int32 WaitForThread(); + bool IsRunning(); + + std::string getName() const { return fName; } + +protected: + std::string fName; + TestClass *fObject; + ThreadMethod fMethod; + thread_id fID; + CppUnit::TestResult *fTestResult; + + static long EntryFunction(BThreadManager* manager); + void Run(); + +}; + +template +BThreadManager::BThreadManager( + std::string threadName, + TestClass *object, + ThreadMethod method +) + : fName(threadName) + , fObject(object) + , fMethod(method) + , fID(0) + , fTestResult(NULL) +{ +} + + +template +BThreadManager::~BThreadManager() { + Stop(); +} + + +template +int32 +BThreadManager::WaitForThread() { + int32 result = 0; + if (find_thread(NULL) != fID) + wait_for_thread(fID, &result); + return result; +} + +template +int32 +BThreadManager::Stop() { + int32 result = 0; + if (find_thread(NULL) != fID) { + while (IsRunning()) { + kill(fID, SIGINT); + snooze(1000000); + } + result = WaitForThread(); + } + return result; +} + + +template +bool +BThreadManager::IsRunning(void) { + if (fID != 0) { + thread_info info; + if (get_thread_info(fID, &info) == B_OK) + return true; + else + fID = 0; + } + return false; +} + + +template +status_t +BThreadManager::LaunchThread(CppUnit::TestResult *result) { + if (IsRunning()) + return B_ALREADY_RUNNING; + + fTestResult = result; + fID = spawn_thread((thread_entry)(BThreadManager::EntryFunction), + fName.c_str(), B_NORMAL_PRIORITY, this); + + status_t err; + if (fID == B_NO_MORE_THREADS || fID == B_NO_MEMORY) { + err = fID; + fID = 0; + } else { + err = resume_thread(fID); + } + return err; +} + + +template +long +BThreadManager::EntryFunction(BThreadManager *manager) { + manager->Run(); + return 0; +} + +template +void +BThreadManager::Run(void) { + // These outer try/catch blocks handle unexpected exceptions. + // Said exceptions are caught and noted in the TestResult + // class, but not allowed to escape and disrupt the other + // threads that are assumingly running concurrently. + try { + + // Our parent ThreadedTestCaller should check fObject to be non-NULL, + // but we'll do it here too just to be sure. + if (!fObject) + throw CppUnit::Exception("BThreadManager::Run() -- NULL fObject pointer"); + + // Before running, we need to add this thread's name to + // the object's id->(name,subtestnum) map. + fObject->InitThreadInfo(fID, fName); + + // This inner try/catch block is for expected exceptions. + // If we get through it without an exception, we have to + // raise a different exception that makes note of the fact + // that the exception we were expecting didn't arrive. If + // no exception is expected, then nothing is done (see + // "cppunit/TestCaller.h" for detail on the classes used + // to handle this behaviour). + try { + (fObject->*fMethod)(); + } catch ( ExpectedException & ) { + return; + } + CppUnit::ExpectedExceptionTraits::expectedException(); + + } catch ( CppUnit::Exception &e ) { + // Add on the thread name, then note the exception + CppUnit::Exception *threadException = new CppUnit::Exception( + std::string(e.what()) + " (thread: " + fName + ")", + e.sourceLine() + ); + fTestResult->addFailure( fObject, threadException ); + } + catch ( std::exception &e ) { + // Add on the thread name, then note the exception + CppUnit::Exception *threadException = new CppUnit::Exception( + std::string(e.what()) + " (thread: " + fName + ")" + ); + fTestResult->addError( fObject, threadException ); + } + catch (...) { + // Add on the thread name, then note the exception + CppUnit::Exception *threadException = new CppUnit::Exception( + "caught unknown exception (thread: " + fName + ")" + ); + fTestResult->addError( fObject, threadException ); + } + +} + + +#endif \ No newline at end of file diff --git a/headers/tools/cppunit/ThreadedTestCaller.h b/headers/tools/cppunit/ThreadedTestCaller.h new file mode 100644 index 0000000000..38da64541e --- /dev/null +++ b/headers/tools/cppunit/ThreadedTestCaller.h @@ -0,0 +1,189 @@ +#ifndef _beos_threaded_test_caller_h_ +#define _beos_threaded_test_caller_h_ + +//#include +#include +#include +#include +#include +#include + +class TestResult; + +template +class BThreadedTestCaller : public CppUnit::TestCase { +public: + /*! \brief Pointer to a test function in the given class. + Each ThreadMethod added with addThread() is run in its own thread. + */ + typedef void (TestClass::*ThreadMethod)(); + + BThreadedTestCaller(std::string name); + BThreadedTestCaller(std::string name, TestClass &object); + BThreadedTestCaller(std::string name, TestClass *object); + virtual ~BThreadedTestCaller(); + + virtual void run(CppUnit::TestResult *result); + + //! Adds a thread to the test. \c threadName must be unique to this BThreadedTestCaller. + void addThread(std::string threadName, ThreadMethod method); + +protected: + virtual void setUp(); + virtual void tearDown(); + virtual std::string toString() const; + + typedef std::map *> ThreadManagerMap; + + bool fOwnObject; + TestClass *fObject; + ThreadManagerMap fThreads; +}; + + +template +BThreadedTestCaller::BThreadedTestCaller(std::string name) + : TestCase(name) + , fOwnObject(true) + , fObject(new TestClass()) +{ +} + +template +BThreadedTestCaller::BThreadedTestCaller(std::string name, TestClass &object) + : TestCase(name) + , fOwnObject(false) + , fObject(&object) +{ +} + +template +BThreadedTestCaller::BThreadedTestCaller(std::string name, TestClass *object) + : TestCase(name) + , fOwnObject(true) + , fObject(object) +{ +} + +template +BThreadedTestCaller::~BThreadedTestCaller() { + if (fOwnObject) + delete fObject; + for (ThreadManagerMap::iterator it = fThreads.begin(); it != fThreads.end (); ++it) { + delete it->second; + } +} + +template +void +BThreadedTestCaller::addThread(std::string threadName, ThreadMethod method) { + if (fThreads.find(threadName) == fThreads.end()) { + // Unused name, go ahead and add + fThreads[threadName] = new BThreadManager(threadName, fObject, method); + } else { + // Duplicate name, throw an exception + throw CppUnit::Exception("BThreadedTestCaller::addThread() - Attempt to add thread under duplicated name ('" + + threadName + "')"); + } +} + +template +void +BThreadedTestCaller::run(CppUnit::TestResult *result) { + result->startTest(this); + + try { + setUp(); + + // This try/catch block should never actually have to catch + // anything (unless some bonehead passes in a NULL pointer to + // the constructor). Each BThreadManager object catches and + // handles exceptions for its respective thread, so as not + // to disrupt the others. + try { + + // Verify we have a valid object first. + if (!fObject) + throw CppUnit::Exception("BThreadedTestCaller::runTest() -- NULL fObject pointer"); + + // Launch all the threads. + for (ThreadManagerMap::iterator i = fThreads.begin(); + i != fThreads.end (); + ++i) + { + status_t err = i->second->LaunchThread(result); + if (err != B_OK) + result->addError(this, new CppUnit::Exception("Error launching thread '" + i->second->getName() + "'")); +// printf("Launch(%s)\n", i->second->getName().c_str()); + } + + // Wait for them all to finish, then clean up + for (ThreadManagerMap::iterator i = fThreads.begin(); + i != fThreads.end (); + ++i) + { +// printf("Wait(%s)...", i->second->getName().c_str()); + fflush(stdout); + i->second->WaitForThread(); +// printf("done\n"); + delete i->second; + } + fThreads.clear(); + + } catch ( CppUnit::Exception &e ) { + // Add on the a note that this exception was caught by the + // thread caller (which is a bad thing), then note the exception + CppUnit::Exception *threadException = new CppUnit::Exception( + std::string(e.what()) + " (NOTE: caught by BThreadedTestCaller)", + e.sourceLine() + ); + result->addFailure( fObject, threadException ); + } + catch ( std::exception &e ) { + // Add on the thread name, then note the exception + CppUnit::Exception *threadException = new CppUnit::Exception( + std::string(e.what()) + " (NOTE: caught by BThreadedTestCaller)" + ); + result->addError( fObject, threadException ); + } + catch (...) { + // Add on the thread name, then note the exception + CppUnit::Exception *threadException = new CppUnit::Exception( + "caught unknown exception (NOTE: caught by BThreadedTestCaller)" + ); + result->addError( fObject, threadException ); + } + + snooze(50000); + + try { + tearDown(); + } catch (...) { + result->addError(this, new CppUnit::Exception("tearDown() failed")); + } + } catch (...) { + result->addError(this, new CppUnit::Exception("setUp() failed")); + } // setUp() try/catch block + + result->endTest(this); +} + +template +void +BThreadedTestCaller::setUp() { + fObject->setUp(); +} + +template +void +BThreadedTestCaller::tearDown() { + fObject->tearDown(); +} + +template +std::string +BThreadedTestCaller::toString() const { + return "BThreadedTestCaller for " + getName(); +} + +#endif // _beos_threaded_test_caller_h_ diff --git a/headers/tools/cppunit/ThreadedTestCase.h b/headers/tools/cppunit/ThreadedTestCase.h new file mode 100644 index 0000000000..a7897b323d --- /dev/null +++ b/headers/tools/cppunit/ThreadedTestCase.h @@ -0,0 +1,38 @@ +#ifndef _beos_threaded_test_case_h_ +#define _beos_threaded_test_case_h_ + +#include +#include +#include +#include +#include + +//! Base class for single threaded unit tests +class BThreadedTestCase : public BTestCase { +public: + BThreadedTestCase(std::string Name = "", std::string progressSeparator = "."); + virtual ~BThreadedTestCase(); + + /*! \brief Displays the next sub test progress indicator for the + thread in which it's called (i.e. [A.0][B.0][A.1][A.2][B.1]...). */ + virtual void NextSubTest(); + + //! Saves the location of the current working directory. + void SaveCWD(); + + //! Restores the current working directory to last directory saved by a call to SaveCWD(). + void RestoreCWD(const char *alternate = NULL); + void InitThreadInfo(thread_id id, std::string threadName); +protected: +// friend class ThreadManager; + std::string fProgressSeparator; + + struct ThreadSubTestInfo { + std::string name; + int32 subTestNum; + }; + std::map fNumberMap; + BLocker *fNumberMapLock; +}; + +#endif // _beos_threaded_test_case_h_