| // Copyright 2018 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 <memory> |
| |
| #include <fuchsia/modular/cpp/fidl.h> |
| #include <lib/callback/scoped_callback.h> |
| #include <lib/component/cpp/connect.h> |
| #include <lib/component/cpp/startup_context.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/fsl/vmo/strings.h> |
| #include <src/lib/fxl/logging.h> |
| #include <src/lib/fxl/macros.h> |
| |
| #include "peridot/lib/rapidjson/rapidjson.h" |
| #include "peridot/lib/testing/component_main.h" |
| #include "peridot/lib/testing/session_shell_base.h" |
| #include "peridot/public/lib/integration_testing/cpp/reporting.h" |
| #include "peridot/public/lib/integration_testing/cpp/testing.h" |
| #include "peridot/tests/common/defs.h" |
| #include "peridot/tests/module_context/defs.h" |
| |
| using ::modular::testing::Await; |
| using ::modular::testing::Signal; |
| using ::modular::testing::TestPoint; |
| |
| namespace { |
| |
| const char kStoryName[] = "story"; |
| |
| // A simple story activity watcher implementation. |
| class StoryActivityWatcherImpl : fuchsia::modular::StoryActivityWatcher { |
| public: |
| StoryActivityWatcherImpl() |
| : binding_(this), |
| on_notify_([](std::string, |
| std::vector<fuchsia::modular::OngoingActivityType>) {}) {} |
| ~StoryActivityWatcherImpl() override = default; |
| |
| void Watch(fuchsia::modular::StoryProvider* const story_provider) { |
| story_provider->WatchActivity(binding_.NewBinding()); |
| } |
| |
| void OnNotify( |
| fit::function<void(std::string, |
| std::vector<fuchsia::modular::OngoingActivityType>)> |
| on_notify) { |
| on_notify_ = std::move(on_notify); |
| } |
| |
| private: |
| // |fuchsia::modular::StoryActivityWatcher| |
| void OnStoryActivityChange( |
| std::string story_id, |
| std::vector<fuchsia::modular::OngoingActivityType> activities) override { |
| on_notify_(std::move(story_id), std::move(activities)); |
| } |
| |
| fidl::Binding<fuchsia::modular::StoryActivityWatcher> binding_; |
| fit::function<void(std::string, |
| std::vector<fuchsia::modular::OngoingActivityType>)> |
| on_notify_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(StoryActivityWatcherImpl); |
| }; |
| |
| class TestApp : public modular::testing::SessionShellBase { |
| public: |
| TestApp(component::StartupContext* const startup_context) |
| : SessionShellBase(startup_context), weak_ptr_factory_(this) { |
| TestInit(__FILE__); |
| |
| startup_context->ConnectToEnvironmentService(puppet_master_.NewRequest()); |
| |
| CreateStory(); |
| } |
| |
| ~TestApp() override = default; |
| |
| private: |
| TestPoint story_create_{"Created story."}; |
| void CreateStory() { |
| std::vector<fuchsia::modular::StoryCommand> commands; |
| { |
| fuchsia::modular::AddMod add_mod; |
| add_mod.mod_name_transitional = kFirstModuleName; |
| add_mod.intent = IntentWithParameterString(kFirstModuleName); |
| |
| fuchsia::modular::StoryCommand command; |
| command.set_add_mod(std::move(add_mod)); |
| commands.push_back(std::move(command)); |
| } |
| { |
| fuchsia::modular::AddMod add_mod; |
| add_mod.mod_name_transitional = kSecondModuleName; |
| add_mod.intent = IntentWithParameterString(kSecondModuleName); |
| |
| fuchsia::modular::StoryCommand command; |
| command.set_add_mod(std::move(add_mod)); |
| commands.push_back(std::move(command)); |
| } |
| { |
| fuchsia::modular::Intent intent; |
| intent.handler = kEntityModuleUrl; |
| intent.action = kEntityIntentAction; |
| fuchsia::modular::AddMod add_mod; |
| add_mod.mod_name_transitional = "entity_module"; |
| add_mod.intent = std::move(intent); |
| add_mod.surface_parent_mod_name.resize(0); |
| |
| fuchsia::modular::StoryCommand command; |
| command.set_add_mod(std::move(add_mod)); |
| commands.push_back(std::move(command)); |
| } |
| |
| puppet_master_->ControlStory(kStoryName, story_puppet_master_.NewRequest()); |
| story_puppet_master_->Enqueue(std::move(commands)); |
| story_puppet_master_->Execute( |
| [this](fuchsia::modular::ExecuteResult result) { |
| story_create_.Pass(); |
| StartStory(); |
| }); |
| } |
| |
| TestPoint story_get_controller_{"Story GetController()"}; |
| // Starts the story and adds two modules to it. |
| void StartStory() { |
| story_provider()->GetController(kStoryName, story_controller_.NewRequest()); |
| story_controller_.set_error_handler([](zx_status_t status) { |
| FXL_LOG(ERROR) << "Story controller for story " << kStoryName |
| << " died. Does this story exist?"; |
| }); |
| |
| story_controller_->RequestStart(); |
| story_controller_->GetInfo( |
| [this](fuchsia::modular::StoryInfo, fuchsia::modular::StoryState) { |
| story_get_controller_.Pass(); |
| PerformWatchActivity(); |
| }); |
| } |
| |
| TestPoint on_watch_ongoing_activities_dispatched{ |
| "When a watcher is registered, ongoing activities should be dispatched."}; |
| void PerformWatchActivity() { |
| story_activity_watcher_.Watch(story_provider()); |
| story_activity_watcher_.OnNotify( |
| [this](std::string story_id, |
| std::vector<fuchsia::modular::OngoingActivityType> activities) { |
| if (story_id == kStoryName && activities.empty()) { |
| on_watch_ongoing_activities_dispatched.Pass(); |
| } |
| PerformFirstModuleStartActivity(); |
| }); |
| } |
| |
| TestPoint on_start_ongoing_activity_dispatched{ |
| "When there is a new ongoing activity, the ongoing activity should be " |
| "dispatched."}; |
| // Signals the first module to call ModuleContext.StartOngoingActivity(). |
| void PerformFirstModuleStartActivity() { |
| Signal(kFirstModuleCallStartActivity); |
| story_activity_watcher_.OnNotify( |
| [this](std::string story_id, |
| std::vector<fuchsia::modular::OngoingActivityType> activities) { |
| if (story_id == kStoryName && activities.size() == 1 && |
| activities[0] == fuchsia::modular::OngoingActivityType::VIDEO) { |
| on_start_ongoing_activity_dispatched.Pass(); |
| } |
| PerformSecondModuleStartActivity(); |
| }); |
| } |
| |
| TestPoint on_start_all_ongoing_activities_dispatched{ |
| "When there is a new ongoing activity, all ongoing activities should be " |
| "dispatched."}; |
| // Signals the second module to call ModuleContext.StartOngoingActivity(). |
| void PerformSecondModuleStartActivity() { |
| Signal(kSecondModuleCallStartActivity); |
| story_activity_watcher_.OnNotify( |
| [this](std::string story_id, |
| std::vector<fuchsia::modular::OngoingActivityType> activities) { |
| if (story_id == kStoryName && activities.size() == 2 && |
| activities[0] == fuchsia::modular::OngoingActivityType::VIDEO && |
| activities[1] == fuchsia::modular::OngoingActivityType::VIDEO) { |
| on_start_all_ongoing_activities_dispatched.Pass(); |
| } |
| PerformSecondModuleStopActivity(); |
| }); |
| } |
| |
| TestPoint on_stop_remaining_ongoing_activities_dispatched{ |
| "When an ongoing activity is stopped, all remaining ongoing activities " |
| "should be dispatched."}; |
| // Signals the second module to stop ongoing activity. |
| void PerformSecondModuleStopActivity() { |
| Signal(kSecondModuleCallStopActivity); |
| story_activity_watcher_.OnNotify( |
| [this](std::string story_id, |
| std::vector<fuchsia::modular::OngoingActivityType> activities) { |
| if (story_id == kStoryName && activities.size() == 1 && |
| activities[0] == fuchsia::modular::OngoingActivityType::VIDEO) { |
| on_stop_remaining_ongoing_activities_dispatched.Pass(); |
| } |
| TestModuleCreatingEntity(); |
| }); |
| } |
| |
| void TestModuleCreatingEntity() { |
| Await(kEntityModuleDoneFirstTask, [this] { |
| Await(kEntityModuleDoneSecondTask, [this] { |
| fuchsia::modular::Intent intent; |
| intent.handler = kEntityModuleUrl; |
| intent.action = kEntityIntentAction; |
| fuchsia::modular::RemoveMod remove_mod; |
| remove_mod.mod_name_transitional = "entity_module"; |
| |
| std::vector<fuchsia::modular::StoryCommand> commands; |
| fuchsia::modular::StoryCommand command; |
| command.set_remove_mod(std::move(remove_mod)); |
| commands.push_back(std::move(command)); |
| |
| puppet_master_->ControlStory(kStoryName, |
| story_puppet_master_.NewRequest()); |
| story_puppet_master_->Enqueue(std::move(commands)); |
| story_puppet_master_->Execute( |
| [this](fuchsia::modular::ExecuteResult result) { |
| PerformFirstModuleDone(); |
| }); |
| }); |
| }); |
| } |
| |
| TestPoint on_done_ongoing_activities_stopped{ |
| "When a module is teared down, the ongoing activity should also be " |
| "stopped"}; |
| TestPoint second_module_active_{ |
| "Only second module is still active after first calls " |
| "RemoveSelfFromStory()"}; |
| // Signals the first module to call ModuleContext.RemoveSelfFromStory(). |
| void PerformFirstModuleDone() { |
| Signal(kFirstModuleCallDone); |
| Await(kFirstModuleTerminated, [this] { |
| // Verify that the second module is still active, but the first one is |
| // not. |
| story_controller_->GetActiveModules( |
| [this](std::vector<fuchsia::modular::ModuleData> module_data) { |
| if (module_data.size() == 1) { |
| second_module_active_.Pass(); |
| } |
| VerifyStoryStillRunning(); |
| }); |
| }); |
| |
| story_activity_watcher_.OnNotify( |
| [this](std::string story_id, |
| std::vector<fuchsia::modular::OngoingActivityType> activities) { |
| if (story_id == kStoryName && activities.empty()) { |
| on_done_ongoing_activities_stopped.Pass(); |
| } |
| }); |
| } |
| |
| TestPoint story_still_active_{ |
| "The story is still active after first module calls " |
| "RemoveSelfFromStory()"}; |
| // Verifies that the story is still running after the first module has called |
| // done and been stopped. |
| void VerifyStoryStillRunning() { |
| IsStoryRunning([this](bool is_running) { |
| if (is_running) { |
| story_still_active_.Pass(); |
| } |
| PerformSecondModuleDone(); |
| }); |
| } |
| |
| TestPoint no_module_active_{ |
| "No modules are active after second mod calls RemoveSelfFromStory()"}; |
| TestPoint story_stopped_{"The story was stopped."}; |
| // Signals the second module to call ModuleContext.Done. |
| void PerformSecondModuleDone() { |
| Signal(kSecondModuleCallDone); |
| Await(kSecondModuleTerminated, [this] { |
| // Verify that the second module is still active. |
| story_controller_->GetActiveModules( |
| [this](std::vector<fuchsia::modular::ModuleData> module_data) { |
| if (module_data.empty()) { |
| no_module_active_.Pass(); |
| } |
| IsStoryRunning([this](bool is_running) { |
| if (!is_running) { |
| story_stopped_.Pass(); |
| } |
| |
| Signal(modular::testing::kTestShutdown); |
| }); |
| }); |
| }); |
| } |
| |
| // Verifies that the story is stopped when the last module that is part of the |
| // story calls ModuleContext.Done and is stopped. |
| void IsStoryRunning(fit::function<void(bool)> callback) { |
| story_controller_->GetInfo( |
| [callback = std::move(callback)](fuchsia::modular::StoryInfo story_info, |
| fuchsia::modular::StoryState state) { |
| callback(state == fuchsia::modular::StoryState::RUNNING); |
| }); |
| } |
| |
| // Creates an intent with one parameter, kLinkName, with the following |
| // contents: { |kLinkKey| : |parameter_string| }. |
| fuchsia::modular::Intent IntentWithParameterString( |
| std::string parameter_string) { |
| fuchsia::modular::Intent intent; |
| intent.handler = kModuleUrl; |
| intent.action = kIntentAction; |
| |
| fuchsia::modular::IntentParameter parameter; |
| parameter.name = kLinkName; |
| |
| rapidjson::Document document; |
| document.SetObject(); |
| document.AddMember(kLinkKey, parameter_string, document.GetAllocator()); |
| |
| fuchsia::modular::IntentParameterData parameter_data; |
| fsl::SizedVmo vmo; |
| FXL_CHECK(fsl::VmoFromString(modular::JsonValueToString(document), &vmo)); |
| parameter_data.set_json(std::move(vmo).ToTransport()); |
| parameter.data = std::move(parameter_data); |
| intent.parameters.push_back(std::move(parameter)); |
| |
| return intent; |
| } |
| |
| fuchsia::modular::PuppetMasterPtr puppet_master_; |
| fuchsia::modular::StoryPuppetMasterPtr story_puppet_master_; |
| fuchsia::modular::StoryControllerPtr story_controller_; |
| StoryActivityWatcherImpl story_activity_watcher_; |
| |
| fxl::WeakPtrFactory<TestApp> weak_ptr_factory_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(TestApp); |
| }; |
| |
| } // namespace |
| |
| int main(int /*argc*/, const char** /*argv*/) { |
| modular::testing::ComponentMain<TestApp>(); |
| return 0; |
| } |