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/edk/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/logging.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/path_service.h"
19 #include "base/process/kill.h"
20 #include "base/process/process_handle.h"
21 #include "base/run_loop.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/task_runner.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "build/build_config.h"
26 #include "mojo/edk/embedder/embedder.h"
27 #include "mojo/edk/embedder/named_platform_handle.h"
28 #include "mojo/edk/embedder/named_platform_handle_utils.h"
29 #include "mojo/edk/embedder/pending_process_connection.h"
30 #include "mojo/edk/embedder/platform_channel_pair.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32
33 #if defined(OS_WIN)
34 #include "base/win/windows_version.h"
35 #elif defined(OS_MACOSX) && !defined(OS_IOS)
36 #include "base/mac/mach_port_broker.h"
37 #endif
38
39 namespace mojo {
40 namespace edk {
41 namespace test {
42
43 namespace {
44
45 const char kMojoPrimordialPipeToken[] = "mojo-primordial-pipe-token";
46 const char kMojoNamedPipeName[] = "mojo-named-pipe-name";
47
48 template <typename Func>
RunClientFunction(Func handler)49 int RunClientFunction(Func handler) {
50 CHECK(MultiprocessTestHelper::primordial_pipe.is_valid());
51 ScopedMessagePipeHandle pipe =
52 std::move(MultiprocessTestHelper::primordial_pipe);
53 return handler(pipe.get().value());
54 }
55
56 } // namespace
57
MultiprocessTestHelper()58 MultiprocessTestHelper::MultiprocessTestHelper() {}
59
~MultiprocessTestHelper()60 MultiprocessTestHelper::~MultiprocessTestHelper() {
61 CHECK(!test_child_.IsValid());
62 }
63
StartChild(const std::string & test_child_name,LaunchType launch_type)64 ScopedMessagePipeHandle MultiprocessTestHelper::StartChild(
65 const std::string& test_child_name,
66 LaunchType launch_type) {
67 return StartChildWithExtraSwitch(test_child_name, std::string(),
68 std::string(), launch_type);
69 }
70
StartChildWithExtraSwitch(const std::string & test_child_name,const std::string & switch_string,const std::string & switch_value,LaunchType launch_type)71 ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch(
72 const std::string& test_child_name,
73 const std::string& switch_string,
74 const std::string& switch_value,
75 LaunchType launch_type) {
76 CHECK(!test_child_name.empty());
77 CHECK(!test_child_.IsValid());
78
79 std::string test_child_main = test_child_name + "TestChildMain";
80
81 // Manually construct the new child's commandline to avoid copying unwanted
82 // values.
83 base::CommandLine command_line(
84 base::GetMultiProcessTestChildBaseCommandLine().GetProgram());
85
86 std::set<std::string> uninherited_args;
87 uninherited_args.insert("mojo-platform-channel-handle");
88 uninherited_args.insert(switches::kTestChildProcess);
89
90 // Copy commandline switches from the parent process, except for the
91 // multiprocess client name and mojo message pipe handle; this allows test
92 // clients to spawn other test clients.
93 for (const auto& entry :
94 base::CommandLine::ForCurrentProcess()->GetSwitches()) {
95 if (uninherited_args.find(entry.first) == uninherited_args.end())
96 command_line.AppendSwitchNative(entry.first, entry.second);
97 }
98
99 PlatformChannelPair channel;
100 NamedPlatformHandle named_pipe;
101 HandlePassingInformation handle_passing_info;
102 if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) {
103 channel.PrepareToPassClientHandleToChildProcess(&command_line,
104 &handle_passing_info);
105 } else if (launch_type == LaunchType::NAMED_CHILD ||
106 launch_type == LaunchType::NAMED_PEER) {
107 #if defined(OS_POSIX)
108 base::FilePath temp_dir;
109 CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir));
110 named_pipe = NamedPlatformHandle(
111 temp_dir.AppendASCII(GenerateRandomToken()).value());
112 #else
113 named_pipe = NamedPlatformHandle(GenerateRandomToken());
114 #endif
115 command_line.AppendSwitchNative(kMojoNamedPipeName, named_pipe.name);
116 }
117
118 if (!switch_string.empty()) {
119 CHECK(!command_line.HasSwitch(switch_string));
120 if (!switch_value.empty())
121 command_line.AppendSwitchASCII(switch_string, switch_value);
122 else
123 command_line.AppendSwitch(switch_string);
124 }
125
126 base::LaunchOptions options;
127 #if defined(OS_POSIX)
128 options.fds_to_remap = &handle_passing_info;
129 #elif defined(OS_WIN)
130 options.start_hidden = true;
131 if (base::win::GetVersion() >= base::win::VERSION_VISTA)
132 options.handles_to_inherit = &handle_passing_info;
133 else
134 options.inherit_handles = true;
135 #else
136 #error "Not supported yet."
137 #endif
138
139 // NOTE: In the case of named pipes, it's important that the server handle be
140 // created before the child process is launched; otherwise the server binding
141 // the pipe path can race with child's connection to the pipe.
142 ScopedPlatformHandle server_handle;
143 if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) {
144 server_handle = channel.PassServerHandle();
145 } else if (launch_type == LaunchType::NAMED_CHILD ||
146 launch_type == LaunchType::NAMED_PEER) {
147 server_handle = CreateServerHandle(named_pipe);
148 }
149
150 PendingProcessConnection process;
151 ScopedMessagePipeHandle pipe;
152 if (launch_type == LaunchType::CHILD ||
153 launch_type == LaunchType::NAMED_CHILD) {
154 std::string pipe_token;
155 pipe = process.CreateMessagePipe(&pipe_token);
156 command_line.AppendSwitchASCII(kMojoPrimordialPipeToken, pipe_token);
157 } else if (launch_type == LaunchType::PEER ||
158 launch_type == LaunchType::NAMED_PEER) {
159 peer_token_ = mojo::edk::GenerateRandomToken();
160 pipe = ConnectToPeerProcess(std::move(server_handle), peer_token_);
161 }
162
163 test_child_ =
164 base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
165 if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER)
166 channel.ChildProcessLaunched();
167
168 if (launch_type == LaunchType::CHILD ||
169 launch_type == LaunchType::NAMED_CHILD) {
170 DCHECK(server_handle.is_valid());
171 process.Connect(test_child_.Handle(),
172 ConnectionParams(std::move(server_handle)),
173 process_error_callback_);
174 }
175
176 CHECK(test_child_.IsValid());
177 return pipe;
178 }
179
WaitForChildShutdown()180 int MultiprocessTestHelper::WaitForChildShutdown() {
181 CHECK(test_child_.IsValid());
182
183 int rv = -1;
184 WaitForMultiprocessTestChildExit(test_child_, TestTimeouts::action_timeout(),
185 &rv);
186 test_child_.Close();
187 return rv;
188 }
189
ClosePeerConnection()190 void MultiprocessTestHelper::ClosePeerConnection() {
191 DCHECK(!peer_token_.empty());
192 ::mojo::edk::ClosePeerConnection(peer_token_);
193 peer_token_.clear();
194 }
195
WaitForChildTestShutdown()196 bool MultiprocessTestHelper::WaitForChildTestShutdown() {
197 return WaitForChildShutdown() == 0;
198 }
199
200 // static
ChildSetup()201 void MultiprocessTestHelper::ChildSetup() {
202 CHECK(base::CommandLine::InitializedForCurrentProcess());
203
204 std::string primordial_pipe_token =
205 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
206 kMojoPrimordialPipeToken);
207 NamedPlatformHandle named_pipe(
208 base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
209 kMojoNamedPipeName));
210 if (!primordial_pipe_token.empty()) {
211 primordial_pipe = CreateChildMessagePipe(primordial_pipe_token);
212 #if defined(OS_MACOSX) && !defined(OS_IOS)
213 CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test"));
214 #endif
215 if (named_pipe.is_valid()) {
216 SetParentPipeHandle(CreateClientHandle(named_pipe));
217 } else {
218 SetParentPipeHandle(
219 PlatformChannelPair::PassClientHandleFromParentProcess(
220 *base::CommandLine::ForCurrentProcess()));
221 }
222 } else {
223 if (named_pipe.is_valid()) {
224 primordial_pipe = ConnectToPeerProcess(CreateClientHandle(named_pipe));
225 } else {
226 primordial_pipe = ConnectToPeerProcess(
227 PlatformChannelPair::PassClientHandleFromParentProcess(
228 *base::CommandLine::ForCurrentProcess()));
229 }
230 }
231 }
232
233 // static
RunClientMain(const base::Callback<int (MojoHandle)> & main)234 int MultiprocessTestHelper::RunClientMain(
235 const base::Callback<int(MojoHandle)>& main) {
236 return RunClientFunction([main](MojoHandle handle){
237 return main.Run(handle);
238 });
239 }
240
241 // static
RunClientTestMain(const base::Callback<void (MojoHandle)> & main)242 int MultiprocessTestHelper::RunClientTestMain(
243 const base::Callback<void(MojoHandle)>& main) {
244 return RunClientFunction([main](MojoHandle handle) {
245 main.Run(handle);
246 return (::testing::Test::HasFatalFailure() ||
247 ::testing::Test::HasNonfatalFailure()) ? 1 : 0;
248 });
249 }
250
251 // static
252 mojo::ScopedMessagePipeHandle MultiprocessTestHelper::primordial_pipe;
253
254 } // namespace test
255 } // namespace edk
256 } // namespace mojo
257