mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
Refactor MachMultiprocess into a Multiprocess base class without any
knowledge of Mach. MachMultiprocess builds on this new base class. TEST=util_test MachMultiprocess.*:Multiprocess.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/506143002
This commit is contained in:
parent
30589d87a9
commit
ff26ea6db9
@ -67,8 +67,8 @@ class ProcessReaderChild final : public MachMultiprocess {
|
||||
|
||||
~ProcessReaderChild() {}
|
||||
|
||||
protected:
|
||||
void Parent() override {
|
||||
private:
|
||||
void MachMultiprocessParent() override {
|
||||
ProcessReader process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(ChildTask()));
|
||||
|
||||
@ -100,7 +100,7 @@ class ProcessReaderChild final : public MachMultiprocess {
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
||||
}
|
||||
|
||||
void Child() override {
|
||||
void MachMultiprocessChild() override {
|
||||
int write_fd = WritePipeFD();
|
||||
|
||||
mach_vm_address_t address =
|
||||
@ -116,7 +116,6 @@ class ProcessReaderChild final : public MachMultiprocess {
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("read");
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessReaderChild);
|
||||
};
|
||||
|
||||
@ -434,8 +433,8 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
|
||||
~ProcessReaderThreadedChild() {}
|
||||
|
||||
protected:
|
||||
void Parent() override {
|
||||
private:
|
||||
void MachMultiprocessParent() override {
|
||||
ProcessReader process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(ChildTask()));
|
||||
|
||||
@ -486,7 +485,7 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
||||
}
|
||||
|
||||
void Child() override {
|
||||
void MachMultiprocessChild() override {
|
||||
TestThreadPool thread_pool;
|
||||
thread_pool.StartThreads(thread_count_);
|
||||
if (testing::Test::HasFatalFailure()) {
|
||||
@ -550,7 +549,6 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("read");
|
||||
}
|
||||
|
||||
private:
|
||||
size_t thread_count_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessReaderThreadedChild);
|
||||
|
@ -17,35 +17,22 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <servers/bootstrap.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/logging.h"
|
||||
#include "base/mac/scoped_mach_port.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/mach/bootstrap.h"
|
||||
#include "util/misc/scoped_forbid_return.h"
|
||||
#include "util/test/errors.h"
|
||||
#include "util/test/mac/mach_errors.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class ScopedNotReached {
|
||||
public:
|
||||
ScopedNotReached() {}
|
||||
~ScopedNotReached() { abort(); }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedNotReached);
|
||||
};
|
||||
|
||||
// The “hello” message contains a send right to the child process’ task port.
|
||||
struct SendHelloMessage : public mach_msg_base_t {
|
||||
mach_msg_port_descriptor_t port_descriptor;
|
||||
@ -67,25 +54,11 @@ namespace internal {
|
||||
struct MachMultiprocessInfo {
|
||||
MachMultiprocessInfo()
|
||||
: service_name(),
|
||||
pipe_c2p_read(-1),
|
||||
pipe_c2p_write(-1),
|
||||
pipe_p2c_read(-1),
|
||||
pipe_p2c_write(-1),
|
||||
child_pid(0),
|
||||
read_pipe_fd(-1),
|
||||
write_pipe_fd(-1),
|
||||
local_port(MACH_PORT_NULL),
|
||||
remote_port(MACH_PORT_NULL),
|
||||
child_task(MACH_PORT_NULL) {}
|
||||
|
||||
std::string service_name;
|
||||
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
|
||||
int read_pipe_fd; // pipe_c2p_read in parent, pipe_p2c_read in child
|
||||
int write_pipe_fd; // pipe_p2c_write in parent, pipe_c2p_write in child
|
||||
base::mac::ScopedMachReceiveRight local_port;
|
||||
base::mac::ScopedMachSendRight remote_port;
|
||||
base::mac::ScopedMachSendRight child_task; // valid only in parent
|
||||
@ -93,7 +66,7 @@ struct MachMultiprocessInfo {
|
||||
|
||||
} // namespace internal
|
||||
|
||||
MachMultiprocess::MachMultiprocess() : info_(NULL) {
|
||||
MachMultiprocess::MachMultiprocess() : Multiprocess(), info_(NULL) {
|
||||
}
|
||||
|
||||
void MachMultiprocess::Run() {
|
||||
@ -103,19 +76,17 @@ void MachMultiprocess::Run() {
|
||||
base::AutoReset<internal::MachMultiprocessInfo*> reset_info(&info_,
|
||||
info.get());
|
||||
|
||||
int pipe_fds_c2p[2];
|
||||
int rv = pipe(pipe_fds_c2p);
|
||||
ASSERT_EQ(0, rv) << ErrnoMessage("pipe");
|
||||
return Multiprocess::Run();
|
||||
}
|
||||
|
||||
info_->pipe_c2p_read.reset(pipe_fds_c2p[0]);
|
||||
info_->pipe_c2p_write.reset(pipe_fds_c2p[1]);
|
||||
MachMultiprocess::~MachMultiprocess() {
|
||||
}
|
||||
|
||||
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]);
|
||||
void MachMultiprocess::PreFork() {
|
||||
Multiprocess::PreFork();
|
||||
if (testing::Test::HasFatalFailure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the parent port and register it with the bootstrap server before
|
||||
// forking, so that it’s guaranteed to be there when the child attempts to
|
||||
@ -131,65 +102,6 @@ void MachMultiprocess::Run() {
|
||||
ASSERT_EQ(BOOTSTRAP_SUCCESS, kr)
|
||||
<< BootstrapErrorMessage(kr, "bootstrap_check_in");
|
||||
info_->local_port.reset(local_port);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
MachMultiprocess::~MachMultiprocess() {
|
||||
}
|
||||
|
||||
pid_t MachMultiprocess::ChildPID() const {
|
||||
EXPECT_NE(0, info_->child_pid);
|
||||
return info_->child_pid;
|
||||
}
|
||||
|
||||
int MachMultiprocess::ReadPipeFD() const {
|
||||
EXPECT_NE(-1, info_->read_pipe_fd);
|
||||
return info_->read_pipe_fd;
|
||||
}
|
||||
|
||||
int MachMultiprocess::WritePipeFD() const {
|
||||
EXPECT_NE(-1, info_->write_pipe_fd);
|
||||
return info_->write_pipe_fd;
|
||||
}
|
||||
|
||||
mach_port_t MachMultiprocess::LocalPort() const {
|
||||
@ -207,13 +119,7 @@ mach_port_t MachMultiprocess::ChildTask() const {
|
||||
return info_->child_task;
|
||||
}
|
||||
|
||||
void MachMultiprocess::RunParent() {
|
||||
// The parent uses the read end of c2p and the write end of p2c.
|
||||
info_->pipe_c2p_write.reset();
|
||||
info_->read_pipe_fd = info_->pipe_c2p_read.get();
|
||||
info_->pipe_p2c_read.reset();
|
||||
info_->write_pipe_fd = info_->pipe_p2c_write.get();
|
||||
|
||||
void MachMultiprocess::MultiprocessParent() {
|
||||
ReceiveHelloMessage message = {};
|
||||
|
||||
kern_return_t kr =
|
||||
@ -299,29 +205,18 @@ void MachMultiprocess::RunParent() {
|
||||
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task");
|
||||
ASSERT_EQ(ChildPID(), mach_pid);
|
||||
|
||||
Parent();
|
||||
MachMultiprocessParent();
|
||||
|
||||
info_->remote_port.reset();
|
||||
info_->local_port.reset();
|
||||
|
||||
info_->read_pipe_fd = -1;
|
||||
info_->pipe_c2p_read.reset();
|
||||
info_->write_pipe_fd = -1;
|
||||
info_->pipe_p2c_write.reset();
|
||||
}
|
||||
|
||||
void MachMultiprocess::RunChild() {
|
||||
ScopedNotReached must_not_leave_this_scope;
|
||||
void MachMultiprocess::MultiprocessChild() {
|
||||
ScopedForbidReturn forbid_return;;
|
||||
|
||||
// local_port is not valid in the forked child process.
|
||||
ignore_result(info_->local_port.release());
|
||||
|
||||
// The child uses the write end of c2p and the read end of p2c.
|
||||
info_->pipe_c2p_read.reset();
|
||||
info_->write_pipe_fd = info_->pipe_c2p_write.get();
|
||||
info_->pipe_p2c_write.reset();
|
||||
info_->read_pipe_fd = info_->pipe_p2c_read.get();
|
||||
|
||||
mach_port_t local_port;
|
||||
kern_return_t kr = mach_port_allocate(
|
||||
mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port);
|
||||
@ -360,22 +255,17 @@ void MachMultiprocess::RunChild() {
|
||||
MACH_PORT_NULL);
|
||||
ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
|
||||
|
||||
Child();
|
||||
MachMultiprocessChild();
|
||||
|
||||
info_->remote_port.reset();
|
||||
info_->local_port.reset();
|
||||
|
||||
info_->write_pipe_fd = -1;
|
||||
info_->pipe_c2p_write.reset();
|
||||
info_->read_pipe_fd = -1;
|
||||
info_->pipe_p2c_read.reset();
|
||||
|
||||
if (Test::HasFailure()) {
|
||||
// Trigger the ScopedNotReached destructor.
|
||||
// Trigger the ScopedForbidReturn destructor.
|
||||
return;
|
||||
}
|
||||
|
||||
exit(0);
|
||||
forbid_return.Disarm();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "util/test/multiprocess.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
@ -29,68 +30,26 @@ struct MachMultiprocessInfo;
|
||||
|
||||
//! \brief Manages a Mach-aware multiprocess test.
|
||||
//!
|
||||
//! These tests are `fork()`-based. The parent process has access to the child
|
||||
//! process’ task port. The parent and child processes are able to communicate
|
||||
//! via Mach IPC, and via a pair of POSIX pipes.
|
||||
//! This is similar to the base Multiprocess test, but adds Mach features. The
|
||||
//! parent process has access to the child process’ task port. The parent and
|
||||
//! child processes are able to communicate via Mach IPC: each process has a
|
||||
//! receive right to its “local port” and a send right to a “remote port”, and
|
||||
//! messages sent to the remote port in one process can be received on the local
|
||||
//! port in the partner process.
|
||||
//!
|
||||
//! Subclasses are expected to implement the parent and child by overriding the
|
||||
//! appropriate methods.
|
||||
class MachMultiprocess {
|
||||
class MachMultiprocess : public Multiprocess {
|
||||
public:
|
||||
MachMultiprocess();
|
||||
|
||||
//! \brief Runs the test.
|
||||
//!
|
||||
//! This method establishes the proper testing environment and calls
|
||||
//! RunParent() in the parent process and RunChild() in the child process.
|
||||
//!
|
||||
//! This method uses gtest assertions to validate the testing environment. If
|
||||
//! the testing environment cannot be set up properly, it is possible that
|
||||
//! Parent() or Child() will not be called. In the parent process, this method
|
||||
//! also waits for the child process to exit after Parent() returns, and
|
||||
//! verifies that it exited cleanly with gtest assertions.
|
||||
void Run();
|
||||
|
||||
protected:
|
||||
~MachMultiprocess();
|
||||
|
||||
//! \brief The subclass-provided parent routine.
|
||||
//!
|
||||
//! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`,
|
||||
//! `FAIL()`, etc.
|
||||
//!
|
||||
//! This method must not use a `wait()`-family system call to wait for the
|
||||
//! child process to exit, as this is handled by RunParent().
|
||||
//!
|
||||
//! Subclasses must implement this method to define how the parent operates.
|
||||
virtual void Parent() = 0;
|
||||
|
||||
//! \brief The subclass-provided child routine.
|
||||
//!
|
||||
//! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`,
|
||||
//! `FAIL()`, etc.
|
||||
//!
|
||||
//! Subclasses must implement this method to define how the child operates.
|
||||
virtual void Child() = 0;
|
||||
|
||||
//! \brief Returns the child process’ process ID.
|
||||
//!
|
||||
//! This method may only be called by the parent process.
|
||||
pid_t ChildPID() const;
|
||||
|
||||
//! \brief Returns the read pipe’s file descriptor.
|
||||
//!
|
||||
//! This method may be called by either the parent or the child process.
|
||||
//! Anything written to the write pipe in the partner process will appear
|
||||
//! on the this file descriptor in this process.
|
||||
int ReadPipeFD() const;
|
||||
|
||||
//! \brief Returns the write pipe’s file descriptor.
|
||||
//!
|
||||
//! This method may be called by either the parent or the child process.
|
||||
//! Anything written to this file descriptor in this process will appear on
|
||||
//! the read pipe in the partner process.
|
||||
int WritePipeFD() const;
|
||||
// Multiprocess:
|
||||
virtual void PreFork() override;
|
||||
|
||||
//! \brief Returns a receive right for the local port.
|
||||
//!
|
||||
@ -112,21 +71,43 @@ class MachMultiprocess {
|
||||
mach_port_t ChildTask() const;
|
||||
|
||||
private:
|
||||
// Multiprocess:
|
||||
|
||||
//! \brief Runs the parent side of the test.
|
||||
//!
|
||||
//! This method establishes the parent’s environment, performs the handshake
|
||||
//! with the child, calls Parent(), and waits for the child process to exit.
|
||||
//! Assuming that the environment can be set up correctly and the child exits
|
||||
//! successfully, the test will pass.
|
||||
void RunParent();
|
||||
//! This method establishes the parent’s environment and calls
|
||||
//! MachMultiprocessParent().
|
||||
//!
|
||||
//! Subclasses must override MachMultiprocessParent() instead of this method.
|
||||
virtual void MultiprocessParent() override final;
|
||||
|
||||
//! \brief Runs the child side of the test.
|
||||
//!
|
||||
//! This method establishes the child’s environment, performs the handshake
|
||||
//! with the parent, calls Child(), and exits cleanly. However, if any failure
|
||||
//! (via fatal or nonfatal gtest assertion) is detected, the child will exit
|
||||
//! with a failure status.
|
||||
void RunChild();
|
||||
//! This method establishes the child’s environment and calls
|
||||
//! MachMultiprocessChild(). If any failure (via fatal or nonfatal gtest
|
||||
//! assertion) is detected, the child will exit with a failure status.
|
||||
//!
|
||||
//! Subclasses must override MachMultiprocessChild() instead of this method.
|
||||
virtual void MultiprocessChild() override final;
|
||||
|
||||
//! \brief The subclass-provided parent routine.
|
||||
//!
|
||||
//! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`,
|
||||
//! `FAIL()`, etc.
|
||||
//!
|
||||
//! This method must not use a `wait()`-family system call to wait for the
|
||||
//! child process to exit, as this is handled by the superclass.
|
||||
//!
|
||||
//! Subclasses must implement this method to define how the parent operates.
|
||||
virtual void MachMultiprocessParent() = 0;
|
||||
|
||||
//! \brief The subclass-provided child routine.
|
||||
//!
|
||||
//! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`,
|
||||
//! `FAIL()`, etc.
|
||||
//!
|
||||
//! Subclasses must implement this method to define how the child operates.
|
||||
virtual void MachMultiprocessChild() = 0;
|
||||
|
||||
internal::MachMultiprocessInfo* info_;
|
||||
|
||||
|
@ -18,8 +18,6 @@
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/file/fd_io.h"
|
||||
#include "util/test/errors.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -32,51 +30,13 @@ class TestMachMultiprocess final : public MachMultiprocess {
|
||||
|
||||
~TestMachMultiprocess() {}
|
||||
|
||||
protected:
|
||||
// The base class will have already exercised the Mach ports for IPC and the
|
||||
// child task port. Just make sure that the pipe is set up correctly and that
|
||||
// ChildPID() works as expected.
|
||||
virtual void Parent() override {
|
||||
int read_fd = ReadPipeFD();
|
||||
char c;
|
||||
ssize_t rv = ReadFD(read_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("read");
|
||||
EXPECT_EQ('M', c);
|
||||
|
||||
pid_t pid;
|
||||
rv = ReadFD(read_fd, &pid, sizeof(pid));
|
||||
ASSERT_EQ(static_cast<ssize_t>(sizeof(pid)), rv) << ErrnoMessage("read");
|
||||
EXPECT_EQ(pid, ChildPID());
|
||||
|
||||
int write_fd = WritePipeFD();
|
||||
c = 'm';
|
||||
rv = WriteFD(write_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
||||
|
||||
// The child will close its end of the pipe and exit. Make sure that the
|
||||
// parent sees EOF.
|
||||
rv = ReadFD(read_fd, &c, 1);
|
||||
ASSERT_EQ(0, rv) << ErrnoMessage("read");
|
||||
}
|
||||
|
||||
virtual void Child() override {
|
||||
int write_fd = WritePipeFD();
|
||||
|
||||
char c = 'M';
|
||||
ssize_t rv = WriteFD(write_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
||||
|
||||
pid_t pid = getpid();
|
||||
rv = WriteFD(write_fd, &pid, sizeof(pid));
|
||||
ASSERT_EQ(static_cast<ssize_t>(sizeof(pid)), rv) << ErrnoMessage("write");
|
||||
|
||||
int read_fd = ReadPipeFD();
|
||||
rv = ReadFD(read_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("read");
|
||||
EXPECT_EQ('m', c);
|
||||
}
|
||||
|
||||
private:
|
||||
// MachMultiprocess will have already exercised the Mach ports for IPC and the
|
||||
// child task port.
|
||||
virtual void MachMultiprocessParent() override {}
|
||||
|
||||
virtual void MachMultiprocessChild() override {}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestMachMultiprocess);
|
||||
};
|
||||
|
||||
|
180
util/test/multiprocess.cc
Normal file
180
util/test/multiprocess.cc
Normal file
@ -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
|
140
util/test/multiprocess.h
Normal file
140
util/test/multiprocess.h
Normal file
@ -0,0 +1,140 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_TEST_MULTIPROCESS_H_
|
||||
#define CRASHPAD_UTIL_TEST_MULTIPROCESS_H_
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
namespace internal {
|
||||
struct MultiprocessInfo;
|
||||
};
|
||||
|
||||
//! \brief Manages a multiprocess test.
|
||||
//!
|
||||
//! These tests are `fork()`-based. The parent and child processes are able to
|
||||
//! communicate via a pair of POSIX pipes.
|
||||
//!
|
||||
//! Subclasses are expected to implement the parent and child by overriding the
|
||||
//! appropriate methods.
|
||||
class Multiprocess {
|
||||
public:
|
||||
Multiprocess();
|
||||
|
||||
//! \brief Runs the test.
|
||||
//!
|
||||
//! This method establishes the proper testing environment by calling
|
||||
//! PreFork(), then calls `fork()`. In the parent process, it calls
|
||||
//! RunParent(), and in the child process, it calls RunChild().
|
||||
//!
|
||||
//! This method uses gtest assertions to validate the testing environment. If
|
||||
//! the testing environment cannot be set up properly, it is possible that
|
||||
//! MultiprocessParent() or MultiprocessChild() will not be called. In the
|
||||
//! parent process, this method also waits for the child process to exit after
|
||||
//! MultiprocessParent() returns, and verifies that it exited cleanly without
|
||||
//! raising any gtest assertions.
|
||||
void Run();
|
||||
|
||||
protected:
|
||||
~Multiprocess();
|
||||
|
||||
//! \brief Establishes the proper testing environment prior to forking.
|
||||
//!
|
||||
//! Subclasses that solely implement a test should not need to override this
|
||||
//! method. Subclasses that do not implement tests but instead implement
|
||||
//! additional testing features on top of this class may override this method
|
||||
//! provided that they call the superclass’ implementation first as follows:
|
||||
//!
|
||||
//! \code
|
||||
//! virtual void PreFork() override {
|
||||
//! Multiprocess::PreFork();
|
||||
//! if (testing::Test::HasFatalAssertions()) {
|
||||
//! return;
|
||||
//! }
|
||||
//!
|
||||
//! // Place subclass-specific pre-fork code here.
|
||||
//! }
|
||||
//! \endcode
|
||||
//!
|
||||
//! Subclass implementations may signal failure by raising their own fatal
|
||||
//! gtest assertions.
|
||||
virtual void PreFork();
|
||||
|
||||
//! \brief Returns the child process’ process ID.
|
||||
//!
|
||||
//! This method may only be called by the parent process.
|
||||
pid_t ChildPID() const;
|
||||
|
||||
//! \brief Returns the read pipe’s file descriptor.
|
||||
//!
|
||||
//! This method may be called by either the parent or the child process.
|
||||
//! Anything written to the write pipe in the partner process will appear
|
||||
//! on the this file descriptor in this process.
|
||||
int ReadPipeFD() const;
|
||||
|
||||
//! \brief Returns the write pipe’s file descriptor.
|
||||
//!
|
||||
//! This method may be called by either the parent or the child process.
|
||||
//! Anything written to this file descriptor in this process will appear on
|
||||
//! the read pipe in the partner process.
|
||||
int WritePipeFD() const;
|
||||
|
||||
private:
|
||||
//! \brief Runs the parent side of the test.
|
||||
//!
|
||||
//! This method establishes the parent’s environment and calls
|
||||
//! MultiprocessParent().
|
||||
void RunParent();
|
||||
|
||||
//! \brief Runs the child side of the test.
|
||||
//!
|
||||
//! This method establishes the child’s environment, calls
|
||||
//! MultiprocessChild(), and exits cleanly. However, if any failure (via fatal
|
||||
//! or nonfatal gtest assertion) is detected, the child will exit with a
|
||||
//! failure status.
|
||||
void RunChild();
|
||||
|
||||
//! \brief The subclass-provided parent routine.
|
||||
//!
|
||||
//! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`,
|
||||
//! `FAIL()`, etc.
|
||||
//!
|
||||
//! This method must not use a `wait()`-family system call to wait for the
|
||||
//! child process to exit, as this is handled by this class.
|
||||
//!
|
||||
//! Subclasses must implement this method to define how the parent operates.
|
||||
virtual void MultiprocessParent() = 0;
|
||||
|
||||
//! \brief The subclass-provided child routine.
|
||||
//!
|
||||
//! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`,
|
||||
//! `FAIL()`, etc.
|
||||
//!
|
||||
//! Subclasses must implement this method to define how the child operates.
|
||||
virtual void MultiprocessChild() = 0;
|
||||
|
||||
internal::MultiprocessInfo* info_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Multiprocess);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_TEST_MULTIPROCESS_H_
|
84
util/test/multiprocess_test.cc
Normal file
84
util/test/multiprocess_test.cc
Normal file
@ -0,0 +1,84 @@
|
||||
// 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 <unistd.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/file/fd_io.h"
|
||||
#include "util/test/errors.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace crashpad;
|
||||
using namespace crashpad::test;
|
||||
|
||||
class TestMultiprocess final : public Multiprocess {
|
||||
public:
|
||||
TestMultiprocess() : Multiprocess() {}
|
||||
|
||||
~TestMultiprocess() {}
|
||||
|
||||
private:
|
||||
virtual void MultiprocessParent() override {
|
||||
int read_fd = ReadPipeFD();
|
||||
char c;
|
||||
ssize_t rv = ReadFD(read_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("read");
|
||||
EXPECT_EQ('M', c);
|
||||
|
||||
pid_t pid;
|
||||
rv = ReadFD(read_fd, &pid, sizeof(pid));
|
||||
ASSERT_EQ(static_cast<ssize_t>(sizeof(pid)), rv) << ErrnoMessage("read");
|
||||
EXPECT_EQ(pid, ChildPID());
|
||||
|
||||
int write_fd = WritePipeFD();
|
||||
c = 'm';
|
||||
rv = WriteFD(write_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
||||
|
||||
// The child will close its end of the pipe and exit. Make sure that the
|
||||
// parent sees EOF.
|
||||
rv = ReadFD(read_fd, &c, 1);
|
||||
ASSERT_EQ(0, rv) << ErrnoMessage("read");
|
||||
}
|
||||
|
||||
virtual void MultiprocessChild() override {
|
||||
int write_fd = WritePipeFD();
|
||||
|
||||
char c = 'M';
|
||||
ssize_t rv = WriteFD(write_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("write");
|
||||
|
||||
pid_t pid = getpid();
|
||||
rv = WriteFD(write_fd, &pid, sizeof(pid));
|
||||
ASSERT_EQ(static_cast<ssize_t>(sizeof(pid)), rv) << ErrnoMessage("write");
|
||||
|
||||
int read_fd = ReadPipeFD();
|
||||
rv = ReadFD(read_fd, &c, 1);
|
||||
ASSERT_EQ(1, rv) << ErrnoMessage("read");
|
||||
EXPECT_EQ('m', c);
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestMultiprocess);
|
||||
};
|
||||
|
||||
TEST(Multiprocess, Multiprocess) {
|
||||
TestMultiprocess multiprocess;
|
||||
multiprocess.Run();
|
||||
}
|
||||
|
||||
} // namespace
|
@ -98,6 +98,8 @@
|
||||
'test/mac/mach_errors.h',
|
||||
'test/mac/mach_multiprocess.cc',
|
||||
'test/mac/mach_multiprocess.h',
|
||||
'test/multiprocess.cc',
|
||||
'test/multiprocess.h',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -132,6 +134,7 @@
|
||||
'posix/process_util_test.cc',
|
||||
'stdlib/strlcpy_test.cc',
|
||||
'test/mac/mach_multiprocess_test.cc',
|
||||
'test/multiprocess_test.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user