Index: util/test/multiprocess.cc |
diff --git a/util/test/multiprocess.cc b/util/test/multiprocess.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..34cbddc4de633a3c9c33665c3691447f3a0a7501 |
--- /dev/null |
+++ b/util/test/multiprocess.cc |
@@ -0,0 +1,180 @@ |
+// Copyright 2014 The Crashpad Authors. All rights reserved. |
+// |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
+// you may not use this file except in compliance with the License. |
+// You may obtain a copy of the License at |
+// |
+// http://www.apache.org/licenses/LICENSE-2.0 |
+// |
+// Unless required by applicable law or agreed to in writing, software |
+// distributed under the License is distributed on an "AS IS" BASIS, |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+// See the License for the specific language governing permissions and |
+// limitations under the License. |
+ |
+#include "util/test/multiprocess.h" |
+ |
+#include <signal.h> |
+#include <stdlib.h> |
+#include <sys/wait.h> |
+ |
+#include <string> |
+ |
+#include "base/auto_reset.h" |
+#include "base/files/scoped_file.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/strings/stringprintf.h" |
+#include "gtest/gtest.h" |
+#include "util/misc/scoped_forbid_return.h" |
+#include "util/test/errors.h" |
+ |
+namespace crashpad { |
+namespace test { |
+ |
+using namespace testing; |
+ |
+namespace internal { |
+ |
+struct MultiprocessInfo { |
+ MultiprocessInfo() |
+ : pipe_c2p_read(-1), |
+ pipe_c2p_write(-1), |
+ pipe_p2c_read(-1), |
+ pipe_p2c_write(-1), |
+ child_pid(0) {} |
+ |
+ base::ScopedFD pipe_c2p_read; // child to parent |
+ base::ScopedFD pipe_c2p_write; // child to parent |
+ base::ScopedFD pipe_p2c_read; // parent to child |
+ base::ScopedFD pipe_p2c_write; // parent to child |
+ pid_t child_pid; // valid only in parent |
+}; |
+ |
+} // namespace internal |
+ |
+Multiprocess::Multiprocess() : info_(NULL) { |
+} |
+ |
+void Multiprocess::Run() { |
+ ASSERT_EQ(NULL, info_); |
+ scoped_ptr<internal::MultiprocessInfo> info(new internal::MultiprocessInfo); |
+ base::AutoReset<internal::MultiprocessInfo*> reset_info(&info_, info.get()); |
+ |
+ PreFork(); |
+ if (testing::Test::HasFatalFailure()) { |
+ return; |
+ } |
+ |
+ pid_t pid = fork(); |
+ ASSERT_GE(pid, 0) << ErrnoMessage("fork"); |
+ |
+ if (pid > 0) { |
+ info_->child_pid = pid; |
+ |
+ RunParent(); |
+ |
+ // Waiting for the child happens here instead of in RunParent() because even |
+ // if RunParent() returns early due to a gtest fatal assertion failure, the |
+ // child should still be reaped. |
+ |
+ // This will make the parent hang up on the child as much as would be |
+ // visible from the child’s perspective. The child’s side of the pipe will |
+ // be broken, the child’s remote port will become a dead name, and an |
+ // attempt by the child to look up the service will fail. If this weren’t |
+ // done, the child might hang while waiting for a parent that has already |
+ // triggered a fatal assertion failure to do something. |
+ info.reset(); |
+ info_ = NULL; |
+ |
+ int status; |
+ pid_t wait_pid = waitpid(pid, &status, 0); |
+ ASSERT_EQ(pid, wait_pid) << ErrnoMessage("waitpid"); |
+ if (status != 0) { |
+ std::string message; |
+ if (WIFEXITED(status)) { |
+ message = base::StringPrintf("Child exited with code %d", |
+ WEXITSTATUS(status)); |
+ } else if (WIFSIGNALED(status)) { |
+ message = base::StringPrintf("Child terminated by signal %d (%s) %s", |
+ WTERMSIG(status), |
+ strsignal(WTERMSIG(status)), |
+ WCOREDUMP(status) ? " (core dumped)" : ""); |
+ } |
+ ASSERT_EQ(0, status) << message; |
+ } |
+ } else { |
+ RunChild(); |
+ } |
+} |
+ |
+Multiprocess::~Multiprocess() { |
+} |
+ |
+void Multiprocess::PreFork() { |
+ int pipe_fds_c2p[2]; |
+ int rv = pipe(pipe_fds_c2p); |
+ ASSERT_EQ(0, rv) << ErrnoMessage("pipe"); |
+ |
+ info_->pipe_c2p_read.reset(pipe_fds_c2p[0]); |
+ info_->pipe_c2p_write.reset(pipe_fds_c2p[1]); |
+ |
+ int pipe_fds_p2c[2]; |
+ rv = pipe(pipe_fds_p2c); |
+ ASSERT_EQ(0, rv) << ErrnoMessage("pipe"); |
+ |
+ info_->pipe_p2c_read.reset(pipe_fds_p2c[0]); |
+ info_->pipe_p2c_write.reset(pipe_fds_p2c[1]); |
+} |
+ |
+pid_t Multiprocess::ChildPID() const { |
+ EXPECT_NE(0, info_->child_pid); |
+ return info_->child_pid; |
+} |
+ |
+int Multiprocess::ReadPipeFD() const { |
+ int fd = info_->child_pid ? info_->pipe_c2p_read.get() |
+ : info_->pipe_p2c_read.get(); |
+ EXPECT_NE(-1, fd); |
+ return fd; |
+} |
+ |
+int Multiprocess::WritePipeFD() const { |
+ int fd = info_->child_pid ? info_->pipe_p2c_write.get() |
+ : info_->pipe_c2p_write.get(); |
+ EXPECT_NE(-1, fd); |
+ return fd; |
+} |
+ |
+void Multiprocess::RunParent() { |
+ // The parent uses the read end of c2p and the write end of p2c. |
+ info_->pipe_c2p_write.reset(); |
+ info_->pipe_p2c_read.reset(); |
+ |
+ MultiprocessParent(); |
+ |
+ info_->pipe_c2p_read.reset(); |
+ info_->pipe_p2c_write.reset(); |
+} |
+ |
+void Multiprocess::RunChild() { |
+ ScopedForbidReturn forbid_return; |
+ |
+ // The child uses the write end of c2p and the read end of p2c. |
+ info_->pipe_c2p_read.reset(); |
+ info_->pipe_p2c_write.reset(); |
+ |
+ MultiprocessChild(); |
+ |
+ info_->pipe_c2p_write.reset(); |
+ info_->pipe_p2c_read.reset(); |
+ |
+ if (Test::HasFailure()) { |
+ // Trigger the ScopedForbidReturn destructor. |
+ return; |
+ } |
+ |
+ exit(0); |
+} |
+ |
+} // namespace test |
+} // namespace crashpad |