blob: 15ec63cd705b2bdd5d77d1c48faef9ed23cb9151 [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/testing/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/modular/testing/cpp/fake_component.h>
#include <lib/syslog/cpp/macros.h>
#include "src/lib/fsl/vmo/strings.h"
#include "src/modular/lib/modular_test_harness/cpp/fake_story_shell.h"
#include "src/modular/lib/modular_test_harness/cpp/test_harness_fixture.h"
#include "src/modular/lib/testing/session_shell_impl.h"
namespace {
// An implementation of the fuchsia.modular.StoryShellFactory FIDL service, to
// be used in session shell components in integration tests.
class TestStoryShellFactory : fuchsia::modular::StoryShellFactory {
public:
using StoryShellRequest = fidl::InterfaceRequest<fuchsia::modular::StoryShell>;
TestStoryShellFactory(sys::ComponentContext* const component_context) {
component_context->outgoing()->AddPublicService(GetHandler());
}
virtual ~TestStoryShellFactory() override = default;
// Produces a handler function that can be used in the outgoing service
// provider.
fidl::InterfaceRequestHandler<fuchsia::modular::StoryShellFactory> GetHandler() {
return bindings_.GetHandler(this);
}
// Whenever StoryShellFactory.AttachStory() is called, the supplied callback
// is invoked with the story ID and StoryShell request.
void set_on_attach_story(
fit::function<void(std::string story_id, StoryShellRequest request)> callback) {
on_attach_story_ = std::move(callback);
}
// Whenever StoryShellFactory.DetachStory() is called, the supplied callback
// is invoked. The return callback of DetachStory() is invoked asynchronously
// after a delay that can be configured by the client with set_detach_delay().
void set_on_detach_story(fit::function<void()> callback) {
on_detach_story_ = std::move(callback);
}
// Configures the delay after which the return callback of DetachStory() is
// invoked. Used to test the timeout behavior of sessionmgr.
void set_detach_delay(zx::duration detach_delay) { detach_delay_ = detach_delay; }
private:
// |StoryShellFactory|
void AttachStory(std::string story_id, StoryShellRequest request) override {
on_attach_story_(std::move(story_id), std::move(request));
}
// |StoryShellFactory|
void DetachStory(std::string story_id, fit::function<void()> done) override {
on_detach_story_();
// Used to simulate a sluggish shell that hits the timeout.
async::PostDelayedTask(async_get_default_dispatcher(), std::move(done), detach_delay_);
}
fidl::BindingSet<fuchsia::modular::StoryShellFactory> bindings_;
fit::function<void(std::string story_id, StoryShellRequest request)> on_attach_story_{
[](std::string, StoryShellRequest) {}};
fit::function<void()> on_detach_story_{[]() {}};
zx::duration detach_delay_{};
};
// A basic fake session shell component: gives access to services
// available to session shells in their environment, as well as an
// implementation of fuchsia::modular::SessionShell built for tests.
class TestSessionShell : public modular_testing::FakeComponent {
public:
TestSessionShell(modular_testing::FakeComponent::Args args)
: modular_testing::FakeComponent(std::move(args)) {}
fuchsia::modular::StoryProvider* story_provider() { return story_provider_.get(); }
TestStoryShellFactory* story_shell_factory() { return story_shell_factory_.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());
story_shell_factory_ = std::make_unique<TestStoryShellFactory>(component_context());
}
modular_testing::SessionShellImpl session_shell_impl_;
fuchsia::modular::SessionShellContextPtr session_shell_context_;
fuchsia::modular::StoryProviderPtr story_provider_;
std::unique_ptr<TestStoryShellFactory> story_shell_factory_;
};
class StoryShellFactoryTest : public modular_testing::TestHarnessFixture {
public:
static constexpr auto kStoryName = "story1";
static constexpr auto kModName = "mod1";
TestSessionShell* test_session_shell() { return test_session_shell_.get(); }
// Initializes the session shell, story shell factory, and story shell
// implementations and starts the modular test harness.
void InitSession() {
// The session shell provides the StoryShellFactory protocol.
fuchsia::modular::testing::TestHarnessSpec spec;
spec.mutable_basemgr_config()->set_use_session_shell_for_story_shell_factory(true);
modular_testing::TestHarnessBuilder builder(std::move(spec));
test_session_shell_ = std::make_unique<TestSessionShell>(modular_testing::FakeComponent::Args{
.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl(),
.sandbox_services = {"fuchsia.modular.SessionShellContext",
"fuchsia.modular.PuppetMaster"}});
// Listen for the module that is created in CreateStory().
test_module_ =
std::make_unique<modular_testing::FakeComponent>(modular_testing::FakeComponent::Args{
.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl()});
builder.InterceptSessionShell(test_session_shell_->BuildInterceptOptions());
builder.InterceptComponent(test_module_->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
// Wait for our session shell to start.
RunLoopUntil([this] { return test_session_shell_->is_running(); });
// Connect to the PuppetMaster service also provided to the session shell.
fuchsia::modular::testing::ModularService modular_service;
modular_service.set_puppet_master(puppet_master_.NewRequest());
test_harness()->ConnectToModularService(std::move(modular_service));
}
void CreateStory() {
// The session shell should be running and connected to PuppetMaster.
FX_CHECK(test_session_shell_->is_running());
// The story should not already be created.
FX_CHECK(!test_module_->is_running());
// Create a story
fuchsia::modular::Intent intent;
intent.handler = test_module_->url();
intent.action = "action";
modular_testing::AddModToStory(test_harness(), kStoryName, kModName, std::move(intent));
// Wait for the story to be created.
RunLoopUntil([this] { return test_module_->is_running(); });
}
void DeleteStory() {
// The session shell should be running and connected to PuppetMaster.
FX_CHECK(test_session_shell_->is_running());
// The story should have been previously created through CreateStory.
FX_CHECK(test_module_->is_running());
puppet_master_->DeleteStory(kStoryName, [] {});
// Wait for the story to be deleted.
RunLoopUntil([this] { return !test_module_->is_running(); });
}
fuchsia::modular::StoryControllerPtr ControlStory() {
// The story should have been previously created through CreateStory.
FX_CHECK(test_module_->is_running());
// Get a story controller.
fuchsia::modular::StoryControllerPtr story_controller;
test_session_shell_->story_provider()->GetController(kStoryName, story_controller.NewRequest());
return story_controller;
}
private:
// Component URL of the |test_module_| intercepted in InitSession().
std::string test_module_url_;
fuchsia::modular::PuppetMasterPtr puppet_master_;
std::unique_ptr<TestSessionShell> test_session_shell_;
std::unique_ptr<modular_testing::FakeComponent> test_module_;
};
TEST_F(StoryShellFactoryTest, AttachCalledOnStoryStart) {
InitSession();
modular_testing::FakeStoryShell fake_story_shell(modular_testing::FakeComponent::Args{
.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl()});
// The StoryShellFactory will be asked to attach a StoryShell when the story
// is started.
bool is_attached{false};
test_session_shell()->story_shell_factory()->set_on_attach_story(
[&](std::string, fidl::InterfaceRequest<fuchsia::modular::StoryShell> request) {
is_attached = true;
fake_story_shell.GetHandler()(std::move(request));
});
CreateStory();
// Start and show the story.
auto story_controller = ControlStory();
story_controller->RequestStart();
// Wait for the StoryShellFactory to attach the StoryShell.
RunLoopUntil([&] { return is_attached; });
};
TEST_F(StoryShellFactoryTest, DetachCalledOnStoryStop) {
InitSession();
// The StoryShellFactory will be asked to detach a StoryShell when the story
// is stopped.
bool is_detached{false};
test_session_shell()->story_shell_factory()->set_on_detach_story([&]() { is_detached = true; });
CreateStory();
// Start and show the story.
auto story_controller = ControlStory();
story_controller->RequestStart();
// Stop the story.
story_controller->Stop([]() {});
// Wait for the StoryShellFactory to detach the StoryShell.
RunLoopUntil([&] { return is_detached; });
};
TEST_F(StoryShellFactoryTest, DetachCalledOnStoryDelete) {
InitSession();
// The StoryShellFactory will be asked to detach a StoryShell when the story
// is deleted.
bool is_detached{false};
test_session_shell()->story_shell_factory()->set_on_detach_story([&]() { is_detached = true; });
CreateStory();
// Start and show the story.
auto story_controller = ControlStory();
story_controller->RequestStart();
DeleteStory();
// Wait for the StoryShellFactory to detach the StoryShell.
RunLoopUntil([&] { return is_detached; });
};
} // namespace