blob: 5ff14c4b9fff019e9ceec71c1881e75a23078384 [file] [log] [blame]
// Copyright 2019 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/cpp/fidl.h>
#include <fuchsia/modular/testing/cpp/fidl.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/modular_test_harness/cpp/fake_component.h>
#include <lib/modular_test_harness/cpp/test_harness_fixture.h>
#include <sdk/lib/sys/cpp/component_context.h>
#include <sdk/lib/sys/cpp/service_directory.h>
#include <sdk/lib/sys/cpp/testing/test_with_environment.h>
#include <src/lib/fxl/logging.h>
#include "gmock/gmock.h"
#include "peridot/lib/testing/session_shell_impl.h"
using testing::ElementsAre;
namespace {
// Timeout for all instances of RunLoopWithTimeoutOrUntil()
constexpr auto kTimeout = zx::sec(30);
// A basic fake story shell component: gives access to services
// available to story shells in their environment, as well as an
// implementation of fuchsia::modular::StoryShell built for tests.
class FakeSessionShell : public modular::testing::FakeComponent {
public:
~FakeSessionShell() override = default;
fuchsia::modular::StoryProvider* story_provider() {
return story_provider_.get();
}
private:
// |modular::testing::FakeComponent|
void OnCreate(fuchsia::sys::StartupInfo startup_info) override {
component_context()->svc()->Connect(session_shell_context_.NewRequest());
session_shell_context_->GetStoryProvider(story_provider_.NewRequest());
component_context()->outgoing()->AddPublicService(
session_shell_impl_.GetHandler());
}
modular::testing::SessionShellImpl session_shell_impl_;
fuchsia::modular::SessionShellContextPtr session_shell_context_;
fuchsia::modular::StoryProviderPtr story_provider_;
};
class FakeStoryShell : public modular::testing::FakeComponent,
fuchsia::modular::StoryShell {
public:
~FakeStoryShell() override = default;
bool is_initialized() const { return !!story_shell_context_; }
fuchsia::modular::StoryShellContext* story_shell_context() {
return story_shell_context_.get();
}
void set_on_add_surface(fit::function<void(fuchsia::modular::ViewConnection,
fuchsia::modular::SurfaceInfo)>
on_add_surface) {
on_add_surface_ = std::move(on_add_surface);
}
private:
// |modular::testing::FakeComponent|
void OnCreate(fuchsia::sys::StartupInfo startup_info) override {
component_context()->outgoing()->AddPublicService(
bindings_.GetHandler(this));
}
// |fuchsia::modular::StoryShell|
void Initialize(fidl::InterfaceHandle<fuchsia::modular::StoryShellContext>
story_shell_context) override {
story_shell_context_ = story_shell_context.Bind();
}
// |fuchsia::modular::StoryShell|
void AddSurface(fuchsia::modular::ViewConnection view_connection,
fuchsia::modular::SurfaceInfo surface_info) override {
if (!on_add_surface_)
return;
on_add_surface_(std::move(view_connection), std::move(surface_info));
}
// |fuchsia::modular::StoryShell|
void AddSurface2(fuchsia::modular::ViewConnection2 view_connection,
fuchsia::modular::SurfaceInfo surface_info) override {
AddSurface(
fuchsia::modular::ViewConnection{
.surface_id = view_connection.surface_id,
.view_holder_token = std::move(view_connection.view_holder_token),
},
std::move(surface_info));
}
// |fuchsia::modular::StoryShell|
void FocusSurface(std::string /*surface_id*/) override {}
// |fuchsia::modular::StoryShell|
void DefocusSurface(std::string /*surface_id*/,
DefocusSurfaceCallback callback) override {
callback();
}
// |fuchsia::modular::StoryShell|
void AddContainer(
std::string /*container_name*/, fidl::StringPtr /*parent_id*/,
fuchsia::modular::SurfaceRelation /*relation*/,
std::vector<fuchsia::modular::ContainerLayout> /*layout*/,
std::vector<fuchsia::modular::ContainerRelationEntry> /* relationships */,
std::vector<fuchsia::modular::ContainerView> /* views */) override {}
// |fuchsia::modular::StoryShell|
void RemoveSurface(std::string /*surface_id*/) override {}
// |fuchsia::modular::StoryShell|
void ReconnectView(
fuchsia::modular::ViewConnection view_connection) override {}
// |fuchsia::modular::StoryShell|
void UpdateSurface(fuchsia::modular::ViewConnection view_connection,
fuchsia::modular::SurfaceInfo /*surface_info*/) override{};
fuchsia::modular::StoryShellContextPtr story_shell_context_;
fidl::BindingSet<fuchsia::modular::StoryShell> bindings_;
fit::function<void(fuchsia::modular::ViewConnection,
fuchsia::modular::SurfaceInfo)>
on_add_surface_;
};
class StoryShellTest : public modular::testing::TestHarnessFixture {
protected:
void StartSession() {
modular::testing::TestHarnessBuilder builder;
builder.InterceptSessionShell(
session_shell_.GetOnCreateHandler(),
{.sandbox_services = {"fuchsia.modular.SessionShellContext"}});
builder.InterceptStoryShell(story_shell_.GetOnCreateHandler());
fake_module_url_ = builder.GenerateFakeUrl();
builder.InterceptComponent(
[this](fuchsia::sys::StartupInfo startup_info,
fidl::InterfaceHandle<
fuchsia::modular::testing::InterceptedComponent>
intercepted_component) {
intercepted_modules_.push_back(std::move(intercepted_component));
},
{.url = fake_module_url_});
test_harness().events().OnNewComponent =
builder.BuildOnNewComponentHandler();
test_harness()->Run(builder.BuildSpec());
fuchsia::modular::testing::ModularService request;
request.set_puppet_master(puppet_master_.NewRequest());
test_harness()->ConnectToModularService(std::move(request));
// Wait for our session shell to start.
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[this] { return session_shell_.is_running(); }, kTimeout));
}
void AddModToStory(std::string story_name, std::string mod_name,
std::string parent_mod_name = "") {
fuchsia::modular::StoryPuppetMasterPtr story_puppet_master;
puppet_master_->ControlStory(story_name, story_puppet_master.NewRequest());
fuchsia::modular::AddMod add_mod;
add_mod.mod_name_transitional = mod_name;
add_mod.intent.handler = fake_module_url_;
if (!parent_mod_name.empty()) {
add_mod.surface_parent_mod_name->push_back(parent_mod_name);
}
std::vector<fuchsia::modular::StoryCommand> commands(1);
commands.at(0).set_add_mod(std::move(add_mod));
story_puppet_master->Enqueue(std::move(commands));
bool created = false;
story_puppet_master->Execute(
[&](fuchsia::modular::ExecuteResult result) { created = true; });
// Wait for the story to be created.
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return created; }, kTimeout));
}
void RestartStory(std::string story_name) {
fuchsia::modular::StoryControllerPtr story_controller;
session_shell_.story_provider()->GetController(
story_name, story_controller.NewRequest());
bool restarted = false;
story_controller->Stop([&] {
story_controller->RequestStart();
restarted = true;
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return restarted; }, kTimeout));
}
fuchsia::modular::PuppetMasterPtr puppet_master_;
FakeSessionShell session_shell_;
FakeStoryShell story_shell_;
// Stories must have modules in them so the stories created above
// contain fake intercepted modules. This list holds onto them so that
// they can be successfully launched and don't die immediately.
std::vector<
fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>>
intercepted_modules_;
std::string fake_module_url_;
};
// Verifies that when the StoryShell writes content to its Link, those data
// are persisted such that when it is restarted it can retrieve the data again.
TEST_F(StoryShellTest, LinkIsPersistent) {
StartSession();
AddModToStory("story1", "a_mod");
// Wait for our story shell to start and initialize.
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[&] { return story_shell_.is_initialized(); }, kTimeout));
// Write link data.
fuchsia::modular::LinkPtr link;
story_shell_.story_shell_context()->GetLink(link.NewRequest());
{
fuchsia::mem::Buffer buffer;
ASSERT_TRUE(fsl::VmoFromString("42", &buffer));
link->Set(/*path=*/nullptr, std::move(buffer));
}
// Wait for confirmation that the write was successful.
bool got_content = false;
std::string content;
link->Get(/*path=*/nullptr,
[&](std::unique_ptr<fuchsia::mem::Buffer> buffer) {
got_content = true;
ASSERT_NE(nullptr, buffer.get());
ASSERT_TRUE(fsl::StringFromVmo(*buffer, &content));
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return got_content; }, kTimeout));
EXPECT_EQ("42", content);
// Restart the story.
RestartStory("story1");
// Wait for it to die and then restart again.
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[&] { return !story_shell_.is_running(); }, kTimeout));
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[&] { return story_shell_.is_running(); }, kTimeout));
// Show that the contents of the Link are still accessible after a restart.
story_shell_.story_shell_context()->GetLink(link.NewRequest());
got_content = false;
content.clear();
link->Get(/*path=*/nullptr,
[&](std::unique_ptr<fuchsia::mem::Buffer> buffer) {
got_content = true;
ASSERT_NE(nullptr, buffer.get());
fsl::StringFromVmo(*buffer, &content);
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return got_content; }, kTimeout));
EXPECT_EQ("42", content);
}
TEST_F(StoryShellTest, GetsModuleMetadata) {
StartSession();
std::vector<std::string> surface_ids_added;
story_shell_.set_on_add_surface(
[&](fuchsia::modular::ViewConnection view_connection,
fuchsia::modular::SurfaceInfo surface_info) {
surface_ids_added.push_back(view_connection.surface_id);
});
AddModToStory("story1", "mod1");
AddModToStory("story1", "mod2", {"mod1"} /* surface relation parent */);
// Wait for the story shell to be notified of the new modules.
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[&] { return surface_ids_added.size() == 2; }, kTimeout));
EXPECT_THAT(surface_ids_added, ElementsAre("mod1", "mod1:mod2"));
// Stop the story shell and restart it. Expect to see the same mods notified
// to the story shell in the same order.
surface_ids_added.clear();
RestartStory("story1");
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[&] { return surface_ids_added.size() == 2; }, kTimeout));
EXPECT_THAT(surface_ids_added, ElementsAre("mod1", "mod1:mod2"));
}
} // namespace