| // 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 "lib/app/cpp/application_context.h" |
| #include "lib/fidl/cpp/bindings/binding.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/macros.h" |
| #include "lib/story/fidl/link.fidl.h" |
| #include "lib/ui/views/fidl/view_manager.fidl.h" |
| #include "lib/user/fidl/user_shell.fidl.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" |
| |
| namespace { |
| |
| constexpr char kModuleUrl[] = "file:///system/test/modular_tests/common_null_module"; |
| |
| // A simple link watcher implementation allows to specify the actual |
| // notification callback as a lambda and update it dynamically. |
| class LinkWatcherImpl : modular::LinkWatcher { |
| public: |
| LinkWatcherImpl() : binding_(this) {} |
| ~LinkWatcherImpl() override = default; |
| |
| // Registers itself as watcher on the given link. Only one link at a time can |
| // be watched. |
| void Watch(modular::LinkPtr* const link) { |
| (*link)->WatchAll(binding_.NewBinding()); |
| } |
| |
| // Sets the function that's called for a notification. |
| void Continue(std::function<void(const fidl::String&)> at) { continue_ = at; } |
| |
| private: |
| // |LinkWatcher| |
| void Notify(const fidl::String& json) override { |
| FXL_LOG(INFO) << "LinkWatcher: " << json; |
| continue_(json); |
| } |
| |
| std::function<void(const fidl::String&)> continue_; |
| fidl::Binding<modular::LinkWatcher> binding_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(LinkWatcherImpl); |
| }; |
| |
| // Tests how data are updated in a Link. |
| class TestApp : public modular::testing::ComponentBase<modular::UserShell> { |
| public: |
| TestApp(app::ApplicationContext* const application_context) |
| : ComponentBase(application_context) { |
| TestInit(__FILE__); |
| } |
| |
| ~TestApp() override = default; |
| |
| private: |
| using TestPoint = modular::testing::TestPoint; |
| |
| TestPoint initialize_{"Initialize()"}; |
| TestPoint story_create_{"Story Create"}; |
| |
| // |UserShell| |
| void Initialize(fidl::InterfaceHandle<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( |
| kModuleUrl, |
| [this](const fidl::String& story_id) { |
| story_create_.Pass(); |
| GetController(story_id); |
| }); |
| } |
| |
| void GetController(const fidl::String& story_id) { |
| story_provider_->GetController(story_id, story_controller_.NewRequest()); |
| |
| story_controller_->GetLink(nullptr, "root", root_link_.NewRequest()); |
| story_provider_->GetLinkPeer(story_id, nullptr, "root", |
| root_peer_.NewRequest()); |
| |
| link_watcher_.Watch(&root_link_); |
| |
| SequentialSet(); |
| } |
| |
| // Both updates 1 and 2 are guaranteed to be delivered, and in the order they |
| // were issued. |
| TestPoint notify_1_{"Notify() 1"}; |
| TestPoint notify_2_{"Notify() 2"}; |
| |
| void SequentialSet() { |
| link_watcher_.Continue([this](const fidl::String& json) { |
| if (json == "1") { |
| notify_1_.Pass(); |
| } else if (json == "2") { |
| notify_2_.Pass(); |
| PeerSet(); |
| } |
| }); |
| |
| root_link_->Set(nullptr, "1"); |
| root_link_->Set(nullptr, "2"); |
| } |
| |
| // Only update 4 is guaranteed to be delivered on link_watcher_, although |
| // if 3 is delivered at all, then it's before 4. |
| TestPoint notify_4_{"Notify() 4"}; |
| |
| void PeerSet() { |
| link_watcher_.Continue([this](const fidl::String& json) { |
| if (json == "4") { |
| notify_4_.Pass(); |
| ConcurrentSet(); |
| } |
| }); |
| |
| // Without this nanosleep() line, 3 and 4 can have keys BEFORE 1 and 2 |
| // because the timestamp is at millisecond resolution with a random number |
| // to break ties, which means that 3 and 4 would not overwrite the 2. |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(2))); |
| |
| // Watch the log to see what values are actually seen by the Watcher. |
| root_peer_->Set(nullptr, "3"); |
| root_peer_->Set(nullptr, "4"); |
| } |
| |
| // The local update 6 is the only update guaranteed to be seen locally. |
| // However, if update 5 is processed by the Link after update 6, it will not |
| // affect the current value and so will not generate a second notification |
| // for update 6. |
| // |
| // NOTE(mesch): There is no ordering guarantee between the two updates. This |
| // is as intended as far as production behavior is concerned. For testing, we |
| // would like to be able to force an ordering, or a conflict, but right now we |
| // cannot. |
| TestPoint notify_6_{"Notify() 6"}; |
| |
| void ConcurrentSet() { |
| std::shared_ptr<bool> called = std::make_shared<bool>(); |
| link_watcher_.Continue([this, called](const fidl::String& json) { |
| if (json == "6") { |
| notify_6_.Pass(); |
| if (!*called) { |
| Logout(); |
| *called = true; |
| } |
| } |
| }); |
| |
| // Watch the log to see what values actually arrive, and in which order. |
| root_peer_->Set(nullptr, "5"); |
| root_link_->Set(nullptr, "6"); |
| |
| fsl::MessageLoop::GetCurrent()->task_runner()->PostDelayedTask( |
| [this, called] { |
| if (!*called) { |
| FXL_LOG(WARNING) << "Shutdown timed out"; |
| Logout(); |
| *called = true; |
| } |
| }, |
| fxl::TimeDelta::FromSeconds(5u)); |
| |
| // The code below does not work because it does not wait for the Ledger |
| // to deliver all of its messages. |
| // root_link_->Sync([this] { root_peer_->Sync([this] { Logout(); }); }); |
| } |
| |
| void Logout() { user_shell_context_->Logout(); } |
| |
| LinkWatcherImpl link_watcher_; |
| |
| modular::UserShellContextPtr user_shell_context_; |
| modular::StoryProviderPtr story_provider_; |
| modular::StoryControllerPtr story_controller_; |
| modular::LinkPtr root_link_; |
| modular::LinkPtr root_peer_; |
| modular::StoryInfoPtr story_info_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(TestApp); |
| }; |
| |
| } // namespace |
| |
| int main(int /*argc*/, const char** /*argv*/) { |
| modular::testing::ComponentMain<TestApp>(); |
| return 0; |
| } |