blob: 699521b26c1834acdaca61b8ef390447c0993b83 [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_REALM_BUILDER_H_
#define LIB_SYS_COMPONENT_CPP_TESTING_REALM_BUILDER_H_
#include <fuchsia/component/config/cpp/fidl.h>
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>
#include <fuchsia/component/runner/cpp/fidl.h>
#include <fuchsia/component/test/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/sys/component/cpp/testing/internal/local_component_runner.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/sys/component/cpp/testing/scoped_child.h>
#include <lib/sys/cpp/service_directory.h>
#include <zircon/availability.h>
#include <zircon/errors.h>
#include <cstddef>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
namespace component_testing {
// Default child options provided to all components.
const ChildOptions kDefaultChildOptions{.startup_mode = StartupMode::LAZY,
.environment = ""
#if __Fuchsia_API_level__ >= 13
,
.config_overrides = {}
#endif
};
// Default child collection name for constructed root.
constexpr char kDefaultCollection[] = "realm_builder";
// Root of a constructed Realm. This object can not be instantiated directly.
// Instead, it can only be constructed with the Realm::Builder/Build().
//
// TODO(https://fxbug.dev/42071205): Remove all deprecated methods below, e.g. |Connect|.
class RealmRoot final {
public:
RealmRoot(RealmRoot&& other) = default;
RealmRoot& operator=(RealmRoot&& other) = default;
RealmRoot(const RealmRoot& other) = delete;
RealmRoot& operator=(const RealmRoot& other) = delete;
virtual ~RealmRoot();
// Connect to an interface in the exposed directory of the root 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 |Connect| 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 = realm.Connect<test::placeholders::Echo>();
// ```
template <typename Interface>
fidl::InterfacePtr<Interface> Connect(const std::string& interface_name = Interface::Name_) const
ZX_DEPRECATED_SINCE(1, 11, "Use component() instead") {
return root_.Connect<Interface>(interface_name);
}
// SynchronousInterfacePtr method overload of |Connect|. See
// method above for more details.
template <typename Interface>
fidl::SynchronousInterfacePtr<Interface> ConnectSync(
const std::string& interface_name = Interface::Name_) const
ZX_DEPRECATED_SINCE(1, 11, "Use component() instead") {
return root_.ConnectSync<Interface>(interface_name);
}
// Connect to exposed directory of the root component.
template <typename Interface>
zx_status_t Connect(fidl::InterfaceRequest<Interface> request) const
ZX_DEPRECATED_SINCE(1, 11, "Use component() instead") {
return root_.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
ZX_DEPRECATED_SINCE(1, 11, "Use component() instead");
// Return a handle to the exposed directory of the root component.
fidl::InterfaceHandle<fuchsia::io::Directory> CloneRoot() const
ZX_DEPRECATED_SINCE(1, 11, "Use component().CloneExposedDir() instead") {
return root_.CloneExposedDir();
}
// Get the child name of the root component.
std::string GetChildName() const ZX_DEPRECATED_SINCE(1, 11, "Use component() instead");
// Destructs the root component and sends Component Manager a request to
// destroy its realm, which will stop all child components. Each
// |LocalComponentImpl| should receive an |OnStop()| callback, and after
// returning, the |LocalComponentImpl| will be destructed.
// |on_teardown_complete| will be invoked when Component Manager has completed
// the realm teardown.
void Teardown(ScopedChild::TeardownCallback on_teardown_complete) ZX_AVAILABLE_SINCE(10);
// Returns reference to underlying |ScopedChild| object. Note that this object
// will be destroyed if |Teardown| is invoked. In that scenario, using this
// value will yield undefined behavior. Invoking this method after |Teardown| is
// invoked will cause this process to panic.
ScopedChild& component() ZX_AVAILABLE_SINCE(11);
const ScopedChild& component() const ZX_AVAILABLE_SINCE(11);
private:
// Friend classes are needed because the constructor is private.
friend class Realm;
friend class RealmBuilder;
RealmRoot(std::unique_ptr<internal::LocalComponentRunner> local_component_runner,
ScopedChild root, async_dispatcher_t* dispatcher);
std::unique_ptr<internal::LocalComponentRunner> local_component_runner_;
ScopedChild root_;
async_dispatcher_t* dispatcher_;
};
// A `Realm` describes a component instance together with its children.
// Clients can use this class to build a realm from scratch,
// programmatically adding children and routes.
//
// Clients may also use this class to recursively build sub-realms by calling
// `AddChildRealm`.
// For more information about RealmBuilder, see the following link.
// https://fuchsia.dev/fuchsia-src/development/testing/components/realm_builder
// For examples on how to use this library, see the integration tests
// found at //sdk/cpp/tests/realm_builder_test.cc
class Realm final {
public:
Realm(Realm&&) = default;
Realm& operator=(Realm&&) = default;
Realm(const Realm&) = delete;
Realm operator=(const Realm&) = delete;
// Add a v2 component (.cm) to this Realm.
// Names must be unique. Duplicate names will result in a panic.
Realm& AddChild(const std::string& child_name, const std::string& url,
const ChildOptions& options = kDefaultChildOptions);
// This method signature is DEPRECATED.
//
// Add a component instance implementation by raw pointer to a
// LocalComponent-derived instance. This component implementation can only be
// started once.
//
// The caller is expected to keep the pointer valid for the lifetime of the
// component instance (typically the lifetime of the constructed RealmRoot,
// unless the component is intentionally stopped earlier). If not, calling
// FIDL bindings handled by the LocalComponent would cause undefined behavior.
//
// |Start()| will be called (asynchronously) sometime after calling
// |RealmBuilder::Build()|. Use |ChildOptions| |StartupMode::EAGER| to request
// component manager start the component automatically.
//
// Names must be unique. Duplicate names will result in a panic.
//
// TODO(https://fxbug.dev/296292544): Remove this method when build support
// for API level 16 is removed.
Realm& AddLocalChild(const std::string& child_name, LocalComponent* local_impl,
const ChildOptions& options = kDefaultChildOptions)
ZX_REMOVED_SINCE(1, 9, 17, "Use AddLocalChild(..., LocalComponentFactory, ...) instead.");
// Add a component by implementing a factory function that creates and returns
// a new instance of a |LocalComponentImpl|-derived class. The factory
// function will be called whenever the local child is started.
//
// After returning the |LocalComponentImpl|, the RealmBuilder framework will
// call |LocalComponentImpl::OnStart()|. Component handles (|ns()|, |svc()|,
// and |outgoing()|) are not available during the |LocalComponentImpl|
// construction, but are available when |OnStart()| is invoked.
//
// If the component's associated |ComponentController| receives a |Stop()|
// request, the |LocalComponentImpl::OnStop()| method will be called. A
// derived |LocalComponentImpl| class can override the |OnStop()| method if
// the component wishes to take some action during component stop.
//
// A |LocalComponentImpl| can also self-terminate, by calling `Exit()`.
//
// Names must be unique. Duplicate names will result in a panic.
Realm& AddLocalChild(const std::string& child_name, LocalComponentFactory local_impl,
const ChildOptions& options = kDefaultChildOptions);
// Create a sub realm as child of this Realm instance. The constructed
// Realm is returned.
Realm AddChildRealm(const std::string& child_name,
const ChildOptions& options = kDefaultChildOptions);
// Route a capability from one child to another.
Realm& AddRoute(Route route);
// Offers a directory capability to a component in this realm. The
// directory will be read-only (i.e. have `r*` rights), and will have the
// contents described in `directory`.
Realm& RouteReadOnlyDirectory(const std::string& name, std::vector<Ref> to,
DirectoryContents directory);
// Load the packaged configuration of the component if available.
Realm& InitMutableConfigFromPackage(const std::string& name);
// Allow setting configuration values without loading packaged configuration.
Realm& InitMutableConfigToEmpty(const std::string& name);
// Replaces the value of a given configuration field
Realm& SetConfigValue(const std::string& name, const std::string& key, ConfigValue value);
#if __Fuchsia_API_level__ >= FUCHSIA_HEAD
// Adds Configuration Capabilities to the root realm.
Realm& AddConfiguration(std::vector<ConfigCapability> configurations);
#endif
// Fetches the Component decl of the given child. This operation is only
// supported for:
//
// * A component with a local implementation
// * A legacy component
// * A component added with a fragment-only component URL (typically,
// components bundled in the same package as the realm builder client,
// sharing the same `/pkg` directory, for example,
// `#meta/other-component.cm`; see
// https://fuchsia.dev/fuchsia-src/reference/components/url#relative-fragment-only)
// * An automatically generated realm (such as the root)
fuchsia::component::decl::Component GetComponentDecl(const std::string& child_name);
// Fetches the Component decl of this Realm.
fuchsia::component::decl::Component GetRealmDecl();
// Updates the Component decl of the given child. This operation is only
// supported for:
//
// * A component with a local implementation
// * A legacy component
// * A component added with a fragment-only component URL (typically,
// components bundled in the same package as the realm builder client,
// sharing the same `/pkg` directory, for example,
// `#meta/other-component.cm`; see
// https://fuchsia.dev/fuchsia-src/reference/components/url#relative-fragment-only)
// * An automatically generated realm (such as the root)
void ReplaceComponentDecl(const std::string& child_name,
fuchsia::component::decl::Component decl);
// Updates the Component decl of this Realm.
void ReplaceRealmDecl(fuchsia::component::decl::Component decl);
friend class RealmBuilder;
private:
explicit Realm(fuchsia::component::test::RealmSyncPtr realm_proxy,
std::shared_ptr<internal::LocalComponentRunner::Builder> runner_builder,
std::vector<std::string> scope = {});
std::string GetResolvedName(const std::string& child_name);
Realm& AddLocalChildImpl(const std::string& child_name, LocalComponentKind local_impl,
const ChildOptions& options = kDefaultChildOptions);
fuchsia::component::test::RealmSyncPtr realm_proxy_;
std::shared_ptr<internal::LocalComponentRunner::Builder> runner_builder_;
std::vector<std::string> scope_;
};
// Use this Builder class to construct a Realm object.
class RealmBuilder final {
public:
// Factory method to create a new Realm::Builder object.
// |svc| must outlive the RealmBuilder object and created Realm object.
// If it's nullptr, then the current process' "/svc" namespace entry is used.
static RealmBuilder Create(std::shared_ptr<sys::ServiceDirectory> svc = nullptr);
// Same as above but the Realm will contain the contents of the manifest
// located in the test package at the path indicated by the fragment-only URL
// (for example, `#meta/other-component.cm`; see
// https://fuchsia.dev/fuchsia-src/reference/components/url#relative-fragment-only).
static RealmBuilder CreateFromRelativeUrl(std::string_view fragment_only_url,
std::shared_ptr<sys::ServiceDirectory> svc = nullptr);
RealmBuilder(RealmBuilder&&) = default;
RealmBuilder& operator=(RealmBuilder&&) = default;
RealmBuilder(const RealmBuilder&) = delete;
RealmBuilder& operator=(const RealmBuilder&) = delete;
// Add a v2 component (.cm) to the root realm being constructed.
// See |Realm.AddChild| for more details.
RealmBuilder& AddChild(const std::string& child_name, const std::string& url,
const ChildOptions& options = kDefaultChildOptions);
// This method signature is DEPRECATED. Use the LocalComponentFactory
// implementation of AddLocalChild instead.
//
// Add a component by raw pointer to a LocalComponent-derived instance.
// See |Realm.AddLocalChild| for more details.
//
// TODO(https://fxbug.dev/296292544): Remove this method when build support
// for API level 16 is removed.
RealmBuilder& AddLocalChild(const std::string& child_name, LocalComponent* local_impl,
const ChildOptions& options = kDefaultChildOptions)
ZX_REMOVED_SINCE(1, 9, 17, "Use AddLocalChild(..., LocalComponentFactory, ...) instead.");
// Add a component by LocalComponentFactory.
//
// See |Realm.AddLocalChild| for more details.
RealmBuilder& AddLocalChild(const std::string& child_name, LocalComponentFactory local_impl,
const ChildOptions& options = kDefaultChildOptions);
// Create a sub realm as child of the root realm. The constructed
// Realm is returned.
// See |Realm.AddChildRealm| for more details.
Realm AddChildRealm(const std::string& child_name,
const ChildOptions& options = kDefaultChildOptions);
// Route a capability for the root realm being constructed.
// See |Realm.AddRoute| for more details.
RealmBuilder& AddRoute(Route route);
// Offers a directory capability to a component for the root realm.
// See |Realm.RouteReadOnlyDirectory| for more details.
RealmBuilder& RouteReadOnlyDirectory(const std::string& name, std::vector<Ref> to,
DirectoryContents directory);
// Load the packaged configuration of the component if available.
RealmBuilder& InitMutableConfigFromPackage(const std::string& name);
// Allow setting configuration values without loading packaged configuration.
RealmBuilder& InitMutableConfigToEmpty(const std::string& name);
#if __Fuchsia_API_level__ >= FUCHSIA_HEAD
// Adds Configuration Capabilities to the root realm.
RealmBuilder& AddConfiguration(std::vector<ConfigCapability> configurations);
#endif
// Replaces the value of a given configuration field for the root realm.
RealmBuilder& SetConfigValue(const std::string& name, const std::string& key, ConfigValue value);
// Fetches the Component decl of the given child of the root realm.
// See |Realm.GetComponentDecl| for more details.
fuchsia::component::decl::Component GetComponentDecl(const std::string& child_name);
// Fetches the Component decl of this root realm.
fuchsia::component::decl::Component GetRealmDecl();
// Updates the Component decl of the given child of the root realm.
// See |Realm.GetRealmDecl| for more details.
void ReplaceComponentDecl(const std::string& child_name,
fuchsia::component::decl::Component decl);
// Updates the Component decl of this root realm.
void ReplaceRealmDecl(fuchsia::component::decl::Component decl);
// Set the name of the collection that the realm will be added to.
// By default this is set to |kDefaultCollection|.
//
// Note that this collection name is referenced in the Realm Builder
// shard (//sdk/lib/sys/component/realm_builder_base.shard.cml) under the
// collection name |kDefaultCollection|. To retain the same routing, component
// authors that override the collection name should make the appropriate
// changes in the test component's manifest.
RealmBuilder& SetRealmCollection(const std::string& collection);
// Set the name for the constructed realm. By default, a randomly
// generated string is used.
RealmBuilder& SetRealmName(const std::string& name);
#if __Fuchsia_API_level__ >= 14
// Sets whether or not the realm will be started when `Build` is called.
RealmBuilder& StartOnBuild(bool start_on_build) {
start_on_build_ = start_on_build;
return *this;
}
#endif
// Build the realm root prepared by the associated builder methods, e.g. |AddComponent|.
// |dispatcher| must be non-null, or |async_get_default_dispatcher| must be
// configured to return a non-null value
// This function can only be called once per Realm::Builder instance.
// Multiple invocations will result in a panic.
// |dispatcher| must outlive the lifetime of the constructed |RealmRoot|.
RealmRoot Build(async_dispatcher_t* dispatcher = nullptr);
// A reference to the root `Realm` object.
Realm& root();
private:
RealmBuilder(std::shared_ptr<sys::ServiceDirectory> svc,
fuchsia::component::test::BuilderSyncPtr builder_proxy,
fuchsia::component::test::RealmSyncPtr test_realm_proxy);
static RealmBuilder CreateImpl(
cpp17::optional<std::string_view> fragment_only_url = cpp17::nullopt,
std::shared_ptr<sys::ServiceDirectory> svc = nullptr);
bool realm_commited_ = false;
bool start_on_build_ = true;
std::string realm_collection_ = kDefaultCollection;
cpp17::optional<std::string> realm_name_ = cpp17::nullopt;
std::shared_ptr<sys::ServiceDirectory> svc_;
fuchsia::component::test::BuilderSyncPtr builder_proxy_;
std::shared_ptr<internal::LocalComponentRunner::Builder> runner_builder_;
Realm root_;
};
} // namespace component_testing
#endif // LIB_SYS_COMPONENT_CPP_TESTING_REALM_BUILDER_H_