]> git.stg.codes - stg.git/blob - tests/tut/tut_posix.hpp
Produce debug output only if SMUX_DEBUG is defined
[stg.git] / tests / tut / tut_posix.hpp
1 #ifndef TUT_FORK_H_GUARD
2 #define TUT_FORK_H_GUARD
3
4 #if defined(TUT_USE_POSIX)
5 #include <errno.h>
6 #include <unistd.h>
7 #include <signal.h>
8 #include <sys/wait.h>
9 #include <sys/time.h>
10 #include <sys/types.h>
11
12 #include <cstring>
13 #include <cstdlib>
14 #include <map>
15 #include <iterator>
16 #include <functional>
17
18 #include "tut_result.hpp"
19 #include "tut_assert.hpp"
20 #include "tut_runner.hpp"
21
22 namespace tut
23 {
24
25 template<typename, int>
26 class test_group;
27
28 template<typename T>
29 class test_object;
30
31 class test_group_posix
32 {
33 private:
34     template<typename, int>
35     friend class test_group;
36
37     template<typename T>
38     void send_result_(const T *obj, const test_result &tr)
39     {
40         if(obj->get_pipe_() == -1)
41         {
42             return;
43         }
44
45         if(tr.result != test_result::ok)
46         {
47             std::stringstream ss;
48             ss << int(tr.result) << "\n"
49                 << tr.group << "\n"
50                 << tr.test << "\n"
51                 << tr.name << "\n"
52                 << tr.exception_typeid << "\n";
53             std::copy( tr.message.begin(), tr.message.end(), std::ostreambuf_iterator<char>(ss.rdbuf()) );
54
55             int size = ss.str().length();
56             int w = write(obj->get_pipe_(), ss.str().c_str(), size);
57             ensure_errno("write() failed", w == size);
58         }
59     }
60 };
61
62 template<typename T>
63 struct tut_posix
64 {
65     pid_t fork()
66     {
67         test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
68         ensure("trying to call 'tut_fork' in ctor of test object", self != NULL);
69
70         return self->fork_();
71     }
72
73     pid_t waitpid(pid_t pid, int *status, int flags = 0)
74     {
75         test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
76         ensure("trying to call 'tut_waitpid' in ctor of test object", self != NULL);
77
78         return self->waitpid_(pid, status, flags);
79     }
80
81     void ensure_child_exit(pid_t pid, int exit_status = 0)
82     {
83         test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
84         ensure("trying to call 'ensure_child_exit' in ctor of test object", self != NULL);
85
86         int status;
87         self->waitpid_(pid, &status);
88
89         self->ensure_child_exit_(status, exit_status);
90     }
91
92
93     void ensure_child_signal(pid_t pid, int signal = SIGTERM)
94     {
95         test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
96         ensure("trying to call 'ensure_child_signal' in ctor of test object", self != NULL);
97
98         int status;
99         self->waitpid_(pid, &status);
100
101         self->ensure_child_signal_(status, signal);
102     }
103
104     std::set<pid_t> get_pids() const
105     {
106         using namespace std;
107
108         const test_object<T> *self = dynamic_cast< const tut::test_object<T>* >(this);
109         ensure("trying to call 'get_pids' in ctor of test object", self != NULL);
110
111         return self->get_pids_();
112     }
113
114     virtual ~tut_posix()
115     {
116     }
117
118 };
119
120 class test_object_posix
121 {
122 public:
123     typedef std::map<pid_t, int> pid_map;
124
125     /**
126      * Default constructor
127      */
128     test_object_posix()
129         : pipe_(-1)
130     {
131     }
132
133
134     virtual ~test_object_posix()
135     {
136         // we have forked
137         if(pipe_ != -1)
138         {
139             // in child, force exit
140             std::exit(0);
141         }
142
143         if(!pids_.empty())
144         {
145             std::stringstream ss;
146
147             // in parent, reap children
148             for(std::map<pid_t, int>::iterator i = pids_.begin(); i != pids_.end(); ++i)
149             {
150                 try {
151                     kill_child_(i->first);
152                 } catch(const rethrown &ex) {
153                     ss << std::endl << "child " << ex.tr.pid << " has thrown an exception: " << ex.what();
154                 } catch(const failure &ex) {
155                     ss << std::endl << ex.what();
156                 }
157             }
158
159             if(!ss.str().empty())
160             {
161                 fail(ss.str().c_str());
162             }
163         }
164     }
165
166 private:
167     template<typename T>
168     friend class tut_posix;
169
170     friend class test_group_posix;
171
172     int get_pipe_() const
173     {
174         return pipe_;
175     }
176
177
178     pid_t fork_()
179     {
180         // create pipe
181         int fds[2];
182         ensure_errno("pipe() failed", ::pipe(fds) == 0);
183
184         pid_t pid = ::fork();
185
186         ensure_errno("fork() failed", pid >= 0);
187
188         if(pid != 0)
189         {
190             // in parent, register pid
191             ensure("duplicated child", pids_.insert( std::make_pair(pid, fds[0]) ).second);
192
193             // close writing side
194             close(fds[1]);
195         }
196         else
197         {
198             // in child, shutdown reporter
199             tut::runner.get().clear_callbacks();
200
201             // close reading side
202             close(fds[0]);
203             pipe_ = fds[1];
204         }
205
206         return pid;
207     }
208
209     void kill_child_(pid_t pid)
210     {
211         int status;
212
213         if(waitpid_(pid, &status, WNOHANG) == pid)
214         {
215             ensure_child_exit_(status, 0);
216             return;
217         }
218
219         if(::kill(pid, SIGTERM) != 0)
220         {
221             if(errno == ESRCH)
222             {
223                 // no such process
224                 return;
225             }
226             else
227             {
228                 // cannot kill, we are in trouble
229                 std::stringstream ss;
230                 char e[1024];
231                 ss << "child " << pid << " could not be killed with SIGTERM, " << strerror_r(errno, e, sizeof(e)) << std::endl;
232                 fail(ss.str());
233             }
234         }
235
236         if(waitpid_(pid, &status, WNOHANG) == pid)
237         {
238             // child killed, check signal
239             ensure_child_signal_(status, SIGTERM);
240
241             ensure_equals("child process exists after SIGTERM", ::kill(pid, 0), -1);
242             return;
243         }
244
245         // child seems to be still exiting, give it some time
246         sleep(2);
247
248         if(waitpid_(pid, &status, WNOHANG) != pid)
249         {
250             // child is still running, kill it
251             if(::kill(pid, SIGKILL) != 0)
252             {
253                 if(errno == ESRCH)
254                 {
255                     // no such process
256                     return;
257                 }
258                 else
259                 {
260                     std::stringstream ss;
261                     char e[1024];
262                     ss << "child " << pid << " could not be killed with SIGKILL, " << strerror_r(errno, e, sizeof(e)) << std::endl;
263                     fail(ss.str());
264                 }
265             }
266
267             ensure_equals("wait after SIGKILL", waitpid_(pid, &status), pid);
268             ensure_child_signal_(status, SIGKILL);
269
270             ensure_equals("child process exists after SIGKILL", ::kill(pid, 0), -1);
271
272             std::stringstream ss;
273             ss << "child " << pid << " had to be killed with SIGKILL";
274             fail(ss.str());
275         }
276     }
277
278     test_result receive_result_(std::istream &ss, pid_t pid)
279     {
280         test_result tr;
281
282         int type;
283         ss >> type;
284         tr.result = test_result::result_type(type);
285         ss.ignore(1024, '\n');
286
287         std::getline(ss, tr.group);
288         ss >> tr.test;
289         ss.ignore(1024, '\n');
290         std::getline(ss, tr.name);
291         std::getline(ss, tr.exception_typeid);
292         std::copy( std::istreambuf_iterator<char>(ss.rdbuf()),
293                    std::istreambuf_iterator<char>(),
294                    std::back_inserter(tr.message) );
295
296         tr.pid = pid;
297
298         return tr;
299     }
300
301     struct fdclose
302     {
303         fdclose(int fd): fd_(fd) { }
304         ~fdclose()
305         {
306             close(fd_);
307         }
308     private:
309         int fd_;
310     };
311
312     pid_t waitpid_(pid_t pid, int *status, int flags = 0)
313     {
314
315         ensure("trying to wait for unknown pid", pids_.count(pid) > 0);
316
317         pid_t p = ::waitpid(pid, status, flags);
318         if( (flags & WNOHANG) && (p != pid) )
319         {
320             return p;
321         }
322
323         // read child result from pipe
324         fd_set fdset;
325         timeval tv;
326         tv.tv_sec = 0;
327         tv.tv_usec = 0;
328
329         FD_ZERO(&fdset);
330
331         int pipe = pids_[pid];
332         fdclose guard(pipe);
333
334         FD_SET(pipe, &fdset);
335
336         int result = select(pipe+1, &fdset, NULL, NULL, &tv);
337         ensure_errno("sanity check on select() failed", result >= 0);
338
339         if(result > 0)
340         {
341             ensure("sanity check on FD_ISSET() failed", FD_ISSET(pipe, &fdset) );
342
343             std::stringstream ss;
344
345             //TODO: max failure length
346             char buffer[1024];
347             int r = read(pipe, buffer, sizeof(buffer));
348             ensure_errno("sanity check on read() failed", r >= 0);
349
350             if(r > 0)
351             {
352                 ss.write(buffer, r);
353                 throw rethrown( receive_result_(ss, pid) );
354             }
355         }
356
357         return pid;
358     }
359
360     void ensure_child_exit_(int status, int exit_status)
361     {
362         if(WIFSIGNALED(status))
363         {
364             std::stringstream ss;
365             ss << "child killed by signal " << WTERMSIG(status)
366                 << ": expected exit with code " << exit_status;
367
368             throw failure(ss.str().c_str());
369         }
370
371         if(WIFEXITED(status))
372         {
373             if(WEXITSTATUS(status) != exit_status)
374             {
375                 std::stringstream ss;
376                 ss << "child exited, expected '"
377                     << exit_status
378                     << "' actual '"
379                     << WEXITSTATUS(status)
380                     << '\'';
381
382                 throw failure(ss.str().c_str());
383             }
384         }
385
386         if(WIFSTOPPED(status))
387         {
388             std::stringstream ss;
389             ss << "child stopped by signal " << WTERMSIG(status)
390                 << ": expected exit with code " << exit_status;
391             throw failure(ss.str().c_str());
392         }
393     }
394
395     void ensure_child_signal_(int status, int signal)
396     {
397         if(WIFSIGNALED(status))
398         {
399             if(WTERMSIG(status) != signal)
400             {
401                 std::stringstream ss;
402                 ss << "child killed by signal, expected '"
403                     << signal
404                     << "' actual '"
405                     << WTERMSIG(status)
406                     << '\'';
407                 throw failure(ss.str().c_str());
408             }
409         }
410
411         if(WIFEXITED(status))
412         {
413             std::stringstream ss;
414             ss << "child exited with code " << WEXITSTATUS(status)
415                 << ": expected signal " << signal;
416
417             throw failure(ss.str().c_str());
418         }
419
420         if(WIFSTOPPED(status))
421         {
422             std::stringstream ss;
423             ss << "child stopped by signal " << WTERMSIG(status)
424                 << ": expected kill by signal " << signal;
425
426             throw failure(ss.str().c_str());
427         }
428     }
429
430     std::set<pid_t> get_pids_() const
431     {
432         using namespace std;
433
434         set<pid_t> pids;
435         for(pid_map::const_iterator i = pids_.begin(); i != pids_.end(); ++i)
436         {
437             pids.insert( i->first );
438         }
439
440         return pids;
441     }
442
443     pid_map         pids_;
444     int             pipe_;
445 };
446
447 } // namespace tut
448
449 #else
450
451 namespace tut
452 {
453
454 struct test_object_posix
455 {
456 };
457
458 struct test_group_posix
459 {
460     template<typename T>
461     void send_result_(const T*, const test_result &)
462     {
463     }
464 };
465
466 } // namespace tut
467
468 #endif
469
470
471 #endif
472