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/command_line.h"
15 #include "base/files/file_path.h"
16 #include "base/lazy_instance.h"
17 #include "base/logging.h"
18 #include "base/memory/ref_counted.h"
19 #include "base/path_service.h"
20 #include "base/process/kill.h"
21 #include "base/process/process_handle.h"
22 #include "base/rand_util.h"
23 #include "base/run_loop.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/task_runner.h"
27 #include "base/threading/thread_task_runner_handle.h"
28 #include "build/build_config.h"
29 #include "mojo/public/cpp/platform/named_platform_channel.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_MACOSX) && !defined(OS_IOS)
39 #include "base/mac/mach_port_broker.h"
40 #endif
41
42 namespace mojo {
43 namespace core {
44 namespace test {
45
46 namespace {
47
48 const char kNamedPipeName[] = "named-pipe-name";
49 const char kRunAsBrokerClient[] = "run-as-broker-client";
50
51 const char kTestChildMessagePipeName[] = "test_pipe";
52
53 // For use (and only valid) in a test child process:
54 base::LazyInstance<IsolatedConnection>::Leaky g_child_isolated_connection;
55
56 template <typename Func>
RunClientFunction(Func handler,bool pass_pipe_ownership_to_main)57 int RunClientFunction(Func handler, bool pass_pipe_ownership_to_main) {
58 CHECK(MultiprocessTestHelper::primordial_pipe.is_valid());
59 ScopedMessagePipeHandle pipe =
60 std::move(MultiprocessTestHelper::primordial_pipe);
61 MessagePipeHandle pipe_handle =
62 pass_pipe_ownership_to_main ? pipe.release() : pipe.get();
63 return handler(pipe_handle.value());
64 }
65
66 } // namespace
67
MultiprocessTestHelper()68 MultiprocessTestHelper::MultiprocessTestHelper() {}
69
~MultiprocessTestHelper()70 MultiprocessTestHelper::~MultiprocessTestHelper() {
71 CHECK(!test_child_.IsValid());
72 }
73
StartChild(const std::string & test_child_name,LaunchType launch_type)74 ScopedMessagePipeHandle MultiprocessTestHelper::StartChild(
75 const std::string& test_child_name,
76 LaunchType launch_type) {
77 return StartChildWithExtraSwitch(test_child_name, std::string(),
78 std::string(), launch_type);
79 }
80
StartChildWithExtraSwitch(const std::string & test_child_name,const std::string & switch_string,const std::string & switch_value,LaunchType launch_type)81 ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch(
82 const std::string& test_child_name,
83 const std::string& switch_string,
84 const std::string& switch_value,
85 LaunchType launch_type) {
86 CHECK(!test_child_name.empty());
87 CHECK(!test_child_.IsValid());
88
89 std::string test_child_main = test_child_name + "TestChildMain";
90
91 // Manually construct the new child's commandline to avoid copying unwanted
92 // values.
93 base::CommandLine command_line(
94 base::GetMultiProcessTestChildBaseCommandLine().GetProgram());
95
96 std::set<std::string> uninherited_args;
97 uninherited_args.insert("mojo-platform-channel-handle");
98 uninherited_args.insert(switches::kTestChildProcess);
99
100 // Copy commandline switches from the parent process, except for the
101 // multiprocess client name and mojo message pipe handle; this allows test
102 // clients to spawn other test clients.
103 for (const auto& entry :
104 base::CommandLine::ForCurrentProcess()->GetSwitches()) {
105 if (uninherited_args.find(entry.first) == uninherited_args.end())
106 command_line.AppendSwitchNative(entry.first, entry.second);
107 }
108
109 mojo::PlatformChannel channel;
110 mojo::NamedPlatformChannel::ServerName server_name;
111 base::LaunchOptions options;
112 if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) {
113 channel.PrepareToPassRemoteEndpoint(&options, &command_line);
114 } else if (launch_type == LaunchType::NAMED_CHILD ||
115 launch_type == LaunchType::NAMED_PEER) {
116 #if defined(OS_FUCHSIA)
117 // TODO(fuchsia): Implement named channels. See crbug.com/754038.
118 NOTREACHED();
119 #elif defined(OS_POSIX)
120 base::FilePath temp_dir;
121 CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir));
122 server_name =
123 temp_dir.AppendASCII(base::NumberToString(base::RandUint64())).value();
124 #elif defined(OS_WIN)
125 server_name = base::NumberToString16(base::RandUint64());
126 #else
127 #error "Platform not yet supported."
128 #endif
129 command_line.AppendSwitchNative(kNamedPipeName, server_name);
130 }
131
132 if (!switch_string.empty()) {
133 CHECK(!command_line.HasSwitch(switch_string));
134 if (!switch_value.empty())
135 command_line.AppendSwitchASCII(switch_string, switch_value);
136 else
137 command_line.AppendSwitch(switch_string);
138 }
139
140 #if defined(OS_WIN)
141 options.start_hidden = true;
142 #endif
143
144 // NOTE: In the case of named pipes, it's important that the server handle be
145 // created before the child process is launched; otherwise the server binding
146 // the pipe path can race with child's connection to the pipe.
147 PlatformChannelEndpoint local_channel_endpoint;
148 PlatformChannelServerEndpoint server_endpoint;
149 if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) {
150 local_channel_endpoint = channel.TakeLocalEndpoint();
151 } else if (launch_type == LaunchType::NAMED_CHILD ||
152 launch_type == LaunchType::NAMED_PEER) {
153 NamedPlatformChannel::Options options;
154 options.server_name = server_name;
155 NamedPlatformChannel named_channel(options);
156 server_endpoint = named_channel.TakeServerEndpoint();
157 }
158
159 OutgoingInvitation child_invitation;
160 ScopedMessagePipeHandle pipe;
161 if (launch_type == LaunchType::CHILD ||
162 launch_type == LaunchType::NAMED_CHILD) {
163 pipe = child_invitation.AttachMessagePipe(kTestChildMessagePipeName);
164 command_line.AppendSwitch(kRunAsBrokerClient);
165 } else if (launch_type == LaunchType::PEER ||
166 launch_type == LaunchType::NAMED_PEER) {
167 isolated_connection_ = std::make_unique<IsolatedConnection>();
168 if (local_channel_endpoint.is_valid()) {
169 pipe = isolated_connection_->Connect(std::move(local_channel_endpoint));
170 } else {
171 #if defined(OS_POSIX) || defined(OS_WIN)
172 DCHECK(server_endpoint.is_valid());
173 pipe = isolated_connection_->Connect(std::move(server_endpoint));
174 #else
175 NOTREACHED();
176 #endif
177 }
178 }
179
180 test_child_ =
181 base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
182 if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER)
183 channel.RemoteProcessLaunchAttempted();
184
185 if (launch_type == LaunchType::CHILD) {
186 DCHECK(local_channel_endpoint.is_valid());
187 OutgoingInvitation::Send(std::move(child_invitation), test_child_.Handle(),
188 std::move(local_channel_endpoint),
189 mojo::ProcessErrorCallback());
190 } else if (launch_type == LaunchType::NAMED_CHILD) {
191 DCHECK(server_endpoint.is_valid());
192 OutgoingInvitation::Send(std::move(child_invitation), test_child_.Handle(),
193 std::move(server_endpoint),
194 mojo::ProcessErrorCallback());
195 }
196
197 CHECK(test_child_.IsValid());
198 return pipe;
199 }
200
WaitForChildShutdown()201 int MultiprocessTestHelper::WaitForChildShutdown() {
202 CHECK(test_child_.IsValid());
203
204 int rv = -1;
205 WaitForMultiprocessTestChildExit(test_child_, TestTimeouts::action_timeout(),
206 &rv);
207 test_child_.Close();
208 return rv;
209 }
210
WaitForChildTestShutdown()211 bool MultiprocessTestHelper::WaitForChildTestShutdown() {
212 return WaitForChildShutdown() == 0;
213 }
214
215 // static
ChildSetup()216 void MultiprocessTestHelper::ChildSetup() {
217 CHECK(base::CommandLine::InitializedForCurrentProcess());
218
219 auto& command_line = *base::CommandLine::ForCurrentProcess();
220 NamedPlatformChannel::ServerName named_pipe(
221 command_line.GetSwitchValueNative(kNamedPipeName));
222 if (command_line.HasSwitch(kRunAsBrokerClient)) {
223 mojo::IncomingInvitation invitation;
224 #if defined(OS_MACOSX) && !defined(OS_IOS)
225 CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test"));
226 #endif
227 if (!named_pipe.empty()) {
228 invitation = mojo::IncomingInvitation::Accept(
229 mojo::NamedPlatformChannel::ConnectToServer(named_pipe));
230 } else {
231 auto endpoint =
232 mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
233 command_line);
234 invitation = IncomingInvitation::Accept(std::move(endpoint));
235 }
236 primordial_pipe = invitation.ExtractMessagePipe(kTestChildMessagePipeName);
237 } else {
238 if (!named_pipe.empty()) {
239 primordial_pipe = g_child_isolated_connection.Get().Connect(
240 NamedPlatformChannel::ConnectToServer(named_pipe));
241 } else {
242 primordial_pipe = g_child_isolated_connection.Get().Connect(
243 PlatformChannel::RecoverPassedEndpointFromCommandLine(command_line));
244 }
245 }
246 }
247
248 // static
RunClientMain(const base::Callback<int (MojoHandle)> & main,bool pass_pipe_ownership_to_main)249 int MultiprocessTestHelper::RunClientMain(
250 const base::Callback<int(MojoHandle)>& main,
251 bool pass_pipe_ownership_to_main) {
252 return RunClientFunction(
253 [main](MojoHandle handle) { return main.Run(handle); },
254 pass_pipe_ownership_to_main);
255 }
256
257 // static
RunClientTestMain(const base::Callback<void (MojoHandle)> & main)258 int MultiprocessTestHelper::RunClientTestMain(
259 const base::Callback<void(MojoHandle)>& main) {
260 return RunClientFunction(
261 [main](MojoHandle handle) {
262 main.Run(handle);
263 return (::testing::Test::HasFatalFailure() ||
264 ::testing::Test::HasNonfatalFailure())
265 ? 1
266 : 0;
267 },
268 true /* pass_pipe_ownership_to_main */);
269 }
270
271 // static
272 mojo::ScopedMessagePipeHandle MultiprocessTestHelper::primordial_pipe;
273
274 } // namespace test
275 } // namespace core
276 } // namespace mojo
277