267 lines
11 KiB
C++
267 lines
11 KiB
C++
// Copyright 2019 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/fuchsia/scoped_service_binding.h"
|
|
|
|
#include <lib/sys/cpp/component_context.h>
|
|
#include <lib/sys/cpp/outgoing_directory.h>
|
|
#include <lib/sys/cpp/service_directory.h>
|
|
|
|
#include "base/fuchsia/process_context.h"
|
|
#include "base/fuchsia/test_component_context_for_process.h"
|
|
#include "base/fuchsia/test_interface_impl.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/task_environment.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base {
|
|
|
|
class ScopedServiceBindingTest : public testing::Test {
|
|
protected:
|
|
ScopedServiceBindingTest() = default;
|
|
~ScopedServiceBindingTest() override = default;
|
|
|
|
const base::test::SingleThreadTaskEnvironment task_environment_{
|
|
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
|
|
|
|
TestComponentContextForProcess test_context_;
|
|
TestInterfaceImpl test_service_;
|
|
};
|
|
|
|
// Verifies that ScopedServiceBinding allows connection more than once.
|
|
TEST_F(ScopedServiceBindingTest, ConnectTwice) {
|
|
ScopedServiceBinding<testfidl::TestInterface> binding(
|
|
ComponentContextForProcess()->outgoing().get(), &test_service_);
|
|
|
|
auto stub =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
auto stub2 =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
|
|
EXPECT_EQ(VerifyTestInterface(stub2), ZX_OK);
|
|
}
|
|
|
|
// Verifies that ScopedServiceBinding allows connection more than once.
|
|
TEST_F(ScopedServiceBindingTest, ConnectTwiceNewName) {
|
|
const char kInterfaceName[] = "fuchsia.TestInterface2";
|
|
|
|
ScopedServiceBinding<testfidl::TestInterface> new_service_binding(
|
|
ComponentContextForProcess()->outgoing().get(), &test_service_,
|
|
kInterfaceName);
|
|
|
|
testfidl::TestInterfacePtr stub, stub2;
|
|
test_context_.published_services()->Connect(kInterfaceName,
|
|
stub.NewRequest().TakeChannel());
|
|
test_context_.published_services()->Connect(kInterfaceName,
|
|
stub2.NewRequest().TakeChannel());
|
|
EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
|
|
EXPECT_EQ(VerifyTestInterface(stub2), ZX_OK);
|
|
}
|
|
|
|
// Verify that we can publish a debug service.
|
|
TEST_F(ScopedServiceBindingTest, ConnectDebugService) {
|
|
vfs::PseudoDir* const debug_dir =
|
|
ComponentContextForProcess()->outgoing()->debug_dir();
|
|
|
|
// Publish the test service to the "debug" directory.
|
|
ScopedServiceBinding<testfidl::TestInterface> debug_service_binding(
|
|
debug_dir, &test_service_);
|
|
|
|
// Connect a ServiceDirectory to the "debug" subdirectory.
|
|
fidl::InterfaceHandle<fuchsia::io::Directory> debug_handle;
|
|
debug_dir->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
|
|
fuchsia::io::OpenFlags::RIGHT_WRITABLE,
|
|
debug_handle.NewRequest().TakeChannel());
|
|
sys::ServiceDirectory debug_directory(std::move(debug_handle));
|
|
|
|
// Attempt to connect via the "debug" directory.
|
|
auto debug_stub = debug_directory.Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(debug_stub), ZX_OK);
|
|
|
|
// Verify that the service does not appear in the outgoing service directory.
|
|
auto release_stub =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(release_stub), ZX_ERR_PEER_CLOSED);
|
|
}
|
|
|
|
// Verifies that ScopedSingleClientServiceBinding allows a different name.
|
|
TEST_F(ScopedServiceBindingTest, SingleClientConnectNewName) {
|
|
const char kInterfaceName[] = "fuchsia.TestInterface2";
|
|
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
|
|
ComponentContextForProcess()->outgoing().get(), &test_service_,
|
|
kInterfaceName);
|
|
|
|
testfidl::TestInterfacePtr stub;
|
|
test_context_.published_services()->Connect(kInterfaceName,
|
|
stub.NewRequest().TakeChannel());
|
|
EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
|
|
}
|
|
|
|
// Verify that if we connect twice to a prefer-new bound service, the existing
|
|
// connection gets closed.
|
|
TEST_F(ScopedServiceBindingTest, SingleClientPreferNew) {
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface,
|
|
ScopedServiceBindingPolicy::kPreferNew>
|
|
binding(ComponentContextForProcess()->outgoing().get(), &test_service_);
|
|
|
|
// Connect the first client, and verify that it is functional.
|
|
auto existing_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
|
|
|
|
// Connect the second client, so the existing one should be disconnected and
|
|
// the new should be functional.
|
|
auto new_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(existing_client);
|
|
EXPECT_EQ(VerifyTestInterface(new_client), ZX_OK);
|
|
}
|
|
|
|
// Verify that if we connect twice to a prefer-existing bound service, the new
|
|
// connection gets closed.
|
|
TEST_F(ScopedServiceBindingTest, SingleClientPreferExisting) {
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface,
|
|
ScopedServiceBindingPolicy::kPreferExisting>
|
|
binding(ComponentContextForProcess()->outgoing().get(), &test_service_);
|
|
|
|
// Connect the first client, and verify that it is functional.
|
|
auto existing_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
|
|
|
|
// Connect the second client, then verify that the it gets closed and the
|
|
// existing one remains functional.
|
|
auto new_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(new_client);
|
|
EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
|
|
}
|
|
|
|
// Verify that the default single-client binding policy is prefer-new.
|
|
TEST_F(ScopedServiceBindingTest, SingleClientDefaultIsPreferNew) {
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
|
|
ComponentContextForProcess()->outgoing().get(), &test_service_);
|
|
|
|
// Connect the first client, and verify that it is functional.
|
|
auto existing_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
|
|
|
|
// Connect the second client, so the existing one should be disconnected and
|
|
// the new should be functional.
|
|
auto new_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(existing_client);
|
|
EXPECT_EQ(VerifyTestInterface(new_client), ZX_OK);
|
|
}
|
|
|
|
// Verify that single-client bindings support publishing to a PseudoDir.
|
|
TEST_F(ScopedServiceBindingTest, SingleClientPublishToPseudoDir) {
|
|
vfs::PseudoDir* const debug_dir =
|
|
ComponentContextForProcess()->outgoing()->debug_dir();
|
|
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
|
|
debug_dir, &test_service_);
|
|
|
|
// Connect a ServiceDirectory to the "debug" subdirectory.
|
|
fidl::InterfaceHandle<fuchsia::io::Directory> debug_handle;
|
|
debug_dir->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
|
|
fuchsia::io::OpenFlags::RIGHT_WRITABLE,
|
|
debug_handle.NewRequest().TakeChannel());
|
|
sys::ServiceDirectory debug_directory(std::move(debug_handle));
|
|
|
|
// Attempt to connect via the "debug" directory.
|
|
auto debug_stub = debug_directory.Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(debug_stub), ZX_OK);
|
|
|
|
// Verify that the service does not appear in the outgoing service directory.
|
|
auto release_stub =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(release_stub), ZX_ERR_PEER_CLOSED);
|
|
}
|
|
|
|
TEST_F(ScopedServiceBindingTest, SingleBindingSetOnLastClientCallback) {
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface>
|
|
single_service_binding(ComponentContextForProcess()->outgoing().get(),
|
|
&test_service_);
|
|
|
|
base::RunLoop run_loop;
|
|
single_service_binding.SetOnLastClientCallback(run_loop.QuitClosure());
|
|
|
|
auto current_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(current_client), ZX_OK);
|
|
current_client = nullptr;
|
|
|
|
run_loop.Run();
|
|
}
|
|
|
|
// Test the kConnectOnce option for ScopedSingleClientServiceBinding properly
|
|
// stops publishing the service after a first disconnect.
|
|
TEST_F(ScopedServiceBindingTest, ConnectOnce_OnlyFirstConnectionSucceeds) {
|
|
ScopedSingleClientServiceBinding<testfidl::TestInterface,
|
|
ScopedServiceBindingPolicy::kConnectOnce>
|
|
binding(ComponentContextForProcess()->outgoing().get(), &test_service_);
|
|
|
|
// Connect the first client, and verify that it is functional.
|
|
auto existing_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
|
|
|
|
// Connect the second client, then verify that it gets closed and the existing
|
|
// one remains functional.
|
|
auto new_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(new_client);
|
|
EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
|
|
|
|
// Disconnect the first client.
|
|
existing_client.Unbind().TakeChannel().reset();
|
|
RunLoop().RunUntilIdle();
|
|
|
|
// Re-connect the second client, then verify that it gets closed.
|
|
new_client =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(new_client);
|
|
}
|
|
|
|
// Test the last client callback is called every time the number of active
|
|
// clients reaches 0.
|
|
TEST_F(ScopedServiceBindingTest, MultipleLastClientCallback) {
|
|
ScopedServiceBinding<testfidl::TestInterface> binding(
|
|
ComponentContextForProcess()->outgoing().get(), &test_service_);
|
|
int disconnect_count = 0;
|
|
binding.SetOnLastClientCallback(
|
|
BindLambdaForTesting([&disconnect_count]() { ++disconnect_count; }));
|
|
|
|
// Connect a client, verify it is functional.
|
|
auto stub =
|
|
test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
|
|
|
|
// Disconnect the client, the callback should have been called once.
|
|
stub = nullptr;
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_EQ(disconnect_count, 1);
|
|
|
|
// Re-connect the client, verify it is functional.
|
|
stub = test_context_.published_services()->Connect<testfidl::TestInterface>();
|
|
EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
|
|
|
|
// Disconnect the client, the callback should have been called a second time.
|
|
stub = nullptr;
|
|
RunLoop().RunUntilIdle();
|
|
EXPECT_EQ(disconnect_count, 2);
|
|
}
|
|
|
|
} // namespace base
|