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