• 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/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