| // Copyright 2017 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 <fuchsia/ui/viewsv1token/cpp/fidl.h> |
| #include <lib/component/cpp/connect.h> |
| #include <lib/component/cpp/startup_context.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/fxl/macros.h> |
| |
| #include "peridot/lib/rapidjson/rapidjson.h" |
| #include "peridot/lib/testing/component_base.h" |
| #include "peridot/lib/testing/reporting.h" |
| #include "peridot/lib/testing/testing.h" |
| #include "peridot/tests/common/defs.h" |
| #include "peridot/tests/story_update/defs.h" |
| |
| using modular::testing::TestPoint; |
| |
| namespace { |
| |
| // Tests how modules are updated in a story. |
| class TestApp |
| : public modular::testing::ComponentBase<fuchsia::modular::UserShell> { |
| public: |
| TestApp(component::StartupContext* const startup_context) |
| : ComponentBase(startup_context) { |
| TestInit(__FILE__); |
| } |
| |
| ~TestApp() override = default; |
| |
| private: |
| TestPoint initialize_{"Initialize()"}; |
| TestPoint story_create_{"Story Create"}; |
| |
| // |fuchsia::modular::UserShell| |
| void Initialize(fidl::InterfaceHandle<fuchsia::modular::UserShellContext> |
| user_shell_context) override { |
| initialize_.Pass(); |
| |
| user_shell_context_.Bind(std::move(user_shell_context)); |
| user_shell_context_->GetStoryProvider(story_provider_.NewRequest()); |
| |
| story_provider_->CreateStory(kCommonNullModule, |
| [this](fidl::StringPtr story_id) { |
| story_create_.Pass(); |
| GetController(story_id); |
| }); |
| } |
| |
| TestPoint root_running_{"Root Module RUNNING"}; |
| |
| void GetController(fidl::StringPtr story_id) { |
| story_provider_->GetController(story_id, story_controller_.NewRequest()); |
| |
| fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner> story_view; |
| story_controller_->Start(story_view.NewRequest()); |
| |
| fidl::VectorPtr<fidl::StringPtr> module_path; |
| module_path.push_back("root"); |
| |
| module0_controller_.events().OnStateChange = |
| [this](fuchsia::modular::ModuleState module_state) { |
| if (module_state == fuchsia::modular::ModuleState::RUNNING) { |
| root_running_.Pass(); |
| PipelinedAddGetStop(); |
| } |
| }; |
| |
| story_controller_->GetModuleController(std::move(module_path), |
| module0_controller_.NewRequest()); |
| } |
| |
| TestPoint module1_stopped_{"Module1 STOPPED"}; |
| TestPoint module1_gone_{"Module1 gone"}; |
| |
| void PipelinedAddGetStop() { |
| // Tests two invariants: |
| // |
| // 1. Pipelined fuchsia::modular::AddModule(), GetModuleController(), |
| // fuchsia::modular::ModuleController.Stop() |
| // transitions to the module state STOPPED. |
| // |
| // 2. After fuchsia::modular::ModuleController.Stop() completes (as observed |
| // by reaching teh |
| // STOPPED state), GetActiveModules() shows the module as not running. |
| // (This cannot be pipelined because the requests are on different |
| // existing connections.) |
| // |
| // TODO(mesch): The API as it is defined now does not allow to guarantee to |
| // observe a transition through the STARTING and RUNNING states. The |
| // implementation also makes no guarantees in the first place to await the |
| // module reaching RUNNING before it gets stopped, irrespective of |
| // observability of the state transitions. |
| // |
| // The observability of the STOPPED state, however, is guaranteed. |
| fuchsia::modular::Intent intent; |
| intent.handler = kCommonNullModule; |
| story_controller_->AddModule(nullptr /* parent_module_path */, "module1", |
| std::move(intent), |
| nullptr /* surface_relation */); |
| |
| fidl::VectorPtr<fidl::StringPtr> module_path; |
| module_path.push_back("module1"); |
| |
| module1_controller_.events().OnStateChange = |
| [this](fuchsia::modular::ModuleState new_state) { |
| if (new_state == fuchsia::modular::ModuleState::STOPPED) { |
| module1_stopped_.Pass(); |
| } |
| }; |
| |
| story_controller_->GetModuleController(std::move(module_path), |
| module1_controller_.NewRequest()); |
| |
| module1_controller_->Stop([this] { GetActiveModules1(); }); |
| } |
| |
| void GetActiveModules1() { |
| story_controller_->GetActiveModules( |
| nullptr, [this](fidl::VectorPtr<fuchsia::modular::ModuleData> modules) { |
| if (modules->size() == 1) { |
| module1_gone_.Pass(); |
| } |
| |
| SequentialAddGetStop(); |
| }); |
| } |
| |
| TestPoint module2_running_{"Module2 RUNNING"}; |
| TestPoint module2_stopped_{"Module2 STOPPED"}; |
| TestPoint module2_gone_{"Module2 gone"}; |
| |
| void SequentialAddGetStop() { |
| // Tests these invariants: |
| // |
| // 1. Pipelined fuchsia::modular::AddModule(), GetModuleController() |
| // transitions to the |
| // module state RUNNING. |
| // |
| // 2. Sequential (sequenced after RUNNING state is reached) |
| // fuchsia::modular::ModuleController.Stop() transitions to the module |
| // state STOPPED. |
| // |
| // 3. Sequential GetActiveModules() (sequenced after STOPPED state is |
| // reached) shows the module as not running. |
| // |
| // TODO(mesch): Like above, the API does not make guarantees to be able to |
| // observe the STARTING state. It only guarantees to observe the RUNNING |
| // state, and only if the module doesn't call Done() in its own. |
| // |
| // TODO(mesch): If the module calls Done() on its context (as |
| // common_done_module, for example, would), it is stopped by the story |
| // runner because it's a top level module. If this happens at the same time |
| // as this call, the callback may never invoked because it's preempted by |
| // the story runner handling the Done() request from the module. Instead, |
| // the controller connection is just closed, and flow of control would need |
| // to resume from the connection error handler of the module controller. |
| fuchsia::modular::Intent intent; |
| intent.handler = kCommonNullModule; |
| story_controller_->AddModule(nullptr /* parent_module_path */, "module2", |
| std::move(intent), |
| nullptr /* surface_relation */); |
| |
| fidl::VectorPtr<fidl::StringPtr> module_path; |
| module_path.push_back("module2"); |
| |
| module2_controller_.events().OnStateChange = |
| [this](fuchsia::modular::ModuleState module_state) { |
| if (module_state == fuchsia::modular::ModuleState::RUNNING) { |
| module2_running_.Pass(); |
| module2_controller_->Stop([this] { GetActiveModules2(); }); |
| |
| } else if (module_state == fuchsia::modular::ModuleState::STOPPED) { |
| module2_stopped_.Pass(); |
| } |
| }; |
| |
| story_controller_->GetModuleController(std::move(module_path), |
| module2_controller_.NewRequest()); |
| } |
| |
| void GetActiveModules2() { |
| story_controller_->GetActiveModules( |
| nullptr, [this](fidl::VectorPtr<fuchsia::modular::ModuleData> modules) { |
| if (modules->size() == 1) { |
| module2_gone_.Pass(); |
| } |
| |
| Logout(); |
| }); |
| } |
| |
| void Logout() { user_shell_context_->Logout(); } |
| |
| fuchsia::modular::UserShellContextPtr user_shell_context_; |
| fuchsia::modular::StoryProviderPtr story_provider_; |
| fuchsia::modular::StoryControllerPtr story_controller_; |
| fuchsia::modular::StoryInfoPtr story_info_; |
| |
| fuchsia::modular::ModuleControllerPtr module0_controller_; |
| fuchsia::modular::ModuleControllerPtr module1_controller_; |
| fuchsia::modular::ModuleControllerPtr module2_controller_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(TestApp); |
| }; |
| |
| } // namespace |
| |
| int main(int /*argc*/, const char** /*argv*/) { |
| modular::testing::ComponentMain<TestApp>(); |
| return 0; |
| } |