1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/fuchsia/scoped_service_binding.h"
6
7 #include <lib/sys/cpp/component_context.h>
8 #include <lib/sys/cpp/outgoing_directory.h>
9 #include <lib/sys/cpp/service_directory.h>
10
11 #include "base/fuchsia/process_context.h"
12 #include "base/fuchsia/test_component_context_for_process.h"
13 #include "base/fuchsia/test_interface_impl.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string_piece.h"
16 #include "base/test/bind.h"
17 #include "base/test/task_environment.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19
20 namespace base {
21
22 class ScopedServiceBindingTest : public testing::Test {
23 protected:
24 ScopedServiceBindingTest() = default;
25 ~ScopedServiceBindingTest() override = default;
26
27 const base::test::SingleThreadTaskEnvironment task_environment_{
28 base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
29
30 TestComponentContextForProcess test_context_;
31 TestInterfaceImpl test_service_;
32 };
33
34 // Verifies that ScopedServiceBinding allows connection more than once.
TEST_F(ScopedServiceBindingTest,ConnectTwice)35 TEST_F(ScopedServiceBindingTest, ConnectTwice) {
36 ScopedServiceBinding<testfidl::TestInterface> binding(
37 ComponentContextForProcess()->outgoing().get(), &test_service_);
38
39 auto stub =
40 test_context_.published_services()->Connect<testfidl::TestInterface>();
41 auto stub2 =
42 test_context_.published_services()->Connect<testfidl::TestInterface>();
43 EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
44 EXPECT_EQ(VerifyTestInterface(stub2), ZX_OK);
45 }
46
47 // Verifies that ScopedServiceBinding allows connection more than once.
TEST_F(ScopedServiceBindingTest,ConnectTwiceNewName)48 TEST_F(ScopedServiceBindingTest, ConnectTwiceNewName) {
49 const char kInterfaceName[] = "fuchsia.TestInterface2";
50
51 ScopedServiceBinding<testfidl::TestInterface> new_service_binding(
52 ComponentContextForProcess()->outgoing().get(), &test_service_,
53 kInterfaceName);
54
55 testfidl::TestInterfacePtr stub, stub2;
56 test_context_.published_services()->Connect(kInterfaceName,
57 stub.NewRequest().TakeChannel());
58 test_context_.published_services()->Connect(kInterfaceName,
59 stub2.NewRequest().TakeChannel());
60 EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
61 EXPECT_EQ(VerifyTestInterface(stub2), ZX_OK);
62 }
63
64 // Verify that we can publish a debug service.
TEST_F(ScopedServiceBindingTest,ConnectDebugService)65 TEST_F(ScopedServiceBindingTest, ConnectDebugService) {
66 vfs::PseudoDir* const debug_dir =
67 ComponentContextForProcess()->outgoing()->debug_dir();
68
69 // Publish the test service to the "debug" directory.
70 ScopedServiceBinding<testfidl::TestInterface> debug_service_binding(
71 debug_dir, &test_service_);
72
73 // Connect a ServiceDirectory to the "debug" subdirectory.
74 fidl::InterfaceHandle<fuchsia::io::Directory> debug_handle;
75 debug_dir->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
76 fuchsia::io::OpenFlags::RIGHT_WRITABLE,
77 debug_handle.NewRequest().TakeChannel());
78 sys::ServiceDirectory debug_directory(std::move(debug_handle));
79
80 // Attempt to connect via the "debug" directory.
81 auto debug_stub = debug_directory.Connect<testfidl::TestInterface>();
82 EXPECT_EQ(VerifyTestInterface(debug_stub), ZX_OK);
83
84 // Verify that the service does not appear in the outgoing service directory.
85 auto release_stub =
86 test_context_.published_services()->Connect<testfidl::TestInterface>();
87 EXPECT_EQ(VerifyTestInterface(release_stub), ZX_ERR_PEER_CLOSED);
88 }
89
90 // Verifies that ScopedSingleClientServiceBinding allows a different name.
TEST_F(ScopedServiceBindingTest,SingleClientConnectNewName)91 TEST_F(ScopedServiceBindingTest, SingleClientConnectNewName) {
92 const char kInterfaceName[] = "fuchsia.TestInterface2";
93
94 ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
95 ComponentContextForProcess()->outgoing().get(), &test_service_,
96 kInterfaceName);
97
98 testfidl::TestInterfacePtr stub;
99 test_context_.published_services()->Connect(kInterfaceName,
100 stub.NewRequest().TakeChannel());
101 EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
102 }
103
104 // Verify that if we connect twice to a prefer-new bound service, the existing
105 // connection gets closed.
TEST_F(ScopedServiceBindingTest,SingleClientPreferNew)106 TEST_F(ScopedServiceBindingTest, SingleClientPreferNew) {
107 ScopedSingleClientServiceBinding<testfidl::TestInterface,
108 ScopedServiceBindingPolicy::kPreferNew>
109 binding(ComponentContextForProcess()->outgoing().get(), &test_service_);
110
111 // Connect the first client, and verify that it is functional.
112 auto existing_client =
113 test_context_.published_services()->Connect<testfidl::TestInterface>();
114 EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
115
116 // Connect the second client, so the existing one should be disconnected and
117 // the new should be functional.
118 auto new_client =
119 test_context_.published_services()->Connect<testfidl::TestInterface>();
120 RunLoop().RunUntilIdle();
121 EXPECT_FALSE(existing_client);
122 EXPECT_EQ(VerifyTestInterface(new_client), ZX_OK);
123 }
124
125 // Verify that if we connect twice to a prefer-existing bound service, the new
126 // connection gets closed.
TEST_F(ScopedServiceBindingTest,SingleClientPreferExisting)127 TEST_F(ScopedServiceBindingTest, SingleClientPreferExisting) {
128 ScopedSingleClientServiceBinding<testfidl::TestInterface,
129 ScopedServiceBindingPolicy::kPreferExisting>
130 binding(ComponentContextForProcess()->outgoing().get(), &test_service_);
131
132 // Connect the first client, and verify that it is functional.
133 auto existing_client =
134 test_context_.published_services()->Connect<testfidl::TestInterface>();
135 EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
136
137 // Connect the second client, then verify that the it gets closed and the
138 // existing one remains functional.
139 auto new_client =
140 test_context_.published_services()->Connect<testfidl::TestInterface>();
141 RunLoop().RunUntilIdle();
142 EXPECT_FALSE(new_client);
143 EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
144 }
145
146 // Verify that the default single-client binding policy is prefer-new.
TEST_F(ScopedServiceBindingTest,SingleClientDefaultIsPreferNew)147 TEST_F(ScopedServiceBindingTest, SingleClientDefaultIsPreferNew) {
148 ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
149 ComponentContextForProcess()->outgoing().get(), &test_service_);
150
151 // Connect the first client, and verify that it is functional.
152 auto existing_client =
153 test_context_.published_services()->Connect<testfidl::TestInterface>();
154 EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
155
156 // Connect the second client, so the existing one should be disconnected and
157 // the new should be functional.
158 auto new_client =
159 test_context_.published_services()->Connect<testfidl::TestInterface>();
160 RunLoop().RunUntilIdle();
161 EXPECT_FALSE(existing_client);
162 EXPECT_EQ(VerifyTestInterface(new_client), ZX_OK);
163 }
164
165 // Verify that single-client bindings support publishing to a PseudoDir.
TEST_F(ScopedServiceBindingTest,SingleClientPublishToPseudoDir)166 TEST_F(ScopedServiceBindingTest, SingleClientPublishToPseudoDir) {
167 vfs::PseudoDir* const debug_dir =
168 ComponentContextForProcess()->outgoing()->debug_dir();
169
170 ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
171 debug_dir, &test_service_);
172
173 // Connect a ServiceDirectory to the "debug" subdirectory.
174 fidl::InterfaceHandle<fuchsia::io::Directory> debug_handle;
175 debug_dir->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
176 fuchsia::io::OpenFlags::RIGHT_WRITABLE,
177 debug_handle.NewRequest().TakeChannel());
178 sys::ServiceDirectory debug_directory(std::move(debug_handle));
179
180 // Attempt to connect via the "debug" directory.
181 auto debug_stub = debug_directory.Connect<testfidl::TestInterface>();
182 EXPECT_EQ(VerifyTestInterface(debug_stub), ZX_OK);
183
184 // Verify that the service does not appear in the outgoing service directory.
185 auto release_stub =
186 test_context_.published_services()->Connect<testfidl::TestInterface>();
187 EXPECT_EQ(VerifyTestInterface(release_stub), ZX_ERR_PEER_CLOSED);
188 }
189
TEST_F(ScopedServiceBindingTest,SingleBindingSetOnLastClientCallback)190 TEST_F(ScopedServiceBindingTest, SingleBindingSetOnLastClientCallback) {
191 ScopedSingleClientServiceBinding<testfidl::TestInterface>
192 single_service_binding(ComponentContextForProcess()->outgoing().get(),
193 &test_service_);
194
195 base::RunLoop run_loop;
196 single_service_binding.SetOnLastClientCallback(run_loop.QuitClosure());
197
198 auto current_client =
199 test_context_.published_services()->Connect<testfidl::TestInterface>();
200 EXPECT_EQ(VerifyTestInterface(current_client), ZX_OK);
201 current_client = nullptr;
202
203 run_loop.Run();
204 }
205
206 // Test the kConnectOnce option for ScopedSingleClientServiceBinding properly
207 // stops publishing the service after a first disconnect.
TEST_F(ScopedServiceBindingTest,ConnectOnce_OnlyFirstConnectionSucceeds)208 TEST_F(ScopedServiceBindingTest, ConnectOnce_OnlyFirstConnectionSucceeds) {
209 ScopedSingleClientServiceBinding<testfidl::TestInterface,
210 ScopedServiceBindingPolicy::kConnectOnce>
211 binding(ComponentContextForProcess()->outgoing().get(), &test_service_);
212
213 // Connect the first client, and verify that it is functional.
214 auto existing_client =
215 test_context_.published_services()->Connect<testfidl::TestInterface>();
216 EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
217
218 // Connect the second client, then verify that it gets closed and the existing
219 // one remains functional.
220 auto new_client =
221 test_context_.published_services()->Connect<testfidl::TestInterface>();
222 RunLoop().RunUntilIdle();
223 EXPECT_FALSE(new_client);
224 EXPECT_EQ(VerifyTestInterface(existing_client), ZX_OK);
225
226 // Disconnect the first client.
227 existing_client.Unbind().TakeChannel().reset();
228 RunLoop().RunUntilIdle();
229
230 // Re-connect the second client, then verify that it gets closed.
231 new_client =
232 test_context_.published_services()->Connect<testfidl::TestInterface>();
233 RunLoop().RunUntilIdle();
234 EXPECT_FALSE(new_client);
235 }
236
237 // Test the last client callback is called every time the number of active
238 // clients reaches 0.
TEST_F(ScopedServiceBindingTest,MultipleLastClientCallback)239 TEST_F(ScopedServiceBindingTest, MultipleLastClientCallback) {
240 ScopedServiceBinding<testfidl::TestInterface> binding(
241 ComponentContextForProcess()->outgoing().get(), &test_service_);
242 int disconnect_count = 0;
243 binding.SetOnLastClientCallback(
244 BindLambdaForTesting([&disconnect_count]() { ++disconnect_count; }));
245
246 // Connect a client, verify it is functional.
247 auto stub =
248 test_context_.published_services()->Connect<testfidl::TestInterface>();
249 EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
250
251 // Disconnect the client, the callback should have been called once.
252 stub = nullptr;
253 RunLoop().RunUntilIdle();
254 EXPECT_EQ(disconnect_count, 1);
255
256 // Re-connect the client, verify it is functional.
257 stub = test_context_.published_services()->Connect<testfidl::TestInterface>();
258 EXPECT_EQ(VerifyTestInterface(stub), ZX_OK);
259
260 // Disconnect the client, the callback should have been called a second time.
261 stub = nullptr;
262 RunLoop().RunUntilIdle();
263 EXPECT_EQ(disconnect_count, 2);
264 }
265
266 } // namespace base
267