| // 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 <gmock/gmock.h> |
| |
| #include "lib/syslog/cpp/macros.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/test_harness_fixture.h" |
| |
| using testing::ElementsAre; |
| |
| namespace { |
| |
| class ModuleContextTest : public modular_testing::TestHarnessFixture { |
| protected: |
| ModuleContextTest() |
| : session_shell_(modular_testing::FakeSessionShell::CreateWithDefaultOptions()) {} |
| |
| void StartSession(modular_testing::TestHarnessBuilder builder) { |
| builder.InterceptSessionShell(session_shell_->BuildInterceptOptions()); |
| builder.BuildAndRun(test_harness()); |
| |
| // Wait for our session shell to start. |
| RunLoopUntil([this] { return session_shell_->is_running(); }); |
| } |
| |
| 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; }); |
| } |
| |
| std::unique_ptr<modular_testing::FakeSessionShell> session_shell_; |
| }; |
| |
| // A version of FakeModule which captures handled intents in a std::vector<> |
| // and exposes callbacks triggered on certain lifecycle events. |
| class TestModule : public modular_testing::FakeModule { |
| public: |
| explicit TestModule(std::string module_name = "") |
| : modular_testing::FakeModule( |
| {.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl(module_name), |
| .sandbox_services = modular_testing::FakeModule::GetDefaultSandboxServices()}) {} |
| fit::function<void()> on_destroy; |
| fit::function<void()> on_create; |
| |
| private: |
| // |modular_testing::FakeModule| |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) override { |
| modular_testing::FakeModule::OnCreate(std::move(startup_info)); |
| if (on_create) |
| on_create(); |
| } |
| |
| // |modular_testing::FakeModule| |
| void OnDestroy() override { |
| if (on_destroy) |
| on_destroy(); |
| } |
| }; |
| |
| class TestStoryWatcher : fuchsia::modular::StoryWatcher { |
| public: |
| TestStoryWatcher(fit::function<void(fuchsia::modular::StoryState)> on_state_change) |
| : binding_(this), on_state_change_(std::move(on_state_change)) {} |
| ~TestStoryWatcher() override = default; |
| |
| // Registers itself as a watcher on the given story. Only one story at a time |
| // can be watched. |
| void Watch(fuchsia::modular::StoryController* const story_controller) { |
| story_controller->Watch(binding_.NewBinding()); |
| } |
| |
| private: |
| // |fuchsia::modular::StoryWatcher| |
| void OnStateChange(fuchsia::modular::StoryState state) override { on_state_change_(state); } |
| void OnModuleAdded(fuchsia::modular::ModuleData /*module_data*/) override {} |
| void OnModuleFocused(std::vector<std::string> /*module_path*/) override {} |
| |
| fidl::Binding<fuchsia::modular::StoryWatcher> binding_; |
| fit::function<void(fuchsia::modular::StoryState)> on_state_change_; |
| }; |
| |
| // Test that ModuleContext.RemoveSelfFromStory() on the only mod in a story has |
| // the affect of shutting down the module and removing it permanently from the |
| // story (if the story is restarted, it is not relaunched). |
| TEST_F(ModuleContextTest, RemoveSelfFromStory) { |
| TestModule module1("module1"); |
| |
| modular_testing::TestHarnessBuilder builder; |
| builder.InterceptComponent(module1.BuildInterceptOptions()); |
| |
| StartSession(std::move(builder)); |
| modular_testing::AddModToStory(test_harness(), "storyname", "modname1", |
| {.action = "action", .handler = module1.url()}); |
| RunLoopUntil([&] { return module1.is_running(); }); |
| |
| // Instruct module1 to remove itself from the story. Expect to see that |
| // module1 is terminated. |
| module1.module_context()->RemoveSelfFromStory(); |
| RunLoopUntil([&] { return !module1.is_running(); }); |
| |
| // Additionally, restarting the story should not result in module1 being |
| // restarted. |
| fuchsia::modular::StoryControllerPtr story_controller; |
| session_shell_->story_provider()->GetController("storyname", story_controller.NewRequest()); |
| bool story_stopped = false; |
| bool story_restarted = false; |
| TestStoryWatcher story_watcher([&](fuchsia::modular::StoryState state) { |
| if (state == fuchsia::modular::StoryState::STOPPED) { |
| story_stopped = true; |
| } else if (state == fuchsia::modular::StoryState::RUNNING) { |
| story_restarted = true; |
| } |
| }); |
| story_watcher.Watch(story_controller.get()); |
| RestartStory("storyname"); |
| RunLoopUntil([&] { return story_stopped && story_restarted; }); |
| EXPECT_FALSE(module1.is_running()); |
| } |
| |
| // Test that when ModuleContext.RemoveSelfFromStory() is called on one of two |
| // modules in a story, it has the affect of shutting down the module and |
| // removing it permanently from the story (if the story is restarted, it is not |
| // relaunched). |
| TEST_F(ModuleContextTest, RemoveSelfFromStory_2mods) { |
| modular_testing::TestHarnessBuilder builder; |
| |
| TestModule module1("module1"); |
| TestModule module2("module2"); |
| builder.InterceptComponent(module1.BuildInterceptOptions()); |
| builder.InterceptComponent(module2.BuildInterceptOptions()); |
| |
| StartSession(std::move(builder)); |
| modular_testing::AddModToStory(test_harness(), "storyname", "modname1", |
| {.action = "action", .handler = module1.url()}); |
| modular_testing::AddModToStory(test_harness(), "storyname", "modname2", |
| {.action = "action", .handler = module2.url()}); |
| RunLoopUntil([&] { return module1.is_running() && module2.is_running(); }); |
| |
| // Instruct module1 to remove itself from the story. Expect to see that |
| // module1 is terminated and module2 is not. |
| module1.module_context()->RemoveSelfFromStory(); |
| RunLoopUntil([&] { return !module1.is_running(); }); |
| ASSERT_TRUE(module2.is_running()); |
| |
| // Additionally, restarting the story should not result in module1 being |
| // restarted whereas it should for module2. |
| bool module2_destroyed = false; |
| bool module2_restarted = false; |
| module2.on_destroy = [&] { module2_destroyed = true; }; |
| module2.on_create = [&] { module2_restarted = true; }; |
| RestartStory("storyname"); |
| RunLoopUntil([&] { return module2_restarted; }); |
| EXPECT_FALSE(module1.is_running()); |
| EXPECT_TRUE(module2_destroyed); |
| } |
| |
| } // namespace |