blob: 21613c20a86cb280fa1d99a3a28fd9ff9184b9f3 [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/fidl/cpp/binding_set.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_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/session_shell/defs.h"
using modular::testing::Await;
using modular::testing::Fail;
using modular::testing::Signal;
using modular::testing::TestPoint;
using fuchsia::modular::ViewIdentifier;
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::StoryProvider* 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(std::string 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 in general and how. The test cases are
// described in detail in comments below.
class TestApp : public modular::testing::SessionShellBase {
public:
using ViewId = fuchsia::modular::ViewIdentifier;
explicit TestApp(component::StartupContext* const startup_context)
: SessionShellBase(startup_context) {
TestInit(__FILE__);
startup_context->ConnectToEnvironmentService(puppet_master_.NewRequest());
startup_context->ConnectToEnvironmentService(
component_context_.NewRequest());
story_provider_state_watcher_.Watch(story_provider());
TestComponentContext_GetPackageName_Works();
}
~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();
}
// Test Case GetPackageName:
//
// When we call GetPackageName() on ComponentContext acquired from our
// environment, it works.
TestPoint component_context_package_name_{
"ComponentContext.GetPackageName() works"};
void TestComponentContext_GetPackageName_Works() {
component_context_->GetPackageName([this](fidl::StringPtr name) {
if (name) {
component_context_package_name_.Pass();
}
TestStoryProvider_GetStoryInfo_Null();
});
}
// Test Case GetStoryInfo Null:
//
// The story info of a story that does not exist is null.
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();
});
}
// Test Case SessionShellContext:
//
// The session shell can access a Link.
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();
});
}
// Test Case StoryProvider:
//
// The session shell can access the list of existing stories. This list is
// empty at the outset.
TestPoint previous_stories_{"StoryProvider.GetStories()"};
void TestStoryProvider_GetStories() {
story_provider()->GetStories(
nullptr, [this](std::vector<fuchsia::modular::StoryInfo> stories) {
previous_stories_.Pass();
TestStoryProvider_GetStoryInfo(std::move(stories));
});
}
TestPoint get_story_info_{"StoryProvider.GetStoryInfo()"};
void TestStoryProvider_GetStoryInfo(
std::vector<fuchsia::modular::StoryInfo> stories) {
if (stories.empty()) {
get_story_info_.Pass();
} else {
FXL_LOG(ERROR) << "StoryProvider.GetStoryInfo() " << stories.size();
for (const auto& item : stories) {
FXL_LOG(INFO) << item.id;
}
}
TestStory1();
}
// Test Case Story1:
//
// Create a story with extra information, start, and stop it.
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());
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));
std::vector<fuchsia::modular::StoryCommand> commands;
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());
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.
story_controller_->RequestStart();
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();
});
}
// Test Case Story2:
//
// Verify that when pipelining Start() and GetInfo() calls, GetInfo() yields
// the run state after Start().
//
// Verify that after DeleteStory(), GetInfo() returns null again.
TestPoint story2_create_{"Story2 Create"};
void TestStory2() {
puppet_master_->ControlStory("story2", story_puppet_master_.NewRequest());
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));
std::vector<fuchsia::modular::StoryCommand> commands;
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](std::vector<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.
story_controller_->RequestStart();
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();
}
// Test Case Story3:
//
// Verify that a "kind of proto" story doesn't appear in the list of stories
// of the story provider.
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](std::vector<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) {
FXL_LOG(INFO) << item.id;
}
}
TestStory3_Run();
});
}
TestPoint story3_run_{"Story3 Run"};
void TestStory3_Run() {
story_controller_->RequestStart();
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();
}
TestStory4();
}
// Test Case Story4:
//
// Create a story and start it with RequestStart() rather than Start().
//
// Verify the view is received through SessionShell.AttachView().
//
// Verify that, when the story is stopped, a request for
// SessionShell.DetachView() is received.
TestPoint story4_create_{"Story4 Create"};
void TestStory4() {
puppet_master_->ControlStory("story4", story_puppet_master_.NewRequest());
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));
std::vector<fuchsia::modular::StoryCommand> commands;
commands.push_back(std::move(command));
story_puppet_master_->Enqueue(std::move(commands));
story_puppet_master_->Execute(
[this](fuchsia::modular::ExecuteResult result) {
story4_create_.Pass();
TestStory4_Run();
});
}
TestPoint story4_state_before_run_{"Story4 State before Run"};
TestPoint story4_state_after_run_{"Story4 State after Run"};
TestPoint story4_attach_view_{"Story4 attach View"};
void TestStory4_Run() {
story_provider()->GetController("story4", story_controller_.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
story_info_ = std::move(info);
if (state == fuchsia::modular::StoryState::STOPPED) {
story4_state_before_run_.Pass();
}
});
// Start and show the new story using RequestStart().
story_controller_->RequestStart();
session_shell_impl()->set_on_attach_view(
[this](ViewId) { story4_attach_view_.Pass(); });
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
if (state == fuchsia::modular::StoryState::RUNNING) {
story4_state_after_run_.Pass();
TestStory4_Stop();
}
});
}
TestPoint story4_detach_view_{"Story4 detach View"};
TestPoint story4_stop_{"Story4 Stop"};
void TestStory4_Stop() {
session_shell_impl()->set_on_detach_view(
[this](ViewId) { story4_detach_view_.Pass(); });
story_controller_->Stop([this] {
TeardownStoryController();
story4_stop_.Pass();
TestStory4_DeleteStory();
});
}
TestPoint story4_delete_{"Story4 Delete"};
void TestStory4_DeleteStory() {
puppet_master_->DeleteStory(story_info_.id,
[this] { story4_delete_.Pass(); });
story_provider()->GetStoryInfo(
story_info_.id, [this](fuchsia::modular::StoryInfoPtr info) {
TestStory4_InfoAfterDeleteIsNull(std::move(info));
});
}
TestPoint story4_info_after_delete_{"Story4 Info after Delete is null"};
void TestStory4_InfoAfterDeleteIsNull(fuchsia::modular::StoryInfoPtr info) {
if (!info) {
story4_info_after_delete_.Pass();
}
TestStory5();
}
// Test Case Story5:
//
// Create a story and start it with RequestStart() rather than Start().
//
// Verify that, when the story is stopped, a request for
// SessionShell.DetachView() is received, and if the request is not answered,
// the Stop() request proceeds anyway.
TestPoint story5_create_{"Story5 Create"};
void TestStory5() {
puppet_master_->ControlStory("story5", story_puppet_master_.NewRequest());
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));
std::vector<fuchsia::modular::StoryCommand> commands;
commands.push_back(std::move(command));
story_puppet_master_->Enqueue(std::move(commands));
story_puppet_master_->Execute(
[this](fuchsia::modular::ExecuteResult result) {
story5_create_.Pass();
TestStory5_Run();
});
}
TestPoint story5_state_before_run_{"Story5 State before Run"};
TestPoint story5_state_after_run_{"Story5 State after Run"};
TestPoint story5_attach_view_{"Story5 attach View"};
void TestStory5_Run() {
story_provider()->GetController("story5", story_controller_.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
story_info_ = std::move(info);
if (state == fuchsia::modular::StoryState::STOPPED) {
story5_state_before_run_.Pass();
}
});
// Start and show the new story using RequestStart().
story_controller_->RequestStart();
session_shell_impl()->set_on_attach_view(
[this](ViewId) { story5_attach_view_.Pass(); });
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
if (state == fuchsia::modular::StoryState::RUNNING) {
story5_state_after_run_.Pass();
TestStory5_Stop();
}
});
}
TestPoint story5_stop_{"Story5 Stop"};
void TestStory5_Stop() {
// Ignore the detach view. The delay is larger than the timeout for the
// whole test configured in dev_base_shell.cc, so an attempt to wait for
// this timeout would fail the whole test.
session_shell_impl()->set_detach_delay(
zx::msec(modular::testing::kTestTimeoutMilliseconds * 2));
session_shell_impl()->set_on_detach_view([](ViewId) {});
story_controller_->Stop([this] {
TeardownStoryController();
story5_stop_.Pass();
TestStory5_DeleteStory();
});
}
TestPoint story5_delete_{"Story5 Delete"};
void TestStory5_DeleteStory() {
puppet_master_->DeleteStory(story_info_.id,
[this] { story5_delete_.Pass(); });
story_provider()->GetStoryInfo(
story_info_.id, [this](fuchsia::modular::StoryInfoPtr info) {
TestStory5_InfoAfterDeleteIsNull(std::move(info));
});
}
TestPoint story5_info_after_delete_{"Story5 Info after Delete is null"};
void TestStory5_InfoAfterDeleteIsNull(fuchsia::modular::StoryInfoPtr info) {
if (!info) {
story5_info_after_delete_.Pass();
}
TestStory6();
}
// Test Case Story6:
//
// Create a story and start it with RequestStart() rather than Start().
//
// Verify that, when the story is NOT stopped when the SessionShell is stopped
// (such as at Logout) NO request for SessionShell.DetachView() is received.
TestPoint story6_create_{"Story6 Create"};
void TestStory6() {
puppet_master_->ControlStory("story6", story_puppet_master_.NewRequest());
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));
std::vector<fuchsia::modular::StoryCommand> commands;
commands.push_back(std::move(command));
story_puppet_master_->Enqueue(std::move(commands));
story_puppet_master_->Execute(
[this](fuchsia::modular::ExecuteResult result) {
story6_create_.Pass();
TestStory6_Run();
});
}
TestPoint story6_state_before_run_{"Story6 State before Run"};
TestPoint story6_state_after_run_{"Story6 State after Run"};
TestPoint story6_attach_view_{"Story6 attach View"};
void TestStory6_Run() {
story_provider()->GetController("story6", story_controller_.NewRequest());
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
story_info_ = std::move(info);
if (state == fuchsia::modular::StoryState::STOPPED) {
story6_state_before_run_.Pass();
}
});
// Start and show the new story using RequestStart().
story_controller_->RequestStart();
session_shell_impl()->set_on_attach_view(
[this](ViewId) { story6_attach_view_.Pass(); });
story_controller_->GetInfo([this](fuchsia::modular::StoryInfo info,
fuchsia::modular::StoryState state) {
if (state == fuchsia::modular::StoryState::RUNNING) {
story6_state_after_run_.Pass();
TestStory6_Logout();
}
});
}
void TestStory6_Logout() {
// If we get a DetachView() call during logout, that's a failure.
session_shell_impl()->set_detach_delay(zx::sec(0));
session_shell_impl()->set_on_detach_view(
[](ViewId) { Fail("DetachView() Received on Logout"); });
Signal(modular::testing::kTestShutdown);
}
void TeardownStoryController() { story_controller_.Unbind(); }
StoryProviderStateWatcherImpl story_provider_state_watcher_;
fuchsia::modular::PuppetMasterPtr puppet_master_;
fuchsia::modular::ComponentContextPtr component_context_;
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;
}