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> 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 68 MultiprocessTestHelper::MultiprocessTestHelper() {} 69 70 MultiprocessTestHelper::~MultiprocessTestHelper() { 71 CHECK(!test_child_.IsValid()); 72 } 73 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 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 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 211 bool MultiprocessTestHelper::WaitForChildTestShutdown() { 212 return WaitForChildShutdown() == 0; 213 } 214 215 // static 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 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 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