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> 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 58 MultiprocessTestHelper::MultiprocessTestHelper() {} 59 60 MultiprocessTestHelper::~MultiprocessTestHelper() { 61 CHECK(!test_child_.IsValid()); 62 } 63 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 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 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 190 void MultiprocessTestHelper::ClosePeerConnection() { 191 DCHECK(!peer_token_.empty()); 192 ::mojo::edk::ClosePeerConnection(peer_token_); 193 peer_token_.clear(); 194 } 195 196 bool MultiprocessTestHelper::WaitForChildTestShutdown() { 197 return WaitForChildShutdown() == 0; 198 } 199 200 // static 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 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 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