| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/logger/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/fidl/cpp/string.h> |
| #include <lib/fit/function.h> |
| #include <lib/sys/component/cpp/testing/realm_builder.h> |
| #include <samples/sdk/examples/fidl/cpp/fidl.h> |
| #include <zircon/status.h> |
| |
| #include <memory> |
| |
| #include <gtest/gtest.h> |
| |
| constexpr static char kGreetingService[] = "greeting_service"; |
| |
| // Create a test fixture to manage the async loop required by Realm Builder. |
| class RealmBuilderTest : public ::testing::Test { |
| protected: |
| RealmBuilderTest() : loop_(&kAsyncLoopConfigAttachToCurrentThread) {} |
| |
| async_dispatcher_t* dispatcher() { return loop_.dispatcher(); } |
| |
| void RunLoop() { |
| loop_.Run(); |
| loop_.ResetQuit(); |
| } |
| |
| void QuitLoop() { loop_.Quit(); } |
| |
| fit::closure QuitLoopClosure() { |
| return [this] { loop_.Quit(); }; |
| } |
| |
| private: |
| async::Loop loop_; |
| }; |
| |
| // This first test constructs a realm with a single component and tests that the |
| // component correctly implements the |
| // fuchsia.sdk.examples.fidl.GreetingService protocol. |
| TEST_F(RealmBuilderTest, RoutesFromComponent) { |
| // Component URL of the component to test. |
| static constexpr char kGreetingServiceServerUrl[] = "#meta/greeting_service.cm"; |
| |
| // Use Realm::Builder factory method to initiate builder object. |
| auto realm_builder = component_testing::RealmBuilder::Create(); |
| |
| // Add the Greeting Service component as child of the Realm. |
| realm_builder.AddChild(kGreetingService, kGreetingServiceServerUrl); |
| |
| // Route the fuchsia.logger.LogSink protocol from the test to the |
| // Greeting Service component. |
| realm_builder.AddRoute(component_testing::Route{ |
| .capabilities = {component_testing::Protocol{fuchsia::logger::LogSink::Name_}}, |
| .source = component_testing::ParentRef(), |
| .targets = {component_testing::ChildRef{kGreetingService}}}); |
| |
| // Route the fuchsia.sdk.examples.fidl.GreetingService protocol from |
| // the Greeting Service component to this component. |
| realm_builder.AddRoute( |
| component_testing::Route{.capabilities = {component_testing::Protocol{ |
| samples::sdk::examples::fidl::GreetingService::Name_}}, |
| .source = component_testing::ChildRef{kGreetingService}, |
| .targets = {component_testing::ParentRef()}}); |
| |
| // Build the realm once the topology has been determined. |
| auto realm = realm_builder.Build(dispatcher()); |
| |
| // Since we routed the fuchsia.sdk.examples.fidl.GreetingService |
| // protocol to this component, we should be able to connect to it through |
| // the exposed directory of the created Realm. |
| auto greeting_service = realm.ConnectSync<samples::sdk::examples::fidl::GreetingService>(); |
| auto greeting = samples::sdk::examples::fidl::Greeting::New(); |
| |
| static constexpr char kGreeting[] = "Hello World!"; |
| greeting->text = kGreeting; |
| samples::sdk::examples::fidl::GreetingPtr reply = nullptr; |
| ASSERT_EQ(greeting_service->Greet(std::move(greeting), &reply), ZX_OK); |
| ASSERT_NE(reply, nullptr); |
| EXPECT_EQ(reply->text, kGreeting); |
| } |
| |
| // This test is the same as the one above, except that the implementation |
| // of fuchsia.sdk.examples.fidl.GreetingService comes from a |
| // legacy (CMX) component. |
| TEST_F(RealmBuilderTest, RoutesFromLegacyComponent) { |
| static constexpr char kGreetingServiceServerLegacyUrl[] = |
| "fuchsia-pkg://fuchsia.com/greeting_service#meta/greeting_service.cmx"; |
| |
| auto realm_builder = component_testing::RealmBuilder::Create(); |
| realm_builder.AddLegacyChild(kGreetingService, kGreetingServiceServerLegacyUrl); |
| realm_builder.AddRoute(component_testing::Route{ |
| .capabilities = {component_testing::Protocol{fuchsia::logger::LogSink::Name_}}, |
| .source = component_testing::ParentRef(), |
| .targets = {component_testing::ChildRef{kGreetingService}}}); |
| realm_builder.AddRoute( |
| component_testing::Route{.capabilities = {component_testing::Protocol{ |
| samples::sdk::examples::fidl::GreetingService::Name_}}, |
| .source = component_testing::ChildRef{kGreetingService}, |
| .targets = {component_testing::ParentRef()}}); |
| |
| auto realm = realm_builder.Build(dispatcher()); |
| |
| auto greeting_service = realm.ConnectSync<samples::sdk::examples::fidl::GreetingService>(); |
| auto greeting = samples::sdk::examples::fidl::Greeting::New(); |
| |
| static constexpr char kGreeting[] = "Hello World!"; |
| greeting->text = kGreeting; |
| samples::sdk::examples::fidl::GreetingPtr reply = nullptr; |
| ASSERT_EQ(greeting_service->Greet(std::move(greeting), &reply), ZX_OK); |
| ASSERT_NE(reply, nullptr); |
| EXPECT_EQ(reply->text, kGreeting); |
| } |
| |
| // The next test mocks a component in the Realm instead of using a prebuilt one. |
| // When component_manager issues a Start request for this component, the |
| // `Start` method will be called. |
| class MockGreetingServiceServer : public component_testing::LocalComponent, |
| public samples::sdk::examples::fidl::GreetingService { |
| public: |
| MockGreetingServiceServer(async_dispatcher_t* dispatcher) |
| : dispatcher_(dispatcher), called_(false) {} |
| |
| void Greet(std::unique_ptr<samples::sdk::examples::fidl::Greeting> value, |
| GreetCallback callback) override { |
| called_ = true; |
| callback(std::move(value)); |
| } |
| |
| void Start(std::unique_ptr<component_testing::LocalComponentHandles> mock_handles) override { |
| mock_handles_ = std::move(mock_handles); |
| ASSERT_EQ(mock_handles_->outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)), |
| ZX_OK); |
| } |
| |
| bool WasCalled() const { return called_; } |
| |
| private: |
| async_dispatcher_t* dispatcher_; |
| fidl::BindingSet<samples::sdk::examples::fidl::GreetingService> bindings_; |
| bool called_; |
| std::unique_ptr<component_testing::LocalComponentHandles> mock_handles_; |
| }; |
| |
| // This test differs from the other two in that instead of using a prebuilt |
| // component added to this test's package, it mocks an implementation inside |
| // a local C++ class via the overridden `Start` method. |
| TEST_F(RealmBuilderTest, RoutesFromMockComponent) { |
| auto realm_builder = component_testing::RealmBuilder::Create(); |
| |
| MockGreetingServiceServer mock_server(dispatcher()); |
| realm_builder.AddLocalChild(kGreetingService, &mock_server); |
| realm_builder.AddRoute( |
| component_testing::Route{.capabilities = {component_testing::Protocol{ |
| samples::sdk::examples::fidl::GreetingService::Name_}}, |
| .source = component_testing::ChildRef{kGreetingService}, |
| .targets = {component_testing::ParentRef()}}); |
| |
| auto realm = realm_builder.Build(dispatcher()); |
| |
| samples::sdk::examples::fidl::GreetingServicePtr greeting_service; |
| ASSERT_EQ(realm.Connect(greeting_service.NewRequest()), ZX_OK); |
| auto greeting = samples::sdk::examples::fidl::Greeting::New(); |
| greeting->text = "hello"; |
| greeting_service->Greet(std::move(greeting), |
| [&](samples::sdk::examples::fidl::GreetingPtr response) { |
| // Use EXPECT here so the loop can still quit if the test fails |
| EXPECT_EQ(response->text, "hello"); |
| QuitLoop(); |
| }); |
| |
| // Wait for async callback to complete |
| RunLoop(); |
| |
| EXPECT_TRUE(mock_server.WasCalled()); |
| } |