1#ifndef _beos_threaded_test_caller_h_
2#define _beos_threaded_test_caller_h_
3
4//#include <memory>
5#include <cppunit/TestCase.h>
6#include <cppunit/TestResult.h>
7#include <cppunit/TestCaller.h>
8#include <TestShell.h>
9#include <ThreadManager.h>
10#include <map>
11#include <vector>
12#include <stdio.h>
13
14class TestResult;
15
16template <class TestClass, class ExpectedException = CppUnit::NoExceptionExpected>
17class CPPUNIT_API BThreadedTestCaller : public CppUnit::TestCase {
18public:
19	/*! \brief Pointer to a test function in the given class.
20		Each ThreadMethod added with addThread() is run in its own thread.
21	*/
22	typedef void (TestClass::*ThreadMethod)();
23
24	BThreadedTestCaller(std::string name);
25	BThreadedTestCaller(std::string name, TestClass &object);
26	BThreadedTestCaller(std::string name, TestClass *object);
27	virtual ~BThreadedTestCaller();
28
29    virtual CppUnit::TestResult *run();
30    virtual void run(CppUnit::TestResult *result);
31
32	//! Adds a thread to the test. \c threadName must be unique to this BThreadedTestCaller.
33    void addThread(std::string threadName, ThreadMethod method);
34
35protected:
36	virtual void setUp();
37	virtual void tearDown();
38	virtual std::string toString() const;
39
40	typedef std::map<std::string, BThreadManager<TestClass, ExpectedException> *> ThreadManagerMap;
41
42	bool fOwnObject;
43	TestClass *fObject;
44	ThreadManagerMap fThreads;
45
46	sem_id fThreadSem;
47
48};
49
50
51template <class TestClass, class ExpectedException>
52BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::string name)
53	: TestCase(name)
54	, fOwnObject(true)
55	, fObject(new TestClass())
56	, fThreadSem(-1)
57{
58}
59
60template <class TestClass, class ExpectedException>
61BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::string name, TestClass &object)
62	: TestCase(name)
63	, fOwnObject(false)
64	, fObject(&object)
65	, fThreadSem(-1)
66{
67}
68
69template <class TestClass, class ExpectedException>
70BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::string name, TestClass *object)
71	: TestCase(name)
72	, fOwnObject(true)
73	, fObject(object)
74	, fThreadSem(-1)
75{
76}
77
78template <class TestClass, class ExpectedException>
79BThreadedTestCaller<TestClass, ExpectedException>::~BThreadedTestCaller() {
80	if (fOwnObject)
81		delete fObject;
82	for (typename ThreadManagerMap::iterator it = fThreads.begin(); it != fThreads.end (); ++it) {
83		delete it->second;
84	}
85}
86
87
88template <class TestClass, class ExpectedException>
89void
90BThreadedTestCaller<TestClass, ExpectedException>::addThread(std::string threadName, ThreadMethod method) {
91	if (fThreads.find(threadName) == fThreads.end()) {
92		// Unused name, go ahead and add
93		fThreads[threadName] = new BThreadManager<TestClass, ExpectedException>(threadName, fObject, method, fThreadSem);
94	} else {
95		// Duplicate name, throw an exception
96		throw CppUnit::Exception("BThreadedTestCaller::addThread() - Attempt to add thread under duplicated name ('"
97			+ threadName + "')");
98	}
99}
100
101template <class TestClass, class ExpectedException>
102CppUnit::TestResult *
103BThreadedTestCaller<TestClass, ExpectedException>::run() {
104	CppUnit::TestResult *result = new CppUnit::TestResult;
105	run(result);
106	return result;
107}
108
109template <class TestClass, class ExpectedException>
110void
111BThreadedTestCaller<TestClass, ExpectedException>::run(CppUnit::TestResult *result) {
112	result->startTest(this);
113
114	if (fThreads.size() <= 0)
115		throw CppUnit::Exception("BThreadedTestCaller::run() -- No threads added to BThreadedTestCaller()");
116
117	try {
118		setUp();
119
120		// This try/catch block should never actually have to catch
121		// anything (unless some bonehead passes in a NULL pointer to
122		// the constructor). Each BThreadManager object catches and
123		// handles exceptions for its respective thread, so as not
124		// to disrupt the others.
125		try {
126			// Create our thread semaphore. This semaphore is used to
127			// determine when all the threads have finished executing,
128			// while still allowing *this* thread to handle printing
129			// out NextSubTest() info (since other threads don't appear
130			// to be able to output text while the main thread is
131			// blocked; their output appears later...).
132			//
133			// Each thread will acquire the semaphore once when launched,
134			// thus the initial thread count is equal the number of threads.
135			fThreadSem = create_sem(fThreads.size(), "ThreadSem");
136			if (fThreadSem < B_OK)
137				throw CppUnit::Exception("BThreadedTestCaller::run() -- Error creating fThreadSem");
138
139			// Launch all the threads.
140			for (typename ThreadManagerMap::iterator i = fThreads.begin();
141				   i != fThreads.end ();
142	    	         ++i)
143	    	{
144	    		status_t err = i->second->LaunchThread(result);
145				if (err != B_OK)
146					result->addError(this, new CppUnit::Exception("Error launching thread '" + i->second->getName() + "'"));
147//				printf("Launch(%s)\n", i->second->getName().c_str());
148			}
149
150			// Now we loop. Before you faint, there is a reason for this:
151			// Calls to NextSubTest() from other threads don't actually
152			// print anything while the main thread is blocked waiting
153			// for another thread. Thus, we have NextSubTest() add the
154			// information to be printed into a queue. The main thread
155			// (this code right here), blocks on a semaphore that it
156			// can only acquire after all the test threads have terminated.
157			// If it times out, it checks the NextSubTest() queue, prints
158			// any pending updates, and tries to acquire the semaphore
159			// again. When it finally manages to acquire it, all the
160			// test threads have terminated, and it's safe to clean up.
161
162			status_t err;
163			do {
164				// Try to acquire the semaphore
165				err = acquire_sem_etc(fThreadSem, fThreads.size(), B_RELATIVE_TIMEOUT,	500000);
166
167				// Empty the UpdateList
168				std::vector<std::string> &list = fObject->AcquireUpdateList();
169				for (std::vector<std::string>::iterator i = list.begin();
170					   i != list.end();
171					     i++)
172				{
173					// Only print to standard out if the current global shell
174					// lets us (or if no global shell is designated).
175					if (BTestShell::GlobalBeVerbose()) {
176						printf("%s", (*i).c_str());
177						fflush(stdout);
178					}
179				}
180				list.clear();
181				fObject->ReleaseUpdateList();
182
183			} while (err != B_OK);
184
185			// If we get this far, we actually managed to acquire the semaphore,
186			// so we should release it now.
187			release_sem_etc(fThreadSem, fThreads.size(), 0);
188
189			// Print out a newline for asthetics :-)
190			printf("\n");
191
192/*
193
194
195			// Wait for them all to finish, then clean up
196			for (ThreadManagerMap::iterator i = fThreads.begin();
197				  i != fThreads.end ();
198	    	        ++i)
199			{
200//				printf("Wait(%s)...", i->second->getName().c_str());
201				fflush(stdout);
202				i->second->WaitForThread();
203//				printf("done\n");
204				delete i->second;
205			}
206*/
207
208			fThreads.clear();
209
210		} catch ( CppUnit::Exception &e ) {
211			// Add on the a note that this exception was caught by the
212			// thread caller (which is a bad thing), then note the exception
213	        CppUnit::Exception *threadException = new CppUnit::Exception(
214	        	std::string(e.what()) + " (NOTE: caught by BThreadedTestCaller)",
215	        	e.sourceLine()
216	        );
217			result->addFailure( fObject, threadException );
218		}
219		catch ( std::exception &e ) {
220			// Add on the thread name, then note the exception
221	        CppUnit::Exception *threadException = new CppUnit::Exception(
222	        	std::string(e.what()) + " (NOTE: caught by BThreadedTestCaller)"
223	        );
224			result->addError( fObject, threadException );
225		}
226		catch (...) {
227			// Add on the thread name, then note the exception
228			CppUnit::Exception *threadException = new CppUnit::Exception(
229				"caught unknown exception (NOTE: caught by BThreadedTestCaller)"
230			);
231			result->addError( fObject, threadException );
232		}
233
234		snooze(50000);
235
236		try {
237		    tearDown();
238		} catch (...) {
239			result->addError(this, new CppUnit::Exception("tearDown() failed"));
240		}
241	} catch (...) {
242		result->addError(this, new CppUnit::Exception("setUp() failed"));
243	}	// setUp() try/catch block
244
245	result->endTest(this);
246}
247
248template <class TestClass, class ExpectedException>
249void
250BThreadedTestCaller<TestClass, ExpectedException>::setUp() {
251	// Verify we have a valid object that's not currently in use first.
252	if (!fObject)
253		throw CppUnit::Exception("BThreadedTestCaller::runTest() -- NULL fObject pointer");
254	if (!fObject->RegisterForUse())
255		throw CppUnit::Exception("BThreadedTestCaller::runTest() -- Attempt to reuse ThreadedTestCase object already in use");
256
257	fObject->setUp();
258}
259
260template <class TestClass, class ExpectedException>
261void
262BThreadedTestCaller<TestClass, ExpectedException>::tearDown() {
263	fObject->tearDown();
264}
265
266template <class TestClass, class ExpectedException>
267std::string
268BThreadedTestCaller<TestClass, ExpectedException>::toString() const {
269	return std::string("BThreadedTestCaller for ") + getName();
270}
271
272#endif // _beos_threaded_test_caller_h_
273