// 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 #include #include #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 binding( ComponentContextForProcess()->outgoing().get(), &test_service_); auto stub = test_context_.published_services()->Connect(); auto stub2 = test_context_.published_services()->Connect(); 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 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 debug_service_binding( debug_dir, &test_service_); // Connect a ServiceDirectory to the "debug" subdirectory. fidl::InterfaceHandle 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(); 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(); 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 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 binding(ComponentContextForProcess()->outgoing().get(), &test_service_); // Connect the first client, and verify that it is functional. auto existing_client = test_context_.published_services()->Connect(); 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(); 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 binding(ComponentContextForProcess()->outgoing().get(), &test_service_); // Connect the first client, and verify that it is functional. auto existing_client = test_context_.published_services()->Connect(); 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(); 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 binding( ComponentContextForProcess()->outgoing().get(), &test_service_); // Connect the first client, and verify that it is functional. auto existing_client = test_context_.published_services()->Connect(); 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(); 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 binding( debug_dir, &test_service_); // Connect a ServiceDirectory to the "debug" subdirectory. fidl::InterfaceHandle 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(); 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(); EXPECT_EQ(VerifyTestInterface(release_stub), ZX_ERR_PEER_CLOSED); } TEST_F(ScopedServiceBindingTest, SingleBindingSetOnLastClientCallback) { ScopedSingleClientServiceBinding 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(); 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 binding(ComponentContextForProcess()->outgoing().get(), &test_service_); // Connect the first client, and verify that it is functional. auto existing_client = test_context_.published_services()->Connect(); 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(); 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(); 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 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(); 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(); 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