blob: d516d67a4f6140b30350e8af6b5f63c9b42ab9bf [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fidl/fuchsia.examples/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/component/incoming/cpp/service.h>
#include <lib/component/outgoing/cpp/handlers.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/namespace.h>
#include <lib/fidl/cpp/wire/client.h>
#include <lib/fit/defer.h>
#include <lib/sync/cpp/completion.h>
#include <lib/zx/channel.h>
#include <lib/zx/handle.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <algorithm>
#include <array>
#include <memory>
#include <thread>
#include <utility>
#include <fbl/unique_fd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <src/lib/testing/loop_fixture/real_loop_fixture.h>
#include <src/lib/testing/predicates/status.h>
#include <src/storage/lib/vfs/cpp/managed_vfs.h>
#include <src/storage/lib/vfs/cpp/pseudo_dir.h>
#include <src/storage/lib/vfs/cpp/pseudo_file.h>
namespace {
// Expected path of directory hosting FIDL Services & Protocols.
constexpr char kSvcDirectoryPath[] = "svc";
constexpr char kTestString[] = "FizzBuzz";
constexpr char kTestStringReversed[] = "zzuBzziF";
class EchoImpl final : public fidl::WireServer<fuchsia_examples::Echo> {
public:
explicit EchoImpl(bool reversed) : reversed_(reversed) {}
EchoImpl(bool reversed, fit::deferred_callback on_destruction)
: reversed_(reversed), on_destruction_(std::move(on_destruction)) {}
void SendString(SendStringRequestView request, SendStringCompleter::Sync& completer) override {}
void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
std::string value(request->value.get());
if (reversed_) {
std::reverse(value.begin(), value.end());
}
completer.Reply(fidl::StringView::FromExternal(value));
}
private:
bool reversed_ = false;
std::optional<fit::deferred_callback> on_destruction_ = std::nullopt;
};
class NaturalEchoImpl final : public fidl::Server<fuchsia_examples::Echo> {
public:
explicit NaturalEchoImpl(bool reversed) : reversed_(reversed) {}
void SendString(SendStringRequest& request, SendStringCompleter::Sync& completer) override {}
void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
std::string value(request.value());
if (reversed_) {
std::reverse(value.begin(), value.end());
}
completer.Reply(value);
}
private:
bool reversed_ = false;
};
class OutgoingDirectoryTest : public gtest::RealLoopFixture {
public:
void SetUp() override {
outgoing_directory_ = std::make_unique<component::OutgoingDirectory>(dispatcher());
zx::result server = fidl::CreateEndpoints(&client_end_);
ASSERT_TRUE(server.is_ok()) << server.status_string();
zx::result result = outgoing_directory_->Serve(std::move(server.value()));
ASSERT_TRUE(result.is_ok()) << result.status_string();
}
component::OutgoingDirectory* GetOutgoingDirectory() { return outgoing_directory_.get(); }
std::unique_ptr<component::OutgoingDirectory> TakeOutgoingDirectory() {
return std::move(outgoing_directory_);
}
static fidl::ClientEnd<fuchsia_io::Directory> GetSvcClientEnd(
const fidl::ClientEnd<fuchsia_io::Directory>& root,
fidl::StringView path = kSvcDirectoryPath) {
auto [client_end, server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create();
fidl::OneWayStatus status =
fidl::WireCall(root)->Open(fuchsia_io::wire::OpenFlags::kDirectory, {}, path,
fidl::ServerEnd<fuchsia_io::Node>{server_end.TakeChannel()});
EXPECT_TRUE(status.ok()) << status.FormatDescription();
return std::move(client_end);
}
fidl::ClientEnd<fuchsia_io::Directory> TakeRootClientEnd() { return std::move(client_end_); }
const fidl::ClientEnd<fuchsia_io::Directory>& GetRootClientEnd() { return client_end_; }
protected:
fidl::WireClient<fuchsia_examples::Echo> ConnectToServiceMember(
fuchsia_examples::EchoService::ServiceClient& service, bool reversed) {
zx::result connect_result =
reversed ? service.connect_reversed_echo() : service.connect_regular_echo();
EXPECT_TRUE(connect_result.is_ok()) << connect_result.status_string();
return fidl::WireClient(std::move(connect_result.value()), dispatcher());
}
// Service handler that is pre-populated. This is only used for tests that
// want to test failure paths.
static fuchsia_examples::EchoService::InstanceHandler CreateNonEmptyServiceHandler() {
return fuchsia_examples::EchoService::InstanceHandler({
.regular_echo =
[](fidl::ServerEnd<fuchsia_examples::Echo>) {
// no op
},
.reversed_echo =
[](fidl::ServerEnd<fuchsia_examples::Echo>) {
// no op
},
});
}
private:
std::unique_ptr<component::OutgoingDirectory> outgoing_directory_ = nullptr;
fidl::ClientEnd<fuchsia_io::Directory> client_end_;
};
TEST_F(OutgoingDirectoryTest, MutualExclusionGuarantees_CheckOperations) {
std::optional<component::OutgoingDirectory> outgoing_directory;
outgoing_directory.emplace(dispatcher());
// Cannot mutate it from a foreign thread.
ASSERT_DEATH(
{
std::thread t([&] {
EXPECT_TRUE(outgoing_directory.has_value());
outgoing_directory.value()
.AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(/*reversed=*/false))
.status_value();
});
t.join();
},
"\\|component::OutgoingDirectory\\| is thread-unsafe\\.");
// Cannot destroy it on a foreign thread.
ASSERT_DEATH(
{
std::thread t([&] {
EXPECT_TRUE(outgoing_directory.has_value());
outgoing_directory.reset();
});
t.join();
},
"\\|component::OutgoingDirectory\\| is thread-unsafe\\.");
// Properly destroy it on the main thread.
outgoing_directory.reset();
}
TEST_F(OutgoingDirectoryTest, MutualExclusionGuarantees_CheckDispatcher) {
component::OutgoingDirectory outgoing_directory{dispatcher()};
ASSERT_DEATH(
{
std::thread t([&] { RunLoopUntilIdle(); });
t.join();
},
"\\|component::OutgoingDirectory\\| is thread-unsafe\\.");
}
TEST_F(OutgoingDirectoryTest, CanBeMovedSafely) {
component::OutgoingDirectory outgoing_directory(dispatcher());
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_EQ(outgoing_directory
.AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(/*reversed=*/false))
.status_value(),
ZX_OK);
zx::result result = outgoing_directory.Serve(std::move(endpoints->server));
ASSERT_TRUE(result.is_ok()) << result.status_string();
component::OutgoingDirectory moved_in_constructor(std::move(outgoing_directory));
component::OutgoingDirectory moved_in_assignment = component::OutgoingDirectory(dispatcher());
moved_in_assignment = std::move(moved_in_constructor);
zx::result client_end =
component::ConnectAt<fuchsia_examples::Echo>(GetSvcClientEnd(endpoints->client));
ASSERT_TRUE(client_end.is_ok()) << client_end.status_string();
fidl::WireClient<fuchsia_examples::Echo> client(std::move(client_end.value()), dispatcher());
std::string reply_received;
client->EchoString(kTestString)
.ThenExactlyOnce([&reply_received, quit_loop = QuitLoopClosure()](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.ok()) << "EchoString failed: " << result.error();
auto* response = result.Unwrap();
reply_received = std::string(response->response.data(), response->response.size());
quit_loop();
});
RunLoop();
EXPECT_EQ(reply_received, kTestString);
}
TEST_F(OutgoingDirectoryTest, AddProtocol) {
// Setup fuchsia.examples.Echo server.
ASSERT_EQ(
GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(/*reversed=*/false))
.status_value(),
ZX_OK);
// Setup fuchsia.examples.Echo client.
zx::result client_end =
component::ConnectAt<fuchsia_examples::Echo>(GetSvcClientEnd(GetRootClientEnd()));
ASSERT_EQ(client_end.status_value(), ZX_OK);
fidl::WireClient<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher());
std::string reply_received;
client->EchoString(kTestString)
.ThenExactlyOnce([&reply_received, quit_loop = QuitLoopClosure()](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.ok()) << "EchoString failed: " << result.error();
auto* response = result.Unwrap();
reply_received = std::string(response->response.data(), response->response.size());
quit_loop();
});
RunLoop();
EXPECT_EQ(reply_received, kTestString);
}
TEST_F(OutgoingDirectoryTest, AddProtocolNaturalServer) {
ASSERT_EQ(GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(
std::make_unique<NaturalEchoImpl>(/*reversed=*/false))
.status_value(),
ZX_OK);
// Setup fuchsia.examples.Echo client.
zx::result client_end =
component::ConnectAt<fuchsia_examples::Echo>(GetSvcClientEnd(GetRootClientEnd()));
ASSERT_EQ(client_end.status_value(), ZX_OK);
fidl::Client<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher());
std::string reply_received;
client->EchoString({kTestString})
.ThenExactlyOnce([&reply_received, quit_loop = QuitLoopClosure()](
fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.is_ok()) << "EchoString failed: " << result.error_value();
reply_received = result->response();
quit_loop();
});
RunLoop();
EXPECT_EQ(reply_received, kTestString);
}
// Test that outgoing directory is able to serve multiple service members. In
// this case, the directory will host the `fuchsia.examples.EchoService` which
// contains two `fuchsia.examples.Echo` member. One regular, and one reversed.
TEST_F(OutgoingDirectoryTest, AddServiceServesAllMembers) {
EchoImpl regular_impl(/*reversed=*/false);
EchoImpl reversed_impl(/*reversed=*/true);
{
zx::result result = GetOutgoingDirectory()->AddService<fuchsia_examples::EchoService>(
std::move(fuchsia_examples::EchoService::InstanceHandler({
.regular_echo = regular_impl.bind_handler(dispatcher()),
.reversed_echo = reversed_impl.bind_handler(dispatcher()),
})));
ASSERT_TRUE(result.is_ok()) << result.status_string();
}
// Setup test client.
zx::result open_result =
component::OpenServiceAt<fuchsia_examples::EchoService>(GetSvcClientEnd(GetRootClientEnd()));
ASSERT_TRUE(open_result.is_ok()) << open_result.status_string();
fuchsia_examples::EchoService::ServiceClient service = std::move(open_result.value());
// Assert that service is connected and that proper impl returns expected reply.
for (bool reversed : {true, false}) {
bool message_echoed = false;
fidl::WireClient client = ConnectToServiceMember(service, reversed);
std::string_view expected_reply = reversed ? kTestStringReversed : kTestString;
client->EchoString(kTestString)
.ThenExactlyOnce(
[quit_loop = QuitLoopClosure(), &message_echoed, expected_reply = expected_reply](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& reply) {
EXPECT_TRUE(reply.ok()) << "Reply failed with: " << reply.error().status_string();
EXPECT_EQ(reply.value().response.get(), expected_reply);
message_echoed = true;
quit_loop();
});
RunLoop();
EXPECT_TRUE(message_echoed);
}
// Next, assert that after removing the service, the directory connection to
// the directory housing the service members will be closed.
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
zx::result result = component::internal::OpenNamedServiceAtRaw(
GetSvcClientEnd(GetRootClientEnd()), fuchsia_examples::EchoService::Name,
component::kDefaultInstance, std::move(server));
ASSERT_OK(result.status_value());
RunLoopUntilIdle();
{
zx_signals_t pending = ZX_SIGNAL_NONE;
EXPECT_STATUS(ZX_ERR_TIMED_OUT,
client.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite_past(), &pending));
}
{
zx::result result = GetOutgoingDirectory()->RemoveService<fuchsia_examples::EchoService>();
ASSERT_TRUE(result.is_ok()) << result.status_string();
}
RunLoopUntilIdle();
{
zx_signals_t pending = ZX_SIGNAL_NONE;
EXPECT_OK(client.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite_past(), &pending));
EXPECT_EQ(pending, ZX_CHANNEL_PEER_CLOSED);
}
}
// Test that serving a FIDL Protocol works as expected.
TEST_F(OutgoingDirectoryTest, AddProtocolCanServeMultipleProtocols) {
constexpr static std::array<std::pair<bool, const char*>, 2> kIsReversedAndPaths = {
{{false, "fuchsia.examples.Echo"}, {true, "fuchsia.examples.Ohce"}}};
// Setup fuchsia.examples.Echo servers
for (auto [reversed, path] : kIsReversedAndPaths) {
ASSERT_EQ(GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(reversed), path)
.status_value(),
ZX_OK);
}
// Setup fuchsia.examples.Echo client
for (auto [reversed, path] : kIsReversedAndPaths) {
zx::result client_end =
component::ConnectAt<fuchsia_examples::Echo>(GetSvcClientEnd(GetRootClientEnd()), path);
ASSERT_EQ(client_end.status_value(), ZX_OK);
fidl::WireClient<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher());
std::string reply_received;
client->EchoString(kTestString)
.ThenExactlyOnce([&reply_received, quit_loop = QuitLoopClosure()](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.ok()) << "EchoString failed: " << result.error();
auto* response = result.Unwrap();
reply_received = std::string(response->response.data(), response->response.size());
quit_loop();
});
RunLoop();
std::string_view expected_reply = reversed ? kTestStringReversed : kTestString;
EXPECT_EQ(reply_received, expected_reply);
}
}
// Test that serving a FIDL Protocol from a non-svc directory works as expected.
TEST_F(OutgoingDirectoryTest, AddProtocolAtServesProtocol) {
constexpr static char kDirectory[] = "test";
// Setup fuchsia.examples.Echo servers
ASSERT_EQ(GetOutgoingDirectory()
->AddProtocolAt<fuchsia_examples::Echo>(
kDirectory, std::make_unique<EchoImpl>(/*reversed=*/false))
.status_value(),
ZX_OK);
zx::result client_end = component::ConnectAt<fuchsia_examples::Echo>(
GetSvcClientEnd(GetRootClientEnd(), /*path=*/kDirectory));
ASSERT_EQ(client_end.status_value(), ZX_OK);
fidl::WireClient<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher());
std::string reply_received;
client->EchoString(kTestString)
.ThenExactlyOnce([&reply_received, quit_loop = QuitLoopClosure()](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.ok()) << "EchoString failed: " << result.error().FormatDescription();
auto* response = result.Unwrap();
reply_received = std::string(response->response.data(), response->response.size());
quit_loop();
});
RunLoop();
EXPECT_EQ(reply_received, kTestString);
}
TEST_F(OutgoingDirectoryTest, AddServiceAtServesServiceInNonSvcDirectory) {
constexpr static char kDirectory[] = "test";
EchoImpl regular_impl(/*reversed=*/false);
{
zx::result result = GetOutgoingDirectory()->AddServiceAt<fuchsia_examples::EchoService>(
std::move(fuchsia_examples::EchoService::InstanceHandler({
.regular_echo = regular_impl.bind_handler(dispatcher()),
.reversed_echo = nullptr,
})),
/*path=*/kDirectory);
ASSERT_TRUE(result.is_ok()) << result.status_string();
}
// Setup test client.
zx::result open_result = component::OpenServiceAt<fuchsia_examples::EchoService>(
GetSvcClientEnd(GetRootClientEnd(), /*path=*/kDirectory));
ASSERT_TRUE(open_result.is_ok()) << open_result.status_string();
fuchsia_examples::EchoService::ServiceClient service = std::move(open_result.value());
// Assert that service is connected and that proper impl returns expected reply.
bool message_echoed = false;
fidl::WireClient client = ConnectToServiceMember(service, /*reversed=*/false);
std::string_view expected_reply = kTestString;
client->EchoString(kTestString)
.ThenExactlyOnce(
[quit_loop = QuitLoopClosure(), &message_echoed, expected_reply = expected_reply](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& reply) {
EXPECT_TRUE(reply.ok()) << "Reply failed with: " << reply.error().status_string();
EXPECT_EQ(reply.value().response.get(), expected_reply);
message_echoed = true;
quit_loop();
});
RunLoop();
EXPECT_TRUE(message_echoed);
}
TEST_F(OutgoingDirectoryTest, AddServiceAtServesServiceInNestedPath) {
constexpr static char kDirectory[] = "foo/bar";
EchoImpl regular_impl(/*reversed=*/false);
{
zx::result result = GetOutgoingDirectory()->AddServiceAt<fuchsia_examples::EchoService>(
std::move(fuchsia_examples::EchoService::InstanceHandler({
.regular_echo = regular_impl.bind_handler(dispatcher()),
.reversed_echo = nullptr,
})),
/*path=*/kDirectory);
ASSERT_TRUE(result.is_ok()) << result.status_string();
}
// Setup test client.
zx::result open_result = component::OpenServiceAt<fuchsia_examples::EchoService>(
GetSvcClientEnd(GetRootClientEnd(), /*path=*/kDirectory));
ASSERT_TRUE(open_result.is_ok()) << open_result.status_string();
fuchsia_examples::EchoService::ServiceClient service = std::move(open_result.value());
// Assert that service is connected and that proper impl returns expected reply.
bool message_echoed = false;
fidl::WireClient client = ConnectToServiceMember(service, /*reversed=*/false);
std::string_view expected_reply = kTestString;
client->EchoString(kTestString)
.ThenExactlyOnce(
[quit_loop = QuitLoopClosure(), &message_echoed, expected_reply = expected_reply](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& reply) {
EXPECT_TRUE(reply.ok()) << "Reply failed with: " << reply.error().status_string();
EXPECT_EQ(reply.value().response.get(), expected_reply);
message_echoed = true;
quit_loop();
});
RunLoop();
EXPECT_TRUE(message_echoed);
}
TEST_F(OutgoingDirectoryTest, AddDirectoryAtCanServeADirectory) {
static constexpr char kTestPath[] = "root";
static constexpr char kTestDirectory[] = "diagnostics";
static constexpr char kTestFile[] = "sample.txt";
static constexpr char kTestContent[] = "Hello World!";
fs::ManagedVfs vfs(dispatcher());
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
auto text_file = fbl::MakeRefCounted<fs::BufferedPseudoFile>(
/*read_handler=*/[](fbl::String* output) -> zx_status_t {
*output = kTestContent;
return ZX_OK;
});
auto diagnostics = fbl::MakeRefCounted<fs::PseudoDir>();
diagnostics->AddEntry(kTestFile, text_file);
vfs.ServeDirectory(diagnostics, std::move(endpoints.server));
ASSERT_EQ(GetOutgoingDirectory()
->AddDirectoryAt(std::move(endpoints.client), kTestPath, kTestDirectory)
.status_value(),
ZX_OK);
std::thread([client_end = TakeRootClientEnd().TakeChannel().release(),
quit_loop = QuitLoopClosure()]() {
fbl::unique_fd root_fd;
ASSERT_EQ(fdio_fd_create(client_end, root_fd.reset_and_get_address()), ZX_OK);
std::string path = std::string(kTestPath) + "/" + std::string(kTestDirectory);
fbl::unique_fd dir_fd(openat(root_fd.get(), path.c_str(), O_DIRECTORY));
ASSERT_TRUE(dir_fd.is_valid()) << strerror(errno);
fbl::unique_fd file_fd(openat(dir_fd.get(), kTestFile, O_RDONLY));
ASSERT_TRUE(file_fd.is_valid()) << strerror(errno);
static constexpr size_t kMaxBufferSize = 1024;
static char kReadBuffer[kMaxBufferSize];
ssize_t bytes_read = read(file_fd.get(), reinterpret_cast<void*>(kReadBuffer), kMaxBufferSize);
ASSERT_GE(bytes_read, 0) << strerror(errno);
std::string actual_content(kReadBuffer, bytes_read);
EXPECT_EQ(actual_content, kTestContent);
quit_loop();
}).detach();
RunLoop();
vfs.Shutdown([quit_loop = QuitLoopClosure()](zx_status_t status) {
ASSERT_EQ(status, ZX_OK);
quit_loop();
});
RunLoop();
EXPECT_EQ(GetOutgoingDirectory()->RemoveDirectory(kTestPath).status_value(), ZX_OK);
}
// Test that we can connect to the outgoing directory via multiple connections.
TEST_F(OutgoingDirectoryTest, ServeCanYieldMultipleConnections) {
// Setup fuchsia.examples.Echo server
ASSERT_EQ(
GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(/*reversed=*/false))
.status_value(),
ZX_OK);
// Setup fuchsia.examples.Echo client
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
// First |Serve| is invoked as part of test setup, so we'll assert that a
// subsequent invocation is allowed.
ASSERT_EQ(GetOutgoingDirectory()->Serve(std::move(endpoints.server)).status_value(), ZX_OK);
std::vector<fidl::ClientEnd<fuchsia_io::Directory>> root_client_ends;
// Take client end for channel used during invocation of |Serve| during setup.
root_client_ends.emplace_back(TakeRootClientEnd());
// Take client end for channel used during invocation of |Serve| in this function.
root_client_ends.emplace_back(std::move(endpoints.client));
while (!root_client_ends.empty()) {
fidl::ClientEnd root = std::move(root_client_ends.back());
root_client_ends.pop_back();
zx::result client_end =
component::ConnectAt<fuchsia_examples::Echo>(GetSvcClientEnd(/*root=*/root));
ASSERT_EQ(client_end.status_value(), ZX_OK);
fidl::WireClient<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher());
std::string reply_received;
client->EchoString(kTestString)
.ThenExactlyOnce([&reply_received, quit_loop = QuitLoopClosure()](
fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.ok()) << "EchoString failed: " << result.error();
auto* response = result.Unwrap();
reply_received = std::string(response->response.data(), response->response.size());
quit_loop();
});
RunLoop();
EXPECT_EQ(reply_received, kTestString);
}
}
class EchoEventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> {
public:
EchoEventHandler() = default;
void on_fidl_error(fidl::UnbindInfo error) override { errors_.emplace_back(error); }
std::vector<fidl::UnbindInfo> GetErrors() { return errors_; }
private:
std::vector<fidl::UnbindInfo> errors_;
};
// Test that after removing protocol, all clients are unable to make a call on
// the channel.
TEST_F(OutgoingDirectoryTest, RemoveProtocolClosesAllConnections) {
// For this test case, 3 clients will connect to one Echo protocol.
static constexpr size_t kNumClients = 3;
EchoEventHandler event_handler;
bool destroyed = false;
ASSERT_EQ(GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(
/*reversed*/ false, /*on_destruction=*/fit::deferred_callback(
[&destroyed]() { destroyed = true; })))
.status_value(),
ZX_OK);
fidl::ClientEnd<fuchsia_io::Directory> svc_directory = GetSvcClientEnd(GetRootClientEnd());
std::vector<fidl::Client<fuchsia_examples::Echo>> clients = {};
for (size_t i = 0; i < kNumClients; ++i) {
zx::result client_end = component::ConnectAt<fuchsia_examples::Echo>(svc_directory.borrow());
ASSERT_EQ(client_end.status_value(), ZX_OK);
fidl::Client<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher(),
&event_handler);
client->EchoString(std::string(kTestString))
.ThenExactlyOnce([quit_loop = QuitLoopClosure()](
fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result->response(), kTestString);
quit_loop();
});
RunLoop();
clients.emplace_back(std::move(client));
}
ASSERT_EQ(GetOutgoingDirectory()->RemoveProtocol<fuchsia_examples::Echo>().status_value(), ZX_OK);
RunLoopUntilIdle();
ASSERT_EQ(event_handler.GetErrors().size(), kNumClients);
for (auto& error : event_handler.GetErrors()) {
EXPECT_TRUE(error.is_peer_closed())
<< "Expected peer_closed. Got : " << error.FormatDescription();
}
ASSERT_TRUE(destroyed);
}
TEST_F(OutgoingDirectoryTest, OutgoingDirectoryDestructorClosesAllConnections) {
bool destroyed = false;
ASSERT_EQ(GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(std::make_unique<EchoImpl>(
/*reversed*/ false, /*on_destruction=*/fit::deferred_callback(
[&destroyed]() { destroyed = true; })))
.status_value(),
ZX_OK);
// Setup client
zx::result client_end =
component::ConnectAt<fuchsia_examples::Echo>(GetSvcClientEnd(TakeRootClientEnd()));
ASSERT_EQ(client_end.status_value(), ZX_OK);
EchoEventHandler event_handler;
fidl::Client<fuchsia_examples::Echo> client(std::move(*client_end), dispatcher(), &event_handler);
client->EchoString(std::string(kTestString))
.ThenExactlyOnce([quit_loop = QuitLoopClosure()](
fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result->response(), kTestString);
quit_loop();
});
RunLoop();
// We have to take the outgoing directory instead of creating a local one
// because they would share a dispatcher and may fail synchronization checks.
std::unique_ptr outgoing_directory = TakeOutgoingDirectory();
// Destroy outgoing directory object.
outgoing_directory.reset();
RunLoopUntilIdle();
ASSERT_EQ(event_handler.GetErrors().size(), 1u);
for (auto& error : event_handler.GetErrors()) {
EXPECT_TRUE(error.is_peer_closed())
<< "Expected peer_closed. Got : " << error.FormatDescription();
}
ASSERT_TRUE(destroyed);
}
TEST_F(OutgoingDirectoryTest, CreateFailsIfDispatcherIsNullptr) {
ASSERT_DEATH({ component::OutgoingDirectory outgoing_directory(/*dispatcher=*/nullptr); }, "");
}
TEST_F(OutgoingDirectoryTest, ServeFailsIfHandleInvalid) {
component::OutgoingDirectory outgoing_directory(dispatcher());
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
// Close server end in order to invalidate channel.
endpoints.server.reset();
EXPECT_EQ(outgoing_directory.Serve(std::move(endpoints.server)).status_value(),
ZX_ERR_BAD_HANDLE);
}
TEST_F(OutgoingDirectoryTest, AddServiceFailsIfInstanceNameIsEmpty) {
EXPECT_EQ(GetOutgoingDirectory()
->AddService<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler(),
/*instance=*/"")
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddServiceFailsIfEntryExists) {
ASSERT_EQ(GetOutgoingDirectory()
->AddService<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler())
.status_value(),
ZX_OK);
EXPECT_EQ(GetOutgoingDirectory()
->AddService<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler())
.status_value(),
ZX_ERR_ALREADY_EXISTS);
}
TEST_F(OutgoingDirectoryTest, AddServiceFailsIfServiceHandlerEmpty) {
EXPECT_EQ(GetOutgoingDirectory()
->AddService<fuchsia_examples::EchoService>(
fuchsia_examples::EchoService::InstanceHandler())
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddServiceFailsIfServiceNameIsEmpty) {
EXPECT_EQ(GetOutgoingDirectory()
->AddService(CreateNonEmptyServiceHandler(), /*service=*/"",
/*instance=*/component::kDefaultInstance)
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddProtocolFailsIfImplIsNullptr) {
EXPECT_EQ(GetOutgoingDirectory()
->AddProtocol<fuchsia_examples::Echo>(
/*impl=*/static_cast<std::unique_ptr<fidl::WireServer<fuchsia_examples::Echo>>>(
nullptr))
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddProtocolAtFailsIfEntryExists) {
constexpr std::string_view kProtocolName = "fuchsia.example.Echo";
component::AnyHandler handler = [](zx::channel request) {};
ASSERT_EQ(GetOutgoingDirectory()
->AddUnmanagedProtocolAt(std::move(handler), /*path=*/kSvcDirectoryPath,
/*name=*/kProtocolName)
.status_value(),
ZX_OK);
component::AnyHandler another_handler = [](zx::channel request) {};
EXPECT_EQ(GetOutgoingDirectory()
->AddUnmanagedProtocolAt(std::move(another_handler), /*path=*/kSvcDirectoryPath,
/*name=*/kProtocolName)
.status_value(),
ZX_ERR_ALREADY_EXISTS);
}
TEST_F(OutgoingDirectoryTest, AddProtocolAtFailsIfNameIsEmpty) {
component::AnyHandler handler = [](zx::channel request) {};
EXPECT_EQ(
GetOutgoingDirectory()
->AddUnmanagedProtocolAt(std::move(handler), /*path=*/kSvcDirectoryPath, /*name=*/"")
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddProtocolAtFailsIfDirectoryIsEmpty) {
component::AnyHandler handler = [](zx::channel request) {};
EXPECT_EQ(GetOutgoingDirectory()
->AddUnmanagedProtocolAt(/*handler=*/std::move(handler), /*path=*/"",
/*name=*/"fuchsia.examples.Echo")
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddServiceAtFailsIfDirectoryIsEmpty) {
component::AnyHandler handler = [](zx::channel request) {};
EXPECT_EQ(
GetOutgoingDirectory()
->AddServiceAt<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler(), /*path=*/"")
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddServiceAtFailsIfPathMalformed) {
component::AnyHandler handler = [](zx::channel request) {};
EXPECT_EQ(GetOutgoingDirectory()
->AddServiceAt<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler(),
/*path=*/"/")
.status_value(),
ZX_ERR_INVALID_ARGS);
EXPECT_EQ(GetOutgoingDirectory()
->AddServiceAt<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler(),
/*path=*/"//")
.status_value(),
ZX_ERR_INVALID_ARGS);
EXPECT_EQ(GetOutgoingDirectory()
->AddServiceAt<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler(),
/*path=*/"foo//bar")
.status_value(),
ZX_ERR_INVALID_ARGS);
EXPECT_EQ(GetOutgoingDirectory()
->AddServiceAt<fuchsia_examples::EchoService>(CreateNonEmptyServiceHandler(),
/*path=*/"foo/bar/")
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddDirectoryFailsIfRemoteDirInvalid) {
fidl::ClientEnd<fuchsia_io::Directory> dangling_client_end;
ASSERT_FALSE(dangling_client_end.is_valid());
EXPECT_EQ(GetOutgoingDirectory()
->AddDirectory(std::move(dangling_client_end), "AValidName")
.status_value(),
ZX_ERR_BAD_HANDLE);
}
TEST_F(OutgoingDirectoryTest, AddDirectoryFailsIfDirectoryNameIsEmpty) {
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
EXPECT_EQ(GetOutgoingDirectory()
->AddDirectory(std::move(endpoints.client), /*directory_name=*/"")
.status_value(),
ZX_ERR_INVALID_ARGS);
}
TEST_F(OutgoingDirectoryTest, AddDirectoryFailsIfEntryExists) {
constexpr char kDirectoryName[] = "test";
for (auto expected_status : {ZX_OK, ZX_ERR_ALREADY_EXISTS}) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_TRUE(endpoints.is_ok()) << endpoints.status_string();
EXPECT_EQ(GetOutgoingDirectory()
->AddDirectory(std::move(endpoints->client), kDirectoryName)
.status_value(),
expected_status);
}
}
TEST_F(OutgoingDirectoryTest, AddDirectoryFailsIfNameUsedForAddProtocolAt) {
constexpr char kDirectoryName[] = "diagnostics";
ASSERT_EQ(GetOutgoingDirectory()
->AddProtocolAt<fuchsia_examples::Echo>(
kDirectoryName, std::make_unique<EchoImpl>(/*reversed*/ false))
.status_value(),
ZX_OK);
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
EXPECT_EQ(GetOutgoingDirectory()
->AddDirectory(std::move(endpoints.client), kDirectoryName)
.status_value(),
ZX_ERR_ALREADY_EXISTS);
}
TEST_F(OutgoingDirectoryTest, RemoveServiceFailsIfEntryDoesNotExist) {
EXPECT_EQ(GetOutgoingDirectory()->RemoveService<fuchsia_examples::EchoService>().status_value(),
ZX_ERR_NOT_FOUND);
}
TEST_F(OutgoingDirectoryTest, RemoveProtocolFailsIfEntryDoesNotExist) {
EXPECT_EQ(GetOutgoingDirectory()->RemoveProtocol<fuchsia_examples::Echo>().status_value(),
ZX_ERR_NOT_FOUND);
}
TEST_F(OutgoingDirectoryTest, RemoveDirectoryFailsIfEntryDoesNotExist) {
EXPECT_EQ(GetOutgoingDirectory()->RemoveDirectory(/*directory_name=*/"test").status_value(),
ZX_ERR_NOT_FOUND);
}
class OutgoingDirectoryPathParameterizedFixture
: public testing::TestWithParam<std::pair<std::string, std::string>> {};
TEST_P(OutgoingDirectoryPathParameterizedFixture, BadServicePaths) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
component::OutgoingDirectory outgoing_directory(loop.dispatcher());
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_TRUE(endpoints.is_ok()) << endpoints.status_string();
{
zx::result result = outgoing_directory.Serve(std::move(endpoints->server));
ASSERT_TRUE(result.is_ok()) << result.status_string();
}
auto [service_name, instance_name] = GetParam();
EXPECT_EQ(
outgoing_directory
.AddService(fuchsia_examples::EchoService::InstanceHandler(), service_name, instance_name)
.status_value(),
ZX_ERR_INVALID_ARGS);
}
INSTANTIATE_TEST_SUITE_P(OutgoingDirectoryTestPathTest, OutgoingDirectoryPathParameterizedFixture,
testing::Values(std::make_pair("", component::kDefaultInstance),
std::make_pair(".", component::kDefaultInstance),
std::make_pair("fuchsia.examples.EchoService", ""),
std::make_pair("fuchsia.examples.EchoService", "")));
} // namespace