Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Unified Diff: base/test/multiprocess_test_android.cc

Issue 2549363004: Multiprocess test client: Android child process launcher rework. (Closed)
Patch Set: content_unittests + sync Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/test/multiprocess_test.cc ('k') | base/test/test_support_android.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/test/multiprocess_test_android.cc
diff --git a/base/test/multiprocess_test_android.cc b/base/test/multiprocess_test_android.cc
index f58b452d1cb316184884c6e7e19a196ed6a90c8c..c74f013da1cacc0ca292de356c5ad746c08c5b31 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 : &empty;
-
- 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
« no previous file with comments | « base/test/multiprocess_test.cc ('k') | base/test/test_support_android.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698