diff options
Diffstat (limited to 'base/test/multiprocess_test_android.cc')
-rw-r--r-- | base/test/multiprocess_test_android.cc | 476 |
1 files changed, 56 insertions, 420 deletions
diff --git a/base/test/multiprocess_test_android.cc b/base/test/multiprocess_test_android.cc index f58b452d1c..c74f013da1 100644 --- a/base/test/multiprocess_test_android.cc +++ b/base/test/multiprocess_test_android.cc @@ -4,451 +4,87 @@ #include "base/test/multiprocess_test.h" -#include <errno.h> #include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <unistd.h> - -#include <memory> -#include <utility> #include <vector> +#include "base/android/context_utils.h" +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/scoped_java_ref.h" #include "base/base_switches.h" #include "base/command_line.h" -#include "base/containers/hash_tables.h" -#include "base/lazy_instance.h" #include "base/logging.h" -#include "base/macros.h" -#include "base/pickle.h" -#include "base/posix/global_descriptors.h" -#include "base/posix/unix_domain_socket_linux.h" -#include "testing/multiprocess_func_list.h" +#include "jni/MainReturnCodeResult_jni.h" +#include "jni/MultiprocessTestClientLauncher_jni.h" namespace base { -namespace { - -const int kMaxMessageSize = 1024 * 1024; -const int kFragmentSize = 4096; - -// Message sent between parent process and helper child process. -enum class MessageType : uint32_t { - START_REQUEST, - START_RESPONSE, - WAIT_REQUEST, - WAIT_RESPONSE, -}; - -struct MessageHeader { - uint32_t size; - MessageType type; -}; - -struct StartProcessRequest { - MessageHeader header = - {sizeof(StartProcessRequest), MessageType::START_REQUEST}; - - uint32_t num_args = 0; - uint32_t num_fds = 0; -}; - -struct StartProcessResponse { - MessageHeader header = - {sizeof(StartProcessResponse), MessageType::START_RESPONSE}; - - pid_t child_pid; -}; - -struct WaitProcessRequest { - MessageHeader header = - {sizeof(WaitProcessRequest), MessageType::WAIT_REQUEST}; - - pid_t pid; - uint64_t timeout_ms; -}; - -struct WaitProcessResponse { - MessageHeader header = - {sizeof(WaitProcessResponse), MessageType::WAIT_RESPONSE}; - - bool success = false; - int32_t exit_code = 0; -}; - -// Helper class that implements an alternate test child launcher for -// multi-process tests. The default implementation doesn't work if the child is -// launched after starting threads. However, for some tests (i.e. Mojo), this -// is necessary. This implementation works around that issue by forking a helper -// process very early in main(), before any real work is done. Then, when a -// child needs to be spawned, a message is sent to that helper process, which -// then forks and returns the result to the parent. The forked child then calls -// main() and things look as though a brand new process has been fork/exec'd. -class LaunchHelper { - public: - using MainFunction = int (*)(int, char**); - - LaunchHelper() {} - - // Initialise the alternate test child implementation. - void Init(MainFunction main); - - // Starts a child test helper process. - Process StartChildTestHelper(const std::string& procname, - const CommandLine& base_command_line, - const LaunchOptions& options); - - // Waits for a child test helper process. - bool WaitForChildExitWithTimeout(const Process& process, TimeDelta timeout, - int* exit_code); - - bool IsReady() const { return child_fd_ != -1; } - bool IsChild() const { return is_child_; } - - private: - // Wrappers around sendmsg/recvmsg that supports message fragmentation. - void Send(int fd, const MessageHeader* msg, const std::vector<int>& fds); - ssize_t Recv(int fd, void* buf, std::vector<ScopedFD>* fds); - - // Parent process implementation. - void DoParent(int fd); - // Helper process implementation. - void DoHelper(int fd); - - void StartProcessInHelper(const StartProcessRequest* request, - std::vector<ScopedFD> fds); - void WaitForChildInHelper(const WaitProcessRequest* request); - - bool is_child_ = false; - - // Parent vars. - int child_fd_ = -1; - - // Helper vars. - int parent_fd_ = -1; - MainFunction main_ = nullptr; - - DISALLOW_COPY_AND_ASSIGN(LaunchHelper); -}; - -void LaunchHelper::Init(MainFunction main) { - main_ = main; - - // Create a communication channel between the parent and child launch helper. - // fd[0] belongs to the parent, fd[1] belongs to the child. - int fds[2] = {-1, -1}; - int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); - PCHECK(rv == 0); - CHECK_NE(-1, fds[0]); - CHECK_NE(-1, fds[1]); - - pid_t pid = fork(); - PCHECK(pid >= 0) << "Fork failed"; - if (pid) { - // Parent. - rv = close(fds[1]); - PCHECK(rv == 0); - DoParent(fds[0]); - } else { - // Helper. - rv = close(fds[0]); - PCHECK(rv == 0); - DoHelper(fds[1]); - NOTREACHED(); - _exit(0); - } -} - -void LaunchHelper::Send( - int fd, const MessageHeader* msg, const std::vector<int>& fds) { - uint32_t bytes_remaining = msg->size; - const char* buf = reinterpret_cast<const char*>(msg); - while (bytes_remaining) { - size_t send_size = - (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining; - bool success = UnixDomainSocket::SendMsg( - fd, buf, send_size, - (bytes_remaining == msg->size) ? fds : std::vector<int>()); - CHECK(success); - bytes_remaining -= send_size; - buf += send_size; - } -} - -ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) { - ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds); - if (size <= 0) - return size; - - const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf); - CHECK(header->size < kMaxMessageSize); - uint32_t bytes_remaining = header->size - size; - char* buffer = reinterpret_cast<char*>(buf); - buffer += size; - while (bytes_remaining) { - std::vector<ScopedFD> dummy_fds; - size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds); - if (size <= 0) - return size; - - CHECK(dummy_fds.empty()); - CHECK(size == kFragmentSize || - static_cast<size_t>(size) == bytes_remaining); - bytes_remaining -= size; - buffer += size; - } - return header->size; -} - -void LaunchHelper::DoParent(int fd) { - child_fd_ = fd; -} - -void LaunchHelper::DoHelper(int fd) { - parent_fd_ = fd; - is_child_ = true; - std::unique_ptr<char[]> buf(new char[kMaxMessageSize]); - while (true) { - // Wait for a message from the parent. - std::vector<ScopedFD> fds; - ssize_t size = Recv(parent_fd_, buf.get(), &fds); - if (size == 0 || (size < 0 && errno == ECONNRESET)) { - _exit(0); - } - PCHECK(size > 0); - - const MessageHeader* header = - reinterpret_cast<const MessageHeader*>(buf.get()); - CHECK_EQ(static_cast<ssize_t>(header->size), size); - switch (header->type) { - case MessageType::START_REQUEST: - StartProcessInHelper( - reinterpret_cast<const StartProcessRequest*>(buf.get()), - std::move(fds)); - break; - case MessageType::WAIT_REQUEST: - WaitForChildInHelper( - reinterpret_cast<const WaitProcessRequest*>(buf.get())); - break; - default: - LOG(FATAL) << "Unsupported message type: " - << static_cast<uint32_t>(header->type); - } - } -} - -void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request, - std::vector<ScopedFD> fds) { - pid_t pid = fork(); - PCHECK(pid >= 0) << "Fork failed"; - if (pid) { - // Helper. - StartProcessResponse resp; - resp.child_pid = pid; - Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp), - std::vector<int>()); - } else { - // Child. - PCHECK(close(parent_fd_) == 0); - parent_fd_ = -1; - CommandLine::Reset(); - - Pickle serialised_extra(reinterpret_cast<const char*>(request + 1), - request->header.size - sizeof(StartProcessRequest)); - PickleIterator iter(serialised_extra); - std::vector<std::string> args; - for (size_t i = 0; i < request->num_args; i++) { - std::string arg; - CHECK(iter.ReadString(&arg)); - args.push_back(std::move(arg)); - } - - CHECK_EQ(request->num_fds, fds.size()); - for (size_t i = 0; i < request->num_fds; i++) { - int new_fd; - CHECK(iter.ReadInt(&new_fd)); - int old_fd = fds[i].release(); - if (new_fd != old_fd) { - if (dup2(old_fd, new_fd) < 0) { - PLOG(FATAL) << "dup2"; - } - PCHECK(close(old_fd) == 0); - } - } +// A very basic implementation for Android. On Android tests can run in an APK +// and we don't have an executable to exec*. This implementation does the bare +// minimum to execute the method specified by procname (in the child process). +// - All options except |fds_to_remap| are ignored. +// +// NOTE: This MUST NOT run on the main thread of the NativeTest application. +Process SpawnMultiProcessTestChild(const std::string& procname, + const CommandLine& base_command_line, + const LaunchOptions& options) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); - // argv has argc+1 elements, where the last element is NULL. - std::unique_ptr<char*[]> argv(new char*[args.size() + 1]); - for (size_t i = 0; i < args.size(); i++) { - argv[i] = const_cast<char*>(args[i].c_str()); + std::vector<int> fd_keys; + std::vector<int> fd_fds; + if (options.fds_to_remap) { + for (auto& iter : *options.fds_to_remap) { + fd_keys.push_back(iter.second); + fd_fds.push_back(iter.first); } - argv[args.size()] = nullptr; - _exit(main_(args.size(), argv.get())); - NOTREACHED(); } -} - -void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) { - Process process(request->pid); - TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms); - int exit_code = -1; - bool success = process.WaitForExitWithTimeout(timeout, &exit_code); - - WaitProcessResponse resp; - resp.exit_code = exit_code; - resp.success = success; - Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp), - std::vector<int>()); -} -Process LaunchHelper::StartChildTestHelper(const std::string& procname, - const CommandLine& base_command_line, - const LaunchOptions& options) { + android::ScopedJavaLocalRef<jobjectArray> fds = + android::Java_MultiprocessTestClientLauncher_makeFdInfoArray( + env, base::android::ToJavaIntArray(env, fd_keys), + base::android::ToJavaIntArray(env, fd_fds)); CommandLine command_line(base_command_line); - if (!command_line.HasSwitch(switches::kTestChildProcess)) + if (!command_line.HasSwitch(switches::kTestChildProcess)) { command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); - - StartProcessRequest request; - Pickle serialised_extra; - const CommandLine::StringVector& argv = command_line.argv(); - for (const auto& arg : argv) - CHECK(serialised_extra.WriteString(arg)); - request.num_args = argv.size(); - - std::vector<int> fds_to_send; - if (options.fds_to_remap) { - for (auto p : *options.fds_to_remap) { - CHECK(serialised_extra.WriteInt(p.second)); - fds_to_send.push_back(p.first); - } - request.num_fds = options.fds_to_remap->size(); } - size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size(); - request.header.size = buf_size; - std::unique_ptr<char[]> buffer(new char[buf_size]); - memcpy(buffer.get(), &request, sizeof(StartProcessRequest)); - memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(), - serialised_extra.size()); - - // Send start message. - Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()), - fds_to_send); - - // Synchronously get response. - StartProcessResponse response; - std::vector<ScopedFD> recv_fds; - ssize_t resp_size = Recv(child_fd_, &response, &recv_fds); - PCHECK(resp_size == sizeof(StartProcessResponse)); - - return Process(response.child_pid); + android::ScopedJavaLocalRef<jobjectArray> j_argv = + android::ToJavaArrayOfStrings(env, command_line.argv()); + jint pid = android::Java_MultiprocessTestClientLauncher_launchClient( + env, android::GetApplicationContext(), j_argv, fds); + return Process(pid); } -bool LaunchHelper::WaitForChildExitWithTimeout( - const Process& process, TimeDelta timeout, int* exit_code) { - - WaitProcessRequest request; - request.pid = process.Handle(); - request.timeout_ms = timeout.InMilliseconds(); - - Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request), - std::vector<int>()); - - WaitProcessResponse response; - std::vector<ScopedFD> recv_fds; - ssize_t resp_size = Recv(child_fd_, &response, &recv_fds); - PCHECK(resp_size == sizeof(WaitProcessResponse)); - - if (!response.success) +bool WaitForMultiprocessTestChildExit(const Process& process, + TimeDelta timeout, + int* exit_code) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); + + base::android::ScopedJavaLocalRef<jobject> result_code = + android::Java_MultiprocessTestClientLauncher_waitForMainToReturn( + env, android::GetApplicationContext(), process.Pid(), + static_cast<int32_t>(timeout.InMilliseconds())); + if (result_code.is_null() || + Java_MainReturnCodeResult_hasTimedOut(env, result_code)) { return false; - - *exit_code = response.exit_code; + } + if (exit_code) { + *exit_code = Java_MainReturnCodeResult_getReturnCode(env, result_code); + } return true; } -LazyInstance<LaunchHelper>::Leaky g_launch_helper; - -} // namespace - -void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) { - DCHECK(main); - // Don't allow child processes to themselves create new child processes. - if (g_launch_helper.Get().IsChild()) - return; - g_launch_helper.Get().Init(main); -} - -bool AndroidIsChildProcess() { - return g_launch_helper.Get().IsChild(); -} - -bool AndroidWaitForChildExitWithTimeout( - const Process& process, TimeDelta timeout, int* exit_code) { - CHECK(g_launch_helper.Get().IsReady()); - return g_launch_helper.Get().WaitForChildExitWithTimeout( - process, timeout, exit_code); -} - -// A very basic implementation for Android. On Android tests can run in an APK -// and we don't have an executable to exec*. This implementation does the bare -// minimum to execute the method specified by procname (in the child process). -// - All options except |fds_to_remap| are ignored. -Process SpawnMultiProcessTestChild(const std::string& procname, - const CommandLine& base_command_line, - const LaunchOptions& options) { - if (g_launch_helper.Get().IsReady()) { - return g_launch_helper.Get().StartChildTestHelper( - procname, base_command_line, options); - } - - // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of - // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 - FileHandleMappingVector empty; - const FileHandleMappingVector* fds_to_remap = - options.fds_to_remap ? options.fds_to_remap : ∅ - - pid_t pid = fork(); - - if (pid < 0) { - PLOG(ERROR) << "fork"; - return Process(); - } - if (pid > 0) { - // Parent process. - return Process(pid); - } - // Child process. - base::hash_set<int> fds_to_keep_open; - for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin(); - it != fds_to_remap->end(); ++it) { - fds_to_keep_open.insert(it->first); - } - // Keep standard FDs (stdin, stdout, stderr, etc.) open since this - // is not meant to spawn a daemon. - int base = GlobalDescriptors::kBaseDescriptor; - for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) { - if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) { - close(fd); - } - } - for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin(); - it != fds_to_remap->end(); ++it) { - int old_fd = it->first; - int new_fd = it->second; - if (dup2(old_fd, new_fd) < 0) { - PLOG(FATAL) << "dup2"; - } - close(old_fd); - } - CommandLine::Reset(); - CommandLine::Init(0, nullptr); - CommandLine* command_line = CommandLine::ForCurrentProcess(); - command_line->InitFromArgv(base_command_line.argv()); - if (!command_line->HasSwitch(switches::kTestChildProcess)) - command_line->AppendSwitchASCII(switches::kTestChildProcess, procname); +bool TerminateMultiProcessTestChild(const Process& process, + int exit_code, + bool wait) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); - _exit(multi_process_function_list::InvokeChildProcessTest(procname)); - return Process(); + return android::Java_MultiprocessTestClientLauncher_terminate( + env, android::GetApplicationContext(), process.Pid(), exit_code, wait); } } // namespace base |