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

Side by Side 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/test/multiprocess_test.h" 5 #include "base/test/multiprocess_test.h"
6 6
7 #include <errno.h>
8 #include <string.h> 7 #include <string.h>
9 #include <sys/types.h>
10 #include <sys/socket.h>
11 #include <unistd.h>
12
13 #include <memory>
14 #include <utility>
15 #include <vector> 8 #include <vector>
16 9
10 #include "base/android/context_utils.h"
11 #include "base/android/jni_android.h"
12 #include "base/android/jni_array.h"
13 #include "base/android/scoped_java_ref.h"
17 #include "base/base_switches.h" 14 #include "base/base_switches.h"
18 #include "base/command_line.h" 15 #include "base/command_line.h"
19 #include "base/containers/hash_tables.h"
20 #include "base/lazy_instance.h"
21 #include "base/logging.h" 16 #include "base/logging.h"
22 #include "base/macros.h" 17 #include "jni/MainReturnCodeResult_jni.h"
23 #include "base/pickle.h" 18 #include "jni/MultiprocessTestClientLauncher_jni.h"
24 #include "base/posix/global_descriptors.h"
25 #include "base/posix/unix_domain_socket_linux.h"
26 #include "testing/multiprocess_func_list.h"
27 19
28 namespace base { 20 namespace base {
29 21
30 namespace {
31
32 const int kMaxMessageSize = 1024 * 1024;
33 const int kFragmentSize = 4096;
34
35 // Message sent between parent process and helper child process.
36 enum class MessageType : uint32_t {
37 START_REQUEST,
38 START_RESPONSE,
39 WAIT_REQUEST,
40 WAIT_RESPONSE,
41 };
42
43 struct MessageHeader {
44 uint32_t size;
45 MessageType type;
46 };
47
48 struct StartProcessRequest {
49 MessageHeader header =
50 {sizeof(StartProcessRequest), MessageType::START_REQUEST};
51
52 uint32_t num_args = 0;
53 uint32_t num_fds = 0;
54 };
55
56 struct StartProcessResponse {
57 MessageHeader header =
58 {sizeof(StartProcessResponse), MessageType::START_RESPONSE};
59
60 pid_t child_pid;
61 };
62
63 struct WaitProcessRequest {
64 MessageHeader header =
65 {sizeof(WaitProcessRequest), MessageType::WAIT_REQUEST};
66
67 pid_t pid;
68 uint64_t timeout_ms;
69 };
70
71 struct WaitProcessResponse {
72 MessageHeader header =
73 {sizeof(WaitProcessResponse), MessageType::WAIT_RESPONSE};
74
75 bool success = false;
76 int32_t exit_code = 0;
77 };
78
79 // Helper class that implements an alternate test child launcher for
80 // multi-process tests. The default implementation doesn't work if the child is
81 // launched after starting threads. However, for some tests (i.e. Mojo), this
82 // is necessary. This implementation works around that issue by forking a helper
83 // process very early in main(), before any real work is done. Then, when a
84 // child needs to be spawned, a message is sent to that helper process, which
85 // then forks and returns the result to the parent. The forked child then calls
86 // main() and things look as though a brand new process has been fork/exec'd.
87 class LaunchHelper {
88 public:
89 using MainFunction = int (*)(int, char**);
90
91 LaunchHelper() {}
92
93 // Initialise the alternate test child implementation.
94 void Init(MainFunction main);
95
96 // Starts a child test helper process.
97 Process StartChildTestHelper(const std::string& procname,
98 const CommandLine& base_command_line,
99 const LaunchOptions& options);
100
101 // Waits for a child test helper process.
102 bool WaitForChildExitWithTimeout(const Process& process, TimeDelta timeout,
103 int* exit_code);
104
105 bool IsReady() const { return child_fd_ != -1; }
106 bool IsChild() const { return is_child_; }
107
108 private:
109 // Wrappers around sendmsg/recvmsg that supports message fragmentation.
110 void Send(int fd, const MessageHeader* msg, const std::vector<int>& fds);
111 ssize_t Recv(int fd, void* buf, std::vector<ScopedFD>* fds);
112
113 // Parent process implementation.
114 void DoParent(int fd);
115 // Helper process implementation.
116 void DoHelper(int fd);
117
118 void StartProcessInHelper(const StartProcessRequest* request,
119 std::vector<ScopedFD> fds);
120 void WaitForChildInHelper(const WaitProcessRequest* request);
121
122 bool is_child_ = false;
123
124 // Parent vars.
125 int child_fd_ = -1;
126
127 // Helper vars.
128 int parent_fd_ = -1;
129 MainFunction main_ = nullptr;
130
131 DISALLOW_COPY_AND_ASSIGN(LaunchHelper);
132 };
133
134 void LaunchHelper::Init(MainFunction main) {
135 main_ = main;
136
137 // Create a communication channel between the parent and child launch helper.
138 // fd[0] belongs to the parent, fd[1] belongs to the child.
139 int fds[2] = {-1, -1};
140 int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds);
141 PCHECK(rv == 0);
142 CHECK_NE(-1, fds[0]);
143 CHECK_NE(-1, fds[1]);
144
145 pid_t pid = fork();
146 PCHECK(pid >= 0) << "Fork failed";
147 if (pid) {
148 // Parent.
149 rv = close(fds[1]);
150 PCHECK(rv == 0);
151 DoParent(fds[0]);
152 } else {
153 // Helper.
154 rv = close(fds[0]);
155 PCHECK(rv == 0);
156 DoHelper(fds[1]);
157 NOTREACHED();
158 _exit(0);
159 }
160 }
161
162 void LaunchHelper::Send(
163 int fd, const MessageHeader* msg, const std::vector<int>& fds) {
164 uint32_t bytes_remaining = msg->size;
165 const char* buf = reinterpret_cast<const char*>(msg);
166 while (bytes_remaining) {
167 size_t send_size =
168 (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining;
169 bool success = UnixDomainSocket::SendMsg(
170 fd, buf, send_size,
171 (bytes_remaining == msg->size) ? fds : std::vector<int>());
172 CHECK(success);
173 bytes_remaining -= send_size;
174 buf += send_size;
175 }
176 }
177
178 ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) {
179 ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds);
180 if (size <= 0)
181 return size;
182
183 const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf);
184 CHECK(header->size < kMaxMessageSize);
185 uint32_t bytes_remaining = header->size - size;
186 char* buffer = reinterpret_cast<char*>(buf);
187 buffer += size;
188 while (bytes_remaining) {
189 std::vector<ScopedFD> dummy_fds;
190 size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds);
191 if (size <= 0)
192 return size;
193
194 CHECK(dummy_fds.empty());
195 CHECK(size == kFragmentSize ||
196 static_cast<size_t>(size) == bytes_remaining);
197 bytes_remaining -= size;
198 buffer += size;
199 }
200 return header->size;
201 }
202
203 void LaunchHelper::DoParent(int fd) {
204 child_fd_ = fd;
205 }
206
207 void LaunchHelper::DoHelper(int fd) {
208 parent_fd_ = fd;
209 is_child_ = true;
210 std::unique_ptr<char[]> buf(new char[kMaxMessageSize]);
211 while (true) {
212 // Wait for a message from the parent.
213 std::vector<ScopedFD> fds;
214 ssize_t size = Recv(parent_fd_, buf.get(), &fds);
215 if (size == 0 || (size < 0 && errno == ECONNRESET)) {
216 _exit(0);
217 }
218 PCHECK(size > 0);
219
220 const MessageHeader* header =
221 reinterpret_cast<const MessageHeader*>(buf.get());
222 CHECK_EQ(static_cast<ssize_t>(header->size), size);
223 switch (header->type) {
224 case MessageType::START_REQUEST:
225 StartProcessInHelper(
226 reinterpret_cast<const StartProcessRequest*>(buf.get()),
227 std::move(fds));
228 break;
229 case MessageType::WAIT_REQUEST:
230 WaitForChildInHelper(
231 reinterpret_cast<const WaitProcessRequest*>(buf.get()));
232 break;
233 default:
234 LOG(FATAL) << "Unsupported message type: "
235 << static_cast<uint32_t>(header->type);
236 }
237 }
238 }
239
240 void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request,
241 std::vector<ScopedFD> fds) {
242 pid_t pid = fork();
243 PCHECK(pid >= 0) << "Fork failed";
244 if (pid) {
245 // Helper.
246 StartProcessResponse resp;
247 resp.child_pid = pid;
248 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
249 std::vector<int>());
250 } else {
251 // Child.
252 PCHECK(close(parent_fd_) == 0);
253 parent_fd_ = -1;
254 CommandLine::Reset();
255
256 Pickle serialised_extra(reinterpret_cast<const char*>(request + 1),
257 request->header.size - sizeof(StartProcessRequest));
258 PickleIterator iter(serialised_extra);
259 std::vector<std::string> args;
260 for (size_t i = 0; i < request->num_args; i++) {
261 std::string arg;
262 CHECK(iter.ReadString(&arg));
263 args.push_back(std::move(arg));
264 }
265
266 CHECK_EQ(request->num_fds, fds.size());
267 for (size_t i = 0; i < request->num_fds; i++) {
268 int new_fd;
269 CHECK(iter.ReadInt(&new_fd));
270 int old_fd = fds[i].release();
271 if (new_fd != old_fd) {
272 if (dup2(old_fd, new_fd) < 0) {
273 PLOG(FATAL) << "dup2";
274 }
275 PCHECK(close(old_fd) == 0);
276 }
277 }
278
279 // argv has argc+1 elements, where the last element is NULL.
280 std::unique_ptr<char*[]> argv(new char*[args.size() + 1]);
281 for (size_t i = 0; i < args.size(); i++) {
282 argv[i] = const_cast<char*>(args[i].c_str());
283 }
284 argv[args.size()] = nullptr;
285 _exit(main_(args.size(), argv.get()));
286 NOTREACHED();
287 }
288 }
289
290 void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) {
291 Process process(request->pid);
292 TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms);
293 int exit_code = -1;
294 bool success = process.WaitForExitWithTimeout(timeout, &exit_code);
295
296 WaitProcessResponse resp;
297 resp.exit_code = exit_code;
298 resp.success = success;
299 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
300 std::vector<int>());
301 }
302
303 Process LaunchHelper::StartChildTestHelper(const std::string& procname,
304 const CommandLine& base_command_line,
305 const LaunchOptions& options) {
306
307 CommandLine command_line(base_command_line);
308 if (!command_line.HasSwitch(switches::kTestChildProcess))
309 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname);
310
311 StartProcessRequest request;
312 Pickle serialised_extra;
313 const CommandLine::StringVector& argv = command_line.argv();
314 for (const auto& arg : argv)
315 CHECK(serialised_extra.WriteString(arg));
316 request.num_args = argv.size();
317
318 std::vector<int> fds_to_send;
319 if (options.fds_to_remap) {
320 for (auto p : *options.fds_to_remap) {
321 CHECK(serialised_extra.WriteInt(p.second));
322 fds_to_send.push_back(p.first);
323 }
324 request.num_fds = options.fds_to_remap->size();
325 }
326
327 size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size();
328 request.header.size = buf_size;
329 std::unique_ptr<char[]> buffer(new char[buf_size]);
330 memcpy(buffer.get(), &request, sizeof(StartProcessRequest));
331 memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(),
332 serialised_extra.size());
333
334 // Send start message.
335 Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()),
336 fds_to_send);
337
338 // Synchronously get response.
339 StartProcessResponse response;
340 std::vector<ScopedFD> recv_fds;
341 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
342 PCHECK(resp_size == sizeof(StartProcessResponse));
343
344 return Process(response.child_pid);
345 }
346
347 bool LaunchHelper::WaitForChildExitWithTimeout(
348 const Process& process, TimeDelta timeout, int* exit_code) {
349
350 WaitProcessRequest request;
351 request.pid = process.Handle();
352 request.timeout_ms = timeout.InMilliseconds();
353
354 Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request),
355 std::vector<int>());
356
357 WaitProcessResponse response;
358 std::vector<ScopedFD> recv_fds;
359 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
360 PCHECK(resp_size == sizeof(WaitProcessResponse));
361
362 if (!response.success)
363 return false;
364
365 *exit_code = response.exit_code;
366 return true;
367 }
368
369 LazyInstance<LaunchHelper>::Leaky g_launch_helper;
370
371 } // namespace
372
373 void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) {
374 DCHECK(main);
375 // Don't allow child processes to themselves create new child processes.
376 if (g_launch_helper.Get().IsChild())
377 return;
378 g_launch_helper.Get().Init(main);
379 }
380
381 bool AndroidIsChildProcess() {
382 return g_launch_helper.Get().IsChild();
383 }
384
385 bool AndroidWaitForChildExitWithTimeout(
386 const Process& process, TimeDelta timeout, int* exit_code) {
387 CHECK(g_launch_helper.Get().IsReady());
388 return g_launch_helper.Get().WaitForChildExitWithTimeout(
389 process, timeout, exit_code);
390 }
391
392 // A very basic implementation for Android. On Android tests can run in an APK 22 // A very basic implementation for Android. On Android tests can run in an APK
393 // and we don't have an executable to exec*. This implementation does the bare 23 // and we don't have an executable to exec*. This implementation does the bare
394 // minimum to execute the method specified by procname (in the child process). 24 // minimum to execute the method specified by procname (in the child process).
395 // - All options except |fds_to_remap| are ignored. 25 // - All options except |fds_to_remap| are ignored.
26 //
27 // NOTE: This MUST NOT run on the main thread of the NativeTest application.
396 Process SpawnMultiProcessTestChild(const std::string& procname, 28 Process SpawnMultiProcessTestChild(const std::string& procname,
397 const CommandLine& base_command_line, 29 const CommandLine& base_command_line,
398 const LaunchOptions& options) { 30 const LaunchOptions& options) {
399 if (g_launch_helper.Get().IsReady()) { 31 JNIEnv* env = android::AttachCurrentThread();
400 return g_launch_helper.Get().StartChildTestHelper( 32 DCHECK(env);
401 procname, base_command_line, options); 33
34 std::vector<int> fd_keys;
35 std::vector<int> fd_fds;
36 if (options.fds_to_remap) {
37 for (auto& iter : *options.fds_to_remap) {
38 fd_keys.push_back(iter.second);
39 fd_fds.push_back(iter.first);
40 }
402 } 41 }
403 42
404 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of 43 android::ScopedJavaLocalRef<jobjectArray> fds =
405 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 44 android::Java_MultiprocessTestClientLauncher_makeFdInfoArray(
406 FileHandleMappingVector empty; 45 env, base::android::ToJavaIntArray(env, fd_keys),
407 const FileHandleMappingVector* fds_to_remap = 46 base::android::ToJavaIntArray(env, fd_fds));
408 options.fds_to_remap ? options.fds_to_remap : &empty;
409 47
410 pid_t pid = fork(); 48 CommandLine command_line(base_command_line);
49 if (!command_line.HasSwitch(switches::kTestChildProcess)) {
50 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname);
51 }
411 52
412 if (pid < 0) { 53 android::ScopedJavaLocalRef<jobjectArray> j_argv =
413 PLOG(ERROR) << "fork"; 54 android::ToJavaArrayOfStrings(env, command_line.argv());
414 return Process(); 55 jint pid = android::Java_MultiprocessTestClientLauncher_launchClient(
56 env, android::GetApplicationContext(), j_argv, fds);
57 return Process(pid);
58 }
59
60 bool WaitForMultiprocessTestChildExit(const Process& process,
61 TimeDelta timeout,
62 int* exit_code) {
63 JNIEnv* env = android::AttachCurrentThread();
64 DCHECK(env);
65
66 base::android::ScopedJavaLocalRef<jobject> result_code =
67 android::Java_MultiprocessTestClientLauncher_waitForMainToReturn(
68 env, android::GetApplicationContext(), process.Pid(),
69 static_cast<int32_t>(timeout.InMilliseconds()));
70 if (result_code.is_null() ||
71 Java_MainReturnCodeResult_hasTimedOut(env, result_code)) {
72 return false;
415 } 73 }
416 if (pid > 0) { 74 if (exit_code) {
417 // Parent process. 75 *exit_code = Java_MainReturnCodeResult_getReturnCode(env, result_code);
418 return Process(pid);
419 } 76 }
420 // Child process. 77 return true;
421 base::hash_set<int> fds_to_keep_open; 78 }
422 for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
423 it != fds_to_remap->end(); ++it) {
424 fds_to_keep_open.insert(it->first);
425 }
426 // Keep standard FDs (stdin, stdout, stderr, etc.) open since this
427 // is not meant to spawn a daemon.
428 int base = GlobalDescriptors::kBaseDescriptor;
429 for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) {
430 if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) {
431 close(fd);
432 }
433 }
434 for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
435 it != fds_to_remap->end(); ++it) {
436 int old_fd = it->first;
437 int new_fd = it->second;
438 if (dup2(old_fd, new_fd) < 0) {
439 PLOG(FATAL) << "dup2";
440 }
441 close(old_fd);
442 }
443 CommandLine::Reset();
444 CommandLine::Init(0, nullptr);
445 CommandLine* command_line = CommandLine::ForCurrentProcess();
446 command_line->InitFromArgv(base_command_line.argv());
447 if (!command_line->HasSwitch(switches::kTestChildProcess))
448 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname);
449 79
450 _exit(multi_process_function_list::InvokeChildProcessTest(procname)); 80 bool TerminateMultiProcessTestChild(const Process& process,
451 return Process(); 81 int exit_code,
82 bool wait) {
83 JNIEnv* env = android::AttachCurrentThread();
84 DCHECK(env);
85
86 return android::Java_MultiprocessTestClientLauncher_terminate(
87 env, android::GetApplicationContext(), process.Pid(), exit_code, wait);
452 } 88 }
453 89
454 } // namespace base 90 } // namespace base
OLDNEW
« 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