blob: 53764cb01a6235b58a8681b2f1f08a37112a2882 [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 <lib/sys/cpp/component_context.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/testfidl/cpp/fidl.h"
#include "base/logging.h"
#include "base/test/task_environment.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_(outgoing_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_);
}
void DisconnectClientsAndTeardown() {
AgentImpl::ComponentStateBase::DisconnectClientsAndTeardown();
}
};
class AgentImplTest : public ::testing::Test {
public:
AgentImplTest() {
fidl::InterfaceHandle<::fuchsia::io::Directory> directory;
services_.GetOrCreateDirectory("svc")->Serve(
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
directory.NewRequest().TakeChannel());
services_client_ =
std::make_unique<sys::ServiceDirectory>(std::move(directory));
}
fuchsia::modular::AgentPtr CreateAgentAndConnect() {
DCHECK(!agent_impl_);
agent_impl_ = std::make_unique<AgentImpl>(
&services_, base::BindRepeating(&AgentImplTest::OnComponentConnect,
base::Unretained(this)));
fuchsia::modular::AgentPtr agent;
services_client_->Connect(agent.NewRequest());
return agent;
}
void TeardownKeepAliveComponentState() {
std::move(disconnect_clients_and_teardown_).Run();
}
protected:
std::unique_ptr<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) {
auto component_state =
std::make_unique<KeepAliveComponentState>(component_id);
disconnect_clients_and_teardown_ =
base::BindOnce(&KeepAliveComponentState::DisconnectClientsAndTeardown,
base::Unretained(component_state.get()));
return component_state;
}
return nullptr;
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
sys::OutgoingDirectory services_;
std::unique_ptr<sys::ServiceDirectory> services_client_;
std::unique_ptr<AgentImpl> agent_impl_;
// Set only if a keep-alive component was connected, to allow the test to
// forcibly teardown the ComponentState for it.
base::OnceClosure disconnect_clients_and_teardown_;
DISALLOW_COPY_AND_ASSIGN(AgentImplTest);
};
} // namespace
// Verify that the Agent can publish and unpublish itself.
TEST_F(AgentImplTest, PublishAndUnpublish) {
cr_fuchsia::ResultReceiver<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_->Connect(agent.NewRequest());
agent.set_error_handler(cr_fuchsia::CallbackToFitFunction(
client_disconnect_status2.GetReceiveCallback()));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(*client_disconnect_status2, ZX_ERR_PEER_CLOSED);
}
// Verify that multiple connection attempts with the different component Ids
// to the same service get different instances.
TEST_F(AgentImplTest, DifferentComponentIdSameService) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
// Connect to the Agent twice using the same component Id.
fuchsia::sys::ServiceProviderPtr component_services1;
agent->Connect(kAccumulatorComponentId1, component_services1.NewRequest());
fuchsia::sys::ServiceProviderPtr component_services2;
agent->Connect(kAccumulatorComponentId2, component_services2.NewRequest());
// Request the TestInterface from each of the service directories.
base::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 keep-alive the
// ComponentStateBase keeps it alive after the ServiceProvider is dropped.
TEST_F(AgentImplTest, KeepAliveBinding) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
{
// Connect to the Agent and request the TestInterface.
fuchsia::sys::ServiceProviderPtr component_services;
agent->Connect(kAccumulatorComponentId1, component_services.NewRequest());
base::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();
}
// Verify that connections to a service registered to keep-alive the
// ComponentStateBase is disconnected by DisconnectClientsAndTeardown.
TEST_F(AgentImplTest, DisconnectClientsAndTeardown) {
fuchsia::modular::AgentPtr agent = CreateAgentAndConnect();
{
// Connect to the Agent and request the TestInterface.
fuchsia::sys::ServiceProviderPtr component_services;
agent->Connect(kKeepAliveComponentId, component_services.NewRequest());
base::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 call
// DisconnectClientsAndTeardown().
test_interface.set_error_handler(
[](zx_status_t status) { ZX_LOG_IF(ERROR, status != ZX_OK, status); });
// After disconnecting ServiceProvider, TestInterface should remain valid.
component_services.Unbind();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(test_interface);
// After invoking DisconnectClientsAndTeardown(), TestInterface should
// be disconnected.
TeardownKeepAliveComponentState();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(test_interface);
}
}
} // namespace cr_fuchsia