| // 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 <fuchsia/ui/app/cpp/fidl.h> | |
| #include <lib/modular/testing/cpp/fake_component.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> | |
| #include <zircon/rights.h> | |
| #include <zircon/syscalls/object.h> | |
| #include <zircon/types.h> | |
| #include <tuple> | |
| #include <gmock/gmock.h> | |
| #include "src/lib/fsl/vmo/strings.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/fake_module.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/fake_session_shell.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/fake_story_shell.h" | |
| #include "src/modular/lib/modular_test_harness/cpp/test_harness_fixture.h" | |
| using testing::ElementsAre; | |
| namespace { | |
| class ViewRefModule : public modular_testing::FakeModule, fuchsia::ui::app::ViewProvider { | |
| public: | |
| explicit ViewRefModule(modular_testing::FakeComponent::Args args) | |
| : modular_testing::FakeModule(args) {} | |
| fuchsia::ui::views::ViewRef& view_ref() { return view_ref_; } | |
| bool has_created_view() { return has_created_view_; } | |
| static std::unique_ptr<ViewRefModule> CreateWithDefaultOptions() { | |
| return std::make_unique<ViewRefModule>(modular_testing::FakeComponent::Args{ | |
| .url = modular_testing::TestHarnessBuilder::GenerateFakeUrl(), | |
| .sandbox_services = FakeModule::GetDefaultSandboxServices()}); | |
| } | |
| private: | |
| // |modular::testing::FakeComponent| | |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) override { | |
| component_context()->outgoing()->AddPublicService<fuchsia::ui::app::ViewProvider>( | |
| [this](fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) { | |
| bindings_.AddBinding(this, std::move(request)); | |
| }); | |
| } | |
| virtual void CreateView( | |
| ::zx::eventpair token, | |
| ::fidl::InterfaceRequest<::fuchsia::sys::ServiceProvider> incoming_services, | |
| ::fidl::InterfaceHandle<::fuchsia::sys::ServiceProvider> outgoing_services) override {} | |
| virtual void CreateViewWithViewRef(::zx::eventpair token, | |
| ::fuchsia::ui::views::ViewRefControl view_ref_control, | |
| ::fuchsia::ui::views::ViewRef view_ref) override { | |
| has_created_view_ = true; | |
| view_ref_ = std::move(view_ref); | |
| } | |
| bool has_created_view_ = false; | |
| fuchsia::ui::views::ViewRef view_ref_; | |
| fidl::BindingSet<fuchsia::ui::app::ViewProvider> bindings_; | |
| }; | |
| class StoryShellTest : public modular_testing::TestHarnessFixture { | |
| protected: | |
| StoryShellTest() | |
| : session_shell_(modular_testing::FakeSessionShell::CreateWithDefaultOptions()), | |
| story_shell_({.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl(), | |
| .sandbox_services = {"fuchsia.modular.StoryShellContext"}}) {} | |
| void StartSession() { StartSessionWithInterceptedComponent(nullptr); } | |
| void StartSessionWithInterceptedComponent(modular_testing::FakeComponent* component) { | |
| modular_testing::TestHarnessBuilder builder; | |
| builder.InterceptSessionShell(session_shell_->BuildInterceptOptions()); | |
| builder.InterceptStoryShell(story_shell_.BuildInterceptOptions()); | |
| if (component) { | |
| builder.InterceptComponent(component->BuildInterceptOptions()); | |
| } | |
| fake_module_url_ = modular_testing::TestHarnessBuilder::GenerateFakeUrl("module"); | |
| builder.InterceptComponent( | |
| {.url = fake_module_url_, | |
| .launch_handler = | |
| [this](fuchsia::sys::StartupInfo startup_info, | |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> | |
| intercepted_component) { | |
| intercepted_modules_.push_back( | |
| {std::move(startup_info), std::move(intercepted_component)}); | |
| }}); | |
| builder.BuildAndRun(test_harness()); | |
| fuchsia::modular::testing::ModularService request; | |
| request.set_puppet_master(puppet_master_.NewRequest()); | |
| test_harness()->ConnectToModularService(std::move(request)); | |
| // Wait for our session shell to start. | |
| RunLoopUntil([this] { return session_shell_->is_running(); }); | |
| } | |
| void AddModToStoryWithUrl(std::string story_name, std::string mod_name, | |
| std::string parent_mod_name, std::string url) { | |
| 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 = url; | |
| if (!parent_mod_name.empty()) { | |
| add_mod.surface_parent_mod_name = {parent_mod_name}; | |
| } | |
| std::vector<fuchsia::modular::StoryCommand> commands(1); | |
| commands.at(0).set_add_mod(std::move(add_mod)); | |
| story_puppet_master->Enqueue(std::move(commands)); | |
| bool created = false; | |
| story_puppet_master->Execute([&](fuchsia::modular::ExecuteResult result) { created = true; }); | |
| // Wait for the story to be created. | |
| RunLoopUntil([&] { return created; }); | |
| } | |
| void AddModToStory(std::string story_name, std::string mod_name, | |
| std::string parent_mod_name = "") { | |
| AddModToStoryWithUrl(story_name, mod_name, parent_mod_name, fake_module_url_); | |
| } | |
| void RestartStory(std::string story_name) { | |
| fuchsia::modular::StoryControllerPtr story_controller; | |
| session_shell_->story_provider()->GetController(story_name, story_controller.NewRequest()); | |
| bool restarted = false; | |
| story_controller->Stop([&] { | |
| story_controller->RequestStart(); | |
| restarted = true; | |
| }); | |
| RunLoopUntil([&] { return restarted; }); | |
| } | |
| fuchsia::modular::PuppetMasterPtr puppet_master_; | |
| std::unique_ptr<modular_testing::FakeSessionShell> session_shell_; | |
| modular_testing::FakeStoryShell story_shell_; | |
| std::string fake_module_url_; | |
| // Stories must have modules in them so the stories created above | |
| // contain fake intercepted modules. This list holds onto them so that | |
| // they can be successfully launched and don't die immediately. | |
| std::vector<std::tuple<fuchsia::sys::StartupInfo, | |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>>> | |
| intercepted_modules_; | |
| }; | |
| TEST_F(StoryShellTest, GetsModuleMetadata) { | |
| StartSession(); | |
| std::vector<std::string> surface_ids_added; | |
| story_shell_.set_on_add_surface([&](fuchsia::modular::ViewConnection view_connection, | |
| fuchsia::modular::SurfaceInfo2 surface_info) { | |
| surface_ids_added.push_back(view_connection.surface_id); | |
| }); | |
| AddModToStory("story1", "mod1"); | |
| AddModToStory("story1", "mod2", {"mod1"} /* surface relation parent */); | |
| // Wait for the story shell to be notified of the new modules. | |
| RunLoopUntil([&] { return surface_ids_added.size() == 2; }); | |
| EXPECT_THAT(surface_ids_added, ElementsAre("mod1", "mod1:mod2")); | |
| // Stop the story shell and restart it. Expect to see the same mods notified | |
| // to the story shell in the same order. | |
| surface_ids_added.clear(); | |
| RestartStory("story1"); | |
| RunLoopUntil([&] { return surface_ids_added.size() == 2; }); | |
| EXPECT_THAT(surface_ids_added, ElementsAre("mod1", "mod1:mod2")); | |
| } | |
| TEST_F(StoryShellTest, GetsViewRef) { | |
| StartSession(); | |
| std::vector<bool> has_view_refs; | |
| story_shell_.set_on_add_surface([&](fuchsia::modular::ViewConnection view_connection, | |
| fuchsia::modular::SurfaceInfo2 surface_info) { | |
| has_view_refs.push_back(surface_info.has_view_ref()); | |
| }); | |
| AddModToStory("story1", "mod1"); | |
| AddModToStory("story1", "mod2", {"mod1"} /* surface relation parent */); | |
| // Wait for the story shell to be notified of the new modules. | |
| RunLoopUntil([&] { return has_view_refs.size() == 2; }); | |
| EXPECT_THAT(has_view_refs, ElementsAre(true, true)); | |
| // Stop the story shell and restart it. Expect to see the same mods notified | |
| // to the story shell in the same order. | |
| has_view_refs.clear(); | |
| RestartStory("story1"); | |
| RunLoopUntil([&] { return has_view_refs.size() == 2; }); | |
| EXPECT_THAT(has_view_refs, ElementsAre(true, true)); | |
| } | |
| TEST_F(StoryShellTest, GetsCorrectViewRef) { | |
| auto view_ref_module = ViewRefModule::CreateWithDefaultOptions(); | |
| StartSessionWithInterceptedComponent(view_ref_module.get()); | |
| bool have_seen_view_ref = false; | |
| fuchsia::ui::views::ViewRef seen_view_ref; | |
| story_shell_.set_on_add_surface([&](fuchsia::modular::ViewConnection view_connection, | |
| fuchsia::modular::SurfaceInfo2 surface_info) { | |
| seen_view_ref = fidl::Clone(surface_info.view_ref()); | |
| have_seen_view_ref = true; | |
| }); | |
| AddModToStoryWithUrl("story1", "mod1", "", view_ref_module->url()); | |
| // Wait for the story shell to be notified of the new modules. | |
| RunLoopUntil([&] { return have_seen_view_ref; }); | |
| RunLoopUntil([&] { return view_ref_module->has_created_view(); }); | |
| // Get info about the |view_ref|. | |
| zx_info_handle_basic_t seen_view_info; | |
| zx_status_t seen_view_status = | |
| zx_object_get_info(seen_view_ref.reference.get(), ZX_INFO_HANDLE_BASIC, &seen_view_info, | |
| sizeof(seen_view_info), nullptr, nullptr); | |
| zx_info_handle_basic_t mod_view_info; | |
| zx_status_t mod_view_status = | |
| zx_object_get_info(view_ref_module->view_ref().reference.get(), ZX_INFO_HANDLE_BASIC, | |
| &mod_view_info, sizeof(mod_view_info), nullptr, nullptr); | |
| EXPECT_EQ(mod_view_status, ZX_OK); | |
| EXPECT_EQ(seen_view_status, ZX_OK); | |
| EXPECT_EQ(mod_view_info.koid, seen_view_info.koid); | |
| } | |
| } // namespace |