Googletest export

[Fuchsia] Make the child process stderr redirection use a socket.

This changes the stderr redirection mechanism for the child process in Fuchsia death tests to use a Zircon socket rather than fd redirection. This should improve performance and reliability of the redirection process.
This also includes some minor style cleanups.

PiperOrigin-RevId: 218903196
This commit is contained in:
Abseil Team 2018-10-26 16:19:25 -04:00 committed by Gennadiy Civil
parent b57c703963
commit 2e308484d9

View File

@ -64,6 +64,10 @@
# if GTEST_OS_FUCHSIA
# include <lib/fdio/io.h>
# include <lib/fdio/spawn.h>
# include <lib/fdio/util.h>
# include <lib/zx/socket.h>
# include <lib/zx/port.h>
# include <lib/zx/process.h>
# include <zircon/processargs.h>
# include <zircon/syscalls.h>
# include <zircon/syscalls/port.h>
@ -422,6 +426,9 @@ class DeathTestImpl : public DeathTest {
// case of unexpected codes.
void ReadAndInterpretStatusByte();
// Returns stderr output from the child process.
virtual std::string GetErrorLogs();
private:
// The textual content of the code this object is testing. This class
// doesn't own this string and should not attempt to delete it.
@ -490,6 +497,10 @@ void DeathTestImpl::ReadAndInterpretStatusByte() {
set_read_fd(-1);
}
std::string DeathTestImpl::GetErrorLogs() {
return GetCapturedStderr();
}
// Signals that the death test code which should have exited, didn't.
// Should be called only in a death test child process.
// Writes a status byte to the child's status file descriptor, then
@ -558,7 +569,7 @@ bool DeathTestImpl::Passed(bool status_ok) {
if (!spawned())
return false;
const std::string error_message = GetCapturedStderr();
const std::string error_message = GetErrorLogs();
bool success = false;
Message buffer;
@ -810,12 +821,6 @@ class FuchsiaDeathTest : public DeathTestImpl {
const char* file,
int line)
: DeathTestImpl(a_statement, a_regex), file_(file), line_(line) {}
virtual ~FuchsiaDeathTest() {
zx_status_t status = zx_handle_close(child_process_);
GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
status = zx_handle_close(port_);
GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
}
// All of these virtual functions are inherited from DeathTest.
virtual int Wait();
@ -826,9 +831,12 @@ class FuchsiaDeathTest : public DeathTestImpl {
const char* const file_;
// The line number on which the death test is located.
const int line_;
// The stderr data captured by the child process.
std::string captured_stderr_;
zx_handle_t child_process_ = ZX_HANDLE_INVALID;
zx_handle_t port_ = ZX_HANDLE_INVALID;
zx::process child_process_;
zx::port port_;
zx::socket stderr_socket_;
};
// Utility class for accumulating command-line arguments.
@ -872,51 +880,74 @@ class Arguments {
// status, or 0 if no child process exists. As a side effect, sets the
// outcome data member.
int FuchsiaDeathTest::Wait() {
const int kProcessKey = 0;
const int kSocketKey = 1;
if (!spawned())
return 0;
// Register to wait for the child process to terminate.
zx_status_t status_zx;
status_zx = zx_object_wait_async(child_process_,
port_,
0 /* key */,
ZX_PROCESS_TERMINATED,
ZX_WAIT_ASYNC_ONCE);
status_zx = child_process_.wait_async(
port_, kProcessKey, ZX_PROCESS_TERMINATED, ZX_WAIT_ASYNC_ONCE);
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
// Register to wait for the socket to be readable or closed.
status_zx = stderr_socket_.wait_async(
port_, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED,
ZX_WAIT_ASYNC_REPEATING);
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
// Wait for it to terminate, or an exception to be received.
zx_port_packet_t packet;
status_zx = zx_port_wait(port_, ZX_TIME_INFINITE, &packet);
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
if (ZX_PKT_IS_EXCEPTION(packet.type)) {
// Process encountered an exception. Kill it directly rather than letting
// other handlers process the event.
status_zx = zx_task_kill(child_process_);
bool process_terminated = false;
bool socket_closed = false;
do {
zx_port_packet_t packet = {};
status_zx = port_.wait(zx::time::infinite(), &packet);
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
// Now wait for |child_process_| to terminate.
zx_signals_t signals = 0;
status_zx = zx_object_wait_one(
child_process_, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, &signals);
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
GTEST_DEATH_TEST_CHECK_(signals & ZX_PROCESS_TERMINATED);
} else {
// Process terminated.
GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type));
GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED);
}
if (packet.key == kProcessKey) {
if (ZX_PKT_IS_EXCEPTION(packet.type)) {
// Process encountered an exception. Kill it directly rather than
// letting other handlers process the event. We will get a second
// kProcessKey event when the process actually terminates.
status_zx = child_process_.kill();
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
} else {
// Process terminated.
GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type));
GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED);
process_terminated = true;
}
} else if (packet.key == kSocketKey) {
GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_REP(packet.type));
if (packet.signal.observed & ZX_SOCKET_READABLE) {
// Read data from the socket.
constexpr size_t kBufferSize = 1024;
do {
size_t old_length = captured_stderr_.length();
size_t bytes_read = 0;
captured_stderr_.resize(old_length + kBufferSize);
status_zx = stderr_socket_.read(
0, &captured_stderr_.front() + old_length, kBufferSize,
&bytes_read);
captured_stderr_.resize(old_length + bytes_read);
} while (status_zx == ZX_OK);
if (status_zx == ZX_ERR_PEER_CLOSED) {
socket_closed = true;
} else {
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_ERR_SHOULD_WAIT);
}
} else {
GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_SOCKET_PEER_CLOSED);
socket_closed = true;
}
}
} while (!process_terminated && !socket_closed);
ReadAndInterpretStatusByte();
zx_info_process_t buffer;
status_zx = zx_object_get_info(
child_process_,
ZX_INFO_PROCESS,
&buffer,
sizeof(buffer),
nullptr,
nullptr);
status_zx = child_process_.get_info(
ZX_INFO_PROCESS, &buffer, sizeof(buffer), nullptr, nullptr);
GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
GTEST_DEATH_TEST_CHECK_(buffer.exited);
@ -943,7 +974,6 @@ DeathTest::TestRole FuchsiaDeathTest::AssumeRole() {
return EXECUTE_TEST;
}
CaptureStderr();
// Flush the log buffers since the log streams are shared with the child.
FlushInfoLog();
@ -970,29 +1000,55 @@ DeathTest::TestRole FuchsiaDeathTest::AssumeRole() {
set_read_fd(status);
// Set the pipe handle for the child.
fdio_spawn_action_t add_handle_action = {};
add_handle_action.action = FDIO_SPAWN_ACTION_ADD_HANDLE;
add_handle_action.h.id = PA_HND(type, kFuchsiaReadPipeFd);
add_handle_action.h.handle = child_pipe_handle;
fdio_spawn_action_t spawn_actions[2] = {};
fdio_spawn_action_t* add_handle_action = &spawn_actions[0];
add_handle_action->action = FDIO_SPAWN_ACTION_ADD_HANDLE;
add_handle_action->h.id = PA_HND(type, kFuchsiaReadPipeFd);
add_handle_action->h.handle = child_pipe_handle;
// Create a socket pair will be used to receive the child process' stderr.
zx::socket stderr_producer_socket;
status =
zx::socket::create(0, &stderr_producer_socket, &stderr_socket_);
GTEST_DEATH_TEST_CHECK_(status >= 0);
int stderr_producer_fd = -1;
zx_handle_t producer_handle[1] = { stderr_producer_socket.release() };
uint32_t producer_handle_type[1] = { PA_FDIO_SOCKET };
status = fdio_create_fd(
producer_handle, producer_handle_type, 1, &stderr_producer_fd);
GTEST_DEATH_TEST_CHECK_(status >= 0);
// Make the stderr socket nonblocking.
GTEST_DEATH_TEST_CHECK_(fcntl(stderr_producer_fd, F_SETFL, 0) == 0);
fdio_spawn_action_t* add_stderr_action = &spawn_actions[1];
add_stderr_action->action = FDIO_SPAWN_ACTION_CLONE_FD;
add_stderr_action->fd.local_fd = stderr_producer_fd;
add_stderr_action->fd.target_fd = STDERR_FILENO;
// Spawn the child process.
status = fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL,
args.Argv()[0], args.Argv(), nullptr, 1,
&add_handle_action, &child_process_, nullptr);
status = fdio_spawn_etc(
ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, args.Argv()[0], args.Argv(),
nullptr, 2, spawn_actions, child_process_.reset_and_get_address(),
nullptr);
GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
// Create an exception port and attach it to the |child_process_|, to allow
// us to suppress the system default exception handler from firing.
status = zx_port_create(0, &port_);
status = zx::port::create(0, &port_);
GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
status = zx_task_bind_exception_port(
child_process_, port_, 0 /* key */, 0 /*options */);
status = child_process_.bind_exception_port(
port_, 0 /* key */, 0 /*options */);
GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
set_spawned(true);
return OVERSEE_TEST;
}
std::string FuchsiaDeathTest::GetErrorLogs() {
return captured_stderr_;
}
#else // We are neither on Windows, nor on Fuchsia.
// ForkingDeathTest provides implementations for most of the abstract