• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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