| // 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_ |