// Copyright 2019 The Chromium 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/base/agent_impl.h" #include #include "base/fuchsia/fuchsia_logging.h" #include "base/logging.h" #include "base/test/task_environment.h" #include "base/testfidl/cpp/fidl.h" #include "fuchsia/base/fit_adapter.h" #include "fuchsia/base/result_receiver.h" #include "testing/gtest/include/gtest/gtest.h" namespace cr_fuchsia { namespace { const char kNoServicesComponentId[] = "no-services"; const char kAccumulatorComponentId1[] = "accumulator1"; const char kAccumulatorComponentId2[] = "accumulator2"; const char kKeepAliveComponentId[] = "keep-alive"; class EmptyComponentState : public AgentImpl::ComponentStateBase { public: EmptyComponentState(base::StringPiece component) : ComponentStateBase(component) {} }; class AccumulatingTestInterfaceImpl : public base::testfidl::TestInterface { public: AccumulatingTestInterfaceImpl() = default; // TestInterface implementation: void Add(int32_t a, int32_t b, AddCallback callback) override { accumulated_ += a + b; callback(accumulated_); } private: int32_t accumulated_ = 0; DISALLOW_COPY_AND_ASSIGN(AccumulatingTestInterfaceImpl); }; class AccumulatorComponentState : public AgentImpl::ComponentStateBase { public: AccumulatorComponentState(base::StringPiece component) : ComponentStateBase(component), service_binding_(outgoing_directory(), &service_) {} protected: AccumulatingTestInterfaceImpl service_; base::fuchsia::ScopedServiceBinding service_binding_; }; class KeepAliveComponentState : public AccumulatorComponentState { public: KeepAliveComponentState(base::StringPiece component) : AccumulatorComponentState(component) { AddKeepAliveBinding(&service_binding_); } void DisconnectClientsAndTeardown() { AgentImpl::ComponentStateBase::DisconnectClientsAndTeardown(); } }; class AgentImplTest : public ::testing::Test { public: AgentImplTest() { fidl::InterfaceHandle<::fuchsia::io::Directory> directory; services_.GetOrCreateDirectory("svc")->Serve( fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE, directory.NewRequest().TakeChannel()); services_client_ = std::make_unique(std::move(directory)); } fuchsia::modular::AgentPtr CreateAgentAndConnect() { DCHECK(!agent_impl_); agent_impl_ = std::make_unique( &services_, base::BindRepeating(&AgentImplTest::OnComponentConnect, base::Unretained(this))); fuchsia::modular::AgentPtr agent; services_client_->Connect(agent.NewRequest()); return agent; } void TeardownKeepAliveComponentState() { std::move(disconnect_clients_and_teardown_).Run(); } protected: std::unique_ptr OnComponentConnect( base::StringPiece component_id) { if (component_id == kNoServicesComponentId) { return std::make_unique(component_id); } else if (component_id == kAccumulatorComponentId1 || component_id == kAccumulatorComponentId2) { return std::make_unique(component_id); } else if (component_id == kKeepAliveComponentId) { auto component_state = std::make_unique(component_id); disconnect_clients_and_teardown_ = base::BindOnce(&KeepAliveComponentState::DisconnectClientsAndTeardown, base::Unretained(component_state.get())); return component_state; } return nullptr; } base::test::SingleThreadTaskEnvironment task_environment_{ base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; sys::OutgoingDirectory services_; std::unique_ptr services_client_; std::unique_ptr agent_impl_; // Set only if a keep-alive component was connected, to allow the test to // forcibly teardown the ComponentState for it. base::OnceClosure disconnect_clients_and_teardown_; DISALLOW_COPY_AND_ASSIGN(AgentImplTest); }; } // namespace // Verify that the Agent can publish and unpublish itself. TEST_F(AgentImplTest, PublishAndUnpublish) { cr_fuchsia::ResultReceiver client_disconnect_status1; fuchsia::modular::AgentPtr agent = CreateAgentAndConnect(); agent.set_error_handler(cr_fuchsia::CallbackToFitFunction( client_disconnect_status1.GetReceiveCallback())); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(client_disconnect_status1.has_value()); // Teardown the Agent. agent_impl_.reset(); // Verify that the client got disconnected on teardown. base::RunLoop().RunUntilIdle(); EXPECT_EQ(*client_disconnect_status1, ZX_ERR_PEER_CLOSED); // Verify that the Agent service is no longer available. cr_fuchsia::ResultReceiver client_disconnect_status2; services_client_->Connect(agent.NewRequest()); agent.set_error_handler(cr_fuchsia::CallbackToFitFunction( client_disconnect_status2.GetReceiveCallback())); base::RunLoop().RunUntilIdle(); EXPECT_EQ(*client_disconnect_status2, ZX_ERR_PEER_CLOSED); } // Verify that multiple connection attempts with the different component Ids // to the same service get different instances. TEST_F(AgentImplTest, DifferentComponentIdSameService) { fuchsia::modular::AgentPtr agent = CreateAgentAndConnect(); // Connect to the Agent twice using the same component Id. fuchsia::sys::ServiceProviderPtr component_services1; agent->Connect(kAccumulatorComponentId1, component_services1.NewRequest()); fuchsia::sys::ServiceProviderPtr component_services2; agent->Connect(kAccumulatorComponentId2, component_services2.NewRequest()); // Request the TestInterface from each of the service directories. base::testfidl::TestInterfacePtr test_interface1; component_services1->ConnectToService( base::testfidl::TestInterface::Name_, test_interface1.NewRequest().TakeChannel()); base::testfidl::TestInterfacePtr test_interface2; component_services2->ConnectToService( base::testfidl::TestInterface::Name_, test_interface2.NewRequest().TakeChannel()); // Both TestInterface pointers should remain valid until we are done. test_interface1.set_error_handler([](zx_status_t status) { ZX_LOG(ERROR, status); ADD_FAILURE(); }); test_interface2.set_error_handler([](zx_status_t status) { ZX_LOG(ERROR, status); ADD_FAILURE(); }); // Call Add() via one TestInterface and verify accumulator had been at zero. { base::RunLoop loop; test_interface1->Add(1, 2, [quit_loop = loop.QuitClosure()](int32_t result) { EXPECT_EQ(result, 3); quit_loop.Run(); }); loop.RunUntilIdle(); } // Call Add() via the second TestInterface, and verify that first Add() call's // effects aren't visible. { base::RunLoop loop; test_interface2->Add(3, 4, [quit_loop = loop.QuitClosure()](int32_t result) { EXPECT_EQ(result, 7); quit_loop.Run(); }); loop.RunUntilIdle(); } // Cleanly unbind the test interfaces now that we're done with them. test_interface1 = nullptr; test_interface2 = nullptr; // Tear down connections to the agent and let the error handlers unwind. { base::RunLoop loop; component_services1.Unbind(); component_services2.Unbind(); loop.RunUntilIdle(); } } // Verify that multiple connection attempts with the same component Id connect // it to the same service instances. TEST_F(AgentImplTest, SameComponentIdSameService) { fuchsia::modular::AgentPtr agent = CreateAgentAndConnect(); // Connect to the Agent twice using the same component Id. fuchsia::sys::ServiceProviderPtr component_services1; agent->Connect(kAccumulatorComponentId1, component_services1.NewRequest()); fuchsia::sys::ServiceProviderPtr component_services2; agent->Connect(kAccumulatorComponentId1, component_services2.NewRequest()); // Request the TestInterface from each of the service directories. base::testfidl::TestInterfacePtr test_interface1; component_services1->ConnectToService( base::testfidl::TestInterface::Name_, test_interface1.NewRequest().TakeChannel()); base::testfidl::TestInterfacePtr test_interface2; component_services2->ConnectToService( base::testfidl::TestInterface::Name_, test_interface2.NewRequest().TakeChannel()); // Both TestInterface pointers should remain valid until we are done. test_interface1.set_error_handler([](zx_status_t status) { ZX_LOG(ERROR, status); ADD_FAILURE(); }); test_interface2.set_error_handler([](zx_status_t status) { ZX_LOG(ERROR, status); ADD_FAILURE(); }); // Call Add() via one TestInterface and verify accumulator had been at zero. { base::RunLoop loop; test_interface1->Add(1, 2, [quit_loop = loop.QuitClosure()](int32_t result) { EXPECT_EQ(result, 3); quit_loop.Run(); }); loop.RunUntilIdle(); } // Call Add() via the other TestInterface, and verify that the result of the // previous Add() was already in the accumulator. { base::RunLoop loop; test_interface2->Add(3, 4, [quit_loop = loop.QuitClosure()](int32_t result) { EXPECT_EQ(result, 10); quit_loop.Run(); }); loop.RunUntilIdle(); } // Cleanly unbind the test interfaces now that we're done with them. test_interface1 = nullptr; test_interface2 = nullptr; // Tear down connections to the agent and let the error handlers unwind. { base::RunLoop loop; component_services1.Unbind(); component_services2.Unbind(); loop.RunUntilIdle(); } } // Verify that connections to a service registered to keep-alive the // ComponentStateBase keeps it alive after the ServiceProvider is dropped. TEST_F(AgentImplTest, KeepAliveBinding) { fuchsia::modular::AgentPtr agent = CreateAgentAndConnect(); { // Connect to the Agent and request the TestInterface. fuchsia::sys::ServiceProviderPtr component_services; agent->Connect(kAccumulatorComponentId1, component_services.NewRequest()); base::testfidl::TestInterfacePtr test_interface; component_services->ConnectToService( base::testfidl::TestInterface::Name_, test_interface.NewRequest().TakeChannel()); // The TestInterface pointer should be closed as soon as we Unbind() the // ServiceProvider. test_interface.set_error_handler( [](zx_status_t status) { EXPECT_EQ(status, ZX_ERR_PEER_CLOSED); }); // After disconnecting ServiceProvider, TestInterface should remain valid. component_services.Unbind(); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(test_interface); } { // Connect to the Agent and request the TestInterface. fuchsia::sys::ServiceProviderPtr component_services; agent->Connect(kKeepAliveComponentId, component_services.NewRequest()); base::testfidl::TestInterfacePtr test_interface; component_services->ConnectToService( base::testfidl::TestInterface::Name_, test_interface.NewRequest().TakeChannel()); // The TestInterface pointer should remain valid until we are done. test_interface.set_error_handler([](zx_status_t status) { ZX_LOG(ERROR, status); ADD_FAILURE(); }); // After disconnecting ServiceProvider, TestInterface should remain valid. component_services.Unbind(); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(test_interface); } // Spin the MessageLoop to let the AgentImpl see that TestInterface is gone. base::RunLoop().RunUntilIdle(); } // Verify that connections to a service registered to keep-alive the // ComponentStateBase is disconnected by DisconnectClientsAndTeardown. TEST_F(AgentImplTest, DisconnectClientsAndTeardown) { fuchsia::modular::AgentPtr agent = CreateAgentAndConnect(); { // Connect to the Agent and request the TestInterface. fuchsia::sys::ServiceProviderPtr component_services; agent->Connect(kKeepAliveComponentId, component_services.NewRequest()); base::testfidl::TestInterfacePtr test_interface; component_services->ConnectToService( base::testfidl::TestInterface::Name_, test_interface.NewRequest().TakeChannel()); // The TestInterface pointer should remain valid until we call // DisconnectClientsAndTeardown(). test_interface.set_error_handler( [](zx_status_t status) { ZX_LOG_IF(ERROR, status != ZX_OK, status); }); // After disconnecting ServiceProvider, TestInterface should remain valid. component_services.Unbind(); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(test_interface); // After invoking DisconnectClientsAndTeardown(), TestInterface should // be disconnected. TeardownKeepAliveComponentState(); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(test_interface); } } } // namespace cr_fuchsia