|  | // Copyright 2018 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_CPP_TESTING_ENCLOSING_ENVIRONMENT_H_ | 
|  | #define LIB_SYS_CPP_TESTING_ENCLOSING_ENVIRONMENT_H_ | 
|  |  | 
|  | #include <fuchsia/io/cpp/fidl.h> | 
|  | #include <fuchsia/sys/cpp/fidl.h> | 
|  | #include <lib/async/default.h> | 
|  | #include <lib/fidl/cpp/interface_handle.h> | 
|  | #include <lib/fidl/cpp/interface_request.h> | 
|  | #include <lib/fit/function.h> | 
|  | #include <lib/sys/cpp/service_directory.h> | 
|  | #include <lib/sys/cpp/testing/launcher_impl.h> | 
|  | #include <lib/vfs/cpp/pseudo_dir.h> | 
|  | #include <lib/vfs/cpp/service.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <unordered_map> | 
|  |  | 
|  | namespace sys { | 
|  | namespace testing { | 
|  |  | 
|  | class EnclosingEnvironment; | 
|  |  | 
|  | // EnvironmentServices acts as a container of services to EnclosingEnvironment. | 
|  | // | 
|  | // By default, EnvironmentServices supplies only the parent environment's loader | 
|  | // service. Additional services can be provided through |AddService| and | 
|  | // friends. Typically, this is used to inject fake services for tests, or to | 
|  | // pass through services from the parent environment. | 
|  | // | 
|  | // Every EnclosingEnvironment takes EnvironmentServices as an argument to | 
|  | // instantiation. Services should not be added after the EnclosingEnvironment is | 
|  | // created. | 
|  | class EnvironmentServices { | 
|  | public: | 
|  | using ServiceTerminatedCallback = | 
|  | fit::function<void(const std::string&, int64_t, fuchsia::sys::TerminationReason)>; | 
|  |  | 
|  | struct ParentOverrides final { | 
|  | ParentOverrides(); | 
|  |  | 
|  | ParentOverrides(const ParentOverrides&) = delete; | 
|  | ParentOverrides& operator=(const ParentOverrides&) = delete; | 
|  | ParentOverrides(ParentOverrides&&) noexcept; | 
|  |  | 
|  | std::shared_ptr<vfs::Service> loader_service_; | 
|  | std::shared_ptr<vfs::Service> debug_data_service_; | 
|  | }; | 
|  |  | 
|  | EnvironmentServices(const EnvironmentServices&) = delete; | 
|  | EnvironmentServices& operator=(const EnvironmentServices&) = delete; | 
|  | EnvironmentServices(EnvironmentServices&&) = delete; | 
|  |  | 
|  | // Creates services with some of parent's service. | 
|  | static std::unique_ptr<EnvironmentServices> Create(const fuchsia::sys::EnvironmentPtr& parent_env, | 
|  | async_dispatcher_t* dispatcher = nullptr); | 
|  |  | 
|  | // Creates services with custom parent overrides. | 
|  | static std::unique_ptr<EnvironmentServices> CreateWithParentOverrides( | 
|  | const fuchsia::sys::EnvironmentPtr& parent_env, ParentOverrides parent_overrides, | 
|  | async_dispatcher_t* dispatcher = nullptr); | 
|  |  | 
|  | // Adds the specified interface to the set of services. | 
|  | // | 
|  | // Adds a supported service with the given |service_name|, using the given | 
|  | // |interface_request_handler|, which should remain valid for the lifetime | 
|  | // of this object. | 
|  | // | 
|  | // A typical usage may be: | 
|  | // | 
|  | //   AddService(foobar_bindings_.GetHandler(this)); | 
|  | // | 
|  | template <typename Interface> | 
|  | zx_status_t AddService(fidl::InterfaceRequestHandler<Interface> handler, | 
|  | const std::string& service_name = Interface::Name_) { | 
|  | svc_names_.push_back(service_name); | 
|  | return svc_.AddEntry( | 
|  | service_name, | 
|  | std::make_unique<vfs::Service>( | 
|  | [handler = std::move(handler)](zx::channel channel, async_dispatcher_t* dispatcher) { | 
|  | handler(fidl::InterfaceRequest<Interface>(std::move(channel))); | 
|  | })); | 
|  | } | 
|  |  | 
|  | // Adds the specified service to the set of services. | 
|  | zx_status_t AddSharedService(const std::shared_ptr<vfs::Service>& service, | 
|  | const std::string& service_name); | 
|  |  | 
|  | // Adds the specified service to the set of services. | 
|  | zx_status_t AddService(std::unique_ptr<vfs::Service> service, const std::string& service_name); | 
|  |  | 
|  | // Adds the specified service to the set of services. | 
|  | // | 
|  | // Adds a supported service with the given |service_name|, using the given | 
|  | // |launch_info|, it only starts the component when the service is | 
|  | // requested. | 
|  | // Note: Only url and arguments fields of provided launch_info are used, if | 
|  | // you need to use other fields, use the Handler signature. | 
|  | zx_status_t AddServiceWithLaunchInfo(fuchsia::sys::LaunchInfo launch_info, | 
|  | const std::string& service_name); | 
|  |  | 
|  | // Adds the specified service to the set of services. | 
|  | // | 
|  | // Adds a supported service with the given |service_name|, using the given | 
|  | // handler to generate launch info, it only starts the component when the | 
|  | // service is requested. | 
|  | // The provided singleton_id argument is used to keep track of singleton | 
|  | // instances, generally you want to use the URL that'll be used for launch | 
|  | // info. | 
|  | zx_status_t AddServiceWithLaunchInfo(std::string singleton_id, | 
|  | fit::function<fuchsia::sys::LaunchInfo()> handler, | 
|  | const std::string& service_name); | 
|  |  | 
|  | // Allows child components to access parent service with name | 
|  | // |service_name|. | 
|  | // | 
|  | // This will only work if parent environment actually provides said service | 
|  | // and the service is in the test component's service whitelist. | 
|  | zx_status_t AllowParentService(const std::string& service_name); | 
|  |  | 
|  | // Serve service directory using |flags| and returns a new |InterfaceHandle|; | 
|  | // Will cause exception if serving fails. | 
|  | fidl::InterfaceHandle<fuchsia::io::Directory> ServeServiceDir( | 
|  | uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE); | 
|  |  | 
|  | // Serves service directory using passed |request| and returns status. | 
|  | zx_status_t ServeServiceDir(fidl::InterfaceRequest<fuchsia::io::Directory> request, | 
|  | uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE | | 
|  | fuchsia::io::OPEN_RIGHT_WRITABLE); | 
|  |  | 
|  | // Serves service directory using passed |request| and returns status. | 
|  | zx_status_t ServeServiceDir(zx::channel request, | 
|  | uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE | | 
|  | fuchsia::io::OPEN_RIGHT_WRITABLE); | 
|  |  | 
|  | // Sets a callback to be triggered whenever a singleton service launched | 
|  | // by |AddServiceWithLaunchInfo| terminates. The callback provides the | 
|  | // singleton identifier (typically fuchsia-pkg URL) and the termination | 
|  | // information. | 
|  | void SetServiceTerminatedCallback(ServiceTerminatedCallback callback) { | 
|  | service_terminated_callback_ = std::move(callback); | 
|  | } | 
|  |  | 
|  | private: | 
|  | friend class EnclosingEnvironment; | 
|  | EnvironmentServices(const fuchsia::sys::EnvironmentPtr& parent_env, | 
|  | ParentOverrides parent_overrides, async_dispatcher_t* dispatcher = nullptr); | 
|  |  | 
|  | void set_enclosing_env(EnclosingEnvironment* e) { enclosing_env_ = e; } | 
|  |  | 
|  | vfs::PseudoDir svc_; | 
|  | std::vector<std::string> svc_names_; | 
|  | std::shared_ptr<sys::ServiceDirectory> parent_svc_; | 
|  | // Pointer to containing environment. Not owned. | 
|  | EnclosingEnvironment* enclosing_env_ = nullptr; | 
|  | async_dispatcher_t* dispatcher_; | 
|  | ServiceTerminatedCallback service_terminated_callback_; | 
|  |  | 
|  | // Keep track of all singleton services, indexed by url. | 
|  | std::unordered_map<std::string, std::shared_ptr<sys::ServiceDirectory>> singleton_services_; | 
|  | }; | 
|  |  | 
|  | // EnclosingEnvironment wraps a new isolated environment for test |parent_env| | 
|  | // and provides a way to use that environment for integration testing. | 
|  | // | 
|  | // It provides a way to add custom fake services using handlers and singleton | 
|  | // components. By default components under this environment have no access to | 
|  | // any of system services. You need to add your own services by using | 
|  | // |AddService| or |AddServiceWithLaunchInfo| methods. | 
|  | // | 
|  | // It also provides a way to access parent services if needed. | 
|  | class EnclosingEnvironment { | 
|  | public: | 
|  | // Creates environment with the given services. | 
|  | // | 
|  | // |label| is human readable environment name, it can be seen in /hub, for eg | 
|  | // /hub/r/sys/<koid>/r/<label>/<koid> | 
|  | // | 
|  | // |services| are the services the environment will provide. See | 
|  | // |EnvironmentServices| for details. | 
|  | static std::unique_ptr<EnclosingEnvironment> Create( | 
|  | const std::string& label, const fuchsia::sys::EnvironmentPtr& parent_env, | 
|  | std::unique_ptr<EnvironmentServices> services, | 
|  | const fuchsia::sys::EnvironmentOptions& options = {}); | 
|  |  | 
|  | ~EnclosingEnvironment(); | 
|  |  | 
|  | fuchsia::sys::LauncherPtr launcher_ptr() { | 
|  | fuchsia::sys::LauncherPtr launcher; | 
|  | launcher_.AddBinding(launcher.NewRequest()); | 
|  | return launcher; | 
|  | } | 
|  |  | 
|  | // Returns true if underlying environment is running. | 
|  | bool is_running() const { return running_; } | 
|  |  | 
|  | // Kills the underlying environment. | 
|  | void Kill(fit::function<void()> callback = nullptr); | 
|  |  | 
|  | // Creates a real component from |launch_info| in underlying environment. | 
|  | // | 
|  | // That component will only have access to the services added and | 
|  | // any allowed parent service. | 
|  | void CreateComponent(fuchsia::sys::LaunchInfo launch_info, | 
|  | fidl::InterfaceRequest<fuchsia::sys::ComponentController> request); | 
|  |  | 
|  | // Creates a real component from |launch_info| in underlying environment and | 
|  | // returns controller ptr. | 
|  | // | 
|  | // That component will only have access to the services added and | 
|  | // any allowed parent service. | 
|  | fuchsia::sys::ComponentControllerPtr CreateComponent(fuchsia::sys::LaunchInfo launch_info); | 
|  |  | 
|  | // Creates a real component in underlying environment for a url and returns | 
|  | // controller ptr. | 
|  | // | 
|  | // That component will only have access to the services added and | 
|  | // any allowed parent service. | 
|  | fuchsia::sys::ComponentControllerPtr CreateComponentFromUrl(std::string component_url); | 
|  |  | 
|  | // Creates a nested enclosing environment on top of underlying environment. | 
|  | std::unique_ptr<EnclosingEnvironment> CreateNestedEnclosingEnvironment(const std::string& label); | 
|  |  | 
|  | // Creates a nested enclosing environment on top of underlying environment | 
|  | // with custom loader service. | 
|  | std::unique_ptr<EnclosingEnvironment> CreateNestedEnclosingEnvironmentWithLoader( | 
|  | const std::string& label, std::shared_ptr<vfs::Service> loader_service); | 
|  |  | 
|  | std::shared_ptr<sys::ServiceDirectory> service_directory() { return service_provider_; } | 
|  |  | 
|  | // Connects to service provided by this environment. | 
|  | void ConnectToService(std::string service_name, zx::channel channel) { | 
|  | service_provider_->Connect(service_name, std::move(channel)); | 
|  | } | 
|  |  | 
|  | // Connects to service provided by this environment. | 
|  | template <typename Interface> | 
|  | void ConnectToService(fidl::InterfaceRequest<Interface> request, | 
|  | const std::string& service_name = Interface::Name_) { | 
|  | ConnectToService(service_name, request.TakeChannel()); | 
|  | } | 
|  |  | 
|  | // Connects to service provided by this environment. | 
|  | template <typename Interface> | 
|  | fidl::InterfacePtr<Interface> ConnectToService( | 
|  | const std::string& service_name = Interface::Name_) { | 
|  | fidl::InterfacePtr<Interface> ptr; | 
|  | ConnectToService(service_name, ptr.NewRequest().TakeChannel()); | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | // Sets a listener for changes in the running status | 
|  | void SetRunningChangedCallback(fit::function<void(bool)> cb) { | 
|  | running_changed_callback_ = std::move(cb); | 
|  | } | 
|  |  | 
|  | private: | 
|  | EnclosingEnvironment(std::string label, const fuchsia::sys::EnvironmentPtr& parent_env, | 
|  | std::unique_ptr<EnvironmentServices> services, | 
|  | const fuchsia::sys::EnvironmentOptions& options); | 
|  |  | 
|  | void SetRunning(bool running); | 
|  |  | 
|  | bool running_ = false; | 
|  | const std::string label_; | 
|  | fuchsia::sys::EnvironmentControllerPtr env_controller_; | 
|  | std::shared_ptr<sys::ServiceDirectory> service_provider_; | 
|  | LauncherImpl launcher_; | 
|  | std::unique_ptr<EnvironmentServices> services_; | 
|  | fit::function<void(bool)> running_changed_callback_; | 
|  | }; | 
|  |  | 
|  | }  // namespace testing | 
|  | }  // namespace sys | 
|  |  | 
|  | #endif  // LIB_SYS_CPP_TESTING_ENCLOSING_ENVIRONMENT_H_ |