blob: e79e143633128ed33946495e19e92793bf3f0b2f [file] [log] [blame]
// Copyright 2017 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 "src/modular/bin/sessionmgr/agent_runner/agent_runner.h"
#include <fuchsia/modular/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/testing/modular/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/sys/cpp/testing/fake_launcher.h>
#include <lib/zx/object.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <memory>
#include <gtest/gtest.h>
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/storage/vfs/cpp/service.h"
#include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
#include "src/modular/lib/deprecated_svc/service_namespace.h"
#include "src/modular/lib/fidl/app_client.h"
#include "src/modular/lib/fidl/array_to_string.h"
#include "src/modular/lib/modular_config/modular_config.h"
#include "src/modular/lib/pseudo_dir/pseudo_dir_server.h"
namespace modular_testing {
namespace {
using ::sys::testing::FakeLauncher;
// The choice of "TestProtocol" as the test service is arbitrary, but the
// ConnectToAgentService() tests require an existing service type.
constexpr char kTestAgentUrl[] = "file:///my_agent";
zx_koid_t get_object_koid(zx_handle_t handle) {
zx_info_handle_basic_t info;
if (zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), NULL, NULL) != ZX_OK) {
return 0;
}
return info.koid;
}
class TestAgent : fuchsia::modular::Agent,
fuchsia::modular::Lifecycle,
public fuchsia::sys::ComponentController {
public:
TestAgent(zx::channel directory_request,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl,
std::unique_ptr<component::ServiceNamespace> services_ptr = nullptr,
bool serve_lifecycle_protocol = false)
: controller_(this, std::move(ctrl)),
agent_binding_(this),
lifecycle_binding_(this),
services_ptr_(std::move(services_ptr)) {
auto outgoing_dir = std::make_unique<vfs::PseudoDir>();
outgoing_dir_ptr_ = outgoing_dir.get();
outgoing_dir->AddEntry(
fuchsia::modular::Agent::Name_,
std::make_unique<vfs::Service>([this](zx::channel channel, async_dispatcher_t* /*unused*/) {
agent_binding_.Bind(std::move(channel));
}));
if (serve_lifecycle_protocol) {
outgoing_dir->AddEntry(fuchsia::modular::Lifecycle::Name_,
std::make_unique<vfs::Service>(
[this](zx::channel channel, async_dispatcher_t* /*unused*/) {
lifecycle_binding_.Bind(std::move(channel));
}));
}
outgoing_dir_server_ = std::make_unique<modular::PseudoDirServer>(std::move(outgoing_dir));
outgoing_dir_server_->Serve(std::move(directory_request));
controller_.set_error_handler(
[this](zx_status_t /*unused*/) { controller_connected_ = false; });
}
template <class Interface>
void AddOutgoingService(fidl::InterfaceRequestHandler<Interface> handler) {
outgoing_dir_ptr_->AddEntry(
Interface::Name_,
std::make_unique<vfs::Service>(
[handler = std::move(handler)](zx::channel channel, async_dispatcher_t* /*unused*/) {
handler(fidl::InterfaceRequest<Interface>(std::move(channel)));
}));
}
void KillApplication() { controller_.Unbind(); }
int connect_call_count() { return connect_call_count_; }
bool lifecycle_terminate_called() { return lifecycle_terminate_called_; }
bool controller_connected() { return controller_connected_; }
private:
// |ComponentController|
void Kill() override { FX_NOTREACHED(); }
// |ComponentController|
void Detach() override { FX_NOTREACHED(); }
// |fuchsia::modular::Agent|
void Connect(std::string requestor_url,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> outgoing_services) override {
++connect_call_count_;
if (services_ptr_) {
services_ptr_->AddBinding(std::move(outgoing_services));
}
}
// |fuchsia::modular::Lifecycle|
void Terminate() override {
lifecycle_terminate_called_ = true;
controller_connected_ = false;
controller_.Close(ZX_OK);
}
private:
fidl::Binding<fuchsia::sys::ComponentController> controller_;
fidl::Binding<fuchsia::modular::Agent> agent_binding_;
fidl::Binding<fuchsia::modular::Lifecycle> lifecycle_binding_;
std::unique_ptr<component::ServiceNamespace> services_ptr_;
// `outgoing_dir_server_` must be initialized after `agent_binding_` (which itself serves
// `services_ptr_`) so that it is guaranteed to be destroyed *before* `agent_binding_` to protect
// access to `services_ptr_`. See fxbug.dev/49304.
std::unique_ptr<modular::PseudoDirServer> outgoing_dir_server_;
vfs::PseudoDir* outgoing_dir_ptr_;
// The number of times Connect() has been called.
int connect_call_count_ = 0;
// When true, the agent has been gracefully torn down via |fuchsia::modular::Lifecycle|.
bool lifecycle_terminate_called_ = false;
// When true, the ComponentController is bound. AgentContextImpl disconnects from
// ComponentController when it wants to terminate the agent, which sets this to false.
// This is also set to false when the agent is gracefully torn down in Terminate.
bool controller_connected_ = true;
FXL_DISALLOW_COPY_AND_ASSIGN(TestAgent);
};
class AgentRunnerTest : public gtest::RealLoopFixture {
public:
AgentRunnerTest() : config_(modular::DefaultConfig()) {}
void SetUp() override { gtest::RealLoopFixture::SetUp(); }
void TearDown() override {
agent_runner_.reset();
gtest::RealLoopFixture::TearDown();
}
protected:
modular::AgentRunner* GetOrCreateAgentRunner() {
if (agent_runner_ == nullptr) {
config_accessor_ = std::make_unique<modular::ModularConfigAccessor>(std::move(config_));
agent_runner_ = std::make_unique<modular::AgentRunner>(
config_accessor_.get(), &launcher_, /*agent_services_factory=*/nullptr,
&node_, /*on_critical_agent_crash=*/
[this] {
if (on_session_restart_callback_) {
on_session_restart_callback_();
}
},
std::move(agent_service_index_),
/*session_agents=*/std::vector<std::string>(),
std::move(restart_session_on_agent_crash_));
}
return agent_runner_.get();
}
fuchsia::modular::session::ModularConfig& modular_config() { return config_; }
void set_agent_service_index(std::map<std::string, std::string> agent_service_index) {
agent_service_index_ = std::move(agent_service_index);
}
void set_restart_session_on_agent_crash(std::vector<std::string> restart_session_on_agent_crash) {
restart_session_on_agent_crash_ = std::move(restart_session_on_agent_crash);
}
void set_on_session_restart_callback(std::function<void()> on_session_restart_callback) {
on_session_restart_callback_ = std::move(on_session_restart_callback);
}
FakeLauncher* launcher() { return &launcher_; }
private:
FakeLauncher launcher_;
inspect::Node node_;
files::ScopedTempDir mq_data_dir_;
fuchsia::modular::session::ModularConfig config_;
std::unique_ptr<modular::ModularConfigAccessor> config_accessor_;
std::unique_ptr<modular::AgentRunner> agent_runner_;
std::map<std::string, std::string> agent_service_index_;
std::vector<std::string> restart_session_on_agent_crash_;
std::function<void()> on_session_restart_callback_;
FXL_DISALLOW_COPY_AND_ASSIGN(AgentRunnerTest);
};
} // namespace
// Test that connecting to an agent will start it up.
// Then there should be an fuchsia::modular::Agent.Connect().
TEST_F(AgentRunnerTest, ConnectToAgent) {
int agent_launch_count = 0;
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent, &agent_launch_count](
fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
++agent_launch_count;
});
fuchsia::sys::ServiceProviderPtr incoming_services;
fuchsia::modular::AgentControllerPtr agent_controller;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url", kTestAgentUrl,
incoming_services.NewRequest(),
agent_controller.NewRequest());
RunLoopUntil([&test_agent] { return test_agent && test_agent->connect_call_count() > 0; });
EXPECT_EQ(1, agent_launch_count);
EXPECT_EQ(1, test_agent->connect_call_count());
// Connecting to the same agent again shouldn't launch a new instance and
// shouldn't re-initialize the existing instance of the agent application,
// but should call |Connect()|.
fuchsia::modular::AgentControllerPtr agent_controller2;
fuchsia::sys::ServiceProviderPtr incoming_services2;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url2", kTestAgentUrl,
incoming_services2.NewRequest(),
agent_controller2.NewRequest());
RunLoopUntil([&test_agent] { return test_agent && test_agent->connect_call_count() > 1; });
EXPECT_EQ(1, agent_launch_count);
EXPECT_EQ(2, test_agent->connect_call_count());
}
// Test that if an agent application dies, it is removed from agent runner
// (which means outstanding AgentControllers are closed).
TEST_F(AgentRunnerTest, AgentController) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
});
fuchsia::sys::ServiceProviderPtr incoming_services;
fuchsia::modular::AgentControllerPtr agent_controller;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url", kTestAgentUrl,
incoming_services.NewRequest(),
agent_controller.NewRequest());
RunLoopUntil([&test_agent] { return !!test_agent; });
test_agent->KillApplication();
// fuchsia::modular::Agent application died, so check that
// fuchsia::modular::AgentController dies here.
agent_controller.set_error_handler(
[&agent_controller](zx_status_t status) { agent_controller.Unbind(); });
RunLoopUntil([&agent_controller] { return !agent_controller.is_bound(); });
EXPECT_FALSE(agent_controller.is_bound());
}
// AgentServiceRequest.service_name is required: when not provided, expect an error
// in the epitaph for the service channel.
TEST_F(AgentRunnerTest, ConnectToAgentService_NoServiceNameInAgentServiceRequest) {
fuchsia::testing::modular::TestProtocolPtr service_ptr;
zx_status_t status;
bool channel_closed = false;
service_ptr.set_error_handler([&](zx_status_t s) {
channel_closed = true;
status = s;
});
// The agent should never be launched.
launcher()->RegisterComponent(
kTestAgentUrl,
[](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) { FX_NOTREACHED(); });
fuchsia::modular::AgentServiceRequest request;
request.set_handler(kTestAgentUrl);
request.set_channel(service_ptr.NewRequest().TakeChannel());
GetOrCreateAgentRunner()->ConnectToAgentService("requestor_url", std::move(request));
RunLoopUntil([&] { return channel_closed; });
EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
}
// AgentServiceRequest.channel is required: when not provided, expect an error on
// the AgentController.
TEST_F(AgentRunnerTest, ConnectToAgentService_NoChannelInAgentServiceRequest) {
fuchsia::modular::AgentControllerPtr agent_controller;
zx_status_t status;
bool channel_closed = false;
agent_controller.set_error_handler([&](zx_status_t s) {
channel_closed = true;
status = s;
});
// The agent should never be launched.
launcher()->RegisterComponent(
kTestAgentUrl,
[](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) { FX_NOTREACHED(); });
fuchsia::modular::AgentServiceRequest request;
request.set_handler(kTestAgentUrl);
request.set_service_name(fuchsia::testing::modular::TestProtocol::Name_);
request.set_agent_controller(agent_controller.NewRequest());
GetOrCreateAgentRunner()->ConnectToAgentService("requestor_url", std::move(request));
RunLoopUntil([&] { return channel_closed; });
EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
}
// When no handler is provided, and the agent_service_index does not contain
// an entry for this service, expect a ZX_ERR_NOT_FOUND on the service channel.
TEST_F(AgentRunnerTest, ConnectToAgentService_NoAgentForServiceName) {
fuchsia::testing::modular::TestProtocolPtr service_ptr;
zx_status_t status;
bool channel_closed = false;
service_ptr.set_error_handler([&](zx_status_t s) {
channel_closed = true;
status = s;
});
// The agent should never be launched.
launcher()->RegisterComponent(
kTestAgentUrl,
[](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) { FX_NOTREACHED(); });
set_agent_service_index({{"different_service", kTestAgentUrl}});
fuchsia::modular::AgentServiceRequest request;
request.set_service_name(fuchsia::testing::modular::TestProtocol::Name_);
request.set_channel(service_ptr.NewRequest().TakeChannel());
GetOrCreateAgentRunner()->ConnectToAgentService("requestor_url", std::move(request));
RunLoopUntil([&] { return channel_closed; });
EXPECT_EQ(status, ZX_ERR_NOT_FOUND);
}
// ConnectToAgentService, when successful, should launch the agent specified in the
// agent_service_index, and provide it with the channel endpoint in AgentServiceRequest.channel.
TEST_F(AgentRunnerTest, ConnectToAgentService_ConnectToServiceNameSuccess) {
fuchsia::testing::modular::TestProtocolPtr service_ptr;
auto service_ptr_request = service_ptr.NewRequest();
zx_status_t status;
bool channel_closed = false;
service_ptr.set_error_handler([&](zx_status_t s) {
channel_closed = true;
status = s;
});
// Create a ServiceNamespace with the test service in it.
auto service_namespace = std::make_unique<component::ServiceNamespace>();
bool agent_got_service_request = false;
service_namespace->AddService<fuchsia::testing::modular::TestProtocol>(
[&agent_got_service_request,
cached_service_request_koid = get_object_koid(service_ptr_request.channel().get())](
fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) {
// Expect the same channel object that was originally provided in AgentServiceRequest.
EXPECT_EQ(get_object_koid(request.channel().get()), cached_service_request_koid);
agent_got_service_request = true;
});
// Register to intercept the agent launch.
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent = std::make_unique<TestAgent>(std::move(launch_info.directory_request),
std::move(ctrl), std::move(service_namespace));
});
set_agent_service_index({{fuchsia::testing::modular::TestProtocol::Name_, kTestAgentUrl}});
fuchsia::modular::AgentServiceRequest request;
request.set_service_name(fuchsia::testing::modular::TestProtocol::Name_);
request.set_channel(service_ptr_request.TakeChannel());
GetOrCreateAgentRunner()->ConnectToAgentService("requestor_url", std::move(request));
RunLoopUntil([&] { return agent_got_service_request; });
}
// Test that adding an agent that is already running (as encapsulated by an AppClient<> instance)
// can serve services.
TEST_F(AgentRunnerTest, AddRunningAgent_CanConnectToAgentService) {
fuchsia::testing::modular::TestProtocolPtr service_ptr;
auto service_ptr_request = service_ptr.NewRequest();
// Create a ServiceNamespace with the test service in it.
auto service_namespace = std::make_unique<component::ServiceNamespace>();
bool agent_got_service_request = false;
service_namespace->AddService<fuchsia::testing::modular::TestProtocol>(
[&agent_got_service_request,
cached_service_request_koid = get_object_koid(service_ptr_request.channel().get())](
fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) {
// Expect the same channel object that was originally provided in AgentServiceRequest.
EXPECT_EQ(get_object_koid(request.channel().get()), cached_service_request_koid);
agent_got_service_request = true;
});
// Register to intercept the agent launch.
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
FX_CHECK(!test_agent); // Should not launch agent more than once!
test_agent = std::make_unique<TestAgent>(std::move(launch_info.directory_request),
std::move(ctrl), std::move(service_namespace));
});
set_agent_service_index({{fuchsia::testing::modular::TestProtocol::Name_, kTestAgentUrl}});
auto agent_runner = GetOrCreateAgentRunner();
fuchsia::modular::session::AppConfig agent_app_config;
agent_app_config.set_url(kTestAgentUrl);
auto agent_app_client = std::make_unique<modular::AppClient<fuchsia::modular::Lifecycle>>(
launcher(), std::move(agent_app_config));
agent_runner->AddRunningAgent(kTestAgentUrl, std::move(agent_app_client));
fuchsia::modular::AgentServiceRequest request;
request.set_service_name(fuchsia::testing::modular::TestProtocol::Name_);
request.set_channel(service_ptr_request.TakeChannel());
agent_runner->ConnectToAgentService("requestor_url", std::move(request));
RunLoopUntil([&] { return agent_got_service_request; });
}
TEST_F(AgentRunnerTest, AddRunningAgent_IsGracefullyTornDown) {
fuchsia::testing::modular::TestProtocolPtr service_ptr;
auto service_ptr_request = service_ptr.NewRequest();
// Register to intercept the agent launch.
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent = std::make_unique<TestAgent>(
std::move(launch_info.directory_request), std::move(ctrl),
/*services_ptr=*/nullptr, /*serve_lifecycle_protocol=*/true);
});
auto agent_runner = GetOrCreateAgentRunner();
fuchsia::modular::session::AppConfig agent_app_config;
agent_app_config.set_url(kTestAgentUrl);
auto agent_app_client = std::make_unique<modular::AppClient<fuchsia::modular::Lifecycle>>(
launcher(), std::move(agent_app_config), /* data_origin = */ "");
agent_runner->AddRunningAgent(kTestAgentUrl, std::move(agent_app_client));
// Teardown the agent runner.
auto is_torn_down = false;
agent_runner->Teardown([&is_torn_down] { is_torn_down = true; });
RunLoopUntil([&] { return is_torn_down; });
// The agent should have been instructed to tear down gracefully.
RunLoopUntil([&] { return test_agent->lifecycle_terminate_called(); });
// The agent should have been terminated.
RunLoopUntil([&] { return !test_agent->controller_connected(); });
}
TEST_F(AgentRunnerTest, AddRunningAgent_CanBeCriticalAgent) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
});
// The session should be restarted when the agent terminates.
set_restart_session_on_agent_crash({kTestAgentUrl});
auto is_restart_called = false;
set_on_session_restart_callback([&is_restart_called] { is_restart_called = true; });
auto agent_runner = GetOrCreateAgentRunner();
fuchsia::modular::session::AppConfig agent_app_config;
agent_app_config.set_url(kTestAgentUrl);
auto agent_app_client = std::make_unique<modular::AppClient<fuchsia::modular::Lifecycle>>(
launcher(), std::move(agent_app_config), /* data_origin = */ "");
agent_runner->AddRunningAgent(kTestAgentUrl, std::move(agent_app_client));
RunLoopUntil([&test_agent] { return !!test_agent; });
// The agent is now running, so the session should not have been restarted yet.
EXPECT_FALSE(is_restart_called);
// Terminate the agent.
test_agent->KillApplication();
RunLoopUntil([&is_restart_called] { return is_restart_called; });
}
// Test that the critical agent restart is short-circuited when using
// AddRunningAgent if the behavior is disabled in the ModularConfig.
TEST_F(AgentRunnerTest, AddRunningAgent_NotRestartedIfRestartDisabled) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
});
set_restart_session_on_agent_crash({kTestAgentUrl});
auto is_restart_called = false;
set_on_session_restart_callback([&is_restart_called] { is_restart_called = true; });
modular_config().mutable_sessionmgr_config()->set_disable_agent_restart_on_crash(true);
auto agent_runner = GetOrCreateAgentRunner();
fuchsia::modular::session::AppConfig agent_app_config;
agent_app_config.set_url(kTestAgentUrl);
auto agent_app_client = std::make_unique<modular::AppClient<fuchsia::modular::Lifecycle>>(
launcher(), std::move(agent_app_config), /* data_origin = */ "");
agent_runner->AddRunningAgent(kTestAgentUrl, std::move(agent_app_client));
RunLoopUntil([&test_agent] { return !!test_agent; });
// The agent is now running, so the session should not have been restarted yet.
EXPECT_FALSE(is_restart_called);
// Terminate the agent.
test_agent->KillApplication();
// Wait for a bit to ensuire that the session wasn't restarted.
sleep(3);
EXPECT_FALSE(is_restart_called);
}
// Tests that GetAgentOutgoingServices() return null for a non-existent agent, and returns
// a pointer to the component::Services for the agent component when given a valid running
// agent.
TEST_F(AgentRunnerTest, GetAgentOutgoingServices) {
// Register to intercept the agent launch.
std::unique_ptr<TestAgent> test_agent;
int service_connect_requests = 0;
launcher()->RegisterComponent(
kTestAgentUrl, [&](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
test_agent->AddOutgoingService(
fidl::InterfaceRequestHandler<fuchsia::testing::modular::TestProtocol>(
[&](fidl::InterfaceRequest<fuchsia::testing::modular::TestProtocol> request) {
++service_connect_requests;
}));
});
set_agent_service_index({{fuchsia::testing::modular::TestProtocol::Name_, kTestAgentUrl}});
auto agent_runner = GetOrCreateAgentRunner();
EXPECT_EQ(nullptr, agent_runner->GetAgentOutgoingServices("noexist"));
fuchsia::testing::modular::TestProtocolPtr service_ptr1, service_ptr2;
fuchsia::modular::AgentServiceRequest request;
request.set_service_name(fuchsia::testing::modular::TestProtocol::Name_);
request.set_channel(service_ptr1.NewRequest().TakeChannel());
agent_runner->ConnectToAgentService("requestor_url", std::move(request));
auto agent_services = agent_runner->GetAgentOutgoingServices(kTestAgentUrl);
ASSERT_NE(nullptr, agent_services);
agent_services->ConnectToService(service_ptr2.NewRequest());
RunLoopUntil([&] { return service_connect_requests == 2; });
}
// Tests that AgentRunner terminates an agent component on teardown. In this case, the agent does
// not serve |fuchsia::modular::Lifecycle| protocol that allows a graceful teardown.
TEST_F(AgentRunnerTest, TerminateOnTeardown) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
});
fuchsia::sys::ServiceProviderPtr incoming_services;
fuchsia::modular::AgentControllerPtr agent_controller;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url", kTestAgentUrl,
incoming_services.NewRequest(),
agent_controller.NewRequest());
RunLoopUntil([&test_agent] { return !!test_agent; });
EXPECT_TRUE(agent_controller.is_bound());
// Teardown the agent runner.
auto is_torn_down = false;
GetOrCreateAgentRunner()->Teardown([&is_torn_down] { is_torn_down = true; });
RunLoopUntil([&is_torn_down] { return is_torn_down; });
// The agent should have been terminated.
RunLoopUntil([&] { return !test_agent->controller_connected(); });
// Closing a channel is akin to sending a final message on that channel.
// Run the run loop until that message is received to see that the
// AgentController was indeed closed.
RunLoopUntil([&] { return !agent_controller.is_bound(); });
}
// Tests that AgentRunner terminates an agent component on teardown. In this case, the agent
// serves the |fuchsia::modular::Lifecycle| protocol that allows a graceful teardown.
TEST_F(AgentRunnerTest, TerminateGracefullyOnTeardown) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent = std::make_unique<TestAgent>(
std::move(launch_info.directory_request), std::move(ctrl),
/*services_ptr=*/nullptr, /*serve_lifecycle_protocol=*/true);
});
fuchsia::sys::ServiceProviderPtr incoming_services;
fuchsia::modular::AgentControllerPtr agent_controller;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url", kTestAgentUrl,
incoming_services.NewRequest(),
agent_controller.NewRequest());
RunLoopUntil([&] { return !!test_agent; });
EXPECT_TRUE(agent_controller.is_bound());
// Teardown the agent runner.
auto is_torn_down = false;
GetOrCreateAgentRunner()->Teardown([&is_torn_down] { is_torn_down = true; });
RunLoopUntil([&] { return is_torn_down; });
// The agent should have been instructed to tear down gracefully.
EXPECT_TRUE(test_agent->lifecycle_terminate_called());
// The agent should have been terminated.
EXPECT_FALSE(test_agent->controller_connected());
// Closing a channel is akin to sending a final message on that channel.
// Run the run loop until that message is received to see that the
// AgentController was indeed closed.
RunLoopUntil([&] { return !agent_controller.is_bound(); });
}
// When an agent dies and it is not listed in |restart_session_on_agent_crash|,
// the session should not be restarted.
TEST_F(AgentRunnerTest, CriticalAgents_NoSessionRestartOnCrash) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
});
// The session should not be restarted due to an agent termination.
set_restart_session_on_agent_crash({});
set_on_session_restart_callback(
[] { FX_NOTREACHED() << "SessionRestartController.Restart() was unexpectedly called"; });
fuchsia::sys::ServiceProviderPtr incoming_services;
fuchsia::modular::AgentControllerPtr agent_controller;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url", kTestAgentUrl,
incoming_services.NewRequest(),
agent_controller.NewRequest());
RunLoopUntil([&test_agent] { return !!test_agent; });
// Terminate the agent.
test_agent->KillApplication();
// Teardown the session.
auto is_torn_down = false;
GetOrCreateAgentRunner()->Teardown([&is_torn_down] { is_torn_down = true; });
RunLoopUntil([&is_torn_down] { return is_torn_down; });
}
// When an agent dies and it is listed in |restart_session_on_agent_crash|,
// the session should restarted.
TEST_F(AgentRunnerTest, CriticalAgents_SessionRestartOnCrash) {
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&test_agent](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
test_agent =
std::make_unique<TestAgent>(std::move(launch_info.directory_request), std::move(ctrl));
});
// The session should be restarted when the agent terminates.
set_restart_session_on_agent_crash({kTestAgentUrl});
auto is_restart_called = false;
set_on_session_restart_callback([&is_restart_called] { is_restart_called = true; });
fuchsia::sys::ServiceProviderPtr incoming_services;
fuchsia::modular::AgentControllerPtr agent_controller;
GetOrCreateAgentRunner()->ConnectToAgent("requestor_url", kTestAgentUrl,
incoming_services.NewRequest(),
agent_controller.NewRequest());
RunLoopUntil([&test_agent] { return !!test_agent; });
// The agent is now running, so the session should not have been restarted yet.
EXPECT_FALSE(is_restart_called);
// Terminate the agent.
test_agent->KillApplication();
RunLoopUntil([&is_restart_called] { return is_restart_called; });
// Teardown the session.
auto is_torn_down = false;
GetOrCreateAgentRunner()->Teardown([&is_torn_down] { is_torn_down = true; });
RunLoopUntil([&is_torn_down] { return is_torn_down; });
}
// Tests that AgentContext terminates an agent when it cannot connect to an agent's outgoing
// directory. The agent is marked critical, so this causes the session to restart.
TEST_F(AgentRunnerTest, SessionRestartOnBrokenAgentOutgoingDir) {
bool is_agent_launch_called{false};
std::unique_ptr<TestAgent> test_agent;
launcher()->RegisterComponent(
kTestAgentUrl, [&](fuchsia::sys::LaunchInfo launch_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
// Close the request to the agent's outging directory. AgentContext will be unable to
// clone the directory, and will terminate the agent as a result.
launch_info.directory_request.reset();
is_agent_launch_called = true;
});
// The session should be restarted when the agent terminates.
set_restart_session_on_agent_crash({kTestAgentUrl});
auto is_restart_called = false;
set_on_session_restart_callback([&is_restart_called] { is_restart_called = true; });
auto agent_runner = GetOrCreateAgentRunner();
fuchsia::modular::session::AppConfig agent_app_config;
agent_app_config.set_url(kTestAgentUrl);
auto agent_app_client = std::make_unique<modular::AppClient<fuchsia::modular::Lifecycle>>(
launcher(), std::move(agent_app_config), /*data_origin=*/"");
agent_runner->AddRunningAgent(kTestAgentUrl, std::move(agent_app_client));
RunLoopUntil([&] { return is_agent_launch_called; });
// The session should be restarted because the agent was terminated.
RunLoopUntil([&] { return is_restart_called; });
}
} // namespace modular_testing