1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "mojo/core/test/multiprocess_test_helper.h"
6 
7 #include <functional>
8 #include <set>
9 #include <utility>
10 
11 #include "base/base_paths.h"
12 #include "base/base_switches.h"
13 #include "base/bind.h"
14 #include "base/check.h"
15 #include "base/command_line.h"
16 #include "base/files/file_path.h"
17 #include "base/lazy_instance.h"
18 #include "base/memory/ref_counted.h"
19 #include "base/notreached.h"
20 #include "base/path_service.h"
21 #include "base/process/kill.h"
22 #include "base/process/process_handle.h"
23 #include "base/rand_util.h"
24 #include "base/run_loop.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/stringprintf.h"
27 #include "base/task_runner.h"
28 #include "base/threading/thread_task_runner_handle.h"
29 #include "build/build_config.h"
30 #include "mojo/public/cpp/platform/platform_channel.h"
31 #include "mojo/public/cpp/platform/platform_channel_endpoint.h"
32 #include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
33 #include "mojo/public/cpp/system/invitation.h"
34 #include "mojo/public/cpp/system/isolated_connection.h"
35 #include "mojo/public/cpp/system/platform_handle.h"
36 #include "testing/gtest/include/gtest/gtest.h"
37 
38 #if !defined(OS_FUCHSIA)
39 #include "mojo/public/cpp/platform/named_platform_channel.h"
40 #endif
41 
42 namespace mojo {
43 namespace core {
44 namespace test {
45 
46 namespace {
47 
48 #if !defined(OS_FUCHSIA)
49 const char kNamedPipeName[] = "named-pipe-name";
50 #endif
51 const char kRunAsBrokerClient[] = "run-as-broker-client";
52 const char kAcceptInvitationAsync[] = "accept-invitation-async";
53 const char kTestChildMessagePipeName[] = "test_pipe";
54 
55 // For use (and only valid) in a test child process:
56 base::LazyInstance<IsolatedConnection>::Leaky g_child_isolated_connection;
57 
RunClientFunction(base::OnceCallback<int (MojoHandle)> handler,bool pass_pipe_ownership_to_main)58 int RunClientFunction(base::OnceCallback<int(MojoHandle)> handler,
59                       bool pass_pipe_ownership_to_main) {
60   CHECK(MultiprocessTestHelper::primordial_pipe.is_valid());
61   ScopedMessagePipeHandle pipe =
62       std::move(MultiprocessTestHelper::primordial_pipe);
63   MessagePipeHandle pipe_handle =
64       pass_pipe_ownership_to_main ? pipe.release() : pipe.get();
65   return std::move(handler).Run(pipe_handle.value());
66 }
67 
68 }  // namespace
69 
70 MultiprocessTestHelper::MultiprocessTestHelper() = default;
71 
~MultiprocessTestHelper()72 MultiprocessTestHelper::~MultiprocessTestHelper() {
73   CHECK(!test_child_.IsValid());
74 }
75 
StartChild(const std::string & test_child_name,LaunchType launch_type)76 ScopedMessagePipeHandle MultiprocessTestHelper::StartChild(
77     const std::string& test_child_name,
78     LaunchType launch_type) {
79   return StartChildWithExtraSwitch(test_child_name, std::string(),
80                                    std::string(), launch_type);
81 }
82 
StartChildWithExtraSwitch(const std::string & test_child_name,const std::string & switch_string,const std::string & switch_value,LaunchType launch_type)83 ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch(
84     const std::string& test_child_name,
85     const std::string& switch_string,
86     const std::string& switch_value,
87     LaunchType launch_type) {
88   CHECK(!test_child_name.empty());
89   CHECK(!test_child_.IsValid());
90 
91   std::string test_child_main = test_child_name + "TestChildMain";
92 
93   // Manually construct the new child's commandline to avoid copying unwanted
94   // values.
95   base::CommandLine command_line(
96       base::GetMultiProcessTestChildBaseCommandLine().GetProgram());
97 
98   std::set<std::string> uninherited_args;
99   uninherited_args.insert("mojo-platform-channel-handle");
100   uninherited_args.insert(switches::kTestChildProcess);
101 
102   // Copy commandline switches from the parent process, except for the
103   // multiprocess client name and mojo message pipe handle; this allows test
104   // clients to spawn other test clients.
105   for (const auto& entry :
106        base::CommandLine::ForCurrentProcess()->GetSwitches()) {
107     if (uninherited_args.find(entry.first) == uninherited_args.end())
108       command_line.AppendSwitchNative(entry.first, entry.second);
109   }
110 
111 #if !defined(OS_FUCHSIA)
112   NamedPlatformChannel::ServerName server_name;
113 #endif
114   PlatformChannel channel;
115   base::LaunchOptions options;
116   switch (launch_type) {
117     case LaunchType::CHILD:
118     case LaunchType::PEER:
119     case LaunchType::ASYNC:
120       channel.PrepareToPassRemoteEndpoint(&options, &command_line);
121       break;
122 #if !defined(OS_FUCHSIA)
123     case LaunchType::NAMED_CHILD:
124     case LaunchType::NAMED_PEER: {
125 #if defined(OS_MAC)
126       server_name = NamedPlatformChannel::ServerNameFromUTF8(
127           "mojo.test." + base::NumberToString(base::RandUint64()));
128 #elif defined(OS_POSIX)
129       base::FilePath temp_dir;
130       CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir));
131       server_name =
132           temp_dir.AppendASCII(base::NumberToString(base::RandUint64()))
133               .value();
134 #elif defined(OS_WIN)
135       server_name = base::NumberToString16(base::RandUint64());
136 #else
137 #error "Platform not yet supported."
138 #endif
139       command_line.AppendSwitchNative(kNamedPipeName, server_name);
140       break;
141     }
142 #endif  // !defined(OS_FUCHSIA)
143   }
144 
145   if (!switch_string.empty()) {
146     CHECK(!command_line.HasSwitch(switch_string));
147     if (!switch_value.empty())
148       command_line.AppendSwitchASCII(switch_string, switch_value);
149     else
150       command_line.AppendSwitch(switch_string);
151   }
152 
153 #if defined(OS_WIN)
154   options.start_hidden = true;
155 #endif
156 
157   // NOTE: In the case of named pipes, it's important that the server handle be
158   // created before the child process is launched; otherwise the server binding
159   // the pipe path can race with child's connection to the pipe.
160   PlatformChannelEndpoint local_channel_endpoint;
161   PlatformChannelServerEndpoint server_endpoint;
162   switch (launch_type) {
163     case LaunchType::CHILD:
164     case LaunchType::PEER:
165     case LaunchType::ASYNC:
166       local_channel_endpoint = channel.TakeLocalEndpoint();
167       break;
168 #if !defined(OS_FUCHSIA)
169     case LaunchType::NAMED_CHILD:
170     case LaunchType::NAMED_PEER: {
171       NamedPlatformChannel::Options channel_options;
172       channel_options.server_name = server_name;
173       NamedPlatformChannel named_channel(channel_options);
174       server_endpoint = named_channel.TakeServerEndpoint();
175       break;
176     }
177 #endif  // !defined(OS_FUCHSIA)
178   };
179 
180   OutgoingInvitation child_invitation;
181   ScopedMessagePipeHandle pipe;
182   switch (launch_type) {
183     case LaunchType::ASYNC:
184       command_line.AppendSwitch(kAcceptInvitationAsync);
185       FALLTHROUGH;
186     case LaunchType::CHILD:
187 #if !defined(OS_FUCHSIA)
188     case LaunchType::NAMED_CHILD:
189 #endif
190       pipe = child_invitation.AttachMessagePipe(kTestChildMessagePipeName);
191       command_line.AppendSwitch(kRunAsBrokerClient);
192       break;
193     case LaunchType::PEER:
194 #if !defined(OS_FUCHSIA)
195     case LaunchType::NAMED_PEER:
196 #endif
197       isolated_connection_ = std::make_unique<IsolatedConnection>();
198       if (local_channel_endpoint.is_valid()) {
199         pipe = isolated_connection_->Connect(std::move(local_channel_endpoint));
200       } else {
201 #if defined(OS_POSIX) || defined(OS_WIN)
202         DCHECK(server_endpoint.is_valid());
203         pipe = isolated_connection_->Connect(std::move(server_endpoint));
204 #else
205         NOTREACHED();
206 #endif
207       }
208       break;
209   }
210 
211   test_child_ =
212       base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
213 
214   if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER ||
215       launch_type == LaunchType::ASYNC) {
216     channel.RemoteProcessLaunchAttempted();
217   }
218 
219   if (launch_type == LaunchType::CHILD) {
220     DCHECK(local_channel_endpoint.is_valid());
221     OutgoingInvitation::Send(std::move(child_invitation), test_child_.Handle(),
222                              std::move(local_channel_endpoint),
223                              ProcessErrorCallback());
224   } else if (launch_type == LaunchType::ASYNC) {
225     DCHECK(local_channel_endpoint.is_valid());
226     OutgoingInvitation::SendAsync(
227         std::move(child_invitation), test_child_.Handle(),
228         std::move(local_channel_endpoint), ProcessErrorCallback());
229   }
230 #if !defined(OS_FUCHSIA)
231   else if (launch_type == LaunchType::NAMED_CHILD) {
232     DCHECK(server_endpoint.is_valid());
233     OutgoingInvitation::Send(std::move(child_invitation), test_child_.Handle(),
234                              std::move(server_endpoint),
235                              ProcessErrorCallback());
236   }
237 #endif  //  !defined(OS_FUCHSIA)
238 
239   CHECK(test_child_.IsValid());
240   return pipe;
241 }
242 
WaitForChildShutdown()243 int MultiprocessTestHelper::WaitForChildShutdown() {
244   CHECK(test_child_.IsValid());
245 
246   int rv = -1;
247   WaitForMultiprocessTestChildExit(test_child_, TestTimeouts::action_timeout(),
248                                    &rv);
249   test_child_.Close();
250   return rv;
251 }
252 
WaitForChildTestShutdown()253 bool MultiprocessTestHelper::WaitForChildTestShutdown() {
254   return WaitForChildShutdown() == 0;
255 }
256 
257 // static
ChildSetup()258 void MultiprocessTestHelper::ChildSetup() {
259   CHECK(base::CommandLine::InitializedForCurrentProcess());
260 
261   auto& command_line = *base::CommandLine::ForCurrentProcess();
262 
263   const bool run_as_broker_client = command_line.HasSwitch(kRunAsBrokerClient);
264   const bool async = command_line.HasSwitch(kAcceptInvitationAsync);
265 
266   PlatformChannelEndpoint endpoint;
267 #if !defined(OS_FUCHSIA)
268   NamedPlatformChannel::ServerName named_pipe(
269       command_line.GetSwitchValueNative(kNamedPipeName));
270   if (!named_pipe.empty()) {
271     endpoint = NamedPlatformChannel::ConnectToServer(named_pipe);
272   } else
273 #endif  // !defined(OS_FUCHSIA)
274   {
275     endpoint =
276         PlatformChannel::RecoverPassedEndpointFromCommandLine(command_line);
277   }
278 
279   if (run_as_broker_client) {
280     IncomingInvitation invitation;
281     if (async)
282       invitation = IncomingInvitation::AcceptAsync(std::move(endpoint));
283     else
284       invitation = IncomingInvitation::Accept(std::move(endpoint));
285     primordial_pipe = invitation.ExtractMessagePipe(kTestChildMessagePipeName);
286   } else {
287     primordial_pipe =
288         g_child_isolated_connection.Get().Connect(std::move(endpoint));
289   }
290 }
291 
292 // static
RunClientMain(base::OnceCallback<int (MojoHandle)> main,bool pass_pipe_ownership_to_main)293 int MultiprocessTestHelper::RunClientMain(
294     base::OnceCallback<int(MojoHandle)> main,
295     bool pass_pipe_ownership_to_main) {
296   return RunClientFunction(std::move(main), pass_pipe_ownership_to_main);
297 }
298 
299 // static
RunClientTestMain(base::OnceCallback<void (MojoHandle)> main)300 int MultiprocessTestHelper::RunClientTestMain(
301     base::OnceCallback<void(MojoHandle)> main) {
302   return RunClientFunction(
303       base::BindOnce(
304           [](base::OnceCallback<void(MojoHandle)> main, MojoHandle handle) {
305             std::move(main).Run(handle);
306             return (::testing::Test::HasFatalFailure() ||
307                     ::testing::Test::HasNonfatalFailure())
308                        ? 1
309                        : 0;
310           },
311           std::move(main)),
312       true /* pass_pipe_ownership_to_main */);
313 }
314 
315 ScopedMessagePipeHandle MultiprocessTestHelper::primordial_pipe;
316 
317 }  // namespace test
318 }  // namespace core
319 }  // namespace mojo
320