blob: a65390362f95bcc41aa56c44ca8832a836cf64f6 [file] [log] [blame]
// Copyright 2023 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 <fcntl.h>
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/vfs/cpp/composed_service_dir.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/service.h>
#include <zircon/status.h>
#include <memory>
#include <string>
#include <string_view>
#include <fbl/unique_fd.h>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
namespace {
constexpr std::string_view kServices[]{"service_a", "service_b"};
constexpr std::string_view kFallbackServices[]{"service_b", "service_c"};
constexpr size_t kServiceConnections = 5;
constexpr size_t kFallbackServiceConnections = 2;
// Fixture sets up a composed service directory containing "service_a" and "service_b" which is
// backed by a fallback directory containing "service_b" and "service_c".
class ComposedServiceDirTest : public ::gtest::RealLoopFixture {
protected:
void SetUp() override {
root_ = std::make_unique<vfs::ComposedServiceDir>();
for (const auto service : kServices) {
root_->AddService(std::string(service),
std::make_unique<vfs::Service>(MakeService(service, /*fallback*/ false)));
}
fallback_dir_ = std::make_unique<vfs::PseudoDir>();
for (const auto service : kFallbackServices) {
fallback_dir_->AddEntry(std::string(service), std::make_unique<vfs::Service>(
MakeService(service, /*fallback*/ true)));
}
auto [fallback_client, fallback_server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_EQ(fallback_dir_->Serve(fuchsia_io::kPermReadable, std::move(fallback_server)), ZX_OK);
root_->SetFallback(std::move(fallback_client));
auto [root_client, root_server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_EQ(root_->Serve(fuchsia_io::kPermReadable, std::move(root_server)), ZX_OK);
root_client_ = std::move(root_client);
}
const fidl::ClientEnd<fuchsia_io::Directory>& root_client() { return root_client_; }
auto& connection_attempts() { return connection_attempts_; }
auto& fallback_attempts() { return fallback_attempts_; }
vfs::ComposedServiceDir* root() { return root_.get(); }
private:
fit::function<void(zx::channel channel, async_dispatcher_t* dispatcher)> MakeService(
std::string_view name, bool fallback) {
return [this, name, fallback](zx::channel channel, async_dispatcher_t* dispatcher) {
if (fallback) {
fallback_attempts_[name]++;
} else {
connection_attempts_[name]++;
}
};
}
std::unique_ptr<vfs::ComposedServiceDir> root_;
std::unique_ptr<vfs::PseudoDir> fallback_dir_;
std::map<std::string_view, size_t, std::less<>> connection_attempts_;
std::map<std::string_view, size_t, std::less<>> fallback_attempts_;
fidl::ClientEnd<fuchsia_io::Directory> root_client_;
};
TEST_F(ComposedServiceDirTest, Connect) {
PerformBlockingWork([this] {
for (size_t i = 0; i < kServiceConnections; ++i) {
for (const auto& service : kServices) {
zx::channel client, server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
ASSERT_EQ(fdio_service_connect_at(root_client().channel().get(), service.data(),
server.release()),
ZX_OK);
}
}
for (size_t i = 0; i < kFallbackServiceConnections; ++i) {
for (const auto& service : kFallbackServices) {
zx::channel client, server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
ASSERT_EQ(fdio_service_connect_at(root_client().channel().get(), service.data(),
server.release()),
ZX_OK);
}
}
});
RunLoopUntilIdle();
// service_a is only in the root, service_b is in both, and service_c is only in the fallback.
EXPECT_EQ(connection_attempts()["service_a"], kServiceConnections);
EXPECT_EQ(connection_attempts()["service_b"], kServiceConnections + kFallbackServiceConnections);
EXPECT_EQ(connection_attempts()["service_c"], 0u);
EXPECT_EQ(fallback_attempts()["service_a"], 0u);
EXPECT_EQ(fallback_attempts()["service_b"], 0u);
EXPECT_EQ(fallback_attempts()["service_c"], kFallbackServiceConnections);
}
TEST_F(ComposedServiceDirTest, ServeFailsWithInvalidArgs) {
// Serve should fail with an invalid channel.
ASSERT_EQ(root()->Serve(fuchsia_io::kPermReadable, {}), ZX_ERR_BAD_HANDLE);
// Serve should fail with an invalid set of flags.
{
auto [root_client, root_server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_EQ(root()->Serve(static_cast<fuchsia_io::Flags>(~0ull), std::move(root_server)),
ZX_ERR_INVALID_ARGS);
}
// Non-directory protocols are disallowed.
{
auto [root_client, root_server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_EQ(root()->Serve(fuchsia_io::Flags::kProtocolFile, std::move(root_server)),
ZX_ERR_INVALID_ARGS);
}
}
TEST_F(ComposedServiceDirTest, ServeFailsWithDifferentDispatcher) {
zx::channel root_client, root_server;
// We should be able to serve the same node multiple times as long as we use the same dispatcher.
for (size_t i = 0; i < 3; ++i) {
auto [root_client, root_server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_EQ(root()->Serve(fuchsia_io::kPermReadable, std::move(root_server)), ZX_OK);
}
// Serve should fail with a different dispatcher. Only one dispatcher may be registered to serve
// a given node.
{
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
auto [root_client, root_server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_EQ(root()->Serve(fuchsia_io::kPermReadable, std::move(root_server), loop.dispatcher()),
ZX_ERR_INVALID_ARGS);
}
}
} // namespace