Home | History | Annotate | Download | only in test
      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