| // 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 |