#ifndef TUT_FORK_H_GUARD #define TUT_FORK_H_GUARD #include #if defined(TUT_USE_POSIX) #include #include #include #include #include #include #include #include #include #include #include #include "tut_result.hpp" #include "tut_assert.hpp" #include "tut_runner.hpp" namespace tut { template class test_group; template class test_object; class test_group_posix { private: template friend class test_group; template void send_result_(const T *obj, const test_result &tr) { if(obj->get_pipe_() == -1) { return; } if(tr.result != test_result::ok) { std::ostringstream ss; ss << int(tr.result) << "\n" << tr.group << "\n" << tr.test << "\n" << tr.name << "\n" << tr.exception_typeid << "\n"; std::copy( tr.message.begin(), tr.message.end(), std::ostreambuf_iterator(ss.rdbuf()) ); int size = ss.str().length(); int w = write(obj->get_pipe_(), ss.str().c_str(), size); ensure_errno("write() failed", w == size); } } virtual ~test_group_posix() { } }; template struct tut_posix { pid_t fork() { test_object *self = dynamic_cast< tut::test_object* >(this); ensure("trying to call 'tut_fork' in ctor of test object", self != NULL); return self->fork_(); } pid_t waitpid(pid_t pid, int *status, int flags = 0) { test_object *self = dynamic_cast< tut::test_object* >(this); ensure("trying to call 'tut_waitpid' in ctor of test object", self != NULL); return self->waitpid_(pid, status, flags); } void ensure_child_exit(pid_t pid, int exit_status = 0) { test_object *self = dynamic_cast< tut::test_object* >(this); ensure("trying to call 'ensure_child_exit' in ctor of test object", self != NULL); int status; self->waitpid_(pid, &status); self->ensure_child_exit_(status, exit_status); } void ensure_child_signal(pid_t pid, int signal = SIGTERM) { test_object *self = dynamic_cast< tut::test_object* >(this); ensure("trying to call 'ensure_child_signal' in ctor of test object", self != NULL); int status; self->waitpid_(pid, &status); self->ensure_child_signal_(status, signal); } std::set get_pids() const { using namespace std; const test_object *self = dynamic_cast< const tut::test_object* >(this); ensure("trying to call 'get_pids' in ctor of test object", self != NULL); return self->get_pids_(); } virtual ~tut_posix() { } }; class test_object_posix { public: typedef std::map pid_map; /** * Default constructor */ test_object_posix() : pids_(), pipe_(-1) { } virtual ~test_object_posix() { // we have forked if(pipe_ != -1) { // in child, force exit std::exit(0); } if(!pids_.empty()) { std::ostringstream ss; // in parent, reap children for(std::map::iterator i = pids_.begin(); i != pids_.end(); ++i) { try { kill_child_(i->first); } catch(const rethrown &ex) { ss << std::endl << "child " << ex.tr.pid << " has thrown an exception: " << ex.what(); } catch(const failure &ex) { ss << std::endl << ex.what(); } } if(!ss.str().empty()) { fail(ss.str().c_str()); } } } private: template friend class tut_posix; friend class test_group_posix; int get_pipe_() const { return pipe_; } pid_t fork_() { // create pipe int fds[2]; ensure_errno("pipe() failed", ::pipe(fds) == 0); pid_t pid = ::fork(); ensure_errno("fork() failed", pid >= 0); if(pid != 0) { // in parent, register pid ensure("duplicated child", pids_.insert( std::make_pair(pid, fds[0]) ).second); // close writing side close(fds[1]); } else { // in child, shutdown reporter tut::runner.get().clear_callbacks(); // close reading side close(fds[0]); pipe_ = fds[1]; } return pid; } void kill_child_(pid_t pid) { int status; if(waitpid_(pid, &status, WNOHANG) == pid) { ensure_child_exit_(status, 0); return; } if(::kill(pid, SIGTERM) != 0) { if(errno == ESRCH) { // no such process return; } else { // cannot kill, we are in trouble std::ostringstream ss; char e[1024]; ss << "child " << pid << " could not be killed with SIGTERM, " << strerror_r(errno, e, sizeof(e)) << std::endl; fail(ss.str()); } } if(waitpid_(pid, &status, WNOHANG) == pid) { // child killed, check signal ensure_child_signal_(status, SIGTERM); ensure_equals("child process exists after SIGTERM", ::kill(pid, 0), -1); return; } // child seems to be still exiting, give it some time sleep(2); if(waitpid_(pid, &status, WNOHANG) != pid) { // child is still running, kill it if(::kill(pid, SIGKILL) != 0) { if(errno == ESRCH) { // no such process return; } else { std::ostringstream ss; char e[1024]; ss << "child " << pid << " could not be killed with SIGKILL, " << strerror_r(errno, e, sizeof(e)) << std::endl; fail(ss.str()); } } ensure_equals("wait after SIGKILL", waitpid_(pid, &status), pid); ensure_child_signal_(status, SIGKILL); ensure_equals("child process exists after SIGKILL", ::kill(pid, 0), -1); std::ostringstream ss; ss << "child " << pid << " had to be killed with SIGKILL"; fail(ss.str()); } } test_result receive_result_(std::istream &ss, pid_t pid) { test_result tr; int type; ss >> type; tr.result = test_result::result_type(type); ss.ignore(1024, '\n'); std::getline(ss, tr.group); ss >> tr.test; ss.ignore(1024, '\n'); std::getline(ss, tr.name); std::getline(ss, tr.exception_typeid); std::copy( std::istreambuf_iterator(ss.rdbuf()), std::istreambuf_iterator(), std::back_inserter(tr.message) ); tr.pid = pid; return tr; } struct fdclose { fdclose(int fd): fd_(fd) { } ~fdclose() { close(fd_); } private: int fd_; }; pid_t waitpid_(pid_t pid, int *status, int flags = 0) { ensure("trying to wait for unknown pid", pids_.count(pid) > 0); pid_t p = ::waitpid(pid, status, flags); if( (flags & WNOHANG) && (p != pid) ) { return p; } // read child result from pipe fd_set fdset; timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&fdset); int pipe = pids_[pid]; fdclose guard(pipe); FD_SET(pipe, &fdset); int result = select(pipe+1, &fdset, NULL, NULL, &tv); ensure_errno("sanity check on select() failed", result >= 0); if(result > 0) { ensure("sanity check on FD_ISSET() failed", FD_ISSET(pipe, &fdset) ); std::stringstream ss; //TODO: max failure length char buffer[1024]; int r = read(pipe, buffer, sizeof(buffer)); ensure_errno("sanity check on read() failed", r >= 0); if(r > 0) { ss.write(buffer, r); throw rethrown( receive_result_(ss, pid) ); } } return pid; } void ensure_child_exit_(int status, int exit_status) { if(WIFSIGNALED(status)) { std::ostringstream ss; ss << "child killed by signal " << WTERMSIG(status) << ": expected exit with code " << exit_status; throw failure(ss.str().c_str()); } if(WIFEXITED(status)) { if(WEXITSTATUS(status) != exit_status) { std::ostringstream ss; ss << "child exited, expected '" << exit_status << "' actual '" << WEXITSTATUS(status) << '\''; throw failure(ss.str().c_str()); } } if(WIFSTOPPED(status)) { std::ostringstream ss; ss << "child stopped by signal " << WTERMSIG(status) << ": expected exit with code " << exit_status; throw failure(ss.str().c_str()); } } void ensure_child_signal_(int status, int signal) { if(WIFSIGNALED(status)) { if(WTERMSIG(status) != signal) { std::ostringstream ss; ss << "child killed by signal, expected '" << signal << "' actual '" << WTERMSIG(status) << '\''; throw failure(ss.str().c_str()); } } if(WIFEXITED(status)) { std::ostringstream ss; ss << "child exited with code " << WEXITSTATUS(status) << ": expected signal " << signal; throw failure(ss.str().c_str()); } if(WIFSTOPPED(status)) { std::ostringstream ss; ss << "child stopped by signal " << WTERMSIG(status) << ": expected kill by signal " << signal; throw failure(ss.str().c_str()); } } std::set get_pids_() const { using namespace std; set pids; for(pid_map::const_iterator i = pids_.begin(); i != pids_.end(); ++i) { pids.insert( i->first ); } return pids; } pid_map pids_; int pipe_; }; } // namespace tut #else namespace tut { struct test_object_posix { virtual ~test_object_posix() { } }; struct test_group_posix { template void send_result_(const T*, const test_result &) { } virtual ~test_group_posix() { } }; } // namespace tut #endif #endif