From 0a99bdf48974806e4d624b29b7e402fb20e41a77 Mon Sep 17 00:00:00 2001 From: Tyler Dauwalder Date: Fri, 12 Jul 2002 20:30:45 +0000 Subject: [PATCH] Fixed multithreaded NextSubTest() output problems git-svn-id: file:///srv/svn/repos/haiku/trunk/current@157 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/tools/cppunit/LockerSyncObject.h | 4 +- headers/tools/cppunit/ThreadManager.h | 14 +++- headers/tools/cppunit/ThreadedTestCaller.h | 76 ++++++++++++++++++++-- headers/tools/cppunit/ThreadedTestCase.h | 12 +++- src/tools/cppunit/TestCase.cpp | 2 +- src/tools/cppunit/ThreadedTestCase.cpp | 43 ++++++++++-- 6 files changed, 131 insertions(+), 20 deletions(-) diff --git a/headers/tools/cppunit/LockerSyncObject.h b/headers/tools/cppunit/LockerSyncObject.h index b3367d546e..8b1e8208b6 100644 --- a/headers/tools/cppunit/LockerSyncObject.h +++ b/headers/tools/cppunit/LockerSyncObject.h @@ -1,5 +1,5 @@ -#ifndef _beos_synchronization_object_h_ -#define _beos_synchronization_object_h_ +#ifndef _beos_locker_sync_object_h_ +#define _beos_locker_sync_object_h_ #include #include diff --git a/headers/tools/cppunit/ThreadManager.h b/headers/tools/cppunit/ThreadManager.h index 7a58a3664f..7e38468bc3 100644 --- a/headers/tools/cppunit/ThreadManager.h +++ b/headers/tools/cppunit/ThreadManager.h @@ -20,7 +20,7 @@ class BThreadManager { public: typedef void (TestClass::*ThreadMethod)(); - BThreadManager(std::string threadName, TestClass *object, ThreadMethod method); + BThreadManager(std::string threadName, TestClass *object, ThreadMethod method, sem_id &threadSem); ~BThreadManager(); status_t LaunchThread(CppUnit::TestResult *result); @@ -38,6 +38,7 @@ protected: ThreadMethod fMethod; thread_id fID; CppUnit::TestResult *fTestResult; + sem_id &fThreadSem; static long EntryFunction(BThreadManager* manager); void Run(); @@ -48,13 +49,15 @@ template BThreadManager::BThreadManager( std::string threadName, TestClass *object, - ThreadMethod method + ThreadMethod method, + sem_id &threadSem ) : fName(threadName) , fObject(object) , fMethod(method) , fID(0) , fTestResult(NULL) + , fThreadSem(threadSem) { } @@ -118,6 +121,9 @@ BThreadManager::LaunchThread(CppUnit::TestResult * err = fID; fID = 0; } else { + // Aquire the semaphore, then start the thread. + if (acquire_sem(fThreadSem) != B_OK) + throw CppUnit::Exception("BThreadManager::LaunchThread() -- Error acquiring thread semaphore"); err = resume_thread(fID); } return err; @@ -185,7 +191,9 @@ BThreadManager::Run(void) { ); fTestResult->addError( fObject, threadException ); } - + + // Release the semaphore we acquired earlier + release_sem(fThreadSem); } diff --git a/headers/tools/cppunit/ThreadedTestCaller.h b/headers/tools/cppunit/ThreadedTestCaller.h index 38da64541e..7d459ef1d6 100644 --- a/headers/tools/cppunit/ThreadedTestCaller.h +++ b/headers/tools/cppunit/ThreadedTestCaller.h @@ -37,7 +37,10 @@ protected: bool fOwnObject; TestClass *fObject; - ThreadManagerMap fThreads; + ThreadManagerMap fThreads; + + sem_id fThreadSem; + }; @@ -46,6 +49,7 @@ BThreadedTestCaller::BThreadedTestCaller(std::stri : TestCase(name) , fOwnObject(true) , fObject(new TestClass()) + , fThreadSem(-1) { } @@ -54,6 +58,7 @@ BThreadedTestCaller::BThreadedTestCaller(std::stri : TestCase(name) , fOwnObject(false) , fObject(&object) + , fThreadSem(-1) { } @@ -62,6 +67,7 @@ BThreadedTestCaller::BThreadedTestCaller(std::stri : TestCase(name) , fOwnObject(true) , fObject(object) + , fThreadSem(-1) { } @@ -73,13 +79,14 @@ BThreadedTestCaller::~BThreadedTestCaller() { 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); + fThreads[threadName] = new BThreadManager(threadName, fObject, method, fThreadSem); } else { // Duplicate name, throw an exception throw CppUnit::Exception("BThreadedTestCaller::addThread() - Attempt to add thread under duplicated name ('" @@ -91,6 +98,9 @@ template void BThreadedTestCaller::run(CppUnit::TestResult *result) { result->startTest(this); + + if (fThreads.size() <= 0) + throw CppUnit::Exception("BThreadedTestCaller::run() -- No threads added to BThreadedTestCaller()"); try { setUp(); @@ -101,10 +111,18 @@ BThreadedTestCaller::run(CppUnit::TestResult *resu // 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"); + // Create our thread semaphore. This semaphore is used to + // determine when all the threads have finished executing, + // while still allowing *this* thread to handle printing + // out NextSubTest() info (since other threads don't appear + // to be able to output text while the main thread is + // blocked; their output appears later...). + // + // Each thread will acquire the semaphore once when launched, + // thus the initial thread count is equal the number of threads. + fThreadSem = create_sem(fThreads.size(), "ThreadSem"); + if (fThreadSem < B_OK) + throw CppUnit::Exception("BThreadedTestCaller::run() -- Error creating fThreadSem"); // Launch all the threads. for (ThreadManagerMap::iterator i = fThreads.begin(); @@ -117,6 +135,44 @@ BThreadedTestCaller::run(CppUnit::TestResult *resu // printf("Launch(%s)\n", i->second->getName().c_str()); } + // Now we loop. Before you faint, there is a reason for this: + // Calls to NextSubTest() from other threads don't actually + // print anything while the main thread is blocked waiting + // for another thread. Thus, we have NextSubTest() add the + // information to be printed into a queue. The main thread + // (this code right here), blocks on a semaphore that it + // can only acquire after all the test threads have terminated. + // If it times out, it checks the NextSubTest() queue, prints + // any pending updates, and tries to acquire the semaphore + // again. When it finally manages to acquire it, all the + // test threads have terminated, and it's safe to clean up. + + status_t err; + do { + // Try to acquire the semaphore + err = acquire_sem_etc(fThreadSem, fThreads.size(), B_RELATIVE_TIMEOUT, 500000); + + // Empty the UpdateList + std::vector &list = fObject->AcquireUpdateList(); + for (std::vector::iterator i = list.begin(); + i != list.end(); + i++) + { + printf("%s", (*i).c_str()); + fflush(stdout); + } + list.clear(); + fObject->ReleaseUpdateList(); + + } while (err != B_OK); + + // If we get this far, we actually managed to acquire the semaphore, + // so we should release it now. + release_sem_etc(fThreadSem, fThreads.size(), 0); +/* + + + // Wait for them all to finish, then clean up for (ThreadManagerMap::iterator i = fThreads.begin(); i != fThreads.end (); @@ -128,6 +184,8 @@ BThreadedTestCaller::run(CppUnit::TestResult *resu // printf("done\n"); delete i->second; } +*/ + fThreads.clear(); } catch ( CppUnit::Exception &e ) { @@ -171,6 +229,12 @@ BThreadedTestCaller::run(CppUnit::TestResult *resu template void BThreadedTestCaller::setUp() { + // Verify we have a valid object that's not currently in use first. + if (!fObject) + throw CppUnit::Exception("BThreadedTestCaller::runTest() -- NULL fObject pointer"); + if (!fObject->RegisterForUse()) + throw CppUnit::Exception("BThreadedTestCaller::runTest() -- Attempt to reuse ThreadedTestCase object already in use"); + fObject->setUp(); } diff --git a/headers/tools/cppunit/ThreadedTestCase.h b/headers/tools/cppunit/ThreadedTestCase.h index a7897b323d..dbfd8384b1 100644 --- a/headers/tools/cppunit/ThreadedTestCase.h +++ b/headers/tools/cppunit/ThreadedTestCase.h @@ -6,6 +6,7 @@ #include #include #include +#include //! Base class for single threaded unit tests class BThreadedTestCase : public BTestCase { @@ -23,7 +24,14 @@ public: //! 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); + bool RegisterForUse(); + void UnregisterForUse(); + + std::vector& AcquireUpdateList(); + void ReleaseUpdateList(); protected: + bool fInUse; + // friend class ThreadManager; std::string fProgressSeparator; @@ -32,7 +40,9 @@ protected: int32 subTestNum; }; std::map fNumberMap; - BLocker *fNumberMapLock; + std::vector fUpdateList; + BLocker *fUpdateLock; + }; #endif // _beos_threaded_test_case_h_ diff --git a/src/tools/cppunit/TestCase.cpp b/src/tools/cppunit/TestCase.cpp index 72ffca4bcd..278c82edf8 100644 --- a/src/tools/cppunit/TestCase.cpp +++ b/src/tools/cppunit/TestCase.cpp @@ -22,7 +22,7 @@ BTestCase::NextSubTest() { void BTestCase::NextSubTestBlock() { -// printf("\n"); + printf("\n"); } /*! To return to the last saved working directory, call RestoreCWD(). */ diff --git a/src/tools/cppunit/ThreadedTestCase.cpp b/src/tools/cppunit/ThreadedTestCase.cpp index 0bf371c218..a320317377 100644 --- a/src/tools/cppunit/ThreadedTestCase.cpp +++ b/src/tools/cppunit/ThreadedTestCase.cpp @@ -1,16 +1,17 @@ #include -#include +#include +#include BThreadedTestCase::BThreadedTestCase(std::string name, std::string progressSeparator) : BTestCase(name) , fProgressSeparator(progressSeparator) - , fNumberMapLock(new BLocker()) + , fUpdateLock(new BLocker()) { } BThreadedTestCase::~BThreadedTestCase() { // Kill our locker - delete fNumberMapLock; + delete fUpdateLock; // Clean up for (std::map::iterator i = fNumberMap.begin(); @@ -27,13 +28,16 @@ BThreadedTestCase::NextSubTest() { thread_id id = find_thread(NULL); { - // Lock the number map - SafetyLock lock(fNumberMapLock); + // Acquire the update lock + BAutolock lock(fUpdateLock); 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 << "]"; + char num[32]; + sprintf(num, "%ld", info->subTestNum++); + std::string str = std::string("[") + info->name + fProgressSeparator + num + "]"; + fUpdateList.push_back(str); return; } } @@ -44,7 +48,7 @@ BThreadedTestCase::NextSubTest() { void BThreadedTestCase::InitThreadInfo(thread_id id, std::string threadName) { - SafetyLock lock(fNumberMapLock); // Lock the number map + BAutolock lock(fUpdateLock); // Lock the number map std::map::iterator i = fNumberMap.find(id); if (i != fNumberMap.end() && i->second) { i->second->name = threadName; @@ -57,3 +61,28 @@ BThreadedTestCase::InitThreadInfo(thread_id id, std::string threadName) { fNumberMap[id] = info; } } + +bool +BThreadedTestCase::RegisterForUse() { + if (!fInUse) { + fInUse = true; + return true; + } else + return false; +} + +void +BThreadedTestCase::UnregisterForUse() { + fInUse = false; +} + +std::vector& +BThreadedTestCase::AcquireUpdateList() { + fUpdateLock->Lock(); + return fUpdateList; +} + +void +BThreadedTestCase::ReleaseUpdateList() { + fUpdateLock->Unlock(); +}