From 2e308484d9693f8251748c295f6ed7ed25d767eb Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 26 Oct 2018 16:19:25 -0400 Subject: 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 --- googletest/src/gtest-death-test.cc | 158 +++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 51 deletions(-) diff --git a/googletest/src/gtest-death-test.cc b/googletest/src/gtest-death-test.cc index b3a572a..2c15fdc 100644 --- a/googletest/src/gtest-death-test.cc +++ b/googletest/src/gtest-death-test.cc @@ -64,6 +64,10 @@ # if GTEST_OS_FUCHSIA # include # include +# include +# include +# include +# include # include # include # include @@ -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); - - // 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); + // 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); - 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 -- cgit v0.12