blob: 58a0fd5ab04c32cac613bbe8a89d39126fa068fb [file] [log] [blame]
// Copyright 2021 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 <fuchsia/component/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fidl/cpp/string.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/sys/cpp/component_context.h>
#include <zircon/status.h>
#include <memory>
#include <gtest/gtest.h>
#include <test/placeholders/cpp/fidl.h>
namespace {
// NOLINTNEXTLINE
using namespace sys::testing;
constexpr char kEchoServerUrl[] =
"fuchsia-pkg://fuchsia.com/component_cpp_tests#meta/echo_server.cm";
constexpr char kEchoServerLegacyUrl[] =
"fuchsia-pkg://fuchsia.com/component_cpp_tests#meta/echo_server.cmx";
constexpr char kEchoServerRelativeUrl[] = "#meta/echo_server.cm";
class RealmBuilderTest : public ::testing::Test {
protected:
void SetUp() override {
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
}
void TearDown() override { loop_->Quit(); }
async_dispatcher_t* dispatcher() { return loop_->dispatcher(); }
async::Loop& loop() { return *loop_; }
private:
std::unique_ptr<async::Loop> loop_;
};
TEST_F(RealmBuilderTest, RoutesProtocolFromChild) {
static constexpr auto kEchoServer = Moniker{"echo_server"};
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoServer, Component{.source = ComponentUrl{kEchoServerUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
TEST_F(RealmBuilderTest, RoutesProtocolFromGrandchild) {
static constexpr auto kEchoServer = Moniker{"parent/echo_server"};
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoServer, Component{.source = ComponentUrl{kEchoServerUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
TEST_F(RealmBuilderTest, RoutesProtocolFromLegacyChild) {
static constexpr auto kEchoServer = Moniker{"echo_server"};
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoServer,
Component{.source = LegacyComponentUrl{kEchoServerLegacyUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
TEST_F(RealmBuilderTest, RoutesProtocolFromRelativeChild) {
static constexpr auto kEchoServer = Moniker{"echo_server"};
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoServer,
Component{.source = ComponentUrl{kEchoServerRelativeUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
class MockEchoServer : public test::placeholders::Echo, public MockComponent {
public:
explicit MockEchoServer(async::Loop& loop) : loop_(loop), called_(false) {}
void EchoString(::fidl::StringPtr value, EchoStringCallback callback) override {
callback(std::move(value));
called_ = true;
loop_.Quit();
}
void Start(std::unique_ptr<MockHandles> mock_handles) override {
mock_handles_ = std::move(mock_handles);
ASSERT_EQ(
mock_handles_->outgoing()->AddPublicService(bindings_.GetHandler(this, loop_.dispatcher())),
ZX_OK);
}
bool WasCalled() const { return called_; }
private:
async::Loop& loop_;
fidl::BindingSet<test::placeholders::Echo> bindings_;
bool called_;
std::unique_ptr<MockHandles> mock_handles_;
};
TEST_F(RealmBuilderTest, RoutesProtocolFromMockComponent) {
static constexpr auto kEchoServer = Moniker{"echo_server"};
MockEchoServer mock_echo_server(loop());
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoServer, Component{
.source = Mock{&mock_echo_server},
.eager = false,
});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
test::placeholders::EchoPtr echo;
ASSERT_EQ(realm.Connect(echo.NewRequest()), ZX_OK);
echo->EchoString("hello", [](fidl::StringPtr _) {});
loop().Run();
EXPECT_TRUE(mock_echo_server.WasCalled());
}
class MockEchoClient : public MockComponent {
public:
explicit MockEchoClient(async::Loop& loop) : loop_(loop), called_(false) {}
void Start(std::unique_ptr<MockHandles> mock_handles) override {
mock_handles_ = std::move(mock_handles);
auto svc = mock_handles_->svc();
test::placeholders::EchoSyncPtr echo;
ASSERT_EQ(svc.Connect<test::placeholders::Echo>(echo.NewRequest()), ZX_OK);
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("milk", &response), ZX_OK);
ASSERT_EQ(response, fidl::StringPtr("milk"));
called_ = true;
loop_.Quit();
}
bool WasCalled() const { return called_; }
private:
async::Loop& loop_;
bool called_;
std::unique_ptr<MockHandles> mock_handles_;
};
TEST_F(RealmBuilderTest, RoutesProtocolToMockComponent) {
static constexpr auto kEchoClient = Moniker{"echo_client"};
static constexpr auto kEchoServer = Moniker{"echo_server"};
MockEchoClient mock_echo_client(loop());
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoClient, Component{
.source = Mock{&mock_echo_client},
.eager = true,
});
realm_builder.AddComponent(kEchoServer, Component{.source = ComponentUrl{kEchoServerUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {kEchoClient}});
auto realm = realm_builder.Build(dispatcher());
auto _binder = realm.ConnectSync<fuchsia::component::Binder>();
loop().Run();
EXPECT_TRUE(mock_echo_client.WasCalled());
}
TEST_F(RealmBuilderTest, ConnectsToChannelDirectly) {
static constexpr auto kEchoServer = Moniker{"echo_server"};
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(kEchoServer, Component{.source = ComponentUrl{kEchoServerUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
zx::channel controller, request;
ASSERT_EQ(zx::channel::create(0, &controller, &request), ZX_OK);
fidl::SynchronousInterfacePtr<test::placeholders::Echo> echo;
echo.Bind(std::move(controller));
ASSERT_EQ(realm.Connect(test::placeholders::Echo::Name_, std::move(request)), ZX_OK);
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
// This test is nearly identicaly to the RealmBuilderTest.RoutesProtocolFromChild
// test case above. The only difference is that it provides a svc directory
// from the sys::Context singleton object to the Realm::Builder::Create method.
// If the test passes, it must follow that Realm::Builder supplied a Context
// object internally, otherwise the test component wouldn't be able to connect
// to fuchsia.component.Realm protocol.
TEST_F(RealmBuilderTest, UsesProvidedSvcDirectory) {
auto context = sys::ComponentContext::Create();
static constexpr auto kEchoServer = Moniker{"echo_server"};
auto realm_builder = Realm::Builder::Create(context->svc());
realm_builder.AddComponent(kEchoServer, Component{.source = ComponentUrl{kEchoServerUrl}});
realm_builder.AddRoute(CapabilityRoute{.capability = Protocol{test::placeholders::Echo::Name_},
.source = kEchoServer,
.targets = {AboveRoot()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
TEST_F(RealmBuilderTest, UsesRandomChildName) {
std::string child_name_1 = "";
{
auto realm_builder = Realm::Builder::Create();
auto realm = realm_builder.Build(dispatcher());
child_name_1 = realm.GetChildName();
}
std::string child_name_2 = "";
{
auto realm_builder = Realm::Builder::Create();
auto realm = realm_builder.Build(dispatcher());
child_name_2 = realm.GetChildName();
}
EXPECT_NE(child_name_1, child_name_2);
}
TEST_F(RealmBuilderTest, PanicsWhenBuildCalledMultipleTimes) {
ASSERT_DEATH(
{
auto realm_builder = Realm::Builder::Create();
realm_builder.Build(dispatcher());
realm_builder.Build(dispatcher());
},
"");
}
TEST(RealmBuilderUnittest, PanicsIfMonikerIsBad) {
auto context = sys::ComponentContext::Create();
ASSERT_DEATH(
{
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(Moniker{""}, Component{
.source = ComponentUrl{},
});
},
"");
ASSERT_DEATH(
{
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(Moniker{"/no_leading_slash"}, Component{
.source = ComponentUrl{},
});
},
"");
ASSERT_DEATH(
{
auto realm_builder = Realm::Builder::Create();
realm_builder.AddComponent(Moniker{"no_trailing_slash/"}, Component{
.source = ComponentUrl{},
});
},
"");
}
TEST(RealmBuilderUnittest, PanicsWhenArgsAreNullptr) {
ASSERT_DEATH(
{
auto realm_builder = Realm::Builder::Create();
// Should panic because |async_get_default_dispatcher| was not configured
// to return nullptr.
realm_builder.Build(nullptr);
},
"");
}
} // namespace