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