Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/cppthread
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : // cppthread
21 : //
22 : #include <cppthread/exception.h>
23 : #include <cppthread/fifo.h>
24 : #include <cppthread/guard.h>
25 : #include <cppthread/life.h>
26 : #include <cppthread/log.h>
27 : #include <cppthread/mutex.h>
28 : #include <cppthread/pool.h>
29 : #include <cppthread/runner.h>
30 : #include <cppthread/thread.h>
31 : #include <cppthread/worker.h>
32 :
33 :
34 : // self
35 : //
36 : #include "catch_main.h"
37 :
38 :
39 : // snapdev
40 : //
41 : #include <snapdev/not_reached.h>
42 :
43 :
44 : // C++
45 : //
46 : #include <deque>
47 :
48 :
49 :
50 : namespace
51 : {
52 :
53 :
54 : struct log_message_t
55 : {
56 : typedef std::deque<log_message_t> queue_t;
57 :
58 : cppthread::log_level_t f_level = static_cast<cppthread::log_level_t>(-1); // an invalid level by default
59 : std::string f_message = std::string();
60 : };
61 :
62 :
63 : log_message_t::queue_t g_message_queue = log_message_t::queue_t();
64 : int g_empty_log_queue = 0;
65 : int g_unexpected_log_message = 0;
66 : int g_unexpected_log_level = 0;
67 :
68 :
69 4 : void cppthread_log_callback(cppthread::log_level_t level, std::string const & message)
70 : {
71 4 : cppthread::guard lock(*cppthread::g_system_mutex);
72 :
73 4 : if(g_message_queue.empty())
74 : {
75 2 : ++g_empty_log_queue;
76 : }
77 : else
78 : {
79 2 : if(g_message_queue.front().f_level != level)
80 : {
81 0 : ++g_unexpected_log_level;
82 : }
83 2 : if(g_message_queue.front().f_message != message)
84 : {
85 0 : ++g_unexpected_log_message;
86 : }
87 2 : g_message_queue.pop_front();
88 : }
89 8 : }
90 :
91 :
92 : constexpr int EXIT_THREAD = -1;
93 : constexpr int EXIT_THREAD_WITH_EXCEPTION = -2;
94 :
95 :
96 2 : DECLARE_EXCEPTION(cppthread::cppthread_exception, exit_with_exception);
97 :
98 :
99 : struct data_t
100 : {
101 : typedef std::deque<data_t> queue_t;
102 :
103 : int f_value = 0;
104 :
105 12 : bool operator == (data_t const & rhs)
106 : {
107 12 : return f_value == rhs.f_value;
108 : }
109 : };
110 :
111 :
112 : typedef cppthread::fifo<data_t> fifo_t;
113 :
114 :
115 : class test_runner
116 : : public cppthread::runner
117 : {
118 : public:
119 4 : test_runner()
120 4 : : runner("test-runner")
121 : {
122 4 : }
123 :
124 4 : virtual bool is_ready() const override
125 : {
126 4 : return f_ready;
127 : }
128 :
129 2137 : virtual bool continue_running() const override
130 : {
131 2137 : if(!runner::continue_running())
132 : {
133 0 : return false;
134 : }
135 :
136 2137 : return f_continue_running;
137 : }
138 :
139 3 : virtual void enter() override
140 : {
141 3 : f_entered = true;
142 3 : }
143 :
144 3 : virtual void run() override
145 : {
146 2137 : while(continue_running())
147 : {
148 2137 : data_t data;
149 2137 : if(!f_fifo.pop_front(data, 500))
150 : {
151 2125 : ++f_cycles;
152 2125 : continue;
153 : }
154 12 : if(f_expected.empty())
155 : {
156 0 : ++f_unexpected_content;
157 : }
158 : else
159 : {
160 : //std::cerr << "--- got data: " << data.f_value << " (expects: " << f_expected.front().f_value << ")\n";
161 12 : if(data != f_expected.front())
162 : {
163 0 : ++f_invalid_content;
164 : }
165 : else
166 : {
167 12 : switch(data.f_value)
168 : {
169 1 : case EXIT_THREAD:
170 1 : return;
171 :
172 2 : case EXIT_THREAD_WITH_EXCEPTION:
173 2 : throw exit_with_exception("testing thread exiting with exception.");
174 :
175 : }
176 : }
177 9 : f_expected.pop_front();
178 : }
179 : }
180 :
181 0 : f_stopped_running = true;
182 : }
183 :
184 3 : virtual void leave(cppthread::leave_status_t status) override
185 : {
186 3 : f_leave_status = status;
187 3 : }
188 :
189 : fifo_t f_fifo = fifo_t();
190 : data_t::queue_t f_expected = data_t::queue_t();
191 : bool f_ready = true;
192 : bool f_continue_running = true;
193 : bool f_entered = false;
194 : bool f_stopped_running = false; // if true, the continue_running() function returned false
195 : int f_cycles = 0;
196 : int f_unexpected_content = 0;
197 : int f_invalid_content = 0;
198 : cppthread::leave_status_t f_leave_status = cppthread::leave_status_t();
199 : };
200 :
201 :
202 : cppthread::thread * g_stop_callback_thread = nullptr;
203 :
204 1 : void stop_callback(cppthread::thread * t)
205 : {
206 1 : g_stop_callback_thread = t;
207 1 : }
208 :
209 :
210 :
211 : } // no name namespace
212 :
213 :
214 3 : CATCH_TEST_CASE("cppthread", "[thread][valid]")
215 : {
216 3 : CATCH_START_SECTION("cppthread: simple threading")
217 : {
218 1 : cppthread::set_log_callback(cppthread_log_callback);
219 :
220 1 : test_runner r;
221 3 : cppthread::thread t("test-thread", &r);
222 1 : CATCH_REQUIRE(t.get_runner() == &r);
223 1 : CATCH_REQUIRE(t.get_name() == "test-thread");
224 1 : CATCH_REQUIRE_FALSE(t.is_running());
225 :
226 : // first check that the is_ready() works as expected
227 : //
228 1 : r.f_ready = false;
229 1 : g_message_queue.push_back({
230 : cppthread::log_level_t::warning,
231 : "the thread runner is not ready.",
232 : });
233 1 : CATCH_REQUIRE_FALSE(t.start());
234 1 : CATCH_REQUIRE(g_message_queue.empty());
235 1 : CATCH_REQUIRE_FALSE(t.is_running());
236 1 : r.f_ready = true;
237 :
238 : // WARNING: at the moment this queue is not protected so create it
239 : // at the start and then check once the thread is done that
240 : // it is indeed empty
241 : //
242 1 : r.f_expected.push_back({1});
243 1 : r.f_expected.push_back({2});
244 1 : r.f_expected.push_back({3});
245 1 : r.f_expected.push_back({EXIT_THREAD});
246 :
247 : // now really start the runner
248 : //
249 1 : CATCH_REQUIRE(t.start());
250 1 : CATCH_REQUIRE(t.is_running());
251 :
252 : // now another attempt at calling start() fails with a log message
253 : //
254 1 : g_message_queue.push_back({
255 : cppthread::log_level_t::warning,
256 : "the thread is already running.",
257 : });
258 1 : CATCH_REQUIRE_FALSE(t.start());
259 1 : CATCH_REQUIRE(g_message_queue.empty());
260 :
261 1 : timespec wait = {0, 100'000'000};
262 1 : timespec rem = timespec();
263 :
264 1 : nanosleep(&wait, &rem);
265 1 : r.f_fifo.push_back({1});
266 1 : nanosleep(&wait, &rem);
267 1 : r.f_fifo.push_back({2});
268 1 : nanosleep(&wait, &rem);
269 1 : r.f_fifo.push_back({3});
270 1 : nanosleep(&wait, &rem);
271 1 : CATCH_REQUIRE(t.is_running()); // after this point, we pushed the EXIT_THREAD so the thread may quit any time now
272 1 : r.f_fifo.push_back({EXIT_THREAD});
273 1 : nanosleep(&wait, &rem);
274 :
275 : // wait for the runner to stop (join with the thread)
276 : //
277 1 : t.stop(&stop_callback);
278 1 : CATCH_REQUIRE(g_stop_callback_thread == &t);
279 1 : CATCH_REQUIRE_FALSE(t.is_running());
280 :
281 1 : CATCH_REQUIRE(r.f_cycles > 0);
282 1 : CATCH_REQUIRE_FALSE(r.f_stopped_running);
283 :
284 1 : }
285 3 : CATCH_END_SECTION()
286 :
287 3 : CATCH_START_SECTION("cppthread: create runner and throw an exception")
288 : {
289 1 : cppthread::set_log_callback(cppthread_log_callback);
290 :
291 1 : test_runner r;
292 3 : cppthread::thread t("test-thread", &r);
293 :
294 1 : bool got_exception(false);
295 : try
296 : {
297 : // WARNING: at the moment this queue is not protected so create it
298 : // at the start and then check once the thread is done that
299 : // it is indeed empty
300 : //
301 1 : r.f_expected.push_back({1});
302 1 : r.f_expected.push_back({2});
303 1 : r.f_expected.push_back({3});
304 1 : r.f_expected.push_back({EXIT_THREAD_WITH_EXCEPTION});
305 :
306 : // now really start the runner
307 : //
308 1 : CATCH_REQUIRE(t.start());
309 :
310 1 : timespec wait = {0, 100'000'000};
311 1 : timespec rem = timespec();
312 :
313 1 : nanosleep(&wait, &rem);
314 1 : r.f_fifo.push_back({1});
315 1 : nanosleep(&wait, &rem);
316 1 : r.f_fifo.push_back({2});
317 1 : nanosleep(&wait, &rem);
318 1 : r.f_fifo.push_back({3});
319 1 : nanosleep(&wait, &rem);
320 1 : r.f_fifo.push_back({EXIT_THREAD_WITH_EXCEPTION});
321 : //wait.tv_nsec = 500'000'000;
322 1 : nanosleep(&wait, &rem);
323 :
324 : // now stop the thread
325 : //
326 2 : t.stop();
327 : }
328 1 : catch(exit_with_exception const & e)
329 : {
330 1 : CATCH_REQUIRE(std::string(e.what()) == "cppthread_exception: testing thread exiting with exception.");
331 1 : got_exception = true;
332 1 : }
333 :
334 1 : CATCH_REQUIRE(got_exception);
335 1 : CATCH_REQUIRE(t.get_exception() == std::exception_ptr()); // the stop() clears the exception in the thread
336 1 : CATCH_REQUIRE(r.f_cycles > 0);
337 1 : CATCH_REQUIRE_FALSE(r.f_stopped_running);
338 :
339 1 : CATCH_REQUIRE(g_message_queue.empty());
340 1 : }
341 3 : CATCH_END_SECTION()
342 :
343 3 : CATCH_START_SECTION("cppthread: create runner and throw an exception, but auto-destruction drops the exception")
344 : {
345 1 : cppthread::set_log_callback(cppthread_log_callback);
346 :
347 1 : test_runner r;
348 :
349 1 : bool got_exception(false);
350 : try
351 : {
352 3 : cppthread::thread t("test-thread", &r);
353 :
354 : // WARNING: at the moment this queue is not protected so create it
355 : // at the start and then check once the thread is done that
356 : // it is indeed empty
357 : //
358 1 : r.f_expected.push_back({1});
359 1 : r.f_expected.push_back({2});
360 1 : r.f_expected.push_back({3});
361 1 : r.f_expected.push_back({EXIT_THREAD_WITH_EXCEPTION});
362 :
363 : // now really start the runner
364 : //
365 1 : CATCH_REQUIRE(t.start());
366 :
367 1 : timespec wait = {0, 100'000'000};
368 1 : timespec rem = timespec();
369 :
370 1 : nanosleep(&wait, &rem);
371 1 : r.f_fifo.push_back({1});
372 1 : nanosleep(&wait, &rem);
373 1 : r.f_fifo.push_back({2});
374 1 : nanosleep(&wait, &rem);
375 1 : r.f_fifo.push_back({3});
376 1 : nanosleep(&wait, &rem);
377 1 : r.f_fifo.push_back({EXIT_THREAD_WITH_EXCEPTION});
378 : //wait.tv_nsec = 500'000'000;
379 1 : nanosleep(&wait, &rem);
380 :
381 : // delete the thread using RAII, we lose the exception...
382 : // (the destructor cannot re-throw, only std::terminate()...)
383 1 : }
384 0 : catch(exit_with_exception const & e)
385 : {
386 0 : CATCH_REQUIRE(std::string(e.what()) == "cppthread_exception: testing thread exiting with exception.");
387 0 : got_exception = true;
388 0 : }
389 :
390 1 : CATCH_REQUIRE_FALSE(got_exception);
391 1 : CATCH_REQUIRE(r.f_cycles > 0);
392 1 : CATCH_REQUIRE_FALSE(r.f_stopped_running);
393 :
394 1 : CATCH_REQUIRE(g_message_queue.empty());
395 1 : }
396 3 : CATCH_END_SECTION()
397 3 : }
398 :
399 :
400 2 : CATCH_TEST_CASE("cppthread_errors", "[thread][invalid]")
401 : {
402 2 : CATCH_START_SECTION("cppthread: runner cannot be a null pointer")
403 : {
404 5 : CATCH_REQUIRE_THROWS_MATCHES(
405 : cppthread::thread("test-thread", nullptr)
406 : , cppthread::invalid_error
407 : , Catch::Matchers::ExceptionMessage("cppthread_exception: runner missing in thread() constructor."));
408 :
409 : // null smart pointer has the same effect
410 : //
411 1 : cppthread::runner::pointer_t r;
412 7 : CATCH_REQUIRE_THROWS_MATCHES(
413 : cppthread::thread("test-thread", r)
414 : , cppthread::invalid_error
415 : , Catch::Matchers::ExceptionMessage("cppthread_exception: runner missing in thread() constructor."));
416 1 : }
417 2 : CATCH_END_SECTION()
418 :
419 2 : CATCH_START_SECTION("cppthread: one runner cannot simultaneously be used with multiple threads")
420 : {
421 1 : test_runner r;
422 3 : cppthread::thread t("okay", &r);
423 :
424 5 : CATCH_REQUIRE_THROWS_MATCHES(
425 : cppthread::thread("breaks", &r)
426 : , cppthread::in_use_error
427 : , Catch::Matchers::ExceptionMessage("cppthread_exception: this runner (test-runner) is already in use."));
428 1 : }
429 2 : CATCH_END_SECTION()
430 2 : }
431 :
432 :
433 :
434 : // vim: ts=4 sw=4 et
|