1 // Copyright 2022 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.
5 #include "components/fuchsia_component_support/dynamic_component_host.h"
7 #include <fuchsia/component/cpp/fidl_test_base.h>
8 #include <lib/sys/cpp/component_context.h>
9 #include <lib/sys/cpp/service_directory.h>
10 #include <lib/vfs/cpp/pseudo_dir.h>
11 #include <lib/vfs/cpp/service.h>
16 #include "base/fuchsia/process_context.h"
17 #include "base/fuchsia/scoped_service_binding.h"
18 #include "base/fuchsia/test_component_context_for_process.h"
19 #include "base/test/bind.h"
20 #include "base/test/gtest_util.h"
21 #include "base/test/task_environment.h"
22 #include "components/fuchsia_component_support/mock_realm.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 namespace fuchsia_component_support {
32 MATCHER_P(EqCollectionRef, name, "") {
33 return arg.name == name;
36 MATCHER_P2(EqChildDecl, name, url, "") {
37 return arg.has_name() && arg.name() == name && arg.has_url() &&
41 MATCHER_P2(EqChildRef, name, collection, "") {
42 return arg.name == name && arg.collection == collection;
45 // Verifies that `create_child_args` includes a dynamic offer for "/svc", and
46 // returns a channel connected to it, if so.
47 fidl::InterfaceHandle<fuchsia::io::Directory> GetSvcFromChildArgs(
48 fuchsia::component::CreateChildArgs& create_child_args) {
49 if (!create_child_args.has_dynamic_offers()) {
53 for (auto& offer : create_child_args.dynamic_offers()) {
54 if (!offer.is_directory()) {
58 const auto& directory_offer = offer.directory();
59 if (!directory_offer.has_source_name() ||
60 !directory_offer.has_target_name()) {
63 if (directory_offer.target_name() != "svc") {
67 // Connect to the outgoing directory root, to use to look up the service
69 fidl::InterfacePtr<fuchsia::io::Directory> root_dir;
70 base::ComponentContextForProcess()->outgoing()->root_dir()->Serve(
71 fuchsia::io::OpenFlags::RIGHT_READABLE |
72 fuchsia::io::OpenFlags::RIGHT_WRITABLE |
73 fuchsia::io::OpenFlags::DIRECTORY,
74 root_dir.NewRequest().TakeChannel());
76 // Determine the capability path, relative to the outgoing directory of
77 // the calling process, and request to open it.
78 // The channel will be closed as soon as the Open() call is processed,
79 // if the path cannot be resolved.
80 base::FilePath path(directory_offer.source_name());
81 if (directory_offer.has_subdir()) {
82 path = path.Append(directory_offer.subdir());
84 fidl::InterfaceHandle<fuchsia::io::Node> services_handle;
85 root_dir->Open(fuchsia::io::OpenFlags::RIGHT_READABLE |
86 fuchsia::io::OpenFlags::RIGHT_WRITABLE |
87 fuchsia::io::OpenFlags::DIRECTORY,
88 {}, path.value(), services_handle.NewRequest());
89 return fidl::InterfaceHandle<fuchsia::io::Directory>(
90 services_handle.TakeChannel());
96 bool HasPeerClosedHandle(
97 const fidl::InterfaceHandle<fuchsia::io::Directory>& handle) {
98 return handle.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(),
99 nullptr) != ZX_ERR_TIMED_OUT;
102 constexpr char kTestCollection[] = "test_collection";
103 constexpr char kTestChildId[] = "test-child-id";
104 constexpr char kTestComponentUrl[] = "dummy:url";
106 class DynamicComponentHostTest : public testing::Test {
108 DynamicComponentHostTest() : realm_(test_context_.additional_services()) {
109 // By default simply reply indicating success, from Create/DestroyChild.
110 ON_CALL(realm_, CreateChild)
112 [](fuchsia::component::decl::CollectionRef,
113 fuchsia::component::decl::Child,
114 fuchsia::component::CreateChildArgs,
115 fuchsia::component::Realm::CreateChildCallback callback) {
118 ON_CALL(realm_, DestroyChild)
120 [](fuchsia::component::decl::ChildRef,
121 fuchsia::component::Realm::DestroyChildCallback callback) {
125 // By default connect exposed directory requests to `exposed_`, to simplify
126 // tests for exposed capabilities.
127 ON_CALL(realm_, OpenExposedDir)
129 [this](fuchsia::component::decl::ChildRef,
130 fidl::InterfaceRequest<fuchsia::io::Directory> exposed_dir,
131 fuchsia::component::Realm::OpenExposedDirCallback callback) {
132 exposed_.Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
133 fuchsia::io::OpenFlags::RIGHT_WRITABLE,
134 exposed_dir.TakeChannel());
139 // Sets expectations on CreateChild(), OpenExposedDir() and DestroyChild()
140 // being called, in that order, without expecting particular parameters.
141 void ExpectCreateOpenAndDestroy() {
142 testing::InSequence s;
143 EXPECT_CALL(realm_, CreateChild(_, _, _, _));
144 EXPECT_CALL(realm_, OpenExposedDir(_, _, _));
145 EXPECT_CALL(realm_, DestroyChild(_, _));
148 base::test::SingleThreadTaskEnvironment task_environment_{
149 base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
151 base::TestComponentContextForProcess test_context_;
153 testing::StrictMock<MockRealm> realm_;
155 // Used to fake the "exposed dir" of the component.
156 vfs::PseudoDir exposed_;
159 TEST_F(DynamicComponentHostTest, Basic) {
160 ExpectCreateOpenAndDestroy();
162 // Create and then immediately teardown the component.
164 DynamicComponentHost component(kTestCollection, kTestChildId,
165 kTestComponentUrl, base::DoNothing(),
169 // Spin the loop to allow the calls to reach `realm_`.
170 base::RunLoop().RunUntilIdle();
173 TEST_F(DynamicComponentHostTest, CollectionAndChildName) {
175 testing::InSequence s;
178 CreateChild(EqCollectionRef(kTestCollection),
179 EqChildDecl(kTestChildId, kTestComponentUrl), _, _));
180 EXPECT_CALL(realm_, OpenExposedDir(
181 EqChildRef(kTestChildId, kTestCollection), _, _));
183 DestroyChild(EqChildRef(kTestChildId, kTestCollection), _));
187 DynamicComponentHost component(kTestCollection, kTestChildId,
188 kTestComponentUrl, base::DoNothing(),
192 // Spin the loop to allow the calls to reach `realm_`.
193 base::RunLoop().RunUntilIdle();
196 TEST_F(DynamicComponentHostTest, OnTeardownCalledOnBinderClose) {
197 ExpectCreateOpenAndDestroy();
199 // Publish a fake Binder to the `exposed_` directory.
200 fidl::InterfaceRequest<fuchsia::component::Binder> binder_request;
202 fuchsia::component::Binder::Name_,
203 std::make_unique<vfs::Service>(
204 [&binder_request](zx::channel request, async_dispatcher_t*) {
205 binder_request = fidl::InterfaceRequest<fuchsia::component::Binder>(
210 DynamicComponentHost component(
211 kTestCollection, kTestChildId, kTestComponentUrl,
212 base::MakeExpectedRunClosure(FROM_HERE), nullptr);
214 // Spin the loop to process calls to `realm_` and `exposed_`.
215 base::RunLoop().RunUntilIdle();
216 EXPECT_TRUE(binder_request);
218 // Close `binder_request` and spin the loop to allow that to be observed.
219 binder_request = nullptr;
220 base::RunLoop().RunUntilIdle();
223 // Spin the loop to allow remaining calls to reach `realm_`.
224 base::RunLoop().RunUntilIdle();
227 TEST_F(DynamicComponentHostTest,
228 OnTeardownNotCalledIfDestroyedBeforeBinderClose) {
229 ExpectCreateOpenAndDestroy();
231 // Create and immediately teardown the component, so that Binder teardown
232 // will not be observed until after the DynamicComponentHost has gone.
234 DynamicComponentHost component(
235 kTestCollection, kTestChildId, kTestComponentUrl,
236 base::MakeExpectedNotRunClosure(FROM_HERE), nullptr);
239 // Spin the loop to allow remaining calls to reach `realm_`.
240 base::RunLoop().RunUntilIdle();
243 TEST_F(DynamicComponentHostTest, WithoutServiceDirectory) {
244 // Capture the `CreateChildArgs` from the `Realm.CreateChild()` call.
245 fuchsia::component::CreateChildArgs create_child_args;
247 testing::InSequence s;
248 EXPECT_CALL(realm_, CreateChild(_, _, _, _))
249 .WillOnce([&create_child_args](
250 fuchsia::component::decl::CollectionRef,
251 fuchsia::component::decl::Child,
252 fuchsia::component::CreateChildArgs args,
253 fuchsia::component::Realm::CreateChildCallback callback) {
254 create_child_args = std::move(args);
257 EXPECT_CALL(realm_, OpenExposedDir(_, _, _));
258 EXPECT_CALL(realm_, DestroyChild(_, _));
262 DynamicComponentHost component(kTestCollection, kTestChildId,
263 kTestComponentUrl, base::DoNothing(),
266 // Spin the event loop to process the `CreateChild()` call.
267 base::RunLoop().RunUntilIdle();
269 // Verify that no "svc" directory is offered in the `CreateChildArgs`.
270 fidl::InterfaceHandle<fuchsia::io::Directory> svc_handle =
271 GetSvcFromChildArgs(create_child_args);
272 EXPECT_FALSE(svc_handle);
275 // Spin the loop to allow the teardown calls to reach `realm_`.
276 base::RunLoop().RunUntilIdle();
279 TEST_F(DynamicComponentHostTest, WithServiceDirectory) {
280 // Capture the `CreateChildArgs` from the `Realm.CreateChild()` call.
281 fuchsia::component::CreateChildArgs create_child_args;
283 testing::InSequence s;
284 EXPECT_CALL(realm_, CreateChild(_, _, _, _))
285 .WillOnce([&create_child_args](
286 fuchsia::component::decl::CollectionRef,
287 fuchsia::component::decl::Child,
288 fuchsia::component::CreateChildArgs args,
289 fuchsia::component::Realm::CreateChildCallback callback) {
290 create_child_args = std::move(args);
293 EXPECT_CALL(realm_, OpenExposedDir(_, _, _));
294 EXPECT_CALL(realm_, DestroyChild(_, _));
298 // Create a directory handle for the service directory.
299 fidl::InterfaceHandle<fuchsia::io::Directory> handle;
300 vfs::PseudoDir service_directory;
301 service_directory.Serve(fuchsia::io::OpenFlags::RIGHT_READABLE |
302 fuchsia::io::OpenFlags::RIGHT_WRITABLE,
303 handle.NewRequest().TakeChannel());
305 DynamicComponentHost component(kTestCollection, kTestChildId,
306 kTestComponentUrl, base::DoNothing(),
309 // Spin the event loop to process the `CreateChild()` call.
310 base::RunLoop().RunUntilIdle();
312 // Verify that a "svc" directory was offered in the `CreateChildArgs`.
313 fidl::InterfaceHandle<fuchsia::io::Directory> svc_handle =
314 GetSvcFromChildArgs(create_child_args);
315 EXPECT_TRUE(svc_handle);
317 // Spin the event loop to allow the Open() of the directory attempted by
318 // GetSvcFromChildArgs() to be processed, then verify that the `svc_handle`
319 // was not closed by the peer.
320 base::RunLoop().RunUntilIdle();
321 EXPECT_FALSE(HasPeerClosedHandle(svc_handle));
324 // Spin the loop to allow teardown calls to reach `realm_`.
325 base::RunLoop().RunUntilIdle();
327 // Verify that the "svc" directory offered in the `CreateChildArgs` is no
328 // longer available after the `DynamicComponentHost` has been destroyed.
329 fidl::InterfaceHandle<fuchsia::io::Directory> svc_handle =
330 GetSvcFromChildArgs(create_child_args);
331 EXPECT_TRUE(svc_handle);
333 // Spin the loop to allow the Open() of the "svc" directory to be processed.
334 base::RunLoop().RunUntilIdle();
335 EXPECT_TRUE(HasPeerClosedHandle(svc_handle));
340 } // namespace fuchsia_component_support