[M120 Migration][VD] Enable direct rendering for TVPlus
[platform/framework/web/chromium-efl.git] / components / fuchsia_component_support / dynamic_component_host_unittest.cc
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.
4
5 #include "components/fuchsia_component_support/dynamic_component_host.h"
6
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>
12
13 #include <memory>
14 #include <utility>
15
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"
25
26 namespace fuchsia_component_support {
27
28 namespace {
29
30 using testing::_;
31
32 MATCHER_P(EqCollectionRef, name, "") {
33   return arg.name == name;
34 }
35
36 MATCHER_P2(EqChildDecl, name, url, "") {
37   return arg.has_name() && arg.name() == name && arg.has_url() &&
38          arg.url() == url;
39 }
40
41 MATCHER_P2(EqChildRef, name, collection, "") {
42   return arg.name == name && arg.collection == collection;
43 }
44
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()) {
50     return nullptr;
51   }
52
53   for (auto& offer : create_child_args.dynamic_offers()) {
54     if (!offer.is_directory()) {
55       continue;
56     }
57
58     const auto& directory_offer = offer.directory();
59     if (!directory_offer.has_source_name() ||
60         !directory_offer.has_target_name()) {
61       return nullptr;
62     }
63     if (directory_offer.target_name() != "svc") {
64       continue;
65     }
66
67     // Connect to the outgoing directory root, to use to look up the service
68     // capability.
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());
75
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());
83     }
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());
91   }
92
93   return nullptr;
94 }
95
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;
100 }
101
102 constexpr char kTestCollection[] = "test_collection";
103 constexpr char kTestChildId[] = "test-child-id";
104 constexpr char kTestComponentUrl[] = "dummy:url";
105
106 class DynamicComponentHostTest : public testing::Test {
107  protected:
108   DynamicComponentHostTest() : realm_(test_context_.additional_services()) {
109     // By default simply reply indicating success, from Create/DestroyChild.
110     ON_CALL(realm_, CreateChild)
111         .WillByDefault(
112             [](fuchsia::component::decl::CollectionRef,
113                fuchsia::component::decl::Child,
114                fuchsia::component::CreateChildArgs,
115                fuchsia::component::Realm::CreateChildCallback callback) {
116               callback({});
117             });
118     ON_CALL(realm_, DestroyChild)
119         .WillByDefault(
120             [](fuchsia::component::decl::ChildRef,
121                fuchsia::component::Realm::DestroyChildCallback callback) {
122               callback({});
123             });
124
125     // By default connect exposed directory requests to `exposed_`, to simplify
126     // tests for exposed capabilities.
127     ON_CALL(realm_, OpenExposedDir)
128         .WillByDefault(
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());
135               callback({});
136             });
137   }
138
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(_, _));
146   }
147
148   base::test::SingleThreadTaskEnvironment task_environment_{
149       base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
150
151   base::TestComponentContextForProcess test_context_;
152
153   testing::StrictMock<MockRealm> realm_;
154
155   // Used to fake the "exposed dir" of the component.
156   vfs::PseudoDir exposed_;
157 };
158
159 TEST_F(DynamicComponentHostTest, Basic) {
160   ExpectCreateOpenAndDestroy();
161
162   // Create and then immediately teardown the component.
163   {
164     DynamicComponentHost component(kTestCollection, kTestChildId,
165                                    kTestComponentUrl, base::DoNothing(),
166                                    nullptr);
167   }
168
169   // Spin the loop to allow the calls to reach `realm_`.
170   base::RunLoop().RunUntilIdle();
171 }
172
173 TEST_F(DynamicComponentHostTest, CollectionAndChildName) {
174   {
175     testing::InSequence s;
176     EXPECT_CALL(
177         realm_,
178         CreateChild(EqCollectionRef(kTestCollection),
179                     EqChildDecl(kTestChildId, kTestComponentUrl), _, _));
180     EXPECT_CALL(realm_, OpenExposedDir(
181                             EqChildRef(kTestChildId, kTestCollection), _, _));
182     EXPECT_CALL(realm_,
183                 DestroyChild(EqChildRef(kTestChildId, kTestCollection), _));
184   }
185
186   {
187     DynamicComponentHost component(kTestCollection, kTestChildId,
188                                    kTestComponentUrl, base::DoNothing(),
189                                    nullptr);
190   }
191
192   // Spin the loop to allow the calls to reach `realm_`.
193   base::RunLoop().RunUntilIdle();
194 }
195
196 TEST_F(DynamicComponentHostTest, OnTeardownCalledOnBinderClose) {
197   ExpectCreateOpenAndDestroy();
198
199   // Publish a fake Binder to the `exposed_` directory.
200   fidl::InterfaceRequest<fuchsia::component::Binder> binder_request;
201   exposed_.AddEntry(
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>(
206                 std::move(request));
207           }));
208
209   {
210     DynamicComponentHost component(
211         kTestCollection, kTestChildId, kTestComponentUrl,
212         base::MakeExpectedRunClosure(FROM_HERE), nullptr);
213
214     // Spin the loop to process calls to `realm_` and `exposed_`.
215     base::RunLoop().RunUntilIdle();
216     EXPECT_TRUE(binder_request);
217
218     // Close `binder_request` and spin the loop to allow that to be observed.
219     binder_request = nullptr;
220     base::RunLoop().RunUntilIdle();
221   }
222
223   // Spin the loop to allow remaining calls to reach `realm_`.
224   base::RunLoop().RunUntilIdle();
225 }
226
227 TEST_F(DynamicComponentHostTest,
228        OnTeardownNotCalledIfDestroyedBeforeBinderClose) {
229   ExpectCreateOpenAndDestroy();
230
231   // Create and immediately teardown the component, so that Binder teardown
232   // will not be observed until after the DynamicComponentHost has gone.
233   {
234     DynamicComponentHost component(
235         kTestCollection, kTestChildId, kTestComponentUrl,
236         base::MakeExpectedNotRunClosure(FROM_HERE), nullptr);
237   }
238
239   // Spin the loop to allow remaining calls to reach `realm_`.
240   base::RunLoop().RunUntilIdle();
241 }
242
243 TEST_F(DynamicComponentHostTest, WithoutServiceDirectory) {
244   // Capture the `CreateChildArgs` from the `Realm.CreateChild()` call.
245   fuchsia::component::CreateChildArgs create_child_args;
246   {
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);
255           callback({});
256         });
257     EXPECT_CALL(realm_, OpenExposedDir(_, _, _));
258     EXPECT_CALL(realm_, DestroyChild(_, _));
259   }
260
261   {
262     DynamicComponentHost component(kTestCollection, kTestChildId,
263                                    kTestComponentUrl, base::DoNothing(),
264                                    nullptr);
265
266     // Spin the event loop to process the `CreateChild()` call.
267     base::RunLoop().RunUntilIdle();
268
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);
273   }
274
275   // Spin the loop to allow the teardown calls to reach `realm_`.
276   base::RunLoop().RunUntilIdle();
277 }
278
279 TEST_F(DynamicComponentHostTest, WithServiceDirectory) {
280   // Capture the `CreateChildArgs` from the `Realm.CreateChild()` call.
281   fuchsia::component::CreateChildArgs create_child_args;
282   {
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);
291           callback({});
292         });
293     EXPECT_CALL(realm_, OpenExposedDir(_, _, _));
294     EXPECT_CALL(realm_, DestroyChild(_, _));
295   }
296
297   {
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());
304
305     DynamicComponentHost component(kTestCollection, kTestChildId,
306                                    kTestComponentUrl, base::DoNothing(),
307                                    std::move(handle));
308
309     // Spin the event loop to process the `CreateChild()` call.
310     base::RunLoop().RunUntilIdle();
311
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);
316
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));
322   }
323
324   // Spin the loop to allow teardown calls to reach `realm_`.
325   base::RunLoop().RunUntilIdle();
326
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);
332
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));
336 }
337
338 }  // namespace
339
340 }  // namespace fuchsia_component_support