blob: 0677e12b9a797ee9e3e179e11a98fdfaf29eb4db [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 <errno.h>
#include <fcntl.h>
#include <fidl/test.placeholders/cpp/fidl.h>
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>
#include <fuchsia/data/cpp/fidl.h>
#include <fuchsia/examples/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/sys2/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/dispatcher.h>
#include <lib/fdio/namespace.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fidl/cpp/comparison.h>
#include <lib/fidl/cpp/string.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/optional.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/sys/component/cpp/tests/utils.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/macros.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/availability.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <vector>
#include <gtest/gtest.h>
#include <src/lib/testing/loop_fixture/real_loop_fixture.h>
#include <test/placeholders/cpp/fidl.h>
namespace {
using namespace component_testing;
namespace fdecl = fuchsia::component::decl;
constexpr char kEchoServerUrl[] =
"fuchsia-pkg://fuchsia.com/component_cpp_testing_realm_builder_tests#meta/echo_server.cm";
constexpr char kEchoServerScUrl[] = "#meta/echo_server_sc.cm";
constexpr char kEchoServerFragmentOnlyUrl[] = "#meta/echo_server.cm";
constexpr char kEchoServiceServerUrl[] = "#meta/echo_service_server.cm";
class RealmBuilderTest : public gtest::RealLoopFixture {};
TEST_F(RealmBuilderTest, RoutesProtocolFromChild) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServer, kEchoServerUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
TEST_F(RealmBuilderTest, PackagedConfigValuesOnly) {
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kEchoServerSc}}});
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServerSc},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response,
fidl::StringPtr(
"hello "
"[1][255][65535][4000000000][8000000000][-127][-32766][-2000000000][-4000000000]["
"hello][1,0,][1,2,][2,3,][3,4,][4,5,][-1,-2,][-2,-3,][-3,-4,][-4,-5,][foo,bar,]"));
}
TEST_F(RealmBuilderTest, SetConfigValuesOnly) {
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.InitMutableConfigToEmpty(kEchoServerSc);
realm_builder.SetConfigValue(kEchoServerSc, "my_flag", ConfigValue::Bool(true));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint8", ConfigValue::Uint8(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint16", ConfigValue::Uint16(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint32", ConfigValue::Uint32(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint64", ConfigValue::Uint64(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int8", ConfigValue::Int8(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int16", ConfigValue::Int16(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int32", ConfigValue::Int32(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int64", ConfigValue::Int64(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_string", "foo");
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_flag", std::vector<bool>{false, true});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_uint8", std::vector<uint8_t>{1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_uint16", std::vector<uint16_t>{1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_uint32", std::vector<uint32_t>{1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_uint64", std::vector<uint64_t>{1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_int8", std::vector<int8_t>{-1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_int16", std::vector<int16_t>{-1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_int32", std::vector<int32_t>{-1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_int64", std::vector<int64_t>{-1, 1});
realm_builder.SetConfigValue(kEchoServerSc, "my_vector_of_string",
std::vector<std::string>{"bar", "foo"});
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServerSc},
.targets = {ParentRef()}});
realm_builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kEchoServerSc}}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello "
"[1][1][1][1][1][-1][-1][-1][-1][foo][0,1,][1,1,][1,1,][1,1,]"
"[1,1,][-1,1,][-1,1,][-1,1,][-1,1,][bar,foo,]"));
}
TEST_F(RealmBuilderTest, MixPackagedAndSetConfigValues) {
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.InitMutableConfigFromPackage(kEchoServerSc);
realm_builder.SetConfigValue(kEchoServerSc, "my_flag", ConfigValue::Bool(true));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint8", ConfigValue::Uint8(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint16", ConfigValue::Uint16(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint32", ConfigValue::Uint32(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_uint64", ConfigValue::Uint64(1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int8", ConfigValue::Int8(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int16", ConfigValue::Int16(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int32", ConfigValue::Int32(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_int64", ConfigValue::Int64(-1));
realm_builder.SetConfigValue(kEchoServerSc, "my_string", "foo");
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServerSc},
.targets = {ParentRef()}});
realm_builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kEchoServerSc}}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello "
"[1][1][1][1][1][-1][-1][-1][-1][foo][1,0,][1,2,][2,3,][3,4,]"
"[4,5,][-1,-2,][-2,-3,][-3,-4,][-4,-5,][foo,bar,]"));
}
TEST_F(RealmBuilderTest, SetConfigValueFails) {
ASSERT_DEATH(
{
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServer, kEchoServerFragmentOnlyUrl);
realm_builder.SetConfigValue(kEchoServer, "my_flag", ConfigValue::Bool(true));
},
"");
ASSERT_DEATH(
{
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.SetConfigValue(kEchoServerSc, "doesnt_exist", ConfigValue::Bool(true));
},
"");
ASSERT_DEATH(
{
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.SetConfigValue(kEchoServerSc, "my_string", ConfigValue::Bool(true));
},
"");
ASSERT_DEATH(
{
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.SetConfigValue(kEchoServerSc, "my_string", "abccdefghijklmnop");
},
"");
ASSERT_DEATH(
{
static constexpr char kEchoServerSc[] = "echo_server_sc";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServerSc, kEchoServerScUrl);
realm_builder.SetConfigValue(kEchoServerSc, "my_string",
std::vector<std::string>{"abcdefghijklmnopqrstuvwxyz", "abc"});
},
"");
}
TEST_F(RealmBuilderTest, RoutesProtocolFromRelativeChild) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServer, kEchoServerFragmentOnlyUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
// TODO(https://fxbug.dev/296292544): Remove when build support for API level 16 is removed.
#if __Fuchsia_API_level__ < 17
class LocalEchoServerByPtr : public test::placeholders::Echo, public LocalComponent {
public:
explicit LocalEchoServerByPtr(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
callback(std::move(value));
}
void Start(std::unique_ptr<LocalComponentHandles> handles) override {
handles_ = std::move(handles);
ASSERT_EQ(handles_->outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)),
ZX_OK);
}
private:
async_dispatcher_t* const dispatcher_;
fidl::BindingSet<test::placeholders::Echo> bindings_;
std::unique_ptr<LocalComponentHandles> handles_;
};
#endif
class LocalEchoServer : public test::placeholders::Echo, public LocalComponentImpl {
public:
explicit LocalEchoServer(async_dispatcher_t* dispatcher, fit::closure on_start = nullptr,
fit::closure on_destruct = nullptr, bool exit_after_serve = false)
: dispatcher_(dispatcher),
on_start_(std::move(on_start)),
on_destruct_(std::move(on_destruct)),
exit_after_serve_(exit_after_serve) {}
~LocalEchoServer() override {
if (on_destruct_) {
on_destruct_();
}
}
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
callback(std::move(value));
if (exit_after_serve_) {
Exit(ZX_ERR_CANCELED);
}
}
void OnStart() override {
if (on_start_) {
on_start_();
}
ASSERT_EQ(outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)), ZX_OK);
}
void OnStop() override {
if (on_stop_) {
on_stop_();
}
}
private:
async_dispatcher_t* dispatcher_;
fit::closure on_start_;
fit::closure on_stop_;
fit::closure on_destruct_;
fidl::BindingSet<test::placeholders::Echo> bindings_;
bool exit_after_serve_;
};
// TODO(https://fxbug.dev/296292544): Remove when build support for API level 16 is removed.
#if __Fuchsia_API_level__ < 17
// Tests and demonstrates that the deprecated AddLocalChild(LocalComponent*)
// still works.
//
// The Realm does not manage the lifecycle of LocalComponents added by raw
// pointer, which means the API cannot assume the pointer is always valid, and
// in many existing use cases, the pointer does become invalid while the
// Realm is still active.
//
// A mandatory assumption is that the pointer is valid when the Realm is built
// and calls LocalComponent::Start(). After that, RealmBuilder will not
// interact with the component.
//
// The component cannot be restarted.
TEST_F(RealmBuilderTest, RoutesProtocolFromLocalComponentRawPointer) {
static constexpr char kEchoServer[] = "echo_server";
LocalEchoServerByPtr local_echo_server(dispatcher());
auto realm_builder = RealmBuilder::Create();
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
realm_builder.AddLocalChild(kEchoServer, &local_echo_server);
#pragma clang diagnostic pop
realm_builder.AddRoute(
Route{.capabilities = {Protocol{fidl::DiscoverableProtocolName<test_placeholders::Echo>}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto cleanup = fit::defer([&]() {
bool complete = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
});
auto echo_client = realm.component().Connect<test_placeholders::Echo>();
ASSERT_TRUE(echo_client.is_ok());
fidl::Client echo(std::move(*echo_client), dispatcher());
bool was_called = false;
echo->EchoString({"hello"}).Then([&](fidl::Result<test_placeholders::Echo::EchoString>& result) {
was_called = true;
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result->response(), "hello");
});
RunLoopUntil([&]() { return was_called; });
}
#endif
// Demonstrates the recommended pattern for implementing a restartable
// LocalComponentImpl. A new LocalComponentImpl is returned when requested.
// After instance initialization, the LocalComponentRunner calls `OnStart()`,
// and the handles are accessible via ns(), svc(), and outgoing(). The instance
// should create and store the bindings.
//
// The component instance will continue to serve requests until the component is
// stopped, or until it is destroyed with the Realm.
TEST_F(RealmBuilderTest, RoutesProtocolUsesLocalComponentFactory) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
kEchoServer, [&]() { return std::make_unique<LocalEchoServer>(dispatcher()); },
ChildOptions{});
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto cleanup = fit::defer([&]() {
bool complete = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
});
test::placeholders::EchoPtr echo;
ASSERT_EQ(realm.component().Connect(echo.NewRequest()), ZX_OK);
bool was_called = false;
echo->EchoString("hello", [&](const fidl::StringPtr& response) {
was_called = true;
ASSERT_EQ(response, "hello");
});
RunLoopUntil([&]() { return was_called; });
}
// Demonstrates a LocalComponentImpl can restart after exit.
//
// Note that a test flake was reported in https://fxbug.dev/42062533 in this test.
// This test can occasionally time out, which probably means that at least
// one of the expected events is not always occurring.
//
// I've added error handles on the proxies, and logging to track expected
// events. If the flake happens again, the logs will help me understand why.
TEST_F(RealmBuilderTest, ComponentCanStopAndBeRestarted) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
bool started = false;
bool got_response = false;
bool destructed = false;
bool got_peer_closed = false;
realm_builder.AddLocalChild(kEchoServer, [&]() {
return std::make_unique<LocalEchoServer>(
dispatcher(),
/*on_start=*/
[&started]() {
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: started = true";
started = true;
},
/*on_destruct=*/
[&destructed]() {
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: destructed = true";
destructed = true;
},
/*exit_after_serve=*/true);
});
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
test::placeholders::EchoPtr echo;
ASSERT_EQ(realm.component().Connect(echo.NewRequest()), ZX_OK);
echo.set_error_handler([&](zx_status_t status) {
FX_PLOGS(INFO, status) << "ComponentCanStopAndBeRestarted: Echo proxy error";
if (status == ZX_ERR_PEER_CLOSED) {
got_peer_closed = true;
}
});
echo->EchoString("hello", [&](const fidl::StringPtr& response) {
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: got_response = true, response = " << response;
got_response = true;
ASSERT_EQ(response, "hello");
});
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: waiting for started && got_response && "
"destructed && got_peer_closed";
RunLoopUntil([&]() { return started && got_response && destructed && got_peer_closed; });
// When the local component exits (before the `while` loop), the
// `LocalComponentRunner` closes the component's `ComponentController`.
//
// It's possible (if rare) that a second call to `EchoString()` will not
// connect to a new LocalComponent instance, and a second `ERR_PEER_CLOSED`
// can occur, failing the `EchoString()` request. A theory is that component
// manager did not have a chance to observe the `ComponentController` closed
// event and/or fully stop the component in order to allow the component to
// re-start.
//
// To work around this failure, re-try until it succeeds.
//
// This approach is expected to fix an automatically-reported flake bug
// (https://fxbug.dev/42062533).
got_response = false;
got_peer_closed = false;
while (!got_response && !got_peer_closed) {
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: resetting the flags to call again";
started = false;
destructed = false;
// The component destructed, but it will start up again when another request
// is made.
ASSERT_EQ(realm.component().Connect(echo.NewRequest()), ZX_OK);
echo->EchoString("You're back!", [&](const fidl::StringPtr& response) {
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: got_response = true, second response = "
<< response;
got_response = true;
ASSERT_EQ(response, "You're back!");
});
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: waiting again for started && got_response && "
"destructed";
RunLoopUntil([&]() { return got_peer_closed || (started && got_response && destructed); });
}
FX_LOGS(INFO) << "ComponentCanStopAndBeRestarted: done";
}
TEST_F(RealmBuilderTest, StartAndStop) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(kEchoServer, [&]() {
return std::make_unique<LocalEchoServer>(dispatcher(), /*on_start=*/
nullptr,
/*on_destruct=*/
nullptr,
/*exit_after_serve=*/true);
});
// A component controller can only be acquired for a direct descendant, so we
// need to make the local component the root of the constructed realm. Before
// we do that though, let's use the `AddRoute` call to cause the expose
// declaration for the echo protocol to be added to the local component's
// manifest.
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto child_decl = realm_builder.GetComponentDecl(kEchoServer);
realm_builder.ReplaceRealmDecl(std::move(child_decl));
realm_builder.StartOnBuild(false);
auto realm = realm_builder.Build(dispatcher());
// We can start a component and see it exit on its own.
{
auto execution_controller = realm.component().Start();
auto stopped_payload = std::optional<fuchsia::component::StoppedPayload>{};
execution_controller.OnStop([&](fuchsia::component::StoppedPayload payload) {
stopped_payload = std::make_optional<fuchsia::component::StoppedPayload>(std::move(payload));
});
// Once we use the echo service, the local component will exit.
test::placeholders::EchoPtr echo;
ASSERT_EQ(realm.component().Connect(echo.NewRequest()), ZX_OK);
echo->EchoString({"hello"}, [](fidl::StringPtr response) {});
RunLoopUntil([&]() { return stopped_payload.has_value(); });
ASSERT_EQ(stopped_payload->status(), ZX_ERR_CANCELED);
}
// We can start a component, cause it to stop, and observe that stop.
{
auto execution_controller = realm.component().Start();
auto stopped_payload = std::optional<fuchsia::component::StoppedPayload>{};
execution_controller.OnStop([&](fuchsia::component::StoppedPayload payload) {
stopped_payload = std::make_optional<fuchsia::component::StoppedPayload>(std::move(payload));
});
execution_controller.Stop();
RunLoopUntil([&]() { return stopped_payload.has_value(); });
ASSERT_EQ(stopped_payload->status(), ZX_OK);
}
}
// Tests and demonstrates a discouraged pattern for calling AddLocalChild()
// that pre-builds a component instance, saves a direct raw pointer, and
// std::moves the instance to the LocalComponentFactory. The instance
// will be returned by the factory when the first component instance starts,
// making the captured instance invalid for subsequent requests to start a new
// instance.
//
// Existing C++ RealmBuilder clients migrating from the deprecated
// AddLocalChild(LocalComponent*) function (which only supported a single
// instance) may want to follow this pattern for simpler migration to the
// new AddLocalChild() API.
TEST_F(RealmBuilderTest, RoutesProtocolFromPrebuiltLocalComponentInstance) {
static constexpr char kEchoServer[] = "echo_server";
auto local_echo_server = std::make_unique<LocalEchoServer>(dispatcher());
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
kEchoServer,
[&, local_echo_server = std::move(local_echo_server)]() mutable {
// Note: This lambda does not create a new instance,
// so the component can only be started once.
return std::move(local_echo_server);
},
ChildOptions{});
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto cleanup = fit::defer([&]() {
bool complete = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
});
test::placeholders::EchoPtr echo;
ASSERT_EQ(realm.component().Connect(echo.NewRequest()), ZX_OK);
bool was_called = false;
echo->EchoString("hello", [&](const fidl::StringPtr& response) {
was_called = true;
ASSERT_EQ(response, "hello");
});
RunLoopUntil([&]() { return was_called; });
}
class EchoClientSyncLocalComponent : public LocalComponentImpl {
public:
explicit EchoClientSyncLocalComponent(fit::closure on_success)
: on_success_(std::move(on_success)) {}
void OnStart() override {
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"));
if (on_success_) {
on_success_();
}
Exit();
}
private:
fit::closure on_success_;
};
// Tests and demonstrates creating a local client-only component that can
// terminate immediately, after completing its work.
//
// Note: This client component uses a `SyncPtr` proxy binding, which invokes
// the call and (in the case of Echo::EchoString()) waits for the response
// synchronously.
TEST_F(RealmBuilderTest, RoutesProtocolToLocalComponentSync) {
static constexpr char kEchoClient[] = "echo_client";
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
bool success = false;
realm_builder.AddLocalChild(
kEchoClient,
[&success]() {
return std::make_unique<EchoClientSyncLocalComponent>([&success]() { success = true; });
},
ChildOptions{.startup_mode = StartupMode::EAGER});
realm_builder.AddChild(kEchoServer, kEchoServerUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ChildRef{kEchoClient}}});
realm_builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kEchoServer}}});
auto realm = realm_builder.Build(dispatcher());
RunLoopUntil([&success]() { return success; });
}
class EchoClientLocalComponent : public LocalComponentImpl {
public:
explicit EchoClientLocalComponent(fit::closure on_success) : on_success_(std::move(on_success)) {}
void OnStart() override {
ASSERT_EQ(svc().Connect<test::placeholders::Echo>(echo_.NewRequest()), ZX_OK);
echo_->EchoString("hello", [&](const fidl::StringPtr& response) {
ASSERT_EQ("hello", response);
if (on_success_) {
on_success_();
}
Exit();
});
}
private:
test::placeholders::EchoPtr echo_;
fit::closure on_success_;
};
// Tests and demonstrates creating a local client-only component that uses
// an asynchronous proxy binding to call a service and get a response. Since
// the call is asynchronous, the component must not call `Exit()`
// until it gets a response.
TEST_F(RealmBuilderTest, RoutesProtocolToLocalComponentAsync) {
static constexpr char kEchoClient[] = "echo_client";
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
bool success = false;
realm_builder.AddLocalChild(
kEchoClient,
[&success]() {
return std::make_unique<EchoClientLocalComponent>([&success]() { success = true; });
},
ChildOptions{.startup_mode = StartupMode::EAGER});
realm_builder.AddChild(kEchoServer, kEchoServerUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ChildRef{kEchoClient}}});
realm_builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kEchoServer}}});
auto realm = realm_builder.Build(dispatcher());
RunLoopUntil([&success]() { return success; });
}
TEST_F(RealmBuilderTest, RoutesServiceFromChild) {
static constexpr char kEchoServiceServer[] = "echo_service_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServiceServer, kEchoServiceServerUrl);
realm_builder.AddRoute(Route{.capabilities = {Service{fuchsia::examples::EchoService::Name}},
.source = ChildRef{kEchoServiceServer},
.targets = {ParentRef()}});
realm_builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef(),
.targets = {ChildRef{kEchoServiceServer}}});
auto realm = realm_builder.Build(dispatcher());
auto default_service =
sys::OpenServiceAt<fuchsia::examples::EchoService>(realm.component().CloneExposedDir());
auto regular = default_service.regular_echo().Connect().Bind();
constexpr char kMessage[] = "Ping!";
bool was_called = false;
regular->EchoString(kMessage,
[expected_reply = kMessage, &was_called](const fidl::StringPtr& value) {
was_called = true;
ASSERT_EQ(value, expected_reply);
});
RunLoopUntil([&]() { return was_called; });
}
TEST_F(RealmBuilderTest, ConnectsToChannelDirectly) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServer, kEchoServerUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
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.component().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"));
}
TEST_F(RealmBuilderTest, RoutesProtocolFromLocalComponentInSubRealm) {
static constexpr char kEchoServer[] = "echo_server";
static constexpr char kSubRealm[] = "sub_realm";
auto realm_builder = RealmBuilder::Create();
auto sub_realm = realm_builder.AddChildRealm(kSubRealm);
// Route test.placeholders.Echo from local Echo server impl to parent.
sub_realm.AddLocalChild(kEchoServer,
[&]() { return std::make_unique<LocalEchoServer>(dispatcher()); });
sub_realm.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
// Route test.placeholders.Echo from sub_realm child to parent.
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kSubRealm},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto cleanup = fit::defer([&]() {
bool complete = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
});
test::placeholders::EchoPtr echo;
ASSERT_EQ(realm.component().Connect(echo.NewRequest()), ZX_OK);
bool was_called = false;
echo->EchoString("hello", [&](const fidl::StringPtr& response) {
was_called = true;
ASSERT_EQ("hello", response);
});
RunLoopUntil([&]() { return was_called; });
}
class FileReader : public LocalComponentImpl {
public:
void OnStart() override { started_ = true; }
std::string GetContentsAt(std::string_view dirpath, std::string_view filepath) {
ZX_ASSERT_MSG(started_, "FileReader/GetContentsAt called before FileReader was started.");
constexpr static size_t kMaxBufferSize = 1024;
static char kReadBuffer[kMaxBufferSize];
int dirfd = fdio_ns_opendir(ns());
ZX_ASSERT_MSG(dirfd > 0, "Failed to open root ns as a file descriptor: %s", strerror(errno));
std::stringstream path_builder;
path_builder << dirpath << '/' << filepath;
auto path = path_builder.str();
int filefd = openat(dirfd, path.c_str(), O_RDONLY);
ZX_ASSERT_MSG(filefd > 0, "Failed to open path \"%s\": %s", path.c_str(), strerror(errno));
size_t const bytes_read = read(filefd, reinterpret_cast<void*>(kReadBuffer), kMaxBufferSize);
ZX_ASSERT_MSG(bytes_read > 0, "Read 0 bytes from file at \"%s\": %s", path.c_str(),
strerror(errno));
return std::string(kReadBuffer, bytes_read);
}
bool HasStarted() const { return started_; }
private:
bool started_ = false;
};
TEST_F(RealmBuilderTest, RoutesReadOnlyDirectory) {
static constexpr char kDirectoryName[] = "config";
static constexpr char kFilename[] = "environment";
static constexpr char kContent[] = "DEV";
auto realm_builder = RealmBuilder::Create();
std::unique_ptr file_reader = std::make_unique<FileReader>();
FileReader* file_reader_ptr = file_reader.get();
realm_builder.AddLocalChild(
"file_reader",
[file_reader = std::move(file_reader)]() mutable { return std::move(file_reader); },
ChildOptions{.startup_mode = StartupMode::EAGER});
realm_builder.RouteReadOnlyDirectory(kDirectoryName, {ChildRef{"file_reader"}},
std::move(DirectoryContents().AddFile(kFilename, kContent)));
auto realm = realm_builder.Build(dispatcher());
auto cleanup = fit::defer([&]() {
bool complete = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
});
RunLoopUntil([&]() { return file_reader_ptr->HasStarted(); });
EXPECT_EQ(file_reader_ptr->GetContentsAt(kDirectoryName, kFilename), kContent);
}
// This test is similar to RealmBuilderTest.RoutesProtocolFromChild except
// that its setup is done by mutating the realm's root's decl. This is to
// assert that invoking |ReplaceRealmDecl| works as expected.
TEST_F(RealmBuilderTest, RealmDeclCanBeReplaced) {
static constexpr char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild(kEchoServer, kEchoServerUrl);
auto decl = realm_builder.GetRealmDecl();
fdecl::ExposeProtocol expose_protocol;
expose_protocol.set_source(fdecl::Ref::WithChild(fdecl::ChildRef{.name = "echo_server"}));
expose_protocol.set_target(fdecl::Ref::WithParent(fdecl::ParentRef{}));
expose_protocol.set_source_name("test.placeholders.Echo");
expose_protocol.set_target_name("test.placeholders.Echo");
decl.mutable_exposes()->emplace_back(fdecl::Expose::WithProtocol(std::move(expose_protocol)));
realm_builder.ReplaceRealmDecl(std::move(decl));
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
// This test is similar to RealmBuilderTest.RoutesProtocolFromChild except
// that its setup is done statically via a manifest. This is to assert that
// invoking |CreateFromRelativeUrl| works as expected.
TEST_F(RealmBuilderTest, BuildsRealmFromFragmentOnlyUrl) {
static constexpr char kPrePopulatedRealmUrl[] = "#meta/pre_populated_realm.cm";
auto realm_builder = RealmBuilder::CreateFromRelativeUrl(kPrePopulatedRealmUrl);
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().ConnectSync<test::placeholders::Echo>();
fidl::StringPtr response;
ASSERT_EQ(echo->EchoString("hello", &response), ZX_OK);
EXPECT_EQ(response, fidl::StringPtr("hello"));
}
class SimpleComponent : public component_testing::LocalComponentImpl {
public:
SimpleComponent() = default;
explicit SimpleComponent(fit::closure on_stop, fit::closure on_destruct)
: on_stop_(std::move(on_stop)), on_destruct_(std::move(on_destruct)) {}
~SimpleComponent() override {
if (on_destruct_) {
on_destruct_();
}
}
void OnStart() override { started_ = true; }
void OnStop() override {
stopping_ = true;
if (on_stop_) {
on_stop_();
}
}
bool IsStarted() const { return started_; }
bool IsStopping() const { return stopping_; }
private:
bool started_ = false;
bool stopping_ = false;
fit::closure on_stop_;
fit::closure on_destruct_;
};
// This test asserts that the LocalComponents are started, not stopped, and
// are eventually destructed when destructing the realm.
TEST_F(RealmBuilderTest, LocalComponentGetsDestructedOnExit) {
auto realm_builder = RealmBuilder::Create();
size_t destructors_called = 0;
// hold pointers to the LocalComponents owned by the realm
std::vector<SimpleComponent*> components;
for (size_t i = 0; i < 3; ++i) {
std::string name = "numbered" + std::to_string(i);
auto component = std::make_unique<SimpleComponent>(nullptr, [&]() { destructors_called++; });
components.push_back(component.get());
realm_builder.AddLocalChild(
name, [component = std::move(component)]() mutable { return std::move(component); },
ChildOptions{.startup_mode = StartupMode::EAGER});
}
auto realm = realm_builder.Build(dispatcher());
auto cleanup = fit::defer([&]() {
bool complete = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
});
for (auto& component : components) {
ASSERT_FALSE(component->IsStarted());
ASSERT_FALSE(component->IsStopping());
}
// Verify all components have started.
RunLoopUntil([&]() {
return std::all_of(components.begin(), components.end(),
[](auto& component) { return component->IsStarted(); });
});
for (auto& component : components) {
ASSERT_FALSE(component->IsStopping());
}
cleanup.call();
ASSERT_EQ(destructors_called, components.size());
}
// This test asserts that the LocalComponentImpl::OnStop() method is called when
// the component is stopped (which confirms that the ComponentController
// would have also been dropped).
TEST_F(RealmBuilderTest, LocalComponentGetsLifecycleControllerStop) {
auto realm_builder = RealmBuilder::Create();
realm_builder.AddRoute(
Route{.capabilities = {Protocol{fuchsia::sys2::LifecycleController::Name_}},
.source = FrameworkRef(),
.targets = {ParentRef{}}});
size_t destructors_called = 0;
std::vector<SimpleComponent*> components;
for (size_t i = 0; i < 3; ++i) {
std::string name = "numbered" + std::to_string(i);
auto component = std::make_unique<SimpleComponent>(nullptr, [&]() { destructors_called++; });
components.push_back(component.get());
realm_builder.AddLocalChild(
name, [component = std::move(component)]() mutable { return std::move(component); },
ChildOptions{.startup_mode = StartupMode::EAGER});
}
auto realm = std::make_optional<RealmRoot>(realm_builder.Build(dispatcher()));
for (auto& component : components) {
ASSERT_FALSE(component->IsStarted());
ASSERT_FALSE(component->IsStopping());
}
// Verify all components have started.
for (auto& component : components) {
RunLoopUntil([&]() { return component->IsStarted(); });
}
for (auto& component : components) {
ASSERT_FALSE(component->IsStopping());
}
// Stop the components and verify all components have been asked to stop.
auto lifecycle_controller = realm->component().Connect<fuchsia::sys2::LifecycleController>();
for (size_t i = 0; i < components.size(); ++i) {
size_t orig = destructors_called;
std::string moniker = "./numbered" + std::to_string(i);
lifecycle_controller->StopInstance(moniker,
[](auto result) { ASSERT_TRUE(result.is_response()); });
RunLoopUntil([&]() { return destructors_called == orig + 1; });
}
}
// This test asserts that the `LocalComponentImpl::OnStop()` method is called
// when the RealmRoot is torn down (returned from `Realm::DestroyChild()`), and
// the `RealmRoot::Teardown` callback is called when the realm is destroyed.
TEST_F(RealmBuilderTest, LocalComponentGetsRealmRootTeardownStop) {
auto realm_builder = RealmBuilder::Create();
realm_builder.AddRoute(
Route{.capabilities = {Protocol{fuchsia::sys2::LifecycleController::Name_}},
.source = FrameworkRef(),
.targets = {ParentRef{}}});
size_t components_stopped = 0;
size_t destructors_called = 0;
std::vector<SimpleComponent*> components;
for (size_t i = 0; i < 3; ++i) {
std::string name = "numbered" + std::to_string(i);
auto component = std::make_unique<SimpleComponent>([&]() { components_stopped++; },
[&]() { destructors_called++; });
components.push_back(component.get());
realm_builder.AddLocalChild(
name, [component = std::move(component)]() mutable { return std::move(component); },
ChildOptions{.startup_mode = StartupMode::EAGER});
}
auto realm = realm_builder.Build(dispatcher());
for (auto& component : components) {
ASSERT_FALSE(component->IsStarted());
ASSERT_FALSE(component->IsStopping());
}
// Verify all components have started.
for (auto& component : components) {
RunLoopUntil([&]() { return component->IsStarted(); });
}
for (auto& component : components) {
ASSERT_FALSE(component->IsStopping());
}
ASSERT_EQ(components_stopped, 0u);
ASSERT_EQ(destructors_called, 0u);
bool realm_is_destroyed = false;
realm.Teardown([&](fit::result<fuchsia::component::Error> result) {
// Since the Realm owns the `unique_ptr`s to the SimpleComponents, the
// realm should have stopped and destructed them before this callback is
// invoked, so do not try to use the `components` vector of
// SimpleComponent* raw pointers!
realm_is_destroyed = true;
});
RunLoopUntil([&]() { return realm_is_destroyed; });
// Verify all components were stopped _and_ destructed.
ASSERT_EQ(components_stopped, components.size());
ASSERT_EQ(destructors_called, components.size());
}
// This test is nearly identically 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 char kEchoServer[] = "echo_server";
auto realm_builder = RealmBuilder::Create(context->svc());
realm_builder.AddChild(kEchoServer, kEchoServerUrl);
realm_builder.AddRoute(Route{.capabilities = {Protocol{test::placeholders::Echo::Name_}},
.source = ChildRef{kEchoServer},
.targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());
auto echo = realm.component().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 = RealmBuilder::Create();
auto realm = realm_builder.Build(dispatcher());
child_name_1 = realm.component().GetChildName();
}
std::string child_name_2;
{
auto realm_builder = RealmBuilder::Create();
auto realm = realm_builder.Build(dispatcher());
child_name_2 = realm.component().GetChildName();
}
EXPECT_NE(child_name_1, child_name_2);
}
TEST_F(RealmBuilderTest, CanCreateLongChildName) {
std::string child_name_1;
{
auto realm_builder = RealmBuilder::Create();
{
const std::string long_child_name(fuchsia::component::MAX_NAME_LENGTH + 1, 'a');
// AddChild should not panic.
realm_builder.AddChild(long_child_name, kEchoServerUrl);
}
{
const std::string long_child_name(fuchsia::component::MAX_CHILD_NAME_LENGTH, 'a');
// AddChild should not panic.
realm_builder.AddChild(long_child_name, kEchoServerUrl);
}
{
ASSERT_DEATH(
{
const std::string too_long_child_name(fuchsia::component::MAX_CHILD_NAME_LENGTH + 1,
'a');
realm_builder.AddChild(too_long_child_name, kEchoServerUrl);
},
"");
}
}
}
TEST_F(RealmBuilderTest, PanicsWhenUsingHandlesFromConstructor) {
class UseNsTooEarly : public LocalComponentImpl {
public:
UseNsTooEarly() { ns(); }
void OnStart() override {}
};
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
"child", []() { return std::make_unique<UseNsTooEarly>(); },
ChildOptions{.startup_mode = StartupMode::EAGER});
auto realm = realm_builder.Build(dispatcher());
RunLoop();
},
"LocalComponentImpl::ns\\(\\) cannot be called until RealmBuilder calls OnStart\\(\\)");
class UseSvcTooEarly : public LocalComponentImpl {
public:
UseSvcTooEarly() { svc(); }
void OnStart() override {}
};
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
"child", []() { return std::make_unique<UseSvcTooEarly>(); },
ChildOptions{.startup_mode = StartupMode::EAGER});
auto realm = realm_builder.Build(dispatcher());
RunLoop();
},
"LocalComponentImpl::svc\\(\\) cannot be called until RealmBuilder calls OnStart\\(\\)");
class UseOutgoingTooEarly : public LocalComponentImpl {
public:
UseOutgoingTooEarly() { outgoing(); }
void OnStart() override {}
};
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
"child", []() { return std::make_unique<UseOutgoingTooEarly>(); },
ChildOptions{.startup_mode = StartupMode::EAGER});
auto realm = realm_builder.Build(dispatcher());
RunLoop();
},
"LocalComponentImpl::outgoing\\(\\) cannot be called until RealmBuilder calls OnStart\\(\\)");
class CallExitTooEarly : public LocalComponentImpl {
public:
CallExitTooEarly() { Exit(); }
void OnStart() override {}
};
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
"child", []() { return std::make_unique<CallExitTooEarly>(); },
ChildOptions{.startup_mode = StartupMode::EAGER});
auto realm = realm_builder.Build(dispatcher());
RunLoop();
},
"LocalComponentImpl::Exit\\(\\) cannot be called until RealmBuilder calls OnStart\\(\\)");
}
TEST_F(RealmBuilderTest, PanicsWhenBuildCalledMultipleTimes) {
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.Build(dispatcher());
realm_builder.Build(dispatcher());
},
"");
}
TEST(RealmBuilderUnittest, PanicsIfChildNameIsEmpty) {
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild("", kEchoServerUrl);
},
"");
class BasicLocalImpl : public LocalComponentImpl {
void OnStart() override {}
};
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(
"", []() -> std::unique_ptr<LocalComponentImpl> { return nullptr; });
},
"");
}
TEST(RealmBuilderUnittest, PanicsIfUrlIsEmpty) {
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddChild("some_valid_name", "");
},
"");
}
TEST(RealmBuilderUnittest, PanicsWhenArgsAreNullptr) {
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
// Should panic because |async_get_default_dispatcher| was not configured
// to return nullptr.
realm_builder.Build(nullptr);
},
"");
#if __Fuchsia_API_level__ < 17
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ASSERT_DEATH(
{
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild("some_valid_name", nullptr);
},
"");
#pragma clang diagnostic pop
#endif
}
TEST(DirectoryContentsUnittest, PanicWhenGivenInvalidPath) {
ASSERT_DEATH(
{
auto directory_contents = DirectoryContents();
directory_contents.AddFile("/foo/bar.txt", "Hello World!");
},
"");
ASSERT_DEATH(
{
auto directory_contents = DirectoryContents();
directory_contents.AddFile("foo/bar/", "Hello World!");
},
"");
ASSERT_DEATH(
{
auto directory_contents = DirectoryContents();
directory_contents.AddFile("", "Hello World!");
},
"");
}
class PlaceholderComponent : public LocalComponentImpl {
public:
PlaceholderComponent() = default;
void OnStart() override {}
};
constexpr char kRoutingTestChildName[] = "foobar";
class RealmBuilderRoutingParameterizedFixture
: public testing::TestWithParam<std::pair<Capability, std::shared_ptr<fdecl::Offer>>> {};
TEST_P(RealmBuilderRoutingParameterizedFixture, RoutedCapabilitiesYieldExpectedOfferClauses) {
auto realm_builder = RealmBuilder::Create();
realm_builder.AddLocalChild(kRoutingTestChildName,
[]() { return std::make_unique<PlaceholderComponent>(); });
auto param = GetParam();
auto capability = param.first;
realm_builder.AddRoute(Route{.capabilities = {capability},
.source = ParentRef{},
.targets = {ChildRef{kRoutingTestChildName}}});
auto root_decl = realm_builder.GetRealmDecl();
ASSERT_EQ(root_decl.offers().size(), 1ul);
const fdecl::Offer& actual = root_decl.offers().at(0);
const fdecl::Offer& expected = *param.second;
EXPECT_TRUE(fidl::Equals(actual, expected));
}
INSTANTIATE_TEST_SUITE_P(
RealmBuilderRoutingTest, RealmBuilderRoutingParameterizedFixture,
testing::Values(
std::make_pair(Protocol{.name = "foo", .as = "bar"},
component::tests::CreateFidlProtocolOfferDecl(
/*source_name=*/"foo",
/*source=*/component::tests::CreateFidlParentRef(),
/*target_name=*/"bar",
/*target=*/component::tests::CreateFidlChildRef(kRoutingTestChildName))),
std::make_pair(Service{.name = "foo", .as = "bar"},
component::tests::CreateFidlServiceOfferDecl(
/*source_name=*/"foo",
/*source=*/component::tests::CreateFidlParentRef(),
/*target_name=*/"bar",
/*target=*/component::tests::CreateFidlChildRef(kRoutingTestChildName))),
std::make_pair(Directory{.name = "foo",
.as = "bar",
.subdir = "sub",
.rights = fuchsia::io::RW_STAR_DIR,
.path = "/foo"},
component::tests::CreateFidlDirectoryOfferDecl(
/*source_name=*/"foo",
/*source=*/component::tests::CreateFidlParentRef(),
/*target_name=*/"bar",
/*target=*/component::tests::CreateFidlChildRef(kRoutingTestChildName),
/*subdir=*/"sub",
/*rights=*/fuchsia::io::RW_STAR_DIR)),
std::make_pair(
Storage{.name = "foo", .as = "bar", .path = "/foo"},
component::tests::CreateFidlStorageOfferDecl(
/*source_name=*/"foo", /*source=*/component::tests::CreateFidlParentRef(),
/*target_name=*/"bar",
/*target=*/component::tests::CreateFidlChildRef(kRoutingTestChildName)))));
} // namespace