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