| // Copyright 2019 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/modular/session/cpp/fidl.h> | |
| #include <fuchsia/modular/testing/cpp/fidl.h> | |
| #include <fuchsia/testing/modular/cpp/fidl.h> | |
| #include <lib/vfs/cpp/composed_service_dir.h> | |
| #include <lib/vfs/cpp/pseudo_dir.h> | |
| #include <gmock/gmock.h> | |
| #include <sdk/lib/modular/testing/cpp/fake_agent.h> | |
| #include "src/modular/lib/modular_test_harness/cpp/fake_module.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/fake_session_shell.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/fake_story_shell.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/test_harness_fixture.h" | |
| #include "src/modular/lib/pseudo_dir/pseudo_dir_server.h" | |
| namespace { | |
| const std::string kTestAgentUrl("fuchsia-pkg://fuchsia.com/fake_agent#meta/fake_agent.cmx"); | |
| const std::string kTestServiceName(fuchsia::testing::modular::TestProtocol::Name_); | |
| // Configuration for testing |ComponentContext| DeprecatedConnectToAgentService(). | |
| struct ConnectToAgentServiceTestConfig { | |
| // The map of |service_name|->|agent_url| used to look up a service | |
| // handler |agent_url| by name. | |
| std::map<std::string, std::string> service_to_agent_map; | |
| // The session agents to pass to the modular configuration file. | |
| std::vector<std::string> session_agents; | |
| // If true, include the service_name in the |AgentServiceRequest|. | |
| // This is required for a successful connection. | |
| bool provide_service_name = false; | |
| // If true, include the specific handler (agent URL) in the | |
| // |AgentServiceRequest|. This is *not* required for a successful connection. | |
| bool provide_handler = false; | |
| // If true, include the service client-side channel in the | |
| // |AgentServiceRequest|. This is required for a successful connection. | |
| bool provide_channel = false; | |
| // If true, include the |AgentController| in the |AgentServiceRequest|. | |
| // This is required for a successful connection. | |
| bool provide_agent_controller = false; | |
| template <typename Interface> | |
| fuchsia::modular::AgentServiceRequest MakeAgentServiceRequest( | |
| std::string service_name, Interface service_request, | |
| fidl::InterfaceRequest<fuchsia::modular::AgentController> agent_controller) { | |
| fuchsia::modular::AgentServiceRequest agent_service_request; | |
| if (provide_service_name) { | |
| agent_service_request.set_service_name(service_name); | |
| } | |
| if (provide_handler) { | |
| agent_service_request.set_handler(kTestAgentUrl); | |
| } | |
| if (provide_channel) { | |
| agent_service_request.set_channel(service_request.TakeChannel()); | |
| } | |
| if (provide_agent_controller) { | |
| agent_service_request.set_agent_controller(std::move(agent_controller)); | |
| } | |
| return agent_service_request; | |
| } | |
| }; | |
| class AgentServicesTest : public modular_testing::TestHarnessFixture { | |
| protected: | |
| AgentServicesTest() : fake_agent_(modular_testing::FakeAgent::CreateWithDefaultOptions()) {} | |
| fuchsia::modular::ComponentContextPtr StartTestHarness( | |
| ConnectToAgentServiceTestConfig test_config) { | |
| fuchsia::modular::testing::TestHarnessSpec spec; | |
| std::vector<fuchsia::modular::session::AgentServiceIndexEntry> agent_service_index; | |
| for (const auto& entry : test_config.service_to_agent_map) { | |
| fuchsia::modular::session::AgentServiceIndexEntry agent_service; | |
| agent_service.set_service_name(entry.first); | |
| agent_service.set_agent_url(entry.second); | |
| agent_service_index.emplace_back(std::move(agent_service)); | |
| } | |
| spec.mutable_sessionmgr_config()->set_agent_service_index(std::move(agent_service_index)); | |
| spec.mutable_sessionmgr_config()->set_session_agents(std::move(test_config.session_agents)); | |
| fuchsia::modular::testing::InterceptSpec intercept_spec; | |
| intercept_spec.set_component_url(kTestAgentUrl); | |
| spec.mutable_components_to_intercept()->push_back(std::move(intercept_spec)); | |
| test_harness().events().OnNewComponent = | |
| [this](fuchsia::sys::StartupInfo startup_info, | |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> component) { | |
| ASSERT_EQ(startup_info.launch_info.url, kTestAgentUrl); | |
| fake_agent_->BuildInterceptOptions().launch_handler(std::move(startup_info), | |
| component.Bind()); | |
| }; | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)> | |
| service_handler = | |
| [this](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| if (fake_agent_service_handler_) { | |
| fake_agent_service_handler_(std::move(request)); | |
| } | |
| }; | |
| fake_agent_->AddAgentService(std::move(service_handler)); | |
| test_harness()->Run(std::move(spec)); | |
| fuchsia::modular::ComponentContextPtr component_context; | |
| fuchsia::modular::testing::ModularService modular_service; | |
| modular_service.set_component_context(component_context.NewRequest()); | |
| test_harness()->ConnectToModularService(std::move(modular_service)); | |
| return component_context; | |
| } | |
| // Called by test functions to invoke ConnectToAgentService with various input configurations. | |
| // | |
| // |test_config| Input configurations and setup options. | |
| zx_status_t ExecuteConnectToAgentServiceTest(ConnectToAgentServiceTestConfig test_config) { | |
| fuchsia::modular::ComponentContextPtr component_context = StartTestHarness(test_config); | |
| // Client-side service pointer | |
| fuchsia::testing::modular::TestProtocolPtr service_ptr; | |
| auto service_name = kTestServiceName; | |
| auto service_request = service_ptr.NewRequest(); | |
| zx_status_t service_status = ZX_OK; | |
| bool service_terminated = false; | |
| service_ptr.set_error_handler([&](zx_status_t status) { | |
| service_terminated = true; | |
| service_status = status; | |
| }); | |
| bool got_request = false; | |
| fake_agent_service_handler_ = | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| got_request = true; | |
| }; | |
| fuchsia::modular::AgentControllerPtr agent_controller; | |
| auto agent_service_request = test_config.MakeAgentServiceRequest( | |
| service_name, std::move(service_request), agent_controller.NewRequest()); | |
| component_context->DeprecatedConnectToAgentService(std::move(agent_service_request)); | |
| RunLoopUntil([&] { return got_request || service_terminated; }); | |
| fake_agent_service_handler_ = nullptr; // Callback references local variables. | |
| // Speed up teardown of the test by eagerly terminating the fake agent. | |
| fake_agent_->Exit(0); | |
| // If we got the service request, then routing of the agent service request was successful, even | |
| // though at this point we have already closed the service channel. | |
| if (got_request) { | |
| return ZX_OK; | |
| } else { | |
| EXPECT_NE(service_status, ZX_OK); | |
| } | |
| return service_status; | |
| } | |
| std::unique_ptr<modular_testing::FakeAgent> fake_agent_; | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)> | |
| fake_agent_service_handler_; | |
| }; | |
| // Ensure Session Manager's ConnectToAgentService can successfully find an | |
| // agent for a given session name, and connect to that agent's service. | |
| TEST_F(AgentServicesTest, ValidAndSuccessfulOneEntry) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| test_config.service_to_agent_map = { | |
| {kTestServiceName, kTestAgentUrl}, | |
| }; | |
| EXPECT_EQ(ZX_OK, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Find agent and service successfully among multiple index entries. | |
| TEST_F(AgentServicesTest, ValidAndSuccessfulMultipleEntries) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| test_config.service_to_agent_map = { | |
| {"somenamespace.someappspace.SomeService", | |
| "fuchsia-pkg://fuchsia.com/some_agent#meta/some_agent.cmx"}, | |
| {kTestServiceName, kTestAgentUrl}, | |
| {"fuchsia.anotherappspace.AnotherService", | |
| "fuchsia-pkg://fuchsia.com/another_agent#meta/another_agent.cmx"}, | |
| }; | |
| EXPECT_EQ(ZX_OK, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Find service successfully, from a specific handler. The index specifies this | |
| // agent as the default handler, but should not be necessary. | |
| // | |
| // The handler must be a session agent. | |
| TEST_F(AgentServicesTest, SpecificHandlerProvidedHasService) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_handler = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| test_config.session_agents = {kTestAgentUrl}; | |
| test_config.service_to_agent_map = { | |
| {"somenamespace.someappspace.SomeService", | |
| "fuchsia-pkg://fuchsia.com/some_agent#meta/some_agent.cmx"}, | |
| {kTestServiceName, kTestAgentUrl}, | |
| {"fuchsia.anotherappspace.AnotherService", | |
| "fuchsia-pkg://fuchsia.com/another_agent#meta/another_agent.cmx"}, | |
| }; | |
| EXPECT_EQ(ZX_OK, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Find service successfully, from a specific handler. The index does not | |
| // include the requested service, but it should not be needed since the | |
| // handler is specified. | |
| TEST_F(AgentServicesTest, SpecificHandlerProvidedHasServiceButNotInIndex) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_handler = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| test_config.session_agents = {kTestAgentUrl}; | |
| test_config.service_to_agent_map = { | |
| {"somenamespace.someappspace.SomeService", | |
| "fuchsia-pkg://fuchsia.com/some_agent#meta/some_agent.cmx"}, | |
| {"fuchsia.anotherappspace.AnotherService", | |
| "fuchsia-pkg://fuchsia.com/another_agent#meta/another_agent.cmx"}, | |
| }; | |
| EXPECT_EQ(ZX_OK, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Find service successfully, from a specific handler. The index specifies | |
| // a different agent as the handler, but that agent should not be used since | |
| // a specific agent was specified. | |
| TEST_F(AgentServicesTest, SpecificHandlerProvidedHasServiceButIndexHasDifferentHandler) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_handler = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| test_config.session_agents = {kTestAgentUrl}; | |
| test_config.service_to_agent_map = { | |
| {kTestServiceName, "fuchsia-pkg://fuchsia.com/some_agent#meta/some_agent.cmx"}, | |
| {"fuchsia.anotherappspace.AnotherService", | |
| "fuchsia-pkg://fuchsia.com/another_agent#meta/another_agent.cmx"}, | |
| }; | |
| EXPECT_EQ(ZX_OK, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Bad request | |
| TEST_F(AgentServicesTest, NoServiceNameProvided) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| // test_config.provide_service_name = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| test_config.service_to_agent_map = { | |
| {"fuchsia.anotherappspace.AnotherService", | |
| "fuchsia-pkg://fuchsia.com/another_agent#meta/another_agent.cmx"}, | |
| }; | |
| EXPECT_EQ(ZX_ERR_PEER_CLOSED, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Bad request | |
| TEST_F(AgentServicesTest, NoChannelProvided) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| // test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| EXPECT_EQ(ZX_ERR_PEER_CLOSED, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Attempt to look up the agent based on the service name with no agent | |
| // controller, but it is not in the index. | |
| TEST_F(AgentServicesTest, NoAgentControllerProvided) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_channel = true; | |
| // test_config.provide_agent_controller = true; | |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| // Attempt to look up the agent based on the service name, but it is not in | |
| // the index. | |
| TEST_F(AgentServicesTest, NoHandlerForService) { | |
| ConnectToAgentServiceTestConfig test_config; | |
| test_config.provide_service_name = true; | |
| test_config.provide_channel = true; | |
| test_config.provide_agent_controller = true; | |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, ExecuteConnectToAgentServiceTest(test_config)); | |
| } | |
| class AgentServicesSFWCompatTest : public modular_testing::TestHarnessFixture { | |
| protected: | |
| AgentServicesSFWCompatTest() {} | |
| fuchsia::modular::testing::TestHarnessSpec CreateSpecWithAgentServiceIndex( | |
| std::map<std::string, std::string> agent_service_index_map) { | |
| fuchsia::modular::testing::TestHarnessSpec spec; | |
| std::vector<fuchsia::modular::session::AgentServiceIndexEntry> agent_service_index; | |
| for (const auto& entry : agent_service_index_map) { | |
| fuchsia::modular::session::AgentServiceIndexEntry agent_service; | |
| agent_service.set_service_name(entry.first); | |
| agent_service.set_agent_url(entry.second); | |
| agent_service_index.emplace_back(std::move(agent_service)); | |
| } | |
| spec.mutable_sessionmgr_config()->set_agent_service_index(std::move(agent_service_index)); | |
| return spec; | |
| } | |
| modular_testing::TestHarnessBuilder::InterceptOptions AddSandboxServices( | |
| std::vector<std::string> service_names, | |
| modular_testing::TestHarnessBuilder::InterceptOptions options) { | |
| for (auto service_name : service_names) { | |
| options.sandbox_services.push_back(service_name); | |
| } | |
| return options; | |
| } | |
| }; | |
| // A version of FakeComponent, behaviorally similar to FakeAgent, with the added behavior of | |
| // capturing the `requestor_url` parameter of Agent.Connect() calls and exposing them through | |
| // `requestor_urls()`. | |
| class RequestorIdCapturingAgent : public modular_testing::FakeComponent, | |
| fuchsia::modular::Agent, | |
| fuchsia::sys::ServiceProvider { | |
| public: | |
| RequestorIdCapturingAgent(modular_testing::FakeComponent::Args args) | |
| : FakeComponent(std::move(args)) {} | |
| static std::unique_ptr<RequestorIdCapturingAgent> CreateWithDefaultOptions() { | |
| return std::make_unique<RequestorIdCapturingAgent>(modular_testing::FakeComponent::Args{ | |
| .url = modular_testing::TestHarnessBuilder::GenerateFakeUrl()}); | |
| } | |
| std::vector<std::string> requestor_urls() const { return requestor_urls_; } | |
| template <typename Interface> | |
| void AddAgentService(fidl::InterfaceRequestHandler<Interface> handler) { | |
| service_name_to_handler_[Interface::Name_] = [handler = std::move(handler)](zx::channel req) { | |
| handler(fidl::InterfaceRequest<Interface>(std::move(req))); | |
| }; | |
| } | |
| template <typename Interface> | |
| void AddPublicService(fidl::InterfaceRequestHandler<Interface> handler) { | |
| buffered_add_service_calls_.push_back([this, handler = std::move(handler)]() mutable { | |
| component_context()->outgoing()->AddPublicService(std::move(handler)); | |
| }); | |
| FlushAddServiceCallsIfRunning(); | |
| } | |
| protected: | |
| // |modular_testing::FakeComponent| | |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) { | |
| component_context()->outgoing()->AddPublicService<fuchsia::modular::Agent>( | |
| agent_bindings_.GetHandler(this)); | |
| FlushAddServiceCallsIfRunning(); | |
| } | |
| // |fuchsia::modular::Agent| | |
| void Connect( | |
| std::string requestor_id, | |
| fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> outgoing_services_request) override { | |
| requestor_urls_.push_back(requestor_id); | |
| agent_service_provider_bindings_.AddBinding(this, std::move(outgoing_services_request)); | |
| } | |
| // |fuchsia::sys::ServiceProvider| | |
| void ConnectToService(std::string service_name, zx::channel request) override { | |
| auto it = service_name_to_handler_.find(service_name); | |
| if (it != service_name_to_handler_.end()) { | |
| it->second(std::move(request)); | |
| } | |
| } | |
| void FlushAddServiceCallsIfRunning() { | |
| if (is_running()) { | |
| for (auto& call : buffered_add_service_calls_) { | |
| call(); | |
| } | |
| buffered_add_service_calls_.clear(); | |
| } | |
| } | |
| std::vector<std::string> requestor_urls_; | |
| fidl::BindingSet<fuchsia::modular::Agent> agent_bindings_; | |
| fidl::BindingSet<fuchsia::sys::ServiceProvider> agent_service_provider_bindings_; | |
| // A mapping of `service name -> service connection handle`. | |
| std::unordered_map<std::string, fit::function<void(zx::channel)>> service_name_to_handler_; | |
| std::vector<fit::closure> buffered_add_service_calls_; | |
| }; // namespace | |
| // Test that an Agent service can be acquired from any of another Agent, a Module, Session or Story | |
| // Shells, including testing that calls to the Agent.Connect() method (implemented by the agent) | |
| // result in the correct requestor ids, even if those clients connect via their environment. | |
| TEST_F(AgentServicesSFWCompatTest, ConnectToService_Success) { | |
| auto serving_agent = RequestorIdCapturingAgent::CreateWithDefaultOptions(); | |
| // Intercept the following components in order to test their access to agent services | |
| // through their respective environments. | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| auto session_shell = modular_testing::FakeSessionShell::CreateWithDefaultOptions(); | |
| auto story_shell = modular_testing::FakeStoryShell::CreateWithDefaultOptions(); | |
| auto module = modular_testing::FakeModule::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`. | |
| auto spec = CreateSpecWithAgentServiceIndex( | |
| {{fuchsia::testing::modular::TestProtocol::Name_, serving_agent->url()}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(serving_agent->BuildInterceptOptions()); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| builder.InterceptSessionShell(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| session_shell->BuildInterceptOptions())); | |
| builder.InterceptStoryShell(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| story_shell->BuildInterceptOptions())); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| module->BuildInterceptOptions())); | |
| // Instruct `serving_agent` to serve the TestProtocol, tracking the number of times | |
| // the service was successfully connected. | |
| int num_connections = 0; | |
| std::vector<zx::channel> protocol_requests; | |
| serving_agent->AddAgentService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| ++num_connections; | |
| protocol_requests.push_back(request.TakeChannel()); | |
| })); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running() && session_shell->is_running(); }); | |
| ASSERT_FALSE(serving_agent->is_running()); | |
| // Create a story so that the story shell and module are both run. | |
| fuchsia::modular::Intent intent; | |
| intent.handler = module->url(); | |
| intent.action = "action"; | |
| modular_testing::AddModToStory(test_harness(), "storyName", "modName", std::move(intent)); | |
| RunLoopUntil([&] { return story_shell->is_running() && module->is_running(); }); | |
| // Attempt to connect to the test service from all of our different components. | |
| std::vector<fuchsia::testing::modular::TestProtocolPtr> protocol_ptrs; | |
| protocol_ptrs.push_back( | |
| agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>()); | |
| protocol_ptrs.push_back(session_shell->component_context() | |
| ->svc() | |
| ->Connect<fuchsia::testing::modular::TestProtocol>()); | |
| protocol_ptrs.push_back( | |
| story_shell->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>()); | |
| protocol_ptrs.push_back( | |
| module->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>()); | |
| // Track the number of those connection attempts failed. | |
| int num_errors = 0; | |
| for (auto& ptr : protocol_ptrs) { | |
| ptr.set_error_handler([&](zx_status_t) { ++num_errors; }); | |
| } | |
| constexpr int kTotalRequests = 4; | |
| RunLoopUntil([&] { return num_connections + num_errors == kTotalRequests; }); | |
| EXPECT_TRUE(serving_agent->is_running()); | |
| EXPECT_EQ(num_connections, kTotalRequests); | |
| EXPECT_EQ(num_errors, 0); | |
| EXPECT_THAT(serving_agent->requestor_urls(), | |
| testing::UnorderedElementsAre(agent->url(), session_shell->url(), story_shell->url(), | |
| /* `module` path */ "modName")); | |
| } | |
| // Test that when a component tries to connect to a service through its environment, | |
| // but the agent that serves that service can't be launched, an error is returned. | |
| TEST_F(AgentServicesSFWCompatTest, ConnectToService_FailAgentNotPresent) { | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`, but | |
| // under a name that will not match when `agent` tries to connect. | |
| auto spec = CreateSpecWithAgentServiceIndex( | |
| {{fuchsia::testing::modular::TestProtocol::Name_, "fuchsia-pkg://fuchsia.com/not/found"}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running(); }); | |
| // Attempt to connect to the test service. | |
| bool saw_error = false; | |
| zx_status_t status = ZX_OK; | |
| auto ptr = agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>(); | |
| ptr.set_error_handler([&](zx_status_t s) { | |
| saw_error = true; | |
| status = s; | |
| }); | |
| RunLoopUntil([&] { return saw_error; }); | |
| // appmgr / sysmgr result in a peer closed error. | |
| EXPECT_EQ(status, ZX_ERR_PEER_CLOSED); | |
| } | |
| // Test that when a component tries to connect to a service through its environment, | |
| // but that service is not available, the client encounters an error. | |
| TEST_F(AgentServicesSFWCompatTest, ConnectToService_FailNoAgentMapping) { | |
| auto serving_agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`, but | |
| // under a name that will not match when `agent` tries to connect. | |
| auto spec = | |
| CreateSpecWithAgentServiceIndex({{"fuchsia.testing.modular.NotFound", serving_agent->url()}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(serving_agent->BuildInterceptOptions()); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| // Instruct `serving_agent` to serve the TestProtocol. | |
| serving_agent->AddAgentService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| FAIL() << "Did not expect service connection request to reach the agent."; | |
| })); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running(); }); | |
| ASSERT_FALSE(serving_agent->is_running()); | |
| // Attempt to connect to the test service. | |
| bool saw_error = false; | |
| zx_status_t status = ZX_OK; | |
| auto ptr = agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>(); | |
| ptr.set_error_handler([&](zx_status_t s) { | |
| saw_error = true; | |
| status = s; | |
| }); | |
| RunLoopUntil([&] { return saw_error; }); | |
| EXPECT_FALSE(serving_agent->is_running()); | |
| // appmgr / sysmgr result in a peer closed error. | |
| EXPECT_EQ(status, ZX_ERR_PEER_CLOSED); | |
| } | |
| // Test that an agent can publish its services using its outgoing directory, and that clients | |
| // can connect to those services through either ComponentContext.ConnectToAgent*() or | |
| // sys.ComponentContext.srv().Connect(). | |
| TEST_F(AgentServicesSFWCompatTest, PublishToOutgoingDirectory) { | |
| auto serving_agent = RequestorIdCapturingAgent::CreateWithDefaultOptions(); | |
| // Intercept this agent and use it as a client to connect to `serving_agent`. | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`. | |
| auto spec = CreateSpecWithAgentServiceIndex( | |
| {{fuchsia::testing::modular::TestProtocol::Name_, serving_agent->url()}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(serving_agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(serving_agent->BuildInterceptOptions()); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| // Instruct `serving_agent` to serve the TestProtocol, tracking the number of times | |
| // the service was successfully connected. | |
| int num_connections = 0; | |
| std::vector<zx::channel> protocol_requests; | |
| // Note that TestProtocol is being served using a sys.OutgoingDirectory. | |
| serving_agent->AddPublicService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| ++num_connections; | |
| protocol_requests.push_back(request.TakeChannel()); | |
| })); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running(); }); | |
| // Attempt to connect to the test service in all ways that are currently supported. | |
| std::vector<fuchsia::testing::modular::TestProtocolPtr> protocol_ptrs; | |
| std::vector<fuchsia::modular::AgentControllerPtr> agent_controllers; | |
| // Method 1: Connect using `agent`'s incoming directory | |
| protocol_ptrs.push_back( | |
| agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>()); | |
| // Method 2: Connect using fuchsia.modular.ComponentContext/DeprecatedConnectToAgentService(). | |
| fuchsia::modular::AgentServiceRequest agent_service_request; | |
| protocol_ptrs.emplace_back(); | |
| agent_controllers.emplace_back(); | |
| agent_service_request.set_service_name(fuchsia::testing::modular::TestProtocol::Name_); | |
| agent_service_request.set_channel(protocol_ptrs.back().NewRequest().TakeChannel()); | |
| agent_service_request.set_agent_controller(agent_controllers.back().NewRequest()); | |
| agent_service_request.set_handler(serving_agent->url()); | |
| agent->modular_component_context()->DeprecatedConnectToAgentService( | |
| std::move(agent_service_request)); | |
| // Track the number of those connection attempts failed. | |
| int num_errors = 0; | |
| for (auto& ptr : protocol_ptrs) { | |
| ptr.set_error_handler([&](zx_status_t) { ++num_errors; }); | |
| } | |
| constexpr int kTotalRequests = 2; | |
| RunLoopUntil([&] { return num_connections + num_errors == kTotalRequests; }); | |
| EXPECT_TRUE(serving_agent->is_running()); | |
| EXPECT_EQ(num_connections, kTotalRequests); | |
| EXPECT_EQ(num_errors, 0); | |
| } | |
| // If an agent exposes a service via both its outgoing directory and through fuchsia.modular.Agent, | |
| // prefer the outgoing directory. | |
| TEST_F(AgentServicesSFWCompatTest, PublishToOugoingDirectoryPrioritizesOutoingDirectory) { | |
| auto serving_agent = RequestorIdCapturingAgent::CreateWithDefaultOptions(); | |
| // Intercept this agent and use it as a client to connect to `serving_agent`. | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`. | |
| auto spec = CreateSpecWithAgentServiceIndex( | |
| {{fuchsia::testing::modular::TestProtocol::Name_, serving_agent->url()}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(serving_agent->BuildInterceptOptions()); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| // Publish the service as both an outgoing/public service and an agent service. | |
| bool saw_agent_connection = false; | |
| bool saw_outgoing_connection = false; | |
| serving_agent->AddAgentService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| saw_agent_connection = true; | |
| })); | |
| serving_agent->AddPublicService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| saw_outgoing_connection = true; | |
| })); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running(); }); | |
| ASSERT_FALSE(serving_agent->is_running()); | |
| auto protocol_ptr = | |
| agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>(); | |
| RunLoopUntil([&] { return saw_agent_connection || saw_outgoing_connection; }); | |
| EXPECT_TRUE(saw_outgoing_connection); | |
| EXPECT_FALSE(saw_agent_connection); | |
| } | |
| class NoAgentProtocolAgent : public RequestorIdCapturingAgent { | |
| public: | |
| NoAgentProtocolAgent(modular_testing::FakeComponent::Args args) | |
| : RequestorIdCapturingAgent(std::move(args)) {} | |
| static std::unique_ptr<NoAgentProtocolAgent> CreateWithDefaultOptions() { | |
| return std::make_unique<NoAgentProtocolAgent>(modular_testing::FakeComponent::Args{ | |
| .url = modular_testing::TestHarnessBuilder::GenerateFakeUrl()}); | |
| } | |
| private: | |
| // |modular_testing::FakeComponent| | |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) { | |
| // Don't publish the fuchsia.modular.Agent service! | |
| FlushAddServiceCallsIfRunning(); | |
| } | |
| }; | |
| // Test that an agent can still serve through its outgoing directory even if it does *not* publish | |
| // the fuchsia.modular.Agent protocol at all. | |
| TEST_F(AgentServicesSFWCompatTest, PublishToOutgoingDirectoryStillWorksWithoutAgentProtocol) { | |
| auto serving_agent = NoAgentProtocolAgent::CreateWithDefaultOptions(); | |
| // Intercept this agent and use it as a client to connect to `serving_agent`. | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`. | |
| auto spec = CreateSpecWithAgentServiceIndex( | |
| {{fuchsia::testing::modular::TestProtocol::Name_, serving_agent->url()}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(serving_agent->BuildInterceptOptions()); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| // Publish the service as an outgoing/public service. | |
| bool saw_outgoing_connection = false; | |
| serving_agent->AddPublicService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| saw_outgoing_connection = true; | |
| })); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running(); }); | |
| ASSERT_FALSE(serving_agent->is_running()); | |
| auto protocol_ptr = | |
| agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>(); | |
| RunLoopUntil([&] { return saw_outgoing_connection; }); | |
| } | |
| class BuggyOutgoingDirAgent { | |
| public: | |
| BuggyOutgoingDirAgent(modular_testing::FakeComponent::Args args) | |
| : args_(std::move(args)), hidden_service_dir_(new vfs::PseudoDir()) {} | |
| static std::unique_ptr<BuggyOutgoingDirAgent> CreateWithDefaultOptions() { | |
| return std::make_unique<BuggyOutgoingDirAgent>(modular_testing::FakeComponent::Args{ | |
| .url = modular_testing::TestHarnessBuilder::GenerateFakeUrl()}); | |
| } | |
| modular_testing::TestHarnessBuilder::InterceptOptions BuildInterceptOptions( | |
| async_dispatcher_t* dispatcher = nullptr) { | |
| modular_testing::TestHarnessBuilder::InterceptOptions options; | |
| options.url = args_.url; | |
| options.sandbox_services = args_.sandbox_services; | |
| options.launch_handler = | |
| [this, dispatcher](fuchsia::sys::StartupInfo startup_info, | |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> | |
| intercepted_component) { | |
| intercepted_component_ptr_.Bind(std::move(intercepted_component), dispatcher); | |
| intercepted_component_ptr_.events().OnKill = [this] { OnDestroy(); }; | |
| OnCreate(std::move(startup_info)); | |
| }; | |
| return options; | |
| } | |
| template <typename Interface> | |
| void AddPublicService(fidl::InterfaceRequestHandler<Interface> handler) { | |
| hidden_service_dir_->AddEntry(Interface::Name_, | |
| std::make_unique<vfs::Service>(std::move(handler))); | |
| } | |
| bool is_running() { return is_running_; } | |
| const std::string& url() { return args_.url; } | |
| protected: | |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) { | |
| // Configure a ComposedServiceDir which exhibits fxbug.dev/55769. | |
| // | |
| // outgoing_dir (type: PseudoDir, serves: LaunchInfo.directory_request) | |
| // svc/ -> composed_service_dir (type: ComposedServiceDir) | |
| // with fallback: hidden_service_dir_ (type: PseudoDir) | |
| fuchsia::io::DirectoryPtr hidden_service_dir_ptr; | |
| hidden_service_dir_->Serve(fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE | | |
| fuchsia::io::OPEN_FLAG_DIRECTORY, | |
| hidden_service_dir_ptr.NewRequest().TakeChannel()); | |
| auto composed_service_dir = std::make_unique<vfs::ComposedServiceDir>(); | |
| composed_service_dir->set_fallback(std::move(hidden_service_dir_ptr)); | |
| auto outgoing_dir = std::make_unique<vfs::PseudoDir>(); | |
| outgoing_dir->AddEntry("svc", std::move(composed_service_dir)); | |
| outgoing_dir_server_ = std::make_unique<modular::PseudoDirServer>(std::move(outgoing_dir)); | |
| outgoing_dir_server_->Serve(std::move(startup_info.launch_info.directory_request)); | |
| is_running_ = true; | |
| } | |
| void OnDestroy() { is_running_ = false; } | |
| modular_testing::FakeComponent::Args args_; | |
| std::unique_ptr<modular::PseudoDirServer> outgoing_dir_server_; | |
| std::unique_ptr<vfs::PseudoDir> hidden_service_dir_; | |
| fuchsia::modular::testing::InterceptedComponentPtr intercepted_component_ptr_; | |
| bool is_running_ = false; | |
| }; | |
| // Test that an agent that: | |
| // * Does NOT publish fuchsia.modular.Agent | |
| // * Serves an outgoing directory implementation with a bug (fxbug.dev/55769) | |
| // resulting in it not returning directory entries from ReadDirents() | |
| // * But still serves a service in that directory | |
| // | |
| // will result in successful connections to the service. | |
| // | |
| // TODO(fxbug.dev/55769): Once fixed, remove this test and the class BuggyOutgoingDirAgent. | |
| TEST_F(AgentServicesSFWCompatTest, PublishToBuggyOutgoingDirectoryStillWorksWithoutAgentProtocol) { | |
| auto serving_agent = BuggyOutgoingDirAgent::CreateWithDefaultOptions(); | |
| // Intercept this agent and use it as a client to connect to `serving_agent`. | |
| auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions(); | |
| // Set up the test environment with TestProtocol being served by `serving_agent`. | |
| auto spec = CreateSpecWithAgentServiceIndex( | |
| {{fuchsia::testing::modular::TestProtocol::Name_, serving_agent->url()}}); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url()); | |
| spec.mutable_sessionmgr_config()->mutable_session_agents()->push_back(serving_agent->url()); | |
| modular_testing::TestHarnessBuilder builder(std::move(spec)); | |
| builder.InterceptComponent(serving_agent->BuildInterceptOptions()); | |
| builder.InterceptComponent(AddSandboxServices({fuchsia::testing::modular::TestProtocol::Name_}, | |
| agent->BuildInterceptOptions())); | |
| // Publish fuchsia.modular.Agent so that the test can wait until after sessionmgr has | |
| // observed its request for fuchsia.modular.Agent close. The workaround relies on | |
| // the the Agent channel being closed. | |
| bool saw_agent_service_request = false; | |
| serving_agent->AddPublicService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::modular::Agent>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::modular::Agent> request) { | |
| saw_agent_service_request = true; | |
| request.Close(ZX_ERR_NOT_FOUND); | |
| })); | |
| // Publish the service as an outgoing/public service. | |
| bool saw_test_protocol_request = false; | |
| serving_agent->AddPublicService( | |
| fit::function<void(fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol>)>( | |
| [&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) { | |
| saw_test_protocol_request = true; | |
| })); | |
| builder.BuildAndRun(test_harness()); | |
| RunLoopUntil([&] { return agent->is_running() && serving_agent->is_running(); }); | |
| RunLoopUntil([&] { return saw_agent_service_request; }); | |
| auto protocol_ptr = | |
| agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>(); | |
| RunLoopUntil([&] { return saw_test_protocol_request; }); | |
| } | |
| } // namespace |