blob: 82a82c80493132068f503c74e1bfd68229244ce7 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/edk/test/multiprocess_test_helper.h"
#include <functional>
#include <set>
#include <utility>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/process/kill.h"
#include "base/process/process_handle.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/edk/embedder/platform_channel_pair.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#elif defined(OS_MACOSX) && !defined(OS_IOS)
#include "base/mac/mach_port_broker.h"
#endif
namespace mojo {
namespace edk {
namespace test {
namespace {
const char kMojoPrimordialPipeToken[] = "mojo-primordial-pipe-token";
int RunClientFunction(std::function<int(MojoHandle)> handler) {
CHECK(!MultiprocessTestHelper::primordial_pipe_token.empty());
ScopedMessagePipeHandle pipe = CreateChildMessagePipe(
MultiprocessTestHelper::primordial_pipe_token);
return handler(pipe.get().value());
}
} // namespace
MultiprocessTestHelper::MultiprocessTestHelper() {}
MultiprocessTestHelper::~MultiprocessTestHelper() {
CHECK(!test_child_.IsValid());
}
ScopedMessagePipeHandle MultiprocessTestHelper::StartChild(
const std::string& test_child_name) {
return StartChildWithExtraSwitch(
test_child_name, std::string(), std::string());
}
ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch(
const std::string& test_child_name,
const std::string& switch_string,
const std::string& switch_value) {
CHECK(!test_child_name.empty());
CHECK(!test_child_.IsValid());
std::string test_child_main = test_child_name + "TestChildMain";
// Manually construct the new child's commandline to avoid copying unwanted
// values.
base::CommandLine command_line(
base::GetMultiProcessTestChildBaseCommandLine().GetProgram());
std::set<std::string> uninherited_args;
uninherited_args.insert("mojo-platform-channel-handle");
uninherited_args.insert(switches::kTestChildProcess);
// Copy commandline switches from the parent process, except for the
// multiprocess client name and mojo message pipe handle; this allows test
// clients to spawn other test clients.
for (const auto& entry :
base::CommandLine::ForCurrentProcess()->GetSwitches()) {
if (uninherited_args.find(entry.first) == uninherited_args.end())
command_line.AppendSwitchNative(entry.first, entry.second);
}
PlatformChannelPair channel;
HandlePassingInformation handle_passing_info;
channel.PrepareToPassClientHandleToChildProcess(&command_line,
&handle_passing_info);
std::string pipe_token = mojo::edk::GenerateRandomToken();
command_line.AppendSwitchASCII(kMojoPrimordialPipeToken, pipe_token);
if (!switch_string.empty()) {
CHECK(!command_line.HasSwitch(switch_string));
if (!switch_value.empty())
command_line.AppendSwitchASCII(switch_string, switch_value);
else
command_line.AppendSwitch(switch_string);
}
base::LaunchOptions options;
#if defined(OS_POSIX)
options.fds_to_remap = &handle_passing_info;
#elif defined(OS_WIN)
options.start_hidden = true;
if (base::win::GetVersion() >= base::win::VERSION_VISTA)
options.handles_to_inherit = &handle_passing_info;
else
options.inherit_handles = true;
#else
#error "Not supported yet."
#endif
std::string child_token = mojo::edk::GenerateRandomToken();
ScopedMessagePipeHandle pipe = CreateParentMessagePipe(pipe_token,
child_token);
test_child_ =
base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
channel.ChildProcessLaunched();
ChildProcessLaunched(test_child_.Handle(), channel.PassServerHandle(),
child_token, process_error_callback_);
CHECK(test_child_.IsValid());
return pipe;
}
int MultiprocessTestHelper::WaitForChildShutdown() {
CHECK(test_child_.IsValid());
int rv = -1;
#if defined(OS_ANDROID)
// On Android, we need to use a special function to wait for the child.
CHECK(AndroidWaitForChildExitWithTimeout(
test_child_, TestTimeouts::action_timeout(), &rv));
#else
CHECK(
test_child_.WaitForExitWithTimeout(TestTimeouts::action_timeout(), &rv));
#endif
test_child_.Close();
return rv;
}
bool MultiprocessTestHelper::WaitForChildTestShutdown() {
return WaitForChildShutdown() == 0;
}
// static
void MultiprocessTestHelper::ChildSetup() {
CHECK(base::CommandLine::InitializedForCurrentProcess());
primordial_pipe_token = base::CommandLine::ForCurrentProcess()
->GetSwitchValueASCII(kMojoPrimordialPipeToken);
CHECK(!primordial_pipe_token.empty());
#if defined(OS_MACOSX) && !defined(OS_IOS)
CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test"));
#endif
SetParentPipeHandle(
PlatformChannelPair::PassClientHandleFromParentProcess(
*base::CommandLine::ForCurrentProcess()));
}
// static
int MultiprocessTestHelper::RunClientMain(
const base::Callback<int(MojoHandle)>& main) {
return RunClientFunction([main](MojoHandle handle){
return main.Run(handle);
});
}
// static
int MultiprocessTestHelper::RunClientTestMain(
const base::Callback<void(MojoHandle)>& main) {
return RunClientFunction([main](MojoHandle handle) {
main.Run(handle);
return (::testing::Test::HasFatalFailure() ||
::testing::Test::HasNonfatalFailure()) ? 1 : 0;
});
}
// static
std::string MultiprocessTestHelper::primordial_pipe_token;
} // namespace test
} // namespace edk
} // namespace mojo