| // 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 "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; }); |
| } |
| |
| private: |
| 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; |
| fuchsia::modular::ModuleControllerPtr controller; |
| |
| 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(); |
| } |
| }; |
| |
| // Test that ModuleContext.AddModuleToStory() starts child modules and that |
| // calling it multiple times for the same child has different behavior if the |
| // Intent specifies the same handler, versus if it specifies a different |
| // handler. |
| TEST_F(ModuleContextTest, AddModuleToStory) { |
| modular_testing::TestHarnessBuilder builder; |
| |
| TestModule parent_module("parent_module"); |
| TestModule child_module1("child_module1"); |
| TestModule child_module2("child_module2"); |
| builder.InterceptComponent(parent_module.BuildInterceptOptions()); |
| builder.InterceptComponent(child_module1.BuildInterceptOptions()); |
| builder.InterceptComponent(child_module2.BuildInterceptOptions()); |
| |
| StartSession(std::move(builder)); |
| modular_testing::AddModToStory(test_harness(), "storyname", "modname", |
| {.action = "action", .handler = parent_module.url()}); |
| RunLoopUntil([&] { return parent_module.is_running(); }); |
| |
| // Add a single child module. |
| parent_module.module_context()->AddModuleToStory( |
| "childmodname", {.action = "action", .handler = child_module1.url()}, |
| child_module1.controller.NewRequest(), |
| /*surface_relation=*/nullptr, [&](fuchsia::modular::StartModuleStatus status) { |
| ASSERT_EQ(status, fuchsia::modular::StartModuleStatus::SUCCESS); |
| }); |
| RunLoopUntil([&] { return child_module1.is_running(); }); |
| |
| // Add the same module again but with a different Intent action. |
| bool child_module1_destroyed{false}; |
| child_module1.on_destroy = [&] { child_module1_destroyed = true; }; |
| parent_module.module_context()->AddModuleToStory( |
| "childmodname", {.action = "action2", .handler = child_module1.url()}, |
| child_module1.controller.NewRequest(), |
| /*surface_relation=*/nullptr, [&](fuchsia::modular::StartModuleStatus status) { |
| ASSERT_EQ(status, fuchsia::modular::StartModuleStatus::SUCCESS); |
| }); |
| RunLoopUntil([&] { return child_module1.is_running(); }); |
| // At no time should the child module have been destroyed. |
| EXPECT_EQ(child_module1_destroyed, false); |
| |
| // This time change the handler. Expect the first module to be shut down, |
| // and the second to run in its place. |
| parent_module.module_context()->AddModuleToStory( |
| "childmodname", {.action = "action", .handler = child_module2.url()}, |
| child_module2.controller.NewRequest(), |
| /*surface_relation=*/nullptr, [&](fuchsia::modular::StartModuleStatus status) { |
| ASSERT_EQ(status, fuchsia::modular::StartModuleStatus::SUCCESS); |
| }); |
| RunLoopUntil([&] { return child_module2.is_running(); }); |
| EXPECT_FALSE(child_module1.is_running()); |
| } |
| |
| // Test that ModuleContext.RemoveSelfFromStory() 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) { |
| 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 |