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