blob: ef5e2ee48d7a00e2782c11d8379d9b2c4f16443c [file] [log] [blame]
// 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_