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..c306a46ad08a400e056fa9b6e8b56690f256b1be 100644 |
--- a/base/test/multiprocess_test_android.cc |
+++ b/base/test/multiprocess_test_android.cc |
@@ -4,451 +4,86 @@ |
#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_ids; |
+ std::vector<int> fd_fds; |
+ if (options.fds_to_remap) { |
+ for (auto& iter : *options.fds_to_remap) { |
+ fd_ids.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_ids), |
+ 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(), |
+ 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 |