blob: a4e7cf0fb46b4bb3afcb55e1c0bf4f26dfb3d5cf [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 <lib/async/dispatcher.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.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&&) noexcept;
ScopedChild& operator=(ScopedChild&&) noexcept;
ScopedChild(const ScopedChild&) = delete;
ScopedChild& operator=(const ScopedChild&) = delete;
// When the destructor of this object is invoked, call
// fuchsia.component/Realm.DestroyChild asynchronously. This will make the
// operation non-blocking, which is useful if a test has a slow running
// teardown or if destruction *must* be async for any other reason.
// |dispatcher| must be non-null, or |async_get_default_dispatcher| must be
// configured to return a non-null value
// |dispatcher| must outlive the lifetime of this object.
void MakeTeardownAsync(async_dispatcher_t* dispatcher = nullptr);
// 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 for |ConnectAtExposedDir| 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 overload of |ConnectAtExposedDir|. See
// method above 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 exposed_dir_.Connect<Interface>(std::move(request));
}
// 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;
// Get the child name of this instance.
std::string GetChildName() const;
// Clone the exposed directory.
fidl::InterfaceHandle<fuchsia::io::Directory> CloneExposedDir() const {
return exposed_dir_.CloneChannel();
}
private:
ScopedChild(std::shared_ptr<sys::ServiceDirectory> svc,
fuchsia::component::decl::ChildRef child_ref, sys::ServiceDirectory exposed_dir);
std::shared_ptr<sys::ServiceDirectory> svc_ = nullptr;
fuchsia::component::decl::ChildRef child_ref_;
sys::ServiceDirectory exposed_dir_;
async_dispatcher_t* dispatcher_ = nullptr;
bool has_moved_ = false;
};
} // namespace component_testing
// Until all clients of the API have been migrated, keep the legacy namespace.
// TODO(fxbug.dev/90794): Remove this.
namespace sys {
namespace testing {
using component_testing::ScopedChild;
} // namespace testing
} // namespace sys
#endif // LIB_SYS_COMPONENT_CPP_TESTING_SCOPED_CHILD_H_