| // 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. |
| |
| #include <fuchsia/modular/testing/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/fsl/vmo/strings.h> |
| #include <lib/modular_test_harness/cpp/fake_component.h> |
| #include <lib/modular_test_harness/cpp/test_harness_fixture.h> |
| #include <sdk/lib/sys/cpp/component_context.h> |
| #include <src/lib/fxl/logging.h> |
| |
| #include "peridot/lib/testing/session_shell_impl.h" |
| |
| namespace { |
| |
| // Timeout for each call to RunLoopWithTimeoutOrUntil(). |
| constexpr zx::duration kTimeout = zx::sec(30); |
| |
| // An implementation of the fuchsia.modular.StoryShellFactory FIDL service, to |
| // be used in session shell components in integration tests. |
| class TestStoryShellFactory : fuchsia::modular::StoryShellFactory { |
| public: |
| using StoryShellRequest = |
| fidl::InterfaceRequest<fuchsia::modular::StoryShell>; |
| |
| TestStoryShellFactory(sys::ComponentContext* const component_context) { |
| component_context->outgoing()->AddPublicService(GetHandler()); |
| } |
| |
| virtual ~TestStoryShellFactory() override = default; |
| |
| // Produces a handler function that can be used in the outgoing service |
| // provider. |
| fidl::InterfaceRequestHandler<fuchsia::modular::StoryShellFactory> |
| GetHandler() { |
| return bindings_.GetHandler(this); |
| } |
| |
| // Whenever StoryShellFactory.AttachStory() is called, the supplied callback |
| // is invoked with the story ID and StoryShell request. |
| void set_on_attach_story( |
| fit::function<void(std::string story_id, StoryShellRequest request)> |
| callback) { |
| on_attach_story_ = std::move(callback); |
| } |
| |
| // Whenever StoryShellFactory.DetachStory() is called, the supplied callback |
| // is invoked. The return callback of DetachStory() is invoked asynchronously |
| // after a delay that can be configured by the client with set_detach_delay(). |
| void set_on_detach_story(fit::function<void()> callback) { |
| on_detach_story_ = std::move(callback); |
| } |
| |
| // Configures the delay after which the return callback of DetachStory() is |
| // invoked. Used to test the timeout behavior of sessionmgr. |
| void set_detach_delay(zx::duration detach_delay) { |
| detach_delay_ = detach_delay; |
| } |
| |
| private: |
| // |StoryShellFactory| |
| void AttachStory(std::string story_id, StoryShellRequest request) override { |
| on_attach_story_(std::move(story_id), std::move(request)); |
| } |
| |
| // |StoryShellFactory| |
| void DetachStory(std::string story_id, fit::function<void()> done) override { |
| on_detach_story_(); |
| |
| // Used to simulate a sluggish shell that hits the timeout. |
| async::PostDelayedTask(async_get_default_dispatcher(), std::move(done), |
| detach_delay_); |
| } |
| |
| fidl::BindingSet<fuchsia::modular::StoryShellFactory> bindings_; |
| fit::function<void(std::string story_id, StoryShellRequest request)> |
| on_attach_story_{[](std::string, StoryShellRequest) {}}; |
| fit::function<void()> on_detach_story_{[]() {}}; |
| zx::duration detach_delay_{}; |
| }; |
| |
| // A basic fake session shell component: gives access to services |
| // available to session shells in their environment, as well as an |
| // implementation of fuchsia::modular::SessionShell built for tests. |
| class TestSessionShell : public modular::testing::FakeComponent { |
| public: |
| fuchsia::modular::StoryProvider* story_provider() { |
| return story_provider_.get(); |
| } |
| |
| TestStoryShellFactory* story_shell_factory() { |
| return story_shell_factory_.get(); |
| } |
| |
| private: |
| // |modular::testing::FakeComponent| |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) override { |
| component_context()->svc()->Connect(session_shell_context_.NewRequest()); |
| session_shell_context_->GetStoryProvider(story_provider_.NewRequest()); |
| |
| component_context()->outgoing()->AddPublicService( |
| session_shell_impl_.GetHandler()); |
| |
| story_shell_factory_ = |
| std::make_unique<TestStoryShellFactory>(component_context()); |
| } |
| |
| modular::testing::SessionShellImpl session_shell_impl_; |
| fuchsia::modular::SessionShellContextPtr session_shell_context_; |
| fuchsia::modular::StoryProviderPtr story_provider_; |
| std::unique_ptr<TestStoryShellFactory> story_shell_factory_; |
| }; |
| |
| // An implementation of the fuchsia.modular.StoryShell FIDL service. |
| class TestStoryShell : fuchsia::modular::StoryShell { |
| public: |
| TestStoryShell() = default; |
| ~TestStoryShell() override = default; |
| |
| // Produces a handler function that can be used in the outgoing service |
| // provider. |
| fidl::InterfaceRequestHandler<fuchsia::modular::StoryShell> GetHandler() { |
| return bindings_.GetHandler(this); |
| } |
| |
| private: |
| // |fuchsia::modular::StoryShell| |
| void Initialize(fidl::InterfaceHandle<fuchsia::modular::StoryShellContext> |
| story_shell_context) override {} |
| |
| // |fuchsia::modular::StoryShell| |
| void AddSurface(fuchsia::modular::ViewConnection view_connection, |
| fuchsia::modular::SurfaceInfo surface_info) override {} |
| |
| // |fuchsia::modular::StoryShell| |
| void FocusSurface(std::string /* surface_id */) override {} |
| |
| // |fuchsia::modular::StoryShell| |
| void DefocusSurface(std::string /* surface_id */, |
| DefocusSurfaceCallback callback) override { |
| callback(); |
| } |
| |
| // |fuchsia::modular::StoryShell| |
| void AddContainer( |
| std::string /* container_name */, fidl::StringPtr /* parent_id */, |
| fuchsia::modular::SurfaceRelation /* relation */, |
| std::vector<fuchsia::modular::ContainerLayout> /* layout */, |
| std::vector<fuchsia::modular::ContainerRelationEntry> /* relationships */, |
| std::vector<fuchsia::modular::ContainerView> /* views */) override {} |
| |
| // |fuchsia::modular::StoryShell| |
| void RemoveSurface(std::string /* surface_id */) override {} |
| |
| // |fuchsia::modular::StoryShell| |
| void ReconnectView( |
| fuchsia::modular::ViewConnection view_connection) override {} |
| |
| // |fuchsia::modular::StoryShell| |
| void UpdateSurface( |
| fuchsia::modular::ViewConnection view_connection, |
| fuchsia::modular::SurfaceInfo /* surface_info */) override {} |
| |
| fidl::BindingSet<fuchsia::modular::StoryShell> bindings_; |
| }; |
| |
| class StoryShellFactoryTest : public modular::testing::TestHarnessFixture { |
| public: |
| const std::string story_name = "story1"; |
| const std::string mod_name = "mod1"; |
| |
| TestSessionShell* test_session_shell() { return test_session_shell_.get(); } |
| |
| // Initializes the session shell, story shell factory, and story shell |
| // implementations and starts the modular test harness. |
| void InitSession() { |
| modular::testing::TestHarnessBuilder builder; |
| |
| test_session_shell_ = std::make_unique<TestSessionShell>(); |
| builder.InterceptSessionShell( |
| test_session_shell_->GetOnCreateHandler(), |
| {.sandbox_services = {"fuchsia.modular.SessionShellContext", |
| "fuchsia.modular.PuppetMaster"}}); |
| |
| // Listen for the module that is created in CreateStory(). |
| test_module_ = std::make_unique<modular::testing::FakeComponent>(); |
| test_module_url_ = builder.GenerateFakeUrl(); |
| builder.InterceptComponent(test_module_->GetOnCreateHandler(), |
| {.url = test_module_url_}); |
| |
| test_harness().events().OnNewComponent = |
| builder.BuildOnNewComponentHandler(); |
| auto spec = builder.BuildSpec(); |
| |
| // The session shell also provides the StoryShellFactory protocol. |
| spec.mutable_basemgr_config() |
| ->set_use_session_shell_for_story_shell_factory(true); |
| |
| test_harness()->Run(std::move(spec)); |
| |
| // Wait for our session shell to start. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil( |
| [this] { return test_session_shell_->is_running(); }, kTimeout)); |
| |
| // Connect to the PuppetMaster service also provided to the session shell. |
| fuchsia::modular::testing::ModularService modular_service; |
| modular_service.set_puppet_master(puppet_master_.NewRequest()); |
| test_harness()->ConnectToModularService(std::move(modular_service)); |
| } |
| |
| void CreateStory() { |
| // The session shell should be running and connected to PuppetMaster. |
| FXL_CHECK(test_session_shell_->is_running()); |
| // The story should not already be created. |
| FXL_CHECK(!story_exists_); |
| |
| // Create a story |
| fuchsia::modular::StoryPuppetMasterPtr story_puppet_master; |
| puppet_master_->ControlStory(story_name, story_puppet_master.NewRequest()); |
| |
| fuchsia::modular::AddMod add_mod; |
| add_mod.mod_name_transitional = mod_name; |
| add_mod.intent.handler = test_module_url_; |
| add_mod.intent.action = "action"; |
| |
| std::vector<fuchsia::modular::StoryCommand> commands(1); |
| commands.at(0).set_add_mod(std::move(add_mod)); |
| |
| story_puppet_master->Enqueue(std::move(commands)); |
| story_puppet_master->Execute( |
| [this](fuchsia::modular::ExecuteResult result) { |
| story_exists_ = true; |
| }); |
| |
| // Wait for the story to be created. |
| ASSERT_TRUE( |
| RunLoopWithTimeoutOrUntil([this] { return story_exists_; }, kTimeout)); |
| } |
| |
| void DeleteStory() { |
| // The session shell should be running and connected to PuppetMaster. |
| FXL_CHECK(test_session_shell_->is_running()); |
| // The story should have been previously created through CreateStory. |
| FXL_CHECK(story_exists_); |
| |
| puppet_master_->DeleteStory(story_name, [this] { story_exists_ = true; }); |
| |
| // Wait for the story to be deleted. |
| ASSERT_TRUE( |
| RunLoopWithTimeoutOrUntil([this] { return story_exists_; }, kTimeout)); |
| |
| story_exists_ = false; |
| } |
| |
| fuchsia::modular::StoryControllerPtr ControlStory() { |
| // The story should have been previously created through CreateStory. |
| FXL_CHECK(story_exists_); |
| |
| // Get a story controller. |
| fuchsia::modular::StoryControllerPtr story_controller; |
| test_session_shell_->story_provider()->GetController( |
| story_name, story_controller.NewRequest()); |
| |
| return story_controller; |
| } |
| |
| private: |
| bool story_exists_{false}; |
| |
| // Component URL of the |test_module_| intercepted in InitSession(). |
| std::string test_module_url_; |
| |
| fuchsia::modular::PuppetMasterPtr puppet_master_; |
| std::unique_ptr<TestSessionShell> test_session_shell_; |
| std::unique_ptr<modular::testing::FakeComponent> test_module_; |
| }; |
| |
| TEST_F(StoryShellFactoryTest, AttachCalledOnStoryStart) { |
| InitSession(); |
| |
| TestStoryShell test_story_shell; |
| |
| // The StoryShellFactory will be asked to attach a StoryShell when the story |
| // is started. |
| bool is_attached{false}; |
| test_session_shell()->story_shell_factory()->set_on_attach_story( |
| [&](std::string, |
| fidl::InterfaceRequest<fuchsia::modular::StoryShell> request) { |
| is_attached = true; |
| test_story_shell.GetHandler()(std::move(request)); |
| }); |
| |
| CreateStory(); |
| |
| // Start and show the story. |
| auto story_controller = ControlStory(); |
| story_controller->RequestStart(); |
| |
| // Wait for the StoryShellFactory to attach the StoryShell. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return is_attached; }, kTimeout)); |
| }; |
| |
| TEST_F(StoryShellFactoryTest, DetachCalledOnStoryStop) { |
| InitSession(); |
| |
| // The StoryShellFactory will be asked to detach a StoryShell when the story |
| // is stopped. |
| bool is_detached{false}; |
| test_session_shell()->story_shell_factory()->set_on_detach_story( |
| [&]() { is_detached = true; }); |
| |
| CreateStory(); |
| |
| // Start and show the story. |
| auto story_controller = ControlStory(); |
| story_controller->RequestStart(); |
| |
| // Stop the story. |
| story_controller->Stop([]() {}); |
| |
| // Wait for the StoryShellFactory to detach the StoryShell. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return is_detached; }, kTimeout)); |
| }; |
| |
| TEST_F(StoryShellFactoryTest, DetachCalledOnStoryDelete) { |
| InitSession(); |
| |
| // The StoryShellFactory will be asked to detach a StoryShell when the story |
| // is deleted. |
| bool is_detached{false}; |
| test_session_shell()->story_shell_factory()->set_on_detach_story( |
| [&]() { is_detached = true; }); |
| |
| CreateStory(); |
| |
| // Start and show the story. |
| auto story_controller = ControlStory(); |
| story_controller->RequestStart(); |
| |
| DeleteStory(); |
| |
| // Wait for the StoryShellFactory to detach the StoryShell. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return is_detached; }, kTimeout)); |
| }; |
| |
| } // namespace |