blob: 6e97672c5179f46c0f3b24c67a417579cd412a07 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "fuchsia/base/agent_impl.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/service_directory.h"
#include "base/fuchsia/service_directory_client.h"
#include "base/fuchsia/testfidl/cpp/fidl.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "fuchsia/base/fit_adapter.h"
#include "fuchsia/base/result_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cr_fuchsia {
namespace {
const char kNoServicesComponentId[] = "no-services";
const char kAccumulatorComponentId1[] = "accumulator1";
const char kAccumulatorComponentId2[] = "accumulator2";
const char kKeepAliveComponentId[] = "keep-alive";
class EmptyComponentState : public AgentImpl::ComponentStateBase {
public:
EmptyComponentState(base::StringPiece component)
: ComponentStateBase(component) {}
};
class AccumulatingTestInterfaceImpl
: public base::fuchsia::testfidl::TestInterface {
public:
AccumulatingTestInterfaceImpl() = default;
// TestInterface implementation:
void Add(int32_t a, int32_t b, AddCallback callback) override {
accumulated_ += a + b;
callback(accumulated_);
}
private:
int32_t accumulated_ = 0;
DISALLOW_COPY_AND_ASSIGN(AccumulatingTestInterfaceImpl);
};
class AccumulatorComponentState : public AgentImpl::ComponentStateBase {
public:
AccumulatorComponentState(base::StringPiece component)
: ComponentStateBase(component),
service_binding_(service_directory(), &service_) {}
protected:
AccumulatingTestInterfaceImpl service_;
base::fuchsia::ScopedServiceBinding<base::fuchsia::testfidl::TestInterface>
service_binding_;
};
class KeepAliveComponentState : public AccumulatorComponentState {
public:
KeepAliveComponentState(base::StringPiece component)
: AccumulatorComponentState(component) {
AddKeepAliveBinding(&service_binding_);
}
};
class AgentImplTest : public ::testing::Test {
public:
AgentImplTest() {
fidl::InterfaceHandle<::fuchsia::io::Directory> directory;
services_ = std::make_unique<base::fuchsia::ServiceDirectory>(
directory.NewRequest());
services_client_ = std::make_unique<base::fuchsia::ServiceDirectoryClient>(
std::move(directory));
}
fuchsia::modular::AgentPtr CreateAgentAndConnect() {
DCHECK(!agent_impl_);
agent_impl_ = std::make_unique<AgentImpl>(
services_.get(), base::BindRepeating(&AgentImplTest::OnComponentConnect,
base::Unretained(this)));
fuchsia::modular::AgentPtr agent;
services_client_->ConnectToService(agent.NewRequest());
return agent;
}
protected:
std::unique_ptr<AgentImpl::ComponentStateBase> OnComponentConnect(
base::StringPiece component_id) {
if (component_id == kNoServicesComponentId) {
return std::make_unique<EmptyComponentState>(component_id);
} else if (component_id == kAccumulatorComponentId1 ||
component_id == kAccumulatorComponentId2) {
return std::make_unique<AccumulatorComponentState>(component_id);
} else if (component_id == kKeepAliveComponentId) {
return std::make_unique<KeepAliveComponentState>(component_id);
}
return nullptr;
}
base::MessageLoopForIO message_loop_;
std::unique_ptr<base::fuchsia::ServiceDirectory> services_;
std::unique_ptr<base::fuchsia::ServiceDirectoryClient> services_client_;
std::unique_ptr<AgentImpl> agent_impl_;
DISALLOW_COPY_AND_ASSIGN(AgentImplTest);
};
void SetBoolToTrue(bool* bool_value) {
*bool_value = true;
}
} // namespace
// Verify that the Agent can publish and unpublish itself.
TEST_F(AgentImplTest, PublishAndUnpublish) {
cr_fuchsia::ResultReceiver<zx_status_t> client_disconnect_status1;
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
agent.set_error_handler(cr_fuchsia::CallbackToFitFunction(
client_disconnect_status1.GetReceiveCallback()));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(client_disconnect_status1.has_value());
// Teardown the Agent.
agent_impl_.reset();
// Verify that the client got disconnected on teardown.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(*client_disconnect_status1, ZX_ERR_PEER_CLOSED);
// Verify that the Agent service is no longer available.
cr_fuchsia::ResultReceiver<zx_status_t> client_disconnect_status2;
services_client_->ConnectToService(agent.NewRequest());
agent.set_error_handler(cr_fuchsia::CallbackToFitFunction(
client_disconnect_status2.GetReceiveCallback()));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(*client_disconnect_status2, ZX_ERR_PEER_CLOSED);
}
// Verify that the on-last-client callback is not invoked if the Agent channel
// is closed, until the last component is gone.
TEST_F(AgentImplTest, OnLastClientCallbackAfterComponentOutlivesAgent) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
// Register an on-last-client callback.
bool on_last_client_called = false;
agent_impl_->set_on_last_client_callback(
base::BindOnce(&SetBoolToTrue, base::Unretained(&on_last_client_called)));
// Connect a component to the Agent.
fuchsia::sys::ServiceProviderPtr component_services;
agent->Connect(kNoServicesComponentId, component_services.NewRequest());
// Disconnect from the Agent API.
agent.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(on_last_client_called);
// Disconnect the component.
component_services.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(on_last_client_called);
}
// Verify that the on-last-client callback is not invoked when the last
// component disconnects, until the Agent channel is also closed.
TEST_F(AgentImplTest, OnLastClientCallbackAfterAgentOutlivesComponent) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
// Register an on-last-client callback.
bool on_last_client_called = false;
agent_impl_->set_on_last_client_callback(
base::BindOnce(&SetBoolToTrue, base::Unretained(&on_last_client_called)));
// Connect a component to the Agent.
fuchsia::sys::ServiceProviderPtr component_services;
agent->Connect(kNoServicesComponentId, component_services.NewRequest());
// Disconnect the component.
component_services.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(on_last_client_called);
// Disconnect from the Agent API.
agent.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(on_last_client_called);
}
// Verify that multiple connection attempts with the different component Ids
// to the same service get different instances.
TEST_F(AgentImplTest, DifferentComponentIdSameService) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
// Connect to the Agent twice using the same component Id.
fuchsia::sys::ServiceProviderPtr component_services1;
agent->Connect(kAccumulatorComponentId1, component_services1.NewRequest());
fuchsia::sys::ServiceProviderPtr component_services2;
agent->Connect(kAccumulatorComponentId2, component_services2.NewRequest());
// Request the TestInterface from each of the service directories.
base::fuchsia::testfidl::TestInterfacePtr test_interface1;
component_services1->ConnectToService(
base::fuchsia::testfidl::TestInterface::Name_,
test_interface1.NewRequest().TakeChannel());
base::fuchsia::testfidl::TestInterfacePtr test_interface2;
component_services2->ConnectToService(
base::fuchsia::testfidl::TestInterface::Name_,
test_interface1.NewRequest().TakeChannel());
// Both TestInterface pointers should remain valid until we are done.
test_interface1.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status);
ADD_FAILURE();
});
test_interface2.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status);
ADD_FAILURE();
});
// Call Add() via one TestInterface and verify accumulator had been at zero.
{
base::RunLoop loop;
test_interface1->Add(1, 2,
[quit_loop = loop.QuitClosure()](int32_t result) {
EXPECT_EQ(result, 3);
quit_loop.Run();
});
}
// Call Add() via the second TestInterface, and verify that first Add() call's
// effects aren't visible.
{
base::RunLoop loop;
test_interface2->Add(3, 4,
[quit_loop = loop.QuitClosure()](int32_t result) {
EXPECT_EQ(result, 7);
quit_loop.Run();
});
}
test_interface1.set_error_handler(nullptr);
test_interface2.set_error_handler(nullptr);
}
// Verify that multiple connection attempts with the same component Id connect
// it to the same service instances.
TEST_F(AgentImplTest, SameComponentIdSameService) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
// Connect to the Agent twice using the same component Id.
fuchsia::sys::ServiceProviderPtr component_services1;
agent->Connect(kAccumulatorComponentId1, component_services1.NewRequest());
fuchsia::sys::ServiceProviderPtr component_services2;
agent->Connect(kAccumulatorComponentId1, component_services2.NewRequest());
// Request the TestInterface from each of the service directories.
base::fuchsia::testfidl::TestInterfacePtr test_interface1;
component_services1->ConnectToService(
base::fuchsia::testfidl::TestInterface::Name_,
test_interface1.NewRequest().TakeChannel());
base::fuchsia::testfidl::TestInterfacePtr test_interface2;
component_services2->ConnectToService(
base::fuchsia::testfidl::TestInterface::Name_,
test_interface1.NewRequest().TakeChannel());
// Both TestInterface pointers should remain valid until we are done.
test_interface1.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status);
ADD_FAILURE();
});
test_interface2.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status);
ADD_FAILURE();
});
// Call Add() via one TestInterface and verify accumulator had been at zero.
{
base::RunLoop loop;
test_interface1->Add(1, 2,
[quit_loop = loop.QuitClosure()](int32_t result) {
EXPECT_EQ(result, 3);
quit_loop.Run();
});
}
// Call Add() via the other TestInterface, and verify that the result of the
// previous Add() was already in the accumulator.
{
base::RunLoop loop;
test_interface2->Add(3, 4,
[quit_loop = loop.QuitClosure()](int32_t result) {
EXPECT_EQ(result, 10);
quit_loop.Run();
});
}
test_interface1.set_error_handler(nullptr);
test_interface2.set_error_handler(nullptr);
}
// Verify that connections to a service registered to kee-alive the
// ComponentStateBase keeps it alive after the ServiceProvider is dropped.
TEST_F(AgentImplTest, KeepAliveBinding) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
{
// Connect to the Agent and request the TestInterface.
fuchsia::sys::ServiceProviderPtr component_services;
agent->Connect(kAccumulatorComponentId1, component_services.NewRequest());
base::fuchsia::testfidl::TestInterfacePtr test_interface;
component_services->ConnectToService(
base::fuchsia::testfidl::TestInterface::Name_,
test_interface.NewRequest().TakeChannel());
// The TestInterface pointer should be closed as soon as we Unbind() the
// ServiceProvider.
test_interface.set_error_handler(
[](zx_status_t status) { EXPECT_EQ(status, ZX_ERR_PEER_CLOSED); });
// After disconnecting ServiceProvider, TestInterface should remain valid.
component_services.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(test_interface);
}
{
// Connect to the Agent and request the TestInterface.
fuchsia::sys::ServiceProviderPtr component_services;
agent->Connect(kKeepAliveComponentId, component_services.NewRequest());
base::fuchsia::testfidl::TestInterfacePtr test_interface;
component_services->ConnectToService(
base::fuchsia::testfidl::TestInterface::Name_,
test_interface.NewRequest().TakeChannel());
// The TestInterface pointer should remain valid until we are done.
test_interface.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status);
ADD_FAILURE();
});
// After disconnecting ServiceProvider, TestInterface should remain valid.
component_services.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(test_interface);
}
// Spin the MessageLoop to let the AgentImpl see that TestInterface is gone.
base::RunLoop().RunUntilIdle();
}
} // namespace cr_fuchsia