blob: cedada7bff5b6e4ed5d869df0c0365fa1b1c88c7 [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 <fuchsia/modular/testing/cpp/fidl.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/modular_test_harness/cpp/fake_session_shell.h>
#include <lib/modular_test_harness/cpp/test_harness_fixture.h>
#include <peridot/lib/modular_config/modular_config_constants.h>
#include "gmock/gmock.h"
// TODO(MF-435): Use modular::testing::AddModToStory() throughout the test.
using fuchsia::modular::AddMod;
using fuchsia::modular::StoryCommand;
using fuchsia::modular::StoryInfo;
using fuchsia::modular::StoryInfo2;
using fuchsia::modular::StoryState;
using fuchsia::modular::StoryVisibilityState;
using fuchsia::modular::ViewIdentifier;
using testing::IsNull;
using testing::Not;
constexpr char kFakeModuleUrl[] = "fuchsia-pkg://example.com/FAKE_MODULE_PKG/fake_module.cmx";
namespace {
class SessionShellTest : public modular::testing::TestHarnessFixture {
protected:
// Shared boilerplate for configuring the test harness to intercept the
// session shell, setting up the session shell mock object, running the test
// harness, and waiting for the session shell to be successfully intercepted.
// Note that this method blocks the thread until the session shell has started
// up.
//
// Not done in SetUp() or the constructor to let the test reader know that
// this is happening. Also, certain tests may want to change this flow.
void RunHarnessAndInterceptSessionShell() {
modular_testing::TestHarnessBuilder builder;
builder.InterceptSessionShell(fake_session_shell_.GetOnCreateHandler(),
{.sandbox_services = {"fuchsia.modular.SessionShellContext",
"fuchsia.modular.PuppetMaster"}});
builder.BuildAndRun(test_harness());
// Wait for our session shell to start.
RunLoopUntil([&] { return fake_session_shell_.is_running(); });
}
modular::testing::FakeSessionShell fake_session_shell_;
};
TEST_F(SessionShellTest, GetPackageName) {
fuchsia::modular::testing::TestHarnessSpec spec;
test_harness()->Run(std::move(spec));
fuchsia::modular::ComponentContextPtr component_context;
fuchsia::modular::testing::ModularService svc;
svc.set_component_context(component_context.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
bool got_name = false;
component_context->GetPackageName([&got_name](fidl::StringPtr name) {
EXPECT_THAT(name, Not(IsNull()));
got_name = true;
});
RunLoopUntil([&] { return got_name; });
}
TEST_F(SessionShellTest, GetStoryInfoNonexistentStory) {
RunHarnessAndInterceptSessionShell();
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
bool tried_get_story_info = false;
story_provider->GetStoryInfo2("X",
[&tried_get_story_info](fuchsia::modular::StoryInfo2 story_info) {
EXPECT_TRUE(story_info.IsEmpty());
tried_get_story_info = true;
});
RunLoopUntil([&] { return tried_get_story_info; });
}
TEST_F(SessionShellTest, GetLink) {
RunHarnessAndInterceptSessionShell();
fuchsia::modular::SessionShellContext* session_shell_context;
session_shell_context = fake_session_shell_.session_shell_context();
ASSERT_TRUE(session_shell_context != nullptr);
fuchsia::modular::LinkPtr session_shell_link;
session_shell_context->GetLink(session_shell_link.NewRequest());
bool called_get_link = false;
session_shell_link->Get(nullptr, [&called_get_link](std::unique_ptr<fuchsia::mem::Buffer> value) {
called_get_link = true;
});
RunLoopUntil([&] { return called_get_link; });
}
TEST_F(SessionShellTest, GetStoriesEmpty) {
RunHarnessAndInterceptSessionShell();
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
bool called_get_stories = false;
story_provider->GetStories2(
nullptr, [&called_get_stories](const std::vector<fuchsia::modular::StoryInfo2>& stories) {
EXPECT_THAT(stories, testing::IsEmpty());
called_get_stories = true;
});
RunLoopUntil([&] { return called_get_stories; });
}
TEST_F(SessionShellTest, StartAndStopStoryWithExtraInfoMod) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and launch a new story shell,
// including a mod with extra info.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
// Have the mock session_shell record the sequence of story states it sees,
// and confirm that it only sees the correct story id.
std::vector<StoryState> sequence_of_story_states;
modular::testing::SimpleStoryProviderWatcher watcher;
watcher.set_on_change_2([&sequence_of_story_states, kStoryId](StoryInfo2 story_info,
StoryState story_state,
StoryVisibilityState _) {
ASSERT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
sequence_of_story_states.push_back(story_state);
});
watcher.Watch(story_provider, /*on_get_stories=*/nullptr);
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
fuchsia::modular::IntentParameter param;
param.name = "root";
fsl::SizedVmo vmo;
const std::string initial_json = R"({"created-with-info": true})";
ASSERT_TRUE(fsl::VmoFromString(initial_json, &vmo));
param.data.set_json(std::move(vmo).ToTransport());
add_mod.intent.parameters.emplace();
add_mod.intent.parameters->push_back(std::move(param));
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
bool execute_called = false;
story_master->Execute(
[&execute_called](fuchsia::modular::ExecuteResult result) { execute_called = true; });
RunLoopUntil([&] { return execute_called; });
// Stop the story. Check that the story went through the correct sequence
// of states (see StoryState FIDL file for valid state transitions). Since we
// started it, ran it, and stopped it, the sequence is STOPPED -> RUNNING ->
// STOPPING -> STOPPED.
fuchsia::modular::StoryControllerPtr story_controller;
story_provider->GetController(kStoryId, story_controller.NewRequest());
bool stop_called = false;
story_controller->Stop([&stop_called] { stop_called = true; });
RunLoopUntil([&] { return stop_called; });
// Run the loop until there are the expected number of state changes;
// having called Stop() is not enough to guarantee seeing all updates.
RunLoopUntil([&] { return sequence_of_story_states.size() == 4; });
EXPECT_THAT(sequence_of_story_states,
testing::ElementsAre(StoryState::STOPPED, StoryState::RUNNING, StoryState::STOPPING,
StoryState::STOPPED));
}
TEST_F(SessionShellTest, StoryInfoBeforeAndAfterDelete) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and launch a new story shell.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
bool execute_and_get_story_info_called = false;
story_master->Execute([&execute_and_get_story_info_called, kStoryId,
story_provider](fuchsia::modular::ExecuteResult result) {
// Verify that the newly created story returns something for
// GetStoryInfo().
story_provider->GetStoryInfo2(kStoryId, [&execute_and_get_story_info_called,
kStoryId](fuchsia::modular::StoryInfo2 story_info) {
ASSERT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
execute_and_get_story_info_called = true;
});
});
RunLoopUntil([&] { return execute_and_get_story_info_called; });
// Delete the story and confirm that the story info is null now.
bool delete_called = false;
puppet_master->DeleteStory(kStoryId, [&delete_called, kStoryId, story_provider] {
story_provider->GetStoryInfo2(kStoryId, [](fuchsia::modular::StoryInfo2 story_info) {
EXPECT_TRUE(story_info.IsEmpty());
});
delete_called = true;
});
RunLoopUntil([&] { return delete_called; });
}
TEST_F(SessionShellTest, KindOfProtoStoryNotInStoryList) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and launch a new story shell,
// adding the kind of proto option.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
fuchsia::modular::StoryOptions story_options;
story_options.kind_of_proto_story = true;
story_master->SetCreateOptions(std::move(story_options));
bool called_get_stories = false;
story_master->Execute([&called_get_stories,
story_provider](fuchsia::modular::ExecuteResult result) {
// Confirm that even after the story is created, GetStories() returns
// empty.
story_provider->GetStories2(
nullptr, [&called_get_stories](const std::vector<fuchsia::modular::StoryInfo2>& stories) {
EXPECT_THAT(stories, testing::IsEmpty());
called_get_stories = true;
});
});
RunLoopUntil([&] { return called_get_stories; });
}
TEST_F(SessionShellTest, AttachesAndDetachesView) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and start a new story shell.
// Confirm that AttachView() is called.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
// Have the mock session_shell record the sequence of story states it sees,
// and confirm that it only sees the correct story id.
std::vector<StoryState> sequence_of_story_states;
modular::testing::SimpleStoryProviderWatcher watcher;
watcher.set_on_change_2([&sequence_of_story_states, kStoryId](StoryInfo2 story_info,
StoryState story_state,
StoryVisibilityState _) {
EXPECT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
sequence_of_story_states.push_back(story_state);
});
watcher.Watch(story_provider, /*on_get_stories=*/nullptr);
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
story_master->Execute([](fuchsia::modular::ExecuteResult result) {});
bool called_attach_view = false;
fake_session_shell_.set_on_attach_view(
[&called_attach_view](ViewIdentifier) { called_attach_view = true; });
RunLoopUntil([&] { return called_attach_view; });
// Stop the story. Confirm that:
// a. DetachView() was called.
// b. The story went through the correct sequence
// of states (see StoryState FIDL file for valid state transitions). Since we
// started it, ran it, and stopped it, the sequence is STOPPED -> RUNNING ->
// STOPPING -> STOPPED.
bool called_detach_view = false;
fake_session_shell_.set_on_detach_view(
[&called_detach_view](ViewIdentifier) { called_detach_view = true; });
fuchsia::modular::StoryControllerPtr story_controller;
story_provider->GetController(kStoryId, story_controller.NewRequest());
bool stop_called = false;
story_controller->Stop([&stop_called] { stop_called = true; });
RunLoopUntil([&] { return stop_called; });
// Run the loop until there are the expected number of state changes;
// having called Stop() is not enough to guarantee seeing all updates.
RunLoopUntil([&] { return sequence_of_story_states.size() == 4; });
EXPECT_TRUE(called_detach_view);
EXPECT_THAT(sequence_of_story_states,
testing::ElementsAre(StoryState::STOPPED, StoryState::RUNNING, StoryState::STOPPING,
StoryState::STOPPED));
}
TEST_F(SessionShellTest, StoryStopDoesntWaitOnDetachView) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and start a new story shell.
// Confirm that AttachView() is called.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
// Have the mock session_shell record the sequence of story states it sees,
// and confirm that it only sees the correct story id.
std::vector<StoryState> sequence_of_story_states;
modular::testing::SimpleStoryProviderWatcher watcher;
watcher.set_on_change_2([&sequence_of_story_states, kStoryId](StoryInfo2 story_info,
StoryState story_state,
StoryVisibilityState _) {
EXPECT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
sequence_of_story_states.push_back(story_state);
});
watcher.Watch(story_provider, /*on_get_stories=*/nullptr);
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
story_master->Execute([](fuchsia::modular::ExecuteResult result) {});
bool called_attach_view = false;
fake_session_shell_.set_on_attach_view(
[&called_attach_view](ViewIdentifier) { called_attach_view = true; });
RunLoopUntil([&] { return called_attach_view; });
// Stop the story. Confirm that:
// a. The story stopped, even though it didn't see the DetachView() response
// (it was artificially delayed for 1hr).
// b. The story went through the correct sequence of states (see StoryState
// FIDL file for valid state transitions). Since we started it, ran it, and
// stopped it, the sequence is STOPPED -> RUNNING -> STOPPING -> STOPPED.
fake_session_shell_.set_detach_delay(zx::sec(60 * 60));
fuchsia::modular::StoryControllerPtr story_controller;
story_provider->GetController(kStoryId, story_controller.NewRequest());
bool stop_called = false;
story_controller->Stop([&stop_called] { stop_called = true; });
RunLoopUntil([&] { return stop_called; });
// Run the loop until there are the expected number of state changes;
// having called Stop() is not enough to guarantee seeing all updates.
RunLoopUntil([&] { return sequence_of_story_states.size() == 4; });
EXPECT_THAT(sequence_of_story_states,
testing::ElementsAre(StoryState::STOPPED, StoryState::RUNNING, StoryState::STOPPING,
StoryState::STOPPED));
}
TEST_F(SessionShellTest, GetStoryInfo2HasId) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and launch a new story shell.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
bool execute_and_get_story_info_called = false;
story_master->Execute([&execute_and_get_story_info_called, kStoryId,
story_provider](fuchsia::modular::ExecuteResult result) {
// Verify that the newly created story returns something for
// GetStoryInfo2().
story_provider->GetStoryInfo2(kStoryId, [&execute_and_get_story_info_called,
kStoryId](fuchsia::modular::StoryInfo2 story_info) {
EXPECT_FALSE(story_info.IsEmpty());
EXPECT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
execute_and_get_story_info_called = true;
});
});
RunLoopUntil([&] { return execute_and_get_story_info_called; });
}
TEST_F(SessionShellTest, GetStories2ReturnsStoryInfo) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and launch a new story shell.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
bool execute_and_get_stories_called = false;
story_master->Execute([&execute_and_get_stories_called, kStoryId,
story_provider](fuchsia::modular::ExecuteResult result) {
// Verify that GetStories2 returns the StoryInfo2 for the newly created story
story_provider->GetStories2(/*watcher=*/nullptr,
[&execute_and_get_stories_called,
kStoryId](std::vector<fuchsia::modular::StoryInfo2> story_infos) {
EXPECT_FALSE(story_infos.empty());
const auto& story_info = story_infos.at(0);
EXPECT_FALSE(story_info.IsEmpty());
EXPECT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
execute_and_get_stories_called = true;
});
});
RunLoopUntil([&] { return execute_and_get_stories_called; });
}
TEST_F(SessionShellTest, OnChange2ReturnsStoryInfo2) {
RunHarnessAndInterceptSessionShell();
// Create a new story using PuppetMaster and start a new story shell.
fuchsia::modular::PuppetMasterPtr puppet_master;
fuchsia::modular::StoryPuppetMasterPtr story_master;
fuchsia::modular::testing::ModularService svc;
svc.set_puppet_master(puppet_master.NewRequest());
test_harness()->ConnectToModularService(std::move(svc));
fuchsia::modular::StoryProvider* story_provider = fake_session_shell_.story_provider();
ASSERT_TRUE(story_provider != nullptr);
const char kStoryId[] = "my_story";
// Once the story is created, OnChange2 should be called with a StoryInfo2 that has the story ID.
bool called_on_change_2 = false;
modular::testing::SimpleStoryProviderWatcher watcher;
watcher.set_on_change_2([&called_on_change_2, kStoryId](StoryInfo2 story_info, StoryState _,
StoryVisibilityState __) {
EXPECT_TRUE(story_info.has_id());
EXPECT_EQ(story_info.id(), kStoryId);
called_on_change_2 = true;
});
watcher.Watch(story_provider, /*on_get_stories=*/nullptr);
puppet_master->ControlStory(kStoryId, story_master.NewRequest());
AddMod add_mod;
add_mod.mod_name_transitional = "mod1";
add_mod.intent.handler = kFakeModuleUrl;
StoryCommand command;
command.set_add_mod(std::move(add_mod));
std::vector<StoryCommand> commands;
commands.push_back(std::move(command));
story_master->Enqueue(std::move(commands));
story_master->Execute([](fuchsia::modular::ExecuteResult result) {});
RunLoopUntil([&] { return called_on_change_2; });
}
} // namespace