xref: /haiku/headers/tools/cppunit/ThreadedTestCaller.h (revision 58481f0f6ef1a61ba07283f012cafbc2ed874ead)
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 
14 class TestResult;
15 
16 template <class TestClass, class ExpectedException = CppUnit::NoExceptionExpected>
17 class CPPUNIT_API BThreadedTestCaller : public CppUnit::TestCase {
18 public:
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 
35 protected:
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 
51 template <class TestClass, class ExpectedException>
BThreadedTestCaller(std::string name)52 BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::string name)
53 	: TestCase(name)
54 	, fOwnObject(true)
55 	, fObject(new TestClass())
56 	, fThreadSem(-1)
57 {
58 }
59 
60 template <class TestClass, class ExpectedException>
BThreadedTestCaller(std::string name,TestClass & object)61 BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::string name, TestClass &object)
62 	: TestCase(name)
63 	, fOwnObject(false)
64 	, fObject(&object)
65 	, fThreadSem(-1)
66 {
67 }
68 
69 template <class TestClass, class ExpectedException>
BThreadedTestCaller(std::string name,TestClass * object)70 BThreadedTestCaller<TestClass, ExpectedException>::BThreadedTestCaller(std::string name, TestClass *object)
71 	: TestCase(name)
72 	, fOwnObject(true)
73 	, fObject(object)
74 	, fThreadSem(-1)
75 {
76 }
77 
78 template <class TestClass, class ExpectedException>
~BThreadedTestCaller()79 BThreadedTestCaller<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 
88 template <class TestClass, class ExpectedException>
89 void
addThread(std::string threadName,ThreadMethod method)90 BThreadedTestCaller<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 
101 template <class TestClass, class ExpectedException>
102 CppUnit::TestResult *
run()103 BThreadedTestCaller<TestClass, ExpectedException>::run() {
104 	CppUnit::TestResult *result = new CppUnit::TestResult;
105 	run(result);
106 	return result;
107 }
108 
109 template <class TestClass, class ExpectedException>
110 void
run(CppUnit::TestResult * result)111 BThreadedTestCaller<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 
248 template <class TestClass, class ExpectedException>
249 void
setUp()250 BThreadedTestCaller<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 
260 template <class TestClass, class ExpectedException>
261 void
tearDown()262 BThreadedTestCaller<TestClass, ExpectedException>::tearDown() {
263 	fObject->tearDown();
264 }
265 
266 template <class TestClass, class ExpectedException>
267 std::string
toString()268 BThreadedTestCaller<TestClass, ExpectedException>::toString() const {
269 	return std::string("BThreadedTestCaller for ") + getName();
270 }
271 
272 #endif // _beos_threaded_test_caller_h_
273