[go: nahoru, domu]

blob: cfe77d739dfdcf43a871d87bac30c1a91354966b [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/base_switches.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/process/process.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "base/threading/simple_thread.h"
#include "build/build_config.h"
#include "mojo/core/ipcz_driver/driver.h"
#include "mojo/core/ipcz_driver/transport.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
#include "third_party/ipcz/src/test/multinode_test.h"
namespace mojo::core::ipcz_driver {
namespace {
const char kParentHandle[] = "mojo-ipcz-test-parent-handle";
const char kMojoIpczInProcessTestDriverName[] = "MojoIpczInProcess";
const char kMojoIpczMultiprocessTestDriverName[] = "MojoIpczMultiprocess";
class MojoIpczInProcessTestNodeController
: public ipcz::test::TestNode::TestNodeController {
public:
class NodeThreadDelegate : public base::DelegateSimpleThread::Delegate {
public:
NodeThreadDelegate(std::unique_ptr<ipcz::test::TestNode> node,
ipcz::test::TestDriver* driver)
: node_(std::move(node)), driver_(driver) {}
// base::DelegateSimpleThread::Delegate:
void Run() override {
node_->Initialize(driver_);
node_->NodeBody();
node_.reset();
}
private:
std::unique_ptr<ipcz::test::TestNode> node_;
const raw_ptr<ipcz::test::TestDriver> driver_;
};
MojoIpczInProcessTestNodeController(
ipcz::test::TestNode& source,
const std::string& node_name,
std::unique_ptr<ipcz::test::TestNode> test_node,
ipcz::test::TestDriver* test_driver)
: source_(source),
is_broker_(test_node->GetDetails().is_broker),
node_thread_delegate_(std::move(test_node), test_driver),
node_thread_(&node_thread_delegate_, node_name) {
node_thread_.StartAsync();
}
// TestNode::TestNodeController:
bool WaitForShutdown() override {
if (!node_thread_.HasBeenJoined()) {
node_thread_.Join();
}
return true;
}
ipcz::test::TransportPair CreateNewTransports() override {
ipcz::test::TransportPair transports;
if (is_broker_) {
transports = source_->CreateBrokerToBrokerTransports();
} else {
transports = source_->CreateTransports();
}
Transport::FromHandle(transports.ours)
->set_remote_process(base::Process::Current());
Transport::FromHandle(transports.theirs)
->set_remote_process(base::Process::Current());
return transports;
}
private:
~MojoIpczInProcessTestNodeController() override {
CHECK(node_thread_.HasBeenJoined());
}
const raw_ref<ipcz::test::TestNode> source_;
const bool is_broker_;
NodeThreadDelegate node_thread_delegate_;
base::DelegateSimpleThread node_thread_;
};
class MojoIpczChildTestNodeController
: public ipcz::test::TestNode::TestNodeController {
public:
MojoIpczChildTestNodeController(
ipcz::test::TestNode& source,
const ipcz::test::TestNodeDetails& child_details,
base::Process process)
: source_(source),
is_broker_(child_details.is_broker),
process_(std::move(process)) {}
// ipcz::test::TestNode::TestNodeController:
bool WaitForShutdown() override {
if (!process_.IsValid()) {
DCHECK(result_);
return *result_;
}
int rv = -1;
base::WaitForMultiprocessTestChildExit(process_,
TestTimeouts::action_timeout(), &rv);
process_.Close();
result_ = (rv == 0);
return *result_;
}
ipcz::test::TransportPair CreateNewTransports() override {
ipcz::test::TransportPair transports;
if (is_broker_) {
transports = source_->CreateBrokerToBrokerTransports();
} else {
transports = source_->CreateTransports();
}
Transport::FromHandle(transports.ours)
->set_remote_process(process_.Duplicate());
return transports;
}
private:
~MojoIpczChildTestNodeController() override { DCHECK(result_.has_value()); }
const raw_ref<ipcz::test::TestNode> source_;
const bool is_broker_;
base::Process process_;
absl::optional<bool> result_;
};
// TestDriver implementation for the mojo-ipcz driver to have coverage in ipcz'
// multinode tests.
class MojoIpczTestDriver : public ipcz::test::TestDriver {
public:
enum Mode {
kInProcess,
kMultiprocess,
};
explicit MojoIpczTestDriver(Mode mode) : mode_(mode) {}
const IpczDriver& GetIpczDriver() const override { return kDriver; }
const char* GetName() const override {
if (mode_ == kInProcess) {
return kMojoIpczInProcessTestDriverName;
}
return kMojoIpczMultiprocessTestDriverName;
}
ipcz::test::TransportPair CreateTransports(
ipcz::test::TestNode& source,
bool for_broker_target) const override {
std::pair<scoped_refptr<Transport>, scoped_refptr<Transport>> transports;
transports = Transport::CreatePair(
Transport::kBroker,
for_broker_target ? Transport::kBroker : Transport::kNonBroker);
return {
.ours = Transport::ReleaseAsHandle(std::move(transports.first)),
.theirs = Transport::ReleaseAsHandle(std::move(transports.second)),
};
}
ipcz::Ref<ipcz::test::TestNode::TestNodeController> SpawnTestNode(
ipcz::test::TestNode& source,
const ipcz::test::TestNodeDetails& details,
IpczDriverHandle our_transport,
IpczDriverHandle their_transport) override {
if (mode_ == kInProcess) {
return SpawnTestNodeThread(source, details, our_transport,
their_transport);
}
return SpawnTestNodeProcess(source, details, our_transport,
their_transport);
}
IpczConnectNodeFlags GetExtraClientConnectNodeFlags() const override {
return IPCZ_NO_FLAGS;
}
IpczDriverHandle GetClientTestNodeTransport() override {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
PlatformChannelEndpoint endpoint =
PlatformChannelEndpoint::RecoverFromString(
command_line.GetSwitchValueASCII(PlatformChannel::kHandleSwitch));
base::Process parent_process;
#if BUILDFLAG(IS_WIN)
// If we're launched as a broker, the test will pass us a handle back to its
// process. The Transport uses this to duplicate handles to and from the
// parent process. See SpawnTestNodeProcess().
const std::string parent_handle_switch =
command_line.GetSwitchValueASCII(kParentHandle);
int parent_handle_value;
if (!parent_handle_switch.empty() &&
base::StringToInt(parent_handle_switch, &parent_handle_value)) {
parent_process = base::Process(LongToHandle(parent_handle_value));
}
#endif // BUILDFLAG(IS_WIN)
const bool is_broker = parent_process.IsValid();
return Transport::ReleaseAsHandle(Transport::Create(
{.source = is_broker ? Transport::kBroker : Transport::kNonBroker,
.destination = Transport::kBroker},
std::move(endpoint), std::move(parent_process)));
}
private:
ipcz::Ref<ipcz::test::TestNode::TestNodeController> SpawnTestNodeThread(
ipcz::test::TestNode& source,
const ipcz::test::TestNodeDetails& details,
IpczDriverHandle our_transport,
IpczDriverHandle their_transport) {
Transport::FromHandle(our_transport)
->set_remote_process(base::Process::Current());
if (details.is_broker) {
Transport::FromHandle(their_transport)
->set_remote_process(base::Process::Current());
}
std::unique_ptr<ipcz::test::TestNode> node = details.factory();
node->SetTransport(their_transport);
return ipcz::MakeRefCounted<MojoIpczInProcessTestNodeController>(
source, std::string(details.name.begin(), details.name.end()),
std::move(node), this);
}
ipcz::Ref<ipcz::test::TestNode::TestNodeController> SpawnTestNodeProcess(
ipcz::test::TestNode& source,
const ipcz::test::TestNodeDetails& details,
IpczDriverHandle our_transport,
IpczDriverHandle their_transport) {
const std::string test_child_main = base::StrCat(
{details.name.data(), "/", kMojoIpczMultiprocessTestDriverName});
base::CommandLine command_line(
base::GetMultiProcessTestChildBaseCommandLine().GetProgram());
std::set<std::string> uninherited_args;
uninherited_args.insert(PlatformChannel::kHandleSwitch);
uninherited_args.insert(kParentHandle);
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 (!base::Contains(uninherited_args, entry.first)) {
command_line.AppendSwitchNative(entry.first, entry.second);
}
}
base::LaunchOptions options;
scoped_refptr<Transport> transport =
Transport::TakeFromHandle(their_transport);
PlatformChannelEndpoint endpoint = transport->TakeEndpoint();
endpoint.PrepareToPass(options, command_line);
#if BUILDFLAG(IS_WIN)
options.start_hidden = true;
base::Process this_process;
if (details.is_broker) {
// If we're launching another broker, it needs a handle back to our own
// process so that it can duplicate handles between us and itself. See
// GetClientTestNodeTransport().
HANDLE dupe;
BOOL ok = ::DuplicateHandle(::GetCurrentProcess(), ::GetCurrentProcess(),
::GetCurrentProcess(), &dupe, 0, TRUE,
DUPLICATE_SAME_ACCESS);
CHECK(ok);
this_process = base::Process(dupe);
options.handles_to_inherit.push_back(this_process.Handle());
command_line.AppendSwitchASCII(
kParentHandle,
base::NumberToString(HandleToLong(this_process.Handle())));
}
#endif
base::Process child = base::SpawnMultiProcessTestChild(
test_child_main, command_line, options);
endpoint.ProcessLaunchAttempted();
Transport::FromHandle(our_transport)->set_remote_process(child.Duplicate());
return ipcz::MakeRefCounted<MojoIpczChildTestNodeController>(
source, details, std::move(child));
}
const Mode mode_;
};
ipcz::test::TestDriverRegistration<MojoIpczTestDriver> kRegisterInProcessDriver{
MojoIpczTestDriver::kInProcess};
#if !BUILDFLAG(IS_IOS)
ipcz::test::TestDriverRegistration<MojoIpczTestDriver>
kRegisterMultiprocessDriver{MojoIpczTestDriver::kMultiprocess};
#endif
} // namespace
} // namespace mojo::core::ipcz_driver