blob: c4f8d03bd797898d9b1ed18713e7654eeb7435d2 [file] [log] [blame]
// Copyright 2016 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 <set>
#include <utility>
#include <fuchsia/modular/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/ui/viewsv1token/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fsl/vmo/sized_vmo.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/fxl/command_line.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/macros.h>
#include <lib/fxl/time/time_delta.h>
#include "peridot/lib/rapidjson/rapidjson.h"
#include "peridot/lib/testing/component_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/session_shell/defs.h"
using modular::testing::Await;
using modular::testing::Signal;
using modular::testing::TestPoint;
namespace {
// A simple story provider watcher implementation. Just logs observed state
// transitions.
class StoryProviderStateWatcherImpl : fuchsia::modular::StoryProviderWatcher {
public:
StoryProviderStateWatcherImpl() : binding_(this) {}
~StoryProviderStateWatcherImpl() override = default;
// Registers itself a watcher on the given story provider. Only one story
// provider can be watched at a time.
void Watch(fuchsia::modular::StoryProviderPtr* const story_provider) {
(*story_provider)->Watch(binding_.NewBinding());
}
// Deregisters itself from the watched story provider.
void Reset() { binding_.Unbind(); }
void SetKindOfProtoStory(fidl::StringPtr story_id) {
kind_of_proto_stories_.insert(story_id);
}
private:
TestPoint on_delete_called_once_{"OnDelete() Called"};
int on_delete_called_{};
// |fuchsia::modular::StoryProviderWatcher|
void OnDelete(fidl::StringPtr story_id) override {
FXL_LOG(INFO) << "StoryProviderStateWatcherImpl::OnDelete() " << story_id;
if (++on_delete_called_ == 1) {
on_delete_called_once_.Pass();
}
deleted_stories_.emplace(story_id);
}
TestPoint on_running_called_once_{"OnChange() RUNNING Called"};
int on_running_called_{};
TestPoint on_stopping_called_once_{"OnChange() STOPPING Called"};
int on_stopping_called_{};
TestPoint on_stopped_called_once_{"OnChange() STOPPED Called"};
int on_stopped_called_{};
// |fuchsia::modular::StoryProviderWatcher|
void OnChange(const fuchsia::modular::StoryInfo story_info,
const fuchsia::modular::StoryState story_state,
const fuchsia::modular::StoryVisibilityState
story_visibility_state) override {
FXL_LOG(INFO) << "StoryProviderStateWatcherImpl::OnChange() "
<< " id " << story_info.id << " state "
<< fidl::ToUnderlying(story_state) << " visibility state "
<< fidl::ToUnderlying(story_visibility_state) << " url "
<< story_info.url;
if (deleted_stories_.find(story_info.id) != deleted_stories_.end()) {
FXL_LOG(ERROR) << "Status change notification for deleted story "
<< story_info.id;
modular::testing::Fail("Status change notification for deleted story");
}
if (kind_of_proto_stories_.find(story_info.id) !=
kind_of_proto_stories_.end()) {
modular::testing::Fail(
"Stories with kind_of_proto_story option set shouldn't notify "
"OnChange");
}
// Just check that all states are covered at least once, proving that we get
// state notifications at all from the story provider.
switch (story_state) {
case fuchsia::modular::StoryState::RUNNING:
if (++on_running_called_ == 1) {
on_running_called_once_.Pass();
}
break;
case fuchsia::modular::StoryState::STOPPING:
if (++on_stopping_called_ == 1) {
on_stopping_called_once_.Pass();
}
break;
case fuchsia::modular::StoryState::STOPPED:
if (++on_stopped_called_ == 1) {
on_stopped_called_once_.Pass();
}
break;
}
}
fidl::Binding<fuchsia::modular::StoryProviderWatcher> binding_;
// Remember deleted stories. After a story is deleted, there must be no state
// change notifications for it.
std::set<std::string> deleted_stories_;
std::set<std::string> kind_of_proto_stories_;
FXL_DISALLOW_COPY_AND_ASSIGN(StoryProviderStateWatcherImpl);
};
// Cf. README.md for what this test does and how.
class TestApp : public modular::testing::ComponentBase<void> {
public:
explicit TestApp(component::StartupContext* const startup_context)
: ComponentBase(startup_context) {
TestInit(__FILE__);
session_shell_context_ = startup_context->ConnectToEnvironmentService<
fuchsia::modular::SessionShellContext>();
puppet_master_ =
startup_context
->ConnectToEnvironmentService<fuchsia::modular::PuppetMaster>();
session_shell_context_->GetStoryProvider(story_provider_.NewRequest());
story_provider_state_watcher_.Watch(&story_provider_);
TestStoryProvider_GetStoryInfo_Null();
}
~TestApp() override = default;
private:
TestPoint create_view_{"CreateView()"};
// |SingleServiceApp|
void CreateView(
zx::eventpair /*view_token*/,
fidl::InterfaceRequest<
fuchsia::sys::ServiceProvider> /*incoming_services*/,
fidl::InterfaceHandle<
fuchsia::sys::ServiceProvider> /*outgoing_services*/) override {
create_view_.Pass();
}
TestPoint get_story_info_null_{"StoryProvider.GetStoryInfo() is null"};
void TestStoryProvider_GetStoryInfo_Null() {
story_provider_->GetStoryInfo(
"X", [this](fuchsia::modular::StoryInfoPtr story_info) {
if (!story_info) {
get_story_info_null_.Pass();
}
TestSessionShellContext_GetLink();
});
}
TestPoint get_link_{"SessionShellContext.GetLink()"};
void TestSessionShellContext_GetLink() {
session_shell_context_->GetLink(session_shell_link_.NewRequest());
session_shell_link_->Get(
nullptr, [this](std::unique_ptr<fuchsia::mem::Buffer> value) {
get_link_.Pass();
TestStoryProvider_GetStories();
});
}
TestPoint previous_stories_{"StoryProvider.GetStories()"};
void TestStoryProvider_GetStories() {
story_provider_->GetStories(
nullptr, [this](fidl::VectorPtr<fuchsia::modular::StoryInfo> stories) {
previous_stories_.Pass();
TestStoryProvider_GetStoryInfo(std::move(stories));
});
}
TestPoint get_story_info_{"StoryProvider.GetStoryInfo()"};
void TestStoryProvider_GetStoryInfo(
fidl::VectorPtr<fuchsia::modular::StoryInfo> stories) {
if (stories->empty()) {
get_story_info_.Pass();
} else {
FXL_LOG(ERROR) << "StoryProvider.GetStoryInfo() " << stories->size();
for (const auto& item : stories.get()) {
FXL_LOG(INFO) << item.id;
}
}
TestStory1();
}
TestPoint story1_create_{"Story1 Create"};
void TestStory1() {
const std::string initial_json = R"({"created-with-info": true})";
puppet_master_->ControlStory("story1", story_puppet_master_.NewRequest());
fidl::VectorPtr<fuchsia::modular::StoryCommand> commands;
fuchsia::modular::AddMod add_mod;
add_mod.mod_name.push_back("mod1");
add_mod.intent.handler = kCommonActiveModule;
fuchsia::modular::IntentParameter param;
param.name = "root";
fsl::SizedVmo vmo;
FXL_CHECK(fsl::VmoFromString(initial_json, &vmo));
param.data.set_json(std::move(vmo).ToTransport());
add_mod.intent.parameters.push_back(std::move(param));
fuchsia::modular::StoryCommand command;
command.set_add_mod(std::move(add_mod));
commands.push_back(std::move(command));
story_puppet_master_->Enqueue(std::move(commands));
story_puppet_master_->Execute(
[this](fuchsia::modular::ExecuteResult result) {
story1_create_.Pass();
TestStory1_GetController("story1");
});
}
TestPoint story1_get_controller_{"Story1 GetController"};
void TestStory1_GetController(fidl::StringPtr story_id) {
story_provider_->GetController(story_id, story_controller_.NewRequest());
const std::string initial_json = R"({"created-with-info": true})";
fuchsia::modular::Intent intent;
intent.handler = kCommonActiveModule;
intent.action = kCommonActiveAction;
fuchsia::modular::IntentParameter param;
param.name = nullptr;
fuchsia::mem::Buffer buf;
FXL_CHECK(fsl::VmoFromString(initial_json, &buf));
param.data.set_json(std::move(buf));
intent.parameters.push_back(std::move(param));
story_controller_->AddModule(nullptr, "root_module_name", std::move(intent),
nullptr);
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo story_info,
fuchsia::modular::StoryState state) {
story1_get_controller_.Pass();
story_info_ = std::move(story_info);
TestStory1_Run();
});
}
TestPoint story1_run_{"Story1 Run"};
void TestStory1_Run() {
// Start and show the new story.
fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner> story_view;
story_controller_->Start(story_view.NewRequest());
story1_run_.Pass();
TestStory1_Stop();
}
TestPoint story1_stop_{"Story1 Stop"};
void TestStory1_Stop() {
story_controller_->Stop([this] {
TeardownStoryController();
story1_stop_.Pass();
// When the story is done, we start the next one.
TestStory2();
});
}
TestPoint story2_create_{"Story2 Create"};
void TestStory2() {
puppet_master_->ControlStory("story2", story_puppet_master_.NewRequest());
fidl::VectorPtr<fuchsia::modular::StoryCommand> commands;
fuchsia::modular::AddMod add_mod;
add_mod.mod_name.push_back("mod1");
add_mod.intent.handler = kCommonNullModule;
fuchsia::modular::StoryCommand command;
command.set_add_mod(std::move(add_mod));
commands.push_back(std::move(command));
story_puppet_master_->Enqueue(std::move(commands));
story_puppet_master_->Execute(
[this](fuchsia::modular::ExecuteResult result) {
story2_create_.Pass();
TestStory2_GetController("story2");
});
}
TestPoint story2_get_controller_{"Story2 Get Controller"};
void TestStory2_GetController(fidl::StringPtr story_id) {
story_provider_->GetController(story_id, story_controller_.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo story_info,
fuchsia::modular::StoryState state) {
story_info_ = std::move(story_info);
story2_get_controller_.Pass();
TestStory2_GetModules();
});
}
TestPoint story2_get_modules_{"Story2 Get Modules"};
void TestStory2_GetModules() {
story_controller_->GetModules(
[this](fidl::VectorPtr<fuchsia::modular::ModuleData> modules) {
if (modules->size() == 1) {
story2_get_modules_.Pass();
}
TestStory2_Run();
});
}
TestPoint story2_state_before_run_{"Story2 State before Run"};
TestPoint story2_state_after_run_{"Story2 State after Run"};
void TestStory2_Run() {
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
if (state == fuchsia::modular::StoryState::STOPPED) {
story2_state_before_run_.Pass();
}
});
// Start and show the new story *while* the GetInfo() call above is in
// flight.
fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner> story_view;
story_controller_->Start(story_view.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
if (state == fuchsia::modular::StoryState::RUNNING) {
story2_state_after_run_.Pass();
}
TestStory2_DeleteStory();
});
}
TestPoint story2_delete_{"Story2 Delete"};
void TestStory2_DeleteStory() {
puppet_master_->DeleteStory(story_info_.id,
[this] { story2_delete_.Pass(); });
story_provider_->GetStoryInfo(
story_info_.id, [this](fuchsia::modular::StoryInfoPtr info) {
TestStory2_InfoAfterDeleteIsNull(std::move(info));
});
}
TestPoint story2_info_after_delete_{"Story2 Info After Delete"};
void TestStory2_InfoAfterDeleteIsNull(fuchsia::modular::StoryInfoPtr info) {
story2_info_after_delete_.Pass();
if (info) {
modular::testing::Fail("StoryInfo after DeleteStory() must return null.");
}
TestStory3();
}
TestPoint story3_create_{"Story3 Create"};
void TestStory3() {
story_provider_state_watcher_.Reset();
story_provider_state_watcher_.Watch(&story_provider_);
puppet_master_->ControlStory("story3", story_puppet_master_.NewRequest());
fuchsia::modular::StoryOptions story_options;
story_options.kind_of_proto_story = true;
story_puppet_master_->SetCreateOptions(std::move(story_options));
story_puppet_master_->Execute(
[this](fuchsia::modular::ExecuteResult result) {
story_provider_state_watcher_.SetKindOfProtoStory("story3");
story3_create_.Pass();
TestStory3_GetController("story3");
});
}
TestPoint story3_get_controller_{"Story3 GetController"};
void TestStory3_GetController(fidl::StringPtr story_id) {
story_provider_->GetController(story_id, story_controller_.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo story_info,
fuchsia::modular::StoryState state) {
story_info_ = std::move(story_info);
story3_get_controller_.Pass();
TestStory3_GetStories();
});
}
TestPoint story3_previous_stories_{"Story3 GetGetStories"};
void TestStory3_GetStories() {
story_provider_->GetStories(
nullptr, [this](fidl::VectorPtr<fuchsia::modular::StoryInfo> stories) {
// Since this is a kind-of-proto story, it shouldn't appear in
// GetStories calls. Note that we still expect 1 story to be here
// since Story1 wasn't deleted.
if (stories->size() == 1 && stories->at(0).id != story_info_.id) {
story3_previous_stories_.Pass();
} else {
FXL_LOG(ERROR) << "StoryProvider.GetStories() " << stories->size();
for (const auto& item : stories.get()) {
FXL_LOG(INFO) << item.id;
}
}
TestStory3_Run();
});
}
TestPoint story3_run_{"Story3 Run"};
void TestStory3_Run() {
fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner> story_view;
story_controller_->Start(story_view.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
if (state == fuchsia::modular::StoryState::RUNNING) {
story3_run_.Pass();
}
TestStory3_Stop();
});
}
TestPoint story3_stop_{"Story3 Stop"};
void TestStory3_Stop() {
story_controller_->Stop([this] {
TeardownStoryController();
story3_stop_.Pass();
TestStory3_DeleteStory();
});
}
TestPoint story3_delete_{"Story3 Delete"};
void TestStory3_DeleteStory() {
puppet_master_->DeleteStory(story_info_.id,
[this] { story3_delete_.Pass(); });
story_provider_->GetStoryInfo(
story_info_.id, [this](fuchsia::modular::StoryInfoPtr info) {
TestStory3_InfoAfterDeleteIsNull(std::move(info));
});
}
TestPoint story3_info_after_delete_{"Story3 InfoAfterDeleteIsNull"};
void TestStory3_InfoAfterDeleteIsNull(fuchsia::modular::StoryInfoPtr info) {
if (!info) {
story3_info_after_delete_.Pass();
}
Signal(modular::testing::kTestShutdown);
}
void TeardownStoryController() { story_controller_.Unbind(); }
StoryProviderStateWatcherImpl story_provider_state_watcher_;
fuchsia::modular::SessionShellContextPtr session_shell_context_;
fuchsia::modular::StoryProviderPtr story_provider_;
fuchsia::modular::PuppetMasterPtr puppet_master_;
fuchsia::modular::StoryPuppetMasterPtr story_puppet_master_;
fuchsia::modular::StoryControllerPtr story_controller_;
fuchsia::modular::LinkPtr session_shell_link_;
fuchsia::modular::StoryInfo story_info_;
FXL_DISALLOW_COPY_AND_ASSIGN(TestApp);
};
} // namespace
int main(int argc, const char** argv) {
auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
modular::testing::ComponentMain<TestApp>();
return 0;
}