]>
Commit | Line | Data |
---|---|---|
1 | /////////////////////////////////////////////////////////////////////////////// | |
2 | // Name: tests/thread/misc.cpp | |
3 | // Purpose: Miscellaneous wxThread test cases | |
4 | // Author: Francesco Montorsi (extracted from console sample) | |
5 | // Created: 2010-05-10 | |
6 | // RCS-ID: $Id$ | |
7 | // Copyright: (c) 2010 wxWidgets team | |
8 | /////////////////////////////////////////////////////////////////////////////// | |
9 | ||
10 | // ---------------------------------------------------------------------------- | |
11 | // headers | |
12 | // ---------------------------------------------------------------------------- | |
13 | ||
14 | #include "testprec.h" | |
15 | ||
16 | #ifdef __BORLANDC__ | |
17 | #pragma hdrstop | |
18 | #endif | |
19 | ||
20 | #ifndef WX_PRECOMP | |
21 | #endif // WX_PRECOMP | |
22 | ||
23 | #include "wx/thread.h" | |
24 | #include "wx/utils.h" | |
25 | ||
26 | // ---------------------------------------------------------------------------- | |
27 | // globals | |
28 | // ---------------------------------------------------------------------------- | |
29 | ||
30 | static size_t gs_counter = (size_t)-1; | |
31 | static wxCriticalSection gs_critsect; | |
32 | static wxSemaphore gs_cond; | |
33 | ||
34 | class MyJoinableThread : public wxThread | |
35 | { | |
36 | public: | |
37 | MyJoinableThread(size_t n) : wxThread(wxTHREAD_JOINABLE) | |
38 | { m_n = n; Create(); } | |
39 | ||
40 | // thread execution starts here | |
41 | virtual ExitCode Entry(); | |
42 | ||
43 | private: | |
44 | size_t m_n; | |
45 | }; | |
46 | ||
47 | wxThread::ExitCode MyJoinableThread::Entry() | |
48 | { | |
49 | unsigned long res = 1; | |
50 | for ( size_t n = 1; n < m_n; n++ ) | |
51 | { | |
52 | res *= n; | |
53 | ||
54 | // it's a loooong calculation :-) | |
55 | wxMilliSleep(100); | |
56 | } | |
57 | ||
58 | return (ExitCode)res; | |
59 | } | |
60 | ||
61 | class MyDetachedThread : public wxThread | |
62 | { | |
63 | public: | |
64 | MyDetachedThread(size_t n, wxChar ch) | |
65 | { | |
66 | m_n = n; | |
67 | m_ch = ch; | |
68 | m_cancelled = false; | |
69 | ||
70 | Create(); | |
71 | } | |
72 | ||
73 | // thread execution starts here | |
74 | virtual ExitCode Entry(); | |
75 | ||
76 | // and stops here | |
77 | virtual void OnExit(); | |
78 | ||
79 | private: | |
80 | size_t m_n; // number of characters to write | |
81 | wxChar m_ch; // character to write | |
82 | ||
83 | bool m_cancelled; // false if we exit normally | |
84 | }; | |
85 | ||
86 | wxThread::ExitCode MyDetachedThread::Entry() | |
87 | { | |
88 | { | |
89 | wxCriticalSectionLocker lock(gs_critsect); | |
90 | if ( gs_counter == (size_t)-1 ) | |
91 | gs_counter = 1; | |
92 | else | |
93 | gs_counter++; | |
94 | } | |
95 | ||
96 | for ( size_t n = 0; n < m_n; n++ ) | |
97 | { | |
98 | if ( TestDestroy() ) | |
99 | { | |
100 | m_cancelled = true; | |
101 | ||
102 | break; | |
103 | } | |
104 | ||
105 | //wxPutchar(m_ch); | |
106 | //fflush(stdout); | |
107 | ||
108 | wxMilliSleep(100); | |
109 | } | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | void MyDetachedThread::OnExit() | |
115 | { | |
116 | //wxLogTrace(wxT("thread"), wxT("Thread %ld is in OnExit"), GetId()); | |
117 | ||
118 | wxCriticalSectionLocker lock(gs_critsect); | |
119 | if ( !--gs_counter && !m_cancelled ) | |
120 | gs_cond.Post(); | |
121 | } | |
122 | ||
123 | class MyWaitingThread : public wxThread | |
124 | { | |
125 | public: | |
126 | MyWaitingThread( wxMutex *mutex, wxCondition *condition ) | |
127 | { | |
128 | m_mutex = mutex; | |
129 | m_condition = condition; | |
130 | ||
131 | Create(); | |
132 | } | |
133 | ||
134 | virtual ExitCode Entry() | |
135 | { | |
136 | //wxPrintf(wxT("Thread %lu has started running.\n"), GetId()); | |
137 | gs_cond.Post(); | |
138 | ||
139 | //wxPrintf(wxT("Thread %lu starts to wait...\n"), GetId()); | |
140 | ||
141 | m_mutex->Lock(); | |
142 | m_condition->Wait(); | |
143 | m_mutex->Unlock(); | |
144 | ||
145 | //wxPrintf(wxT("Thread %lu finished to wait, exiting.\n"), GetId()); | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | private: | |
151 | wxMutex *m_mutex; | |
152 | wxCondition *m_condition; | |
153 | }; | |
154 | ||
155 | // semaphore tests | |
156 | #include "wx/datetime.h" | |
157 | ||
158 | class MySemaphoreThread : public wxThread | |
159 | { | |
160 | public: | |
161 | MySemaphoreThread(int i, wxSemaphore *sem) | |
162 | : wxThread(wxTHREAD_JOINABLE), | |
163 | m_sem(sem), | |
164 | m_i(i) | |
165 | { | |
166 | Create(); | |
167 | } | |
168 | ||
169 | virtual ExitCode Entry() | |
170 | { | |
171 | //wxPrintf(wxT("%s: Thread #%d (%ld) starting to wait for semaphore...\n"), | |
172 | // wxDateTime::Now().FormatTime().c_str(), m_i, (long)GetId()); | |
173 | ||
174 | m_sem->Wait(); | |
175 | ||
176 | //wxPrintf(wxT("%s: Thread #%d (%ld) acquired the semaphore.\n"), | |
177 | // wxDateTime::Now().FormatTime().c_str(), m_i, (long)GetId()); | |
178 | ||
179 | Sleep(1000); | |
180 | ||
181 | //wxPrintf(wxT("%s: Thread #%d (%ld) releasing the semaphore.\n"), | |
182 | // wxDateTime::Now().FormatTime().c_str(), m_i, (long)GetId()); | |
183 | ||
184 | m_sem->Post(); | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
189 | private: | |
190 | wxSemaphore *m_sem; | |
191 | int m_i; | |
192 | }; | |
193 | ||
194 | WX_DEFINE_ARRAY_PTR(wxThread *, ArrayThreads); | |
195 | ||
196 | // ---------------------------------------------------------------------------- | |
197 | // test class | |
198 | // ---------------------------------------------------------------------------- | |
199 | ||
200 | class MiscThreadTestCase : public CppUnit::TestCase | |
201 | { | |
202 | public: | |
203 | MiscThreadTestCase(); | |
204 | ||
205 | private: | |
206 | CPPUNIT_TEST_SUITE( MiscThreadTestCase ); | |
207 | CPPUNIT_TEST( TestJoinable ); | |
208 | CPPUNIT_TEST( TestDetached ); | |
209 | CPPUNIT_TEST( TestThreadSuspend ); | |
210 | CPPUNIT_TEST( TestThreadDelete ); | |
211 | CPPUNIT_TEST( TestThreadRun ); | |
212 | CPPUNIT_TEST( TestThreadConditions ); | |
213 | CPPUNIT_TEST( TestSemaphore ); | |
214 | CPPUNIT_TEST_SUITE_END(); | |
215 | ||
216 | void TestJoinable(); | |
217 | void TestDetached(); | |
218 | void TestSemaphore(); | |
219 | ||
220 | void TestThreadSuspend(); | |
221 | void TestThreadDelete(); | |
222 | void TestThreadRun(); | |
223 | void TestThreadConditions(); | |
224 | ||
225 | DECLARE_NO_COPY_CLASS(MiscThreadTestCase) | |
226 | }; | |
227 | ||
228 | // register in the unnamed registry so that these tests are run by default | |
229 | CPPUNIT_TEST_SUITE_REGISTRATION( MiscThreadTestCase ); | |
230 | ||
231 | // also include in its own registry so that these tests can be run alone | |
232 | CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MiscThreadTestCase, "MiscThreadTestCase" ); | |
233 | ||
234 | MiscThreadTestCase::MiscThreadTestCase() | |
235 | { | |
236 | int nCPUs = wxThread::GetCPUCount(); | |
237 | if ( nCPUs != -1 ) | |
238 | wxThread::SetConcurrency(nCPUs); | |
239 | } | |
240 | ||
241 | void MiscThreadTestCase::TestJoinable() | |
242 | { | |
243 | // calc 10! in the background | |
244 | MyJoinableThread thread(10); | |
245 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread.Run() ); | |
246 | CPPUNIT_ASSERT_EQUAL( 362880, (unsigned long)thread.Wait() ); | |
247 | } | |
248 | ||
249 | void MiscThreadTestCase::TestDetached() | |
250 | { | |
251 | static const size_t nThreads = 3; | |
252 | MyDetachedThread *threads[nThreads]; | |
253 | ||
254 | size_t n; | |
255 | for ( n = 0; n < nThreads; n++ ) | |
256 | { | |
257 | threads[n] = new MyDetachedThread(10, 'A' + n); | |
258 | } | |
259 | ||
260 | threads[0]->SetPriority(WXTHREAD_MIN_PRIORITY); | |
261 | threads[1]->SetPriority(WXTHREAD_MAX_PRIORITY); | |
262 | ||
263 | for ( n = 0; n < nThreads; n++ ) | |
264 | { | |
265 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, threads[n]->Run() ); | |
266 | } | |
267 | ||
268 | // wait until all threads terminate | |
269 | CPPUNIT_ASSERT_EQUAL( wxSEMA_NO_ERROR, gs_cond.Wait() ); | |
270 | } | |
271 | ||
272 | void MiscThreadTestCase::TestSemaphore() | |
273 | { | |
274 | static const int SEM_LIMIT = 3; | |
275 | ||
276 | wxSemaphore sem(SEM_LIMIT, SEM_LIMIT); | |
277 | ArrayThreads threads; | |
278 | ||
279 | for ( int i = 0; i < 3*SEM_LIMIT; i++ ) | |
280 | { | |
281 | threads.Add(new MySemaphoreThread(i, &sem)); | |
282 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, threads.Last()->Run() ); | |
283 | } | |
284 | ||
285 | for ( size_t n = 0; n < threads.GetCount(); n++ ) | |
286 | { | |
287 | CPPUNIT_ASSERT_EQUAL( 0, (long)threads[n]->Wait() ); | |
288 | delete threads[n]; | |
289 | } | |
290 | } | |
291 | ||
292 | void MiscThreadTestCase::TestThreadSuspend() | |
293 | { | |
294 | MyDetachedThread *thread = new MyDetachedThread(15, 'X'); | |
295 | ||
296 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread->Run() ); | |
297 | ||
298 | // this is for this demo only, in a real life program we'd use another | |
299 | // condition variable which would be signaled from wxThread::Entry() to | |
300 | // tell us that the thread really started running - but here just wait a | |
301 | // bit and hope that it will be enough (the problem is, of course, that | |
302 | // the thread might still not run when we call Pause() which will result | |
303 | // in an error) | |
304 | wxMilliSleep(300); | |
305 | ||
306 | for ( size_t n = 0; n < 3; n++ ) | |
307 | { | |
308 | thread->Pause(); | |
309 | ||
310 | if ( n > 0 ) | |
311 | { | |
312 | // don't sleep but resume immediately the first time | |
313 | wxMilliSleep(300); | |
314 | } | |
315 | ||
316 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread->Resume() ); | |
317 | } | |
318 | ||
319 | // wait until the thread terminates | |
320 | CPPUNIT_ASSERT_EQUAL( wxSEMA_NO_ERROR, gs_cond.Wait() ); | |
321 | } | |
322 | ||
323 | void MiscThreadTestCase::TestThreadDelete() | |
324 | { | |
325 | // FIXME: | |
326 | // As above, using Sleep() is only for testing here - we must use some | |
327 | // synchronisation object instead to ensure that the thread is still | |
328 | // running when we delete it - deleting a detached thread which already | |
329 | // terminated will lead to a crash! | |
330 | ||
331 | MyDetachedThread *thread0 = new MyDetachedThread(30, 'W'); | |
332 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_MISC_ERROR, thread0->Delete() ); | |
333 | // delete a thread which didn't start to run yet. | |
334 | ||
335 | MyDetachedThread *thread1 = new MyDetachedThread(30, 'Y'); | |
336 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread1->Run() ); | |
337 | wxMilliSleep(300); | |
338 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread1->Delete() ); | |
339 | // delete a running thread | |
340 | ||
341 | MyDetachedThread *thread2 = new MyDetachedThread(30, 'Z'); | |
342 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread2->Run() ); | |
343 | wxMilliSleep(300); | |
344 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread2->Pause() ); | |
345 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread2->Delete() ); | |
346 | // delete a sleeping thread | |
347 | ||
348 | MyJoinableThread thread3(20); | |
349 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread3.Run() ); | |
350 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread3.Delete() ); | |
351 | // delete a joinable running thread | |
352 | ||
353 | MyJoinableThread thread4(2); | |
354 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread4.Run() ); | |
355 | wxMilliSleep(300); | |
356 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread4.Delete() ); | |
357 | // delete a joinable thread which already terminated | |
358 | } | |
359 | ||
360 | void MiscThreadTestCase::TestThreadRun() | |
361 | { | |
362 | MyJoinableThread thread1(2); | |
363 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, thread1.Run() ); | |
364 | thread1.Wait(); // wait until the thread ends | |
365 | ||
366 | // verify that running twice the same thread fails | |
367 | WX_ASSERT_FAILS_WITH_ASSERT( thread1.Run() ); | |
368 | } | |
369 | ||
370 | void MiscThreadTestCase::TestThreadConditions() | |
371 | { | |
372 | wxMutex mutex; | |
373 | wxCondition condition(mutex); | |
374 | ||
375 | // otherwise its difficult to understand which log messages pertain to | |
376 | // which condition | |
377 | //wxLogTrace(wxT("thread"), wxT("Local condition var is %08x, gs_cond = %08x"), | |
378 | // condition.GetId(), gs_cond.GetId()); | |
379 | ||
380 | // create and launch threads | |
381 | MyWaitingThread *threads[10]; | |
382 | ||
383 | size_t n; | |
384 | for ( n = 0; n < WXSIZEOF(threads); n++ ) | |
385 | { | |
386 | threads[n] = new MyWaitingThread( &mutex, &condition ); | |
387 | } | |
388 | ||
389 | for ( n = 0; n < WXSIZEOF(threads); n++ ) | |
390 | { | |
391 | CPPUNIT_ASSERT_EQUAL( wxTHREAD_NO_ERROR, threads[n]->Run() ); | |
392 | } | |
393 | ||
394 | // wait until all threads run | |
395 | // NOTE: main thread is waiting for the other threads to start | |
396 | size_t nRunning = 0; | |
397 | while ( nRunning < WXSIZEOF(threads) ) | |
398 | { | |
399 | CPPUNIT_ASSERT_EQUAL( wxSEMA_NO_ERROR, gs_cond.Wait() ); | |
400 | ||
401 | nRunning++; | |
402 | ||
403 | // note that main thread is already running | |
404 | } | |
405 | ||
406 | wxMilliSleep(500); | |
407 | ||
408 | #if 1 | |
409 | // now wake one of them up | |
410 | CPPUNIT_ASSERT_EQUAL( wxCOND_NO_ERROR, condition.Signal() ); | |
411 | #endif | |
412 | ||
413 | wxMilliSleep(200); | |
414 | ||
415 | // wake all the (remaining) threads up, so that they can exit | |
416 | CPPUNIT_ASSERT_EQUAL( wxCOND_NO_ERROR, condition.Broadcast() ); | |
417 | ||
418 | // give them time to terminate (dirty!) | |
419 | wxMilliSleep(500); | |
420 | } |