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