| // Copyright 2019 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_COMPONENT_INTERCEPTOR_H_ |
| #define LIB_SYS_CPP_TESTING_COMPONENT_INTERCEPTOR_H_ |
| |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/sys/cpp/testing/enclosing_environment.h> |
| |
| #include <mutex> |
| #include <string> |
| |
| namespace sys::testing { |
| |
| using fuchsia::sys::TerminationReason; |
| |
| // A Wrapper class which implements a basic version of |
| // |fuchsia::sys::ComponentController| and gives owner control over lifetime of |
| // this component. |
| class InterceptedComponent : public fuchsia::sys::ComponentController { |
| public: |
| // Called when this component is killed. |
| using OnKill = fit::function<void()>; |
| |
| InterceptedComponent(fidl::InterfaceRequest<fuchsia::sys::ComponentController> request, |
| async_dispatcher_t* dispatcher = nullptr); |
| |
| // resets |on_kill_| to nullptr and calls |Kill()|. |
| ~InterceptedComponent() override; |
| |
| void Exit(int64_t exit_code, TerminationReason reason = TerminationReason::EXITED); |
| |
| void set_on_kill(OnKill on_kill) { on_kill_ = std::move(on_kill); } |
| |
| private: |
| // |ComponentController|. |
| void Detach() override; |
| |
| // |ComponentController|. |
| // |
| // Calls |on_kill_| and call Terminated event on component before clearing the |
| // bindings |
| void Kill() override; |
| |
| private: |
| void KillImpl(); |
| |
| fidl::Binding<fuchsia::sys::ComponentController> binding_; |
| TerminationReason termination_reason_; |
| int64_t exit_code_; |
| OnKill on_kill_; |
| }; |
| |
| // ComponentInterceptor is a utility that helps users construct an |
| // EnvironmentService (to be used alongside EnclosingEnvironment) that is able |
| // to intercept and mock components launched under the EnclosingEnvironment. |
| // |
| // This class is thread-safe. Underlying FIDL communication is processed on the |
| // async dispatcher supplied to this class. |
| class ComponentInterceptor : fuchsia::sys::Loader, fuchsia::sys::Runner { |
| public: |
| using ComponentLaunchHandler = |
| fit::function<void(fuchsia::sys::StartupInfo, std::unique_ptr<InterceptedComponent>)>; |
| |
| ComponentInterceptor(fuchsia::sys::LoaderPtr fallback_loader, |
| async_dispatcher_t* dispatcher = nullptr); |
| |
| virtual ~ComponentInterceptor() override; |
| |
| // Constructs a fallback loader from the given |env|. |
| static ComponentInterceptor CreateWithEnvironmentLoader(const fuchsia::sys::EnvironmentPtr& env, |
| async_dispatcher_t* dispatcher = nullptr); |
| |
| // Creates an |EnvironmentServices| which contains custom Loader and |
| // Runner services which intercept component launch URLs configured using |
| // |InterceptURL|. Calls to |InterceptURL| are effective regardless of if |
| // they're called before or after calls to this method. |
| // |
| // Restrictions: |
| // * Users must not override the fuchsia::sys::Loader and |
| // fuchsia::sys::Runner services. |
| // * An instance of |ComponentInterceptor| must outlive instances of |
| // vended |EnvironmentServices| |
| std::unique_ptr<EnvironmentServices> MakeEnvironmentServices( |
| const fuchsia::sys::EnvironmentPtr& env); |
| |
| // Intercepts |component_url| from being launched under this environment, and |
| // calls the supplied |handler| to handle the runtime of this component. |
| // |
| // |extra_cmx_contents| contains additional component manifest contents |
| // supplied for this component. |
| // * If |extra_cmx_contents| is empty a default one is used: |
| // * {"program": {"binary": ""}} |
| // * The "runner" is always overwritten. |
| // |
| // Returns |false| if |extra_cmx_contents| contains invalid JSON. |
| [[nodiscard]] bool InterceptURL(std::string component_url, std::string extra_cmx_contents, |
| ComponentLaunchHandler handler); |
| |
| private: |
| // Returns a faked fuchsia.sys.Package with a custom runner which forwards |
| // the StartComponent request to environment's fuchsia::sys::Runner |
| // service hosted by this object instance. |
| // |
| // |fuchsia::sys::Loader| |
| void LoadUrl(std::string url, LoadUrlCallback response) override; |
| |
| // We arrive here if our fuchsia::sys::Loader sends a component launch to |
| // the test harness runner component, which forwards it to here. |
| // |
| // |fuchsia::sys::Runner| |
| void StartComponent( |
| fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) override; |
| |
| // Ensures that calls to intercepting URLs remains thread-safe. |
| std::mutex intercept_urls_mu_; |
| |
| struct ComponentLoadInfo { |
| ComponentLaunchHandler handler; |
| // Fake component package directory where we host our fake manifest. |
| std::unique_ptr<vfs::PseudoDir> pkg_dir; |
| }; |
| std::map<std::string, ComponentLoadInfo> intercepted_component_load_info_ |
| __TA_GUARDED(intercept_urls_mu_); |
| |
| fuchsia::sys::LoaderPtr fallback_loader_; |
| |
| std::shared_ptr<vfs::Service> loader_svc_; |
| fidl::BindingSet<fuchsia::sys::Loader> loader_bindings_; |
| fidl::BindingSet<fuchsia::sys::Runner> runner_bindings_; |
| |
| async_dispatcher_t* dispatcher_; |
| |
| std::unique_ptr<EnclosingEnvironment> env_; |
| }; |
| |
| } // namespace sys::testing |
| |
| #endif // LIB_SYS_CPP_TESTING_COMPONENT_INTERCEPTOR_H_ |