| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef LIB_SYS_COMPONENT_CPP_TESTING_SCOPED_CHILD_H_ |
| #define LIB_SYS_COMPONENT_CPP_TESTING_SCOPED_CHILD_H_ |
| |
| #include <fuchsia/component/cpp/fidl.h> |
| #include <fuchsia/component/decl/cpp/fidl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fidl/cpp/interface_handle.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/fidl/cpp/wire/connect_service.h> |
| #include <lib/fit/result.h> |
| #include <lib/sys/component/cpp/testing/execution_controller.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <lib/zx/result.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| namespace component_testing { |
| |
| // A scoped instance of a dynamically created child component. This class |
| // will automatically destroy the child component once it goes out of scope. |
| class ScopedChild final { |
| public: |
| // Create a dynamic child component using the fuchsia.component.Realm API. |
| // |realm_proxy| must be bound to a connection to the fuchsia.component.Realm protocol. |
| // |collection| is the name of the collection to create the child under. This |
| // field must refer to a name in the current component's manifest file. |
| // |name| is the name to assign to the child. |
| // |url| is the component component URL of the child component. |
| static ScopedChild New(fuchsia::component::RealmSyncPtr realm_proxy, std::string collection, |
| std::string name, std::string url); |
| |
| // Same as above with a randomly generated `name`. |
| static ScopedChild New(fuchsia::component::RealmSyncPtr realm_proxy, std::string collection, |
| std::string url); |
| |
| // Create a dynamic child component using the fuchsia.component.Realm API. |
| // |collection| is the name of the collection to create the child under. This |
| // field must refer to a name in the current component's manifest file. |
| // |name| is the name to assign to the child. |
| // |url| is the component component URL of the child component. |
| // |svc| is used to make a connection to the protocol. If it's not provided, |
| // then the namespace entry will be used. |
| static ScopedChild New(std::string collection, std::string name, std::string url, |
| std::shared_ptr<sys::ServiceDirectory> svc = nullptr); |
| |
| // Same as above with a randomly generated `name`. |
| static ScopedChild New(std::string collection, std::string url, |
| std::shared_ptr<sys::ServiceDirectory> svc = nullptr); |
| |
| ~ScopedChild(); |
| |
| ScopedChild(ScopedChild&&) = default; |
| ScopedChild& operator=(ScopedChild&&) = default; |
| |
| ScopedChild(const ScopedChild&) = delete; |
| ScopedChild& operator=(const ScopedChild&) = delete; |
| |
| using TeardownCallback = fit::function<void(fit::result<fuchsia::component::Error>)>; |
| // Calls fuchsia.component/Realm.DestroyChild asynchronously on |dispatcher|. |
| // |callback| will be invoked when Component Manager has completed |
| // the realm teardown. |
| void Teardown(async_dispatcher_t* dispatcher, TeardownCallback callback); |
| |
| // Connect to an interface in the exposed directory of the child component. |
| // |
| // The discovery name of the interface is inferred from the C++ type of the |
| // interface. Callers can supply an interface name explicitly to override |
| // the default name. |
| // |
| // This overload panics if the connection operation doesn't return ZX_OK. |
| // Callers that wish to receive that status should use one of the other |
| // overloads that returns a |zx_status_t|. |
| // |
| // # Example |
| // |
| // ``` |
| // auto echo = instance.Connect<test::placeholders::Echo>(); |
| // ``` |
| template <typename Interface> |
| fidl::InterfacePtr<Interface> Connect( |
| const std::string& interface_name = Interface::Name_) const { |
| fidl::InterfacePtr<Interface> result; |
| zx_status_t status = Connect(interface_name, result.NewRequest().TakeChannel()); |
| ZX_ASSERT_MSG(status == ZX_OK, "Connect to protocol %s on the exposed dir of %s failed: %s", |
| interface_name.c_str(), child_ref_.name.c_str(), zx_status_get_string(status)); |
| return std::move(result); |
| } |
| |
| // SynchronousInterfacePtr method variant of |Connect|. See |Connect| for |
| // more details. |
| template <typename Interface> |
| fidl::SynchronousInterfacePtr<Interface> ConnectSync( |
| const std::string& interface_name = Interface::Name_) const { |
| fidl::SynchronousInterfacePtr<Interface> result; |
| zx_status_t status = Connect(interface_name, result.NewRequest().TakeChannel()); |
| ZX_ASSERT_MSG(status == ZX_OK, "Connect to protocol %s on the exposed dir of %s failed", |
| interface_name.c_str(), child_ref_.name.c_str()); |
| return std::move(result); |
| } |
| |
| // Connect to exposed directory of the child component. |
| template <typename Interface> |
| zx_status_t Connect(fidl::InterfaceRequest<Interface> request) const { |
| return Connect(Interface::Name_, request.TakeChannel()); |
| } |
| |
| // Connect to an interface in the exposed directory using the supplied |
| // channel. |
| zx_status_t Connect(const std::string& interface_name, zx::channel request) const; |
| |
| // ClientEnd variant of |Connect|. See |Connect| for more details. |
| template <typename Protocol, typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>> |
| zx::result<fidl::ClientEnd<Protocol>> Connect( |
| std::string path = fidl::DiscoverableProtocolName<Protocol>) { |
| auto endpoints = fidl::CreateEndpoints<Protocol>(); |
| if (endpoints.is_error()) { |
| return endpoints.take_error(); |
| } |
| |
| if (auto result = Connect(path, endpoints->server.TakeChannel()); result != ZX_OK) { |
| return zx::error(result); |
| } |
| |
| return zx::ok(std::move(endpoints->client)); |
| } |
| |
| // Get the child name of this instance. |
| std::string GetChildName() const; |
| |
| // Clone the exposed directory. |
| fidl::InterfaceHandle<fuchsia::io::Directory> CloneExposedDir() const { |
| fidl::InterfaceHandle<fuchsia::io::Directory> clone; |
| zx_status_t status = exposed_dir_->Clone( |
| fuchsia::io::OpenFlags::CLONE_SAME_RIGHTS, |
| fidl::InterfaceRequest<fuchsia::io::Node>(clone.NewRequest().TakeChannel())); |
| ZX_ASSERT_MSG(status == ZX_OK, "Cloning exposed directory failed: %s", |
| zx_status_get_string(status)); |
| return clone; |
| } |
| |
| // Clone the exposed directory. |
| void CloneExposedDir(fidl::InterfaceRequest<fuchsia::io::Directory> directory_request) const |
| ZX_AVAILABLE_SINCE(11) { |
| zx_status_t status = exposed_dir_->Clone( |
| fuchsia::io::OpenFlags::CLONE_SAME_RIGHTS, |
| fidl::InterfaceRequest<fuchsia::io::Node>(directory_request.TakeChannel())); |
| ZX_ASSERT_MSG(status == ZX_OK, "Cloning exposed directory failed: %s", |
| zx_status_get_string(status)); |
| } |
| |
| // Returns reference to underlying exposed directory handle. |
| const fuchsia::io::DirectorySyncPtr& exposed() const ZX_AVAILABLE_SINCE(11); |
| |
| #if __Fuchsia_API_level__ >= 14 |
| // Starts the component with the provided start arguments (if any). |
| ExecutionController Start(fuchsia::component::StartChildArgs = {}) const; |
| #endif |
| |
| private: |
| ScopedChild(std::shared_ptr<sys::ServiceDirectory> svc, |
| fuchsia::component::decl::ChildRef child_ref, |
| fuchsia::io::DirectorySyncPtr exposed_dir |
| #if __Fuchsia_API_level__ >= 14 |
| , |
| fuchsia::component::ControllerSyncPtr controller_proxy |
| #endif |
| ); |
| |
| // nullptr iff `this` has been moved OR `Teardown` has been called. |
| std::shared_ptr<sys::ServiceDirectory> svc_; |
| fuchsia::component::decl::ChildRef child_ref_; |
| fuchsia::io::DirectorySyncPtr exposed_dir_; |
| #if __Fuchsia_API_level__ >= 14 |
| fuchsia::component::ControllerSyncPtr controller_proxy_; |
| #endif |
| }; |
| |
| } // namespace component_testing |
| |
| #endif // LIB_SYS_COMPONENT_CPP_TESTING_SCOPED_CHILD_H_ |