blob: 4590c9c4881e14575d50af99881913f6b85eb891 [file] [log] [blame]
// 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) override {
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::OpenFlags::RIGHT_READABLE |
fuchsia::io::OpenFlags::RIGHT_WRITABLE |
fuchsia::io::OpenFlags::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