android13/external/libchrome/mojo/public/cpp/system/tests/invitation_unittest.cc

319 lines
11 KiB
C++

// Copyright 2018 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 <utility>
#include "mojo/public/cpp/system/invitation.h"
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/test/bind_test_util.h"
#include "base/test/multiprocess_test.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "mojo/public/cpp/system/wait.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace mojo {
namespace {
enum class InvitationType {
kNormal,
kIsolated,
};
enum class TransportType {
kChannel,
kChannelServer,
};
// Switches and values to tell clients of parameterized test runs what mode they
// should be testing against.
const char kTransportTypeSwitch[] = "test-transport-type";
const char kTransportTypeChannel[] = "channel";
const char kTransportTypeChannelServer[] = "channel-server";
class InvitationCppTest : public testing::Test,
public testing::WithParamInterface<TransportType> {
public:
InvitationCppTest() = default;
~InvitationCppTest() override = default;
protected:
void LaunchChildTestClient(const std::string& test_client_name,
ScopedMessagePipeHandle* primordial_pipes,
size_t num_primordial_pipes,
InvitationType invitation_type,
TransportType transport_type,
const ProcessErrorCallback& error_callback = {}) {
base::CommandLine command_line(
base::GetMultiProcessTestChildBaseCommandLine());
base::LaunchOptions launch_options;
base::Optional<PlatformChannel> channel;
PlatformChannelEndpoint channel_endpoint;
PlatformChannelServerEndpoint server_endpoint;
if (transport_type == TransportType::kChannel) {
command_line.AppendSwitchASCII(kTransportTypeSwitch,
kTransportTypeChannel);
channel.emplace();
channel->PrepareToPassRemoteEndpoint(&launch_options, &command_line);
#if defined(OS_WIN)
launch_options.start_hidden = true;
#endif
channel_endpoint = channel->TakeLocalEndpoint();
} else if (transport_type == TransportType::kChannelServer) {
command_line.AppendSwitchASCII(kTransportTypeSwitch,
kTransportTypeChannelServer);
#if defined(OS_FUCHSIA)
NOTREACHED() << "Named pipe support does not exist for Mojo on Fuchsia.";
#else
NamedPlatformChannel::Options named_channel_options;
#if !defined(OS_WIN)
CHECK(base::PathService::Get(base::DIR_TEMP,
&named_channel_options.socket_dir));
#endif
NamedPlatformChannel named_channel(named_channel_options);
named_channel.PassServerNameOnCommandLine(&command_line);
server_endpoint = named_channel.TakeServerEndpoint();
#endif
}
child_process_ = base::SpawnMultiProcessTestChild(
test_client_name, command_line, launch_options);
if (channel)
channel->RemoteProcessLaunchAttempted();
OutgoingInvitation invitation;
if (invitation_type == InvitationType::kNormal) {
for (uint64_t name = 0; name < num_primordial_pipes; ++name)
primordial_pipes[name] = invitation.AttachMessagePipe(name);
}
if (transport_type == TransportType::kChannel) {
DCHECK(channel_endpoint.is_valid());
if (invitation_type == InvitationType::kNormal) {
OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
std::move(channel_endpoint), error_callback);
} else {
DCHECK(primordial_pipes);
DCHECK_EQ(num_primordial_pipes, 1u);
primordial_pipes[0] =
OutgoingInvitation::SendIsolated(std::move(channel_endpoint));
}
} else if (transport_type == TransportType::kChannelServer) {
DCHECK(server_endpoint.is_valid());
if (invitation_type == InvitationType::kNormal) {
OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
std::move(server_endpoint), error_callback);
} else {
DCHECK(primordial_pipes);
DCHECK_EQ(num_primordial_pipes, 1u);
primordial_pipes[0] =
OutgoingInvitation::SendIsolated(std::move(server_endpoint));
}
}
}
void WaitForChildExit() {
int wait_result = -1;
base::WaitForMultiprocessTestChildExit(
child_process_, TestTimeouts::action_timeout(), &wait_result);
child_process_.Close();
EXPECT_EQ(0, wait_result);
}
static void WriteMessage(const ScopedMessagePipeHandle& pipe,
base::StringPiece message) {
CHECK_EQ(MOJO_RESULT_OK,
WriteMessageRaw(pipe.get(), message.data(), message.size(),
nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
}
static std::string ReadMessage(const ScopedMessagePipeHandle& pipe) {
CHECK_EQ(MOJO_RESULT_OK, Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE));
std::vector<uint8_t> payload;
std::vector<ScopedHandle> handles;
CHECK_EQ(MOJO_RESULT_OK, ReadMessageRaw(pipe.get(), &payload, &handles,
MOJO_READ_MESSAGE_FLAG_NONE));
return std::string(payload.begin(), payload.end());
}
private:
base::test::ScopedTaskEnvironment task_environment_;
base::Process child_process_;
DISALLOW_COPY_AND_ASSIGN(InvitationCppTest);
};
class TestClientBase : public InvitationCppTest {
public:
static PlatformChannelEndpoint RecoverEndpointFromCommandLine() {
const auto& command_line = *base::CommandLine::ForCurrentProcess();
std::string transport_type_string =
command_line.GetSwitchValueASCII(kTransportTypeSwitch);
CHECK(!transport_type_string.empty());
if (transport_type_string == kTransportTypeChannel) {
return PlatformChannel::RecoverPassedEndpointFromCommandLine(
command_line);
} else {
return NamedPlatformChannel::ConnectToServer(command_line);
}
}
static IncomingInvitation AcceptInvitation() {
return IncomingInvitation::Accept(RecoverEndpointFromCommandLine());
}
static ScopedMessagePipeHandle AcceptIsolatedInvitation() {
return IncomingInvitation::AcceptIsolated(RecoverEndpointFromCommandLine());
}
private:
DISALLOW_COPY_AND_ASSIGN(TestClientBase);
};
#define DEFINE_TEST_CLIENT(name) \
class name##Impl : public TestClientBase { \
public: \
static void Run(); \
}; \
MULTIPROCESS_TEST_MAIN(name) { \
name##Impl::Run(); \
return 0; \
} \
void name##Impl::Run()
const char kTestMessage1[] = "hello";
const char kTestMessage2[] = "hello";
TEST_P(InvitationCppTest, Send) {
ScopedMessagePipeHandle pipe;
LaunchChildTestClient("CppSendClient", &pipe, 1, InvitationType::kNormal,
GetParam());
WriteMessage(pipe, kTestMessage1);
WaitForChildExit();
}
DEFINE_TEST_CLIENT(CppSendClient) {
auto invitation = AcceptInvitation();
auto pipe = invitation.ExtractMessagePipe(0);
CHECK_EQ(kTestMessage1, ReadMessage(pipe));
}
TEST_P(InvitationCppTest, SendIsolated) {
ScopedMessagePipeHandle pipe;
LaunchChildTestClient("CppSendIsolatedClient", &pipe, 1,
InvitationType::kIsolated, GetParam());
WriteMessage(pipe, kTestMessage1);
WaitForChildExit();
}
DEFINE_TEST_CLIENT(CppSendIsolatedClient) {
auto pipe = AcceptIsolatedInvitation();
CHECK_EQ(kTestMessage1, ReadMessage(pipe));
}
TEST_P(InvitationCppTest, SendWithMultiplePipes) {
ScopedMessagePipeHandle pipes[2];
LaunchChildTestClient("CppSendWithMultiplePipesClient", pipes, 2,
InvitationType::kNormal, GetParam());
WriteMessage(pipes[0], kTestMessage1);
WriteMessage(pipes[1], kTestMessage2);
WaitForChildExit();
}
DEFINE_TEST_CLIENT(CppSendWithMultiplePipesClient) {
auto invitation = AcceptInvitation();
auto pipe0 = invitation.ExtractMessagePipe(0);
auto pipe1 = invitation.ExtractMessagePipe(1);
CHECK_EQ(kTestMessage1, ReadMessage(pipe0));
CHECK_EQ(kTestMessage2, ReadMessage(pipe1));
}
TEST(InvitationCppTest_NoParam, SendIsolatedInvitationWithDuplicateName) {
base::test::ScopedTaskEnvironment task_environment;
PlatformChannel channel1;
PlatformChannel channel2;
const char kConnectionName[] = "foo";
ScopedMessagePipeHandle pipe0 = OutgoingInvitation::SendIsolated(
channel1.TakeLocalEndpoint(), kConnectionName);
ScopedMessagePipeHandle pipe1 = OutgoingInvitation::SendIsolated(
channel2.TakeLocalEndpoint(), kConnectionName);
Wait(pipe0.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED);
}
const char kErrorMessage[] = "ur bad :{{";
const char kDisconnectMessage[] = "go away plz";
TEST_P(InvitationCppTest, ProcessErrors) {
ProcessErrorCallback actual_error_callback;
ScopedMessagePipeHandle pipe;
LaunchChildTestClient(
"CppProcessErrorsClient", &pipe, 1, InvitationType::kNormal, GetParam(),
base::BindLambdaForTesting([&](const std::string& error_message) {
ASSERT_TRUE(actual_error_callback);
actual_error_callback.Run(error_message);
}));
MojoMessageHandle message;
Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE);
EXPECT_EQ(MOJO_RESULT_OK,
MojoReadMessage(pipe.get().value(), nullptr, &message));
// Report the message as bad and expect to be notified through the process
// error callback.
base::RunLoop error_loop;
actual_error_callback =
base::BindLambdaForTesting([&](const std::string& error_message) {
EXPECT_NE(error_message.find(kErrorMessage), std::string::npos);
error_loop.Quit();
});
EXPECT_EQ(MOJO_RESULT_OK,
MojoNotifyBadMessage(message, kErrorMessage, sizeof(kErrorMessage),
nullptr));
error_loop.Run();
EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
// TODO(https://crbug.com/846833): Once we can rework the C++ invitation API
// to also notify on disconnect, this test should cover that too. For now we
// just tell the process to exit and wait for it to do.
WriteMessage(pipe, kDisconnectMessage);
WaitForChildExit();
}
DEFINE_TEST_CLIENT(CppProcessErrorsClient) {
auto invitation = AcceptInvitation();
auto pipe = invitation.ExtractMessagePipe(0);
WriteMessage(pipe, "ignored");
EXPECT_EQ(kDisconnectMessage, ReadMessage(pipe));
}
INSTANTIATE_TEST_CASE_P(,
InvitationCppTest,
testing::Values(TransportType::kChannel
#if !defined(OS_FUCHSIA)
,
TransportType::kChannelServer
#endif
));
} // namespace
} // namespace mojo