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

Side by Side Diff: base/test/multiprocess_test_android.cc

Issue 1910233003: Implement a new child test helper for Android. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 4 years, 7 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.h ('k') | ipc/mojo/ipc_channel_mojo_unittest.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>
9 #include <sys/types.h>
10 #include <sys/socket.h>
7 #include <unistd.h> 11 #include <unistd.h>
8 12
13 #include <memory>
14 #include <utility>
15 #include <vector>
16
9 #include "base/base_switches.h" 17 #include "base/base_switches.h"
10 #include "base/command_line.h" 18 #include "base/command_line.h"
11 #include "base/containers/hash_tables.h" 19 #include "base/containers/hash_tables.h"
20 #include "base/lazy_instance.h"
12 #include "base/logging.h" 21 #include "base/logging.h"
22 #include "base/macros.h"
23 #include "base/pickle.h"
13 #include "base/posix/global_descriptors.h" 24 #include "base/posix/global_descriptors.h"
25 #include "base/posix/unix_domain_socket_linux.h"
14 #include "testing/multiprocess_func_list.h" 26 #include "testing/multiprocess_func_list.h"
15 27
16 namespace base { 28 namespace base {
17 29
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 parent_fd_ != -1; }
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 // Parent vars.
123 int child_fd_ = -1;
124
125 // Helper vars.
126 int parent_fd_ = -1;
127 MainFunction main_ = nullptr;
128
129 DISALLOW_COPY_AND_ASSIGN(LaunchHelper);
130 };
131
132 void LaunchHelper::Init(MainFunction main) {
133 main_ = main;
134
135 // Create a communication channel between the parent and child launch helper.
136 // fd[0] belongs to the parent, fd[1] belongs to the child.
137 int fds[2] = {-1, -1};
138 int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds);
139 PCHECK(rv == 0);
140 CHECK_NE(-1, fds[0]);
141 CHECK_NE(-1, fds[1]);
142
143 pid_t pid = fork();
144 PCHECK(pid >= 0) << "Fork failed";
145 if (pid) {
146 // Parent.
147 rv = close(fds[1]);
148 PCHECK(rv == 0);
149 DoParent(fds[0]);
150 } else {
151 // Helper.
152 rv = close(fds[0]);
153 PCHECK(rv == 0);
154 DoHelper(fds[1]);
155 NOTREACHED();
156 _exit(0);
157 }
158 }
159
160 void LaunchHelper::Send(
161 int fd, const MessageHeader* msg, const std::vector<int>& fds) {
162 uint32_t bytes_remaining = msg->size;
163 const char* buf = reinterpret_cast<const char*>(msg);
164 while (bytes_remaining) {
165 size_t send_size =
166 (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining;
167 bool success = UnixDomainSocket::SendMsg(
168 fd, buf, send_size,
169 (bytes_remaining == msg->size) ? fds : std::vector<int>());
170 CHECK(success);
171 bytes_remaining -= send_size;
172 buf += send_size;
173 }
174 }
175
176 ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) {
177 ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds);
178 if (size <= 0)
179 return size;
180
181 const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf);
182 CHECK(header->size < kMaxMessageSize);
183 uint32_t bytes_remaining = header->size - size;
184 char* buffer = reinterpret_cast<char*>(buf);
185 buffer += size;
186 while (bytes_remaining) {
187 std::vector<ScopedFD> dummy_fds;
188 size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds);
189 if (size <= 0)
190 return size;
191
192 CHECK(dummy_fds.empty());
193 CHECK(size == kFragmentSize ||
194 static_cast<size_t>(size) == bytes_remaining);
195 bytes_remaining -= size;
196 buffer += size;
197 }
198 return header->size;
199 }
200
201 void LaunchHelper::DoParent(int fd) {
202 child_fd_ = fd;
203 }
204
205 void LaunchHelper::DoHelper(int fd) {
206 parent_fd_ = fd;
207 std::unique_ptr<char[]> buf(new char[kMaxMessageSize]);
208 while (true) {
209 // Wait for a message from the parent.
210 std::vector<ScopedFD> fds;
211 ssize_t size = Recv(parent_fd_, buf.get(), &fds);
212 if (size == 0 || (size < 0 && errno == ECONNRESET)) {
213 _exit(0);
214 }
215 PCHECK(size > 0);
216
217 const MessageHeader* header =
218 reinterpret_cast<const MessageHeader*>(buf.get());
219 CHECK_EQ(static_cast<ssize_t>(header->size), size);
220 switch (header->type) {
221 case MessageType::START_REQUEST:
222 StartProcessInHelper(
223 reinterpret_cast<const StartProcessRequest*>(buf.get()),
224 std::move(fds));
225 break;
226 case MessageType::WAIT_REQUEST:
227 WaitForChildInHelper(
228 reinterpret_cast<const WaitProcessRequest*>(buf.get()));
229 break;
230 default:
231 LOG(FATAL) << "Unsupported message type: "
232 << static_cast<uint32_t>(header->type);
233 }
234 }
235 }
236
237 void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request,
238 std::vector<ScopedFD> fds) {
239 pid_t pid = fork();
240 PCHECK(pid >= 0) << "Fork failed";
241 if (pid) {
242 // Helper.
243 StartProcessResponse resp;
244 resp.child_pid = pid;
245 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
246 std::vector<int>());
247 } else {
248 // Child.
249 PCHECK(close(parent_fd_) == 0);
250 CommandLine::Reset();
251
252 Pickle serialised_extra(reinterpret_cast<const char*>(request + 1),
253 request->header.size - sizeof(StartProcessRequest));
254 PickleIterator iter(serialised_extra);
255 std::vector<std::string> args;
256 for (size_t i = 0; i < request->num_args; i++) {
257 std::string arg;
258 CHECK(iter.ReadString(&arg));
259 args.push_back(std::move(arg));
260 }
261
262 CHECK_EQ(request->num_fds, fds.size());
263 for (size_t i = 0; i < request->num_fds; i++) {
264 int new_fd;
265 CHECK(iter.ReadInt(&new_fd));
266 int old_fd = fds[i].release();
267 if (dup2(old_fd, new_fd) < 0) {
268 PLOG(FATAL) << "dup2";
269 }
270 PCHECK(close(old_fd) == 0);
271 }
272
273 std::unique_ptr<char*[]> argv(new char*[args.size()]);
274 for (size_t i = 0; i < args.size(); i++) {
275 argv[i] = const_cast<char*>(args[i].c_str());
276 }
277 _exit(main_(args.size(), argv.get()));
278 NOTREACHED();
279 }
280 }
281
282 void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) {
283 Process process(request->pid);
284 TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms);
285 int exit_code = -1;
286 bool success = process.WaitForExitWithTimeout(timeout, &exit_code);
287
288 WaitProcessResponse resp;
289 resp.exit_code = exit_code;
290 resp.success = success;
291 Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
292 std::vector<int>());
293 }
294
295 Process LaunchHelper::StartChildTestHelper(const std::string& procname,
296 const CommandLine& base_command_line,
297 const LaunchOptions& options) {
298
299 CommandLine command_line(base_command_line);
300 if (!command_line.HasSwitch(switches::kTestChildProcess))
301 command_line.AppendSwitchASCII(switches::kTestChildProcess, procname);
302
303 StartProcessRequest request;
304 Pickle serialised_extra;
305 const CommandLine::StringVector& argv = command_line.argv();
306 for (const auto& arg : argv)
307 CHECK(serialised_extra.WriteString(arg));
308 request.num_args = argv.size();
309
310 std::vector<int> fds_to_send;
311 if (options.fds_to_remap) {
312 for (auto p : *options.fds_to_remap) {
313 CHECK(serialised_extra.WriteInt(p.second));
314 fds_to_send.push_back(p.first);
315 }
316 request.num_fds = options.fds_to_remap->size();
317 }
318
319 size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size();
320 request.header.size = buf_size;
321 std::unique_ptr<char[]> buffer(new char[buf_size]);
322 memcpy(buffer.get(), &request, sizeof(StartProcessRequest));
323 memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(),
324 serialised_extra.size());
325
326 // Send start message.
327 Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()),
328 fds_to_send);
329
330 // Synchronously get response.
331 StartProcessResponse response;
332 std::vector<ScopedFD> recv_fds;
333 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
334 PCHECK(resp_size == sizeof(StartProcessResponse));
335
336 return Process(response.child_pid);
337 }
338
339 bool LaunchHelper::WaitForChildExitWithTimeout(
340 const Process& process, TimeDelta timeout, int* exit_code) {
341
342 WaitProcessRequest request;
343 request.pid = process.Handle();
344 request.timeout_ms = timeout.InMilliseconds();
345
346 Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request),
347 std::vector<int>());
348
349 WaitProcessResponse response;
350 std::vector<ScopedFD> recv_fds;
351 ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
352 PCHECK(resp_size == sizeof(WaitProcessResponse));
353
354 if (!response.success)
355 return false;
356
357 *exit_code = response.exit_code;
358 return true;
359 }
360
361 LazyInstance<LaunchHelper>::Leaky g_launch_helper;
362
363 } // namespace
364
365 void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) {
366 DCHECK(main);
367 // Don't allow child processes to themselves create new child processes.
368 if (g_launch_helper.Get().IsChild())
369 return;
370 g_launch_helper.Get().Init(main);
371 }
372
373 bool AndroidWaitForChildExitWithTimeout(
374 const Process& process, TimeDelta timeout, int* exit_code) {
375 CHECK(g_launch_helper.Get().IsReady());
376 return g_launch_helper.Get().WaitForChildExitWithTimeout(
377 process, timeout, exit_code);
378 }
379
18 // A very basic implementation for Android. On Android tests can run in an APK 380 // A very basic implementation for Android. On Android tests can run in an APK
19 // and we don't have an executable to exec*. This implementation does the bare 381 // and we don't have an executable to exec*. This implementation does the bare
20 // minimum to execute the method specified by procname (in the child process). 382 // minimum to execute the method specified by procname (in the child process).
21 // - All options except |fds_to_remap| are ignored. 383 // - All options except |fds_to_remap| are ignored.
22 Process SpawnMultiProcessTestChild(const std::string& procname, 384 Process SpawnMultiProcessTestChild(const std::string& procname,
23 const CommandLine& base_command_line, 385 const CommandLine& base_command_line,
24 const LaunchOptions& options) { 386 const LaunchOptions& options) {
387 if (g_launch_helper.Get().IsReady()) {
388 return g_launch_helper.Get().StartChildTestHelper(
389 procname, base_command_line, options);
390 }
391
25 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of 392 // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of
26 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576 393 // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576
27 FileHandleMappingVector empty; 394 FileHandleMappingVector empty;
28 const FileHandleMappingVector* fds_to_remap = 395 const FileHandleMappingVector* fds_to_remap =
29 options.fds_to_remap ? options.fds_to_remap : &empty; 396 options.fds_to_remap ? options.fds_to_remap : &empty;
30 397
31 pid_t pid = fork(); 398 pid_t pid = fork();
32 399
33 if (pid < 0) { 400 if (pid < 0) {
34 PLOG(ERROR) << "fork"; 401 PLOG(ERROR) << "fork";
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
66 CommandLine* command_line = CommandLine::ForCurrentProcess(); 433 CommandLine* command_line = CommandLine::ForCurrentProcess();
67 command_line->InitFromArgv(base_command_line.argv()); 434 command_line->InitFromArgv(base_command_line.argv());
68 if (!command_line->HasSwitch(switches::kTestChildProcess)) 435 if (!command_line->HasSwitch(switches::kTestChildProcess))
69 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname); 436 command_line->AppendSwitchASCII(switches::kTestChildProcess, procname);
70 437
71 _exit(multi_process_function_list::InvokeChildProcessTest(procname)); 438 _exit(multi_process_function_list::InvokeChildProcessTest(procname));
72 return Process(); 439 return Process();
73 } 440 }
74 441
75 } // namespace base 442 } // namespace base
OLDNEW
« no previous file with comments | « base/test/multiprocess_test.h ('k') | ipc/mojo/ipc_channel_mojo_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698