Fixed multithreaded NextSubTest() output problems

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@157 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Tyler Dauwalder
2002-07-12 20:30:45 +00:00
parent 40ea65d602
commit 0a99bdf489
6 changed files with 131 additions and 20 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
#ifndef _beos_synchronization_object_h_ #ifndef _beos_locker_sync_object_h_
#define _beos_synchronization_object_h_ #define _beos_locker_sync_object_h_
#include <cppunit/SynchronizedObject.h> #include <cppunit/SynchronizedObject.h>
#include <Locker.h> #include <Locker.h>
+11 -3
View File
@@ -20,7 +20,7 @@ class BThreadManager {
public: public:
typedef void (TestClass::*ThreadMethod)(); typedef void (TestClass::*ThreadMethod)();
BThreadManager(std::string threadName, TestClass *object, ThreadMethod method); BThreadManager(std::string threadName, TestClass *object, ThreadMethod method, sem_id &threadSem);
~BThreadManager(); ~BThreadManager();
status_t LaunchThread(CppUnit::TestResult *result); status_t LaunchThread(CppUnit::TestResult *result);
@@ -38,6 +38,7 @@ protected:
ThreadMethod fMethod; ThreadMethod fMethod;
thread_id fID; thread_id fID;
CppUnit::TestResult *fTestResult; CppUnit::TestResult *fTestResult;
sem_id &fThreadSem;
static long EntryFunction(BThreadManager<TestClass, ExpectedException>* manager); static long EntryFunction(BThreadManager<TestClass, ExpectedException>* manager);
void Run(); void Run();
@@ -48,13 +49,15 @@ template <class TestClass, class ExpectedException>
BThreadManager<TestClass, ExpectedException>::BThreadManager( BThreadManager<TestClass, ExpectedException>::BThreadManager(
std::string threadName, std::string threadName,
TestClass *object, TestClass *object,
ThreadMethod method ThreadMethod method,
sem_id &threadSem
) )
: fName(threadName) : fName(threadName)
, fObject(object) , fObject(object)
, fMethod(method) , fMethod(method)
, fID(0) , fID(0)
, fTestResult(NULL) , fTestResult(NULL)
, fThreadSem(threadSem)
{ {
} }
@@ -118,6 +121,9 @@ BThreadManager<TestClass, ExpectedException>::LaunchThread(CppUnit::TestResult *
err = fID; err = fID;
fID = 0; fID = 0;
} else { } 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); err = resume_thread(fID);
} }
return err; return err;
@@ -185,7 +191,9 @@ BThreadManager<TestClass, ExpectedException>::Run(void) {
); );
fTestResult->addError( fObject, threadException ); fTestResult->addError( fObject, threadException );
} }
// Release the semaphore we acquired earlier
release_sem(fThreadSem);
} }
+70 -6
View File
@@ -37,7 +37,10 @@ protected:
bool fOwnObject; bool fOwnObject;
TestClass *fObject; TestClass *fObject;
ThreadManagerMap fThreads; ThreadManagerMap fThreads;
sem_id fThreadSem;
}; };
@@ -46,6 +49,7 @@ BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::stri
: TestCase(name) : TestCase(name)
, fOwnObject(true) , fOwnObject(true)
, fObject(new TestClass()) , fObject(new TestClass())
, fThreadSem(-1)
{ {
} }
@@ -54,6 +58,7 @@ BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::stri
: TestCase(name) : TestCase(name)
, fOwnObject(false) , fOwnObject(false)
, fObject(&object) , fObject(&object)
, fThreadSem(-1)
{ {
} }
@@ -62,6 +67,7 @@ BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::stri
: TestCase(name) : TestCase(name)
, fOwnObject(true) , fOwnObject(true)
, fObject(object) , fObject(object)
, fThreadSem(-1)
{ {
} }
@@ -73,13 +79,14 @@ BThreadedTestCaller<TestClass, ExpectedException>::~BThreadedTestCaller() {
delete it->second; delete it->second;
} }
} }
template <class TestClass, class ExpectedException> template <class TestClass, class ExpectedException>
void void
BThreadedTestCaller<TestClass, ExpectedException>::addThread(std::string threadName, ThreadMethod method) { BThreadedTestCaller<TestClass, ExpectedException>::addThread(std::string threadName, ThreadMethod method) {
if (fThreads.find(threadName) == fThreads.end()) { if (fThreads.find(threadName) == fThreads.end()) {
// Unused name, go ahead and add // Unused name, go ahead and add
fThreads[threadName] = new BThreadManager<TestClass, ExpectedException>(threadName, fObject, method); fThreads[threadName] = new BThreadManager<TestClass, ExpectedException>(threadName, fObject, method, fThreadSem);
} else { } else {
// Duplicate name, throw an exception // Duplicate name, throw an exception
throw CppUnit::Exception("BThreadedTestCaller::addThread() - Attempt to add thread under duplicated name ('" throw CppUnit::Exception("BThreadedTestCaller::addThread() - Attempt to add thread under duplicated name ('"
@@ -91,6 +98,9 @@ template <class TestClass, class ExpectedException>
void void
BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *result) { BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *result) {
result->startTest(this); result->startTest(this);
if (fThreads.size() <= 0)
throw CppUnit::Exception("BThreadedTestCaller::run() -- No threads added to BThreadedTestCaller()");
try { try {
setUp(); setUp();
@@ -101,10 +111,18 @@ BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *resu
// handles exceptions for its respective thread, so as not // handles exceptions for its respective thread, so as not
// to disrupt the others. // to disrupt the others.
try { try {
// Create our thread semaphore. This semaphore is used to
// Verify we have a valid object first. // determine when all the threads have finished executing,
if (!fObject) // while still allowing *this* thread to handle printing
throw CppUnit::Exception("BThreadedTestCaller::runTest() -- NULL fObject pointer"); // 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. // Launch all the threads.
for (ThreadManagerMap::iterator i = fThreads.begin(); for (ThreadManagerMap::iterator i = fThreads.begin();
@@ -117,6 +135,44 @@ BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *resu
// printf("Launch(%s)\n", i->second->getName().c_str()); // 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<std::string> &list = fObject->AcquireUpdateList();
for (std::vector<std::string>::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 // Wait for them all to finish, then clean up
for (ThreadManagerMap::iterator i = fThreads.begin(); for (ThreadManagerMap::iterator i = fThreads.begin();
i != fThreads.end (); i != fThreads.end ();
@@ -128,6 +184,8 @@ BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *resu
// printf("done\n"); // printf("done\n");
delete i->second; delete i->second;
} }
*/
fThreads.clear(); fThreads.clear();
} catch ( CppUnit::Exception &e ) { } catch ( CppUnit::Exception &e ) {
@@ -171,6 +229,12 @@ BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *resu
template <class TestClass, class ExpectedException> template <class TestClass, class ExpectedException>
void void
BThreadedTestCaller<TestClass, ExpectedException>::setUp() { BThreadedTestCaller<TestClass, ExpectedException>::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(); fObject->setUp();
} }
+11 -1
View File
@@ -6,6 +6,7 @@
#include <TestCase.h> #include <TestCase.h>
#include <map> #include <map>
#include <string> #include <string>
#include <vector>
//! Base class for single threaded unit tests //! Base class for single threaded unit tests
class BThreadedTestCase : public BTestCase { class BThreadedTestCase : public BTestCase {
@@ -23,7 +24,14 @@ public:
//! Restores the current working directory to last directory saved by a call to SaveCWD(). //! Restores the current working directory to last directory saved by a call to SaveCWD().
void RestoreCWD(const char *alternate = NULL); void RestoreCWD(const char *alternate = NULL);
void InitThreadInfo(thread_id id, std::string threadName); void InitThreadInfo(thread_id id, std::string threadName);
bool RegisterForUse();
void UnregisterForUse();
std::vector<std::string>& AcquireUpdateList();
void ReleaseUpdateList();
protected: protected:
bool fInUse;
// friend class ThreadManager<BThreadedTestCase>; // friend class ThreadManager<BThreadedTestCase>;
std::string fProgressSeparator; std::string fProgressSeparator;
@@ -32,7 +40,9 @@ protected:
int32 subTestNum; int32 subTestNum;
}; };
std::map<thread_id, ThreadSubTestInfo*> fNumberMap; std::map<thread_id, ThreadSubTestInfo*> fNumberMap;
BLocker *fNumberMapLock; std::vector<std::string> fUpdateList;
BLocker *fUpdateLock;
}; };
#endif // _beos_threaded_test_case_h_ #endif // _beos_threaded_test_case_h_
+1 -1
View File
@@ -22,7 +22,7 @@ BTestCase::NextSubTest() {
void void
BTestCase::NextSubTestBlock() { BTestCase::NextSubTestBlock() {
// printf("\n"); printf("\n");
} }
/*! To return to the last saved working directory, call RestoreCWD(). */ /*! To return to the last saved working directory, call RestoreCWD(). */
+36 -7
View File
@@ -1,16 +1,17 @@
#include <ThreadedTestCase.h> #include <ThreadedTestCase.h>
#include <SafetyLock.h> #include <Autolock.h>
#include <stdio.h>
BThreadedTestCase::BThreadedTestCase(std::string name, std::string progressSeparator) BThreadedTestCase::BThreadedTestCase(std::string name, std::string progressSeparator)
: BTestCase(name) : BTestCase(name)
, fProgressSeparator(progressSeparator) , fProgressSeparator(progressSeparator)
, fNumberMapLock(new BLocker()) , fUpdateLock(new BLocker())
{ {
} }
BThreadedTestCase::~BThreadedTestCase() { BThreadedTestCase::~BThreadedTestCase() {
// Kill our locker // Kill our locker
delete fNumberMapLock; delete fUpdateLock;
// Clean up // Clean up
for (std::map<thread_id, ThreadSubTestInfo*>::iterator i = fNumberMap.begin(); for (std::map<thread_id, ThreadSubTestInfo*>::iterator i = fNumberMap.begin();
@@ -27,13 +28,16 @@ BThreadedTestCase::NextSubTest() {
thread_id id = find_thread(NULL); thread_id id = find_thread(NULL);
{ {
// Lock the number map // Acquire the update lock
SafetyLock lock(fNumberMapLock); BAutolock lock(fUpdateLock);
std::map<thread_id, ThreadSubTestInfo*>::iterator i = fNumberMap.find(id); std::map<thread_id, ThreadSubTestInfo*>::iterator i = fNumberMap.find(id);
if (i != fNumberMap.end() && i->second) { if (i != fNumberMap.end() && i->second) {
// Handle multi-threaded case // Handle multi-threaded case
ThreadSubTestInfo *info = i->second; 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; return;
} }
} }
@@ -44,7 +48,7 @@ BThreadedTestCase::NextSubTest() {
void void
BThreadedTestCase::InitThreadInfo(thread_id id, std::string threadName) { BThreadedTestCase::InitThreadInfo(thread_id id, std::string threadName) {
SafetyLock lock(fNumberMapLock); // Lock the number map BAutolock lock(fUpdateLock); // Lock the number map
std::map<thread_id, ThreadSubTestInfo*>::iterator i = fNumberMap.find(id); std::map<thread_id, ThreadSubTestInfo*>::iterator i = fNumberMap.find(id);
if (i != fNumberMap.end() && i->second) { if (i != fNumberMap.end() && i->second) {
i->second->name = threadName; i->second->name = threadName;
@@ -57,3 +61,28 @@ BThreadedTestCase::InitThreadInfo(thread_id id, std::string threadName) {
fNumberMap[id] = info; fNumberMap[id] = info;
} }
} }
bool
BThreadedTestCase::RegisterForUse() {
if (!fInUse) {
fInUse = true;
return true;
} else
return false;
}
void
BThreadedTestCase::UnregisterForUse() {
fInUse = false;
}
std::vector<std::string>&
BThreadedTestCase::AcquireUpdateList() {
fUpdateLock->Lock();
return fUpdateList;
}
void
BThreadedTestCase::ReleaseUpdateList() {
fUpdateLock->Unlock();
}