blob: 31c39c9df24ca986f8095457ee8c637a9e89a0c2 [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/component/cpp/connect.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <zircon/device/vfs.h>
#include <sdk/lib/inspect/testing/cpp/inspect.h>
#include "gmock/gmock.h"
#include "src/lib/files/glob.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/strings/substitute.h"
#include "src/modular/lib/modular_config/modular_config_constants.h"
#include "src/modular/lib/modular_test_harness/cpp/fake_session_shell.h"
#include "src/modular/lib/modular_test_harness/cpp/test_harness_fixture.h"
#include "src/modular/lib/modular_test_harness/cpp/test_harness_impl.h"
namespace {
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 ::fxl::Substitute;
using inspect::testing::DoubleIs;
using inspect::testing::IntIs;
using inspect::testing::NameMatches;
using inspect::testing::NodeMatches;
using inspect::testing::PropertyList;
using inspect::testing::StringIs;
using testing::IsNull;
using testing::Not;
constexpr char kFakeModuleUrl[] = "fuchsia-pkg://example.com/FAKE_MODULE_PKG/fake_module.cmx";
constexpr char kSessionmgrInspectRootGlobPath[] =
"/hub/r/mth_*_inspect/*/c/sessionmgr.cmx/*/out/inspect/root.inspect";
// The initial module's intent parameter data. This needs to be JSON formatted.
constexpr char kInitialIntentParameterData[] = "\"initial\"";
constexpr char kIntentAction[] = "action";
constexpr char kIntentParameterName[] = "intent_parameter";
constexpr char kClipboardAgentUrl[] =
"fuchsia-pkg://fuchsia.com/clipboard_agent#meta/clipboard_agent.cmx";
class InspectSessionTest : public modular_testing::TestHarnessFixture {
protected:
InspectSessionTest()
: fake_session_shell_(modular_testing::FakeSessionShell::CreateWithDefaultOptions()) {}
void RunHarnessAndInterceptSessionShell() {
fuchsia::modular::testing::TestHarnessSpec spec;
spec.set_environment_suffix("inspect");
modular_testing::TestHarnessBuilder builder(std::move(spec));
builder.InterceptSessionShell(fake_session_shell_->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
// Wait for our session shell to start.
RunLoopUntil([&] { return fake_session_shell_->is_running(); });
}
zx_status_t GetInspectVmo(zx::vmo* out_vmo) {
files::Glob glob(kSessionmgrInspectRootGlobPath);
if (glob.size() == 0) {
return ZX_ERR_NOT_FOUND;
}
fuchsia::io::FileSyncPtr file;
zx_status_t status;
status = fdio_open(std::string(*glob.begin()).c_str(), ZX_FS_RIGHT_READABLE,
file.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
return status;
}
EXPECT_TRUE(file.is_bound());
fuchsia::io::NodeInfo info;
auto get_status = file->Describe(&info);
if (get_status != ZX_OK) {
printf("get failed\n");
return get_status;
}
if (!info.is_vmofile()) {
printf("not a vmofile");
return ZX_ERR_NOT_FOUND;
}
*out_vmo = std::move(info.vmofile().vmo);
return ZX_OK;
}
fuchsia::modular::Intent CreateIntent(std::string handler, std::string parameter_name,
std::string parameter_data) {
fuchsia::modular::Intent intent;
intent.handler = handler;
intent.action = kIntentAction;
fuchsia::modular::IntentParameter intent_parameter;
intent_parameter.name = parameter_name;
intent_parameter.data = fuchsia::modular::IntentParameterData();
fsl::SizedVmo vmo;
ZX_ASSERT(fsl::VmoFromString(parameter_data, &vmo));
intent_parameter.data.set_json(std::move(vmo).ToTransport());
intent.parameters.emplace();
intent.parameters->push_back(std::move(intent_parameter));
return intent;
}
std::unique_ptr<modular_testing::FakeSessionShell> fake_session_shell_;
}; // namespace
class TestStoryProviderWatcher : public fuchsia::modular::StoryProviderWatcher {
public:
TestStoryProviderWatcher() : binding_(this) {}
~TestStoryProviderWatcher() override = default;
void OnChange2(fit::function<void(fuchsia::modular::StoryInfo2)> on_change) {
on_change_2_ = std::move(on_change);
}
void Watch(fuchsia::modular::StoryProvider* const story_provider) {
story_provider->Watch(binding_.NewBinding());
}
private:
// |fuchsia::modular::StoryProviderWatcher|
void OnDelete(::std::string story_id) override {}
// |fuchsia::modular::StoryProviderWatcher|
void OnChange2(fuchsia::modular::StoryInfo2 story_info, fuchsia::modular::StoryState story_state,
fuchsia::modular::StoryVisibilityState story_visibility_state) override {
on_change_2_(std::move(story_info));
return;
}
fit::function<void(fuchsia::modular::StoryInfo2)> on_change_2_;
fidl::Binding<fuchsia::modular::StoryProviderWatcher> binding_;
};
TEST_F(InspectSessionTest, NodeHierarchyNoStories) {
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; });
// Check the iquery node hierarchy is properly set up with only a root.
zx::vmo vmo;
ASSERT_EQ(ZX_OK, GetInspectVmo(&vmo));
auto hierarchy = inspect::ReadFromVmo(std::move(vmo)).take_value();
EXPECT_THAT(hierarchy,
AllOf(inspect::testing::NodeMatches(inspect::testing::NameMatches("root"))));
}
TEST_F(InspectSessionTest, DefaultAgentsHierarchy) {
fuchsia::modular::testing::TestHarnessSpec spec;
spec.set_environment_suffix("inspect");
modular_testing::TestHarnessBuilder builder(std::move(spec));
builder.InterceptSessionShell(fake_session_shell_->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
// Wait for our session shell to start.
RunLoopUntil([&] { return fake_session_shell_->is_running(); });
zx::vmo vmo;
ASSERT_EQ(ZX_OK, GetInspectVmo(&vmo));
auto hierarchy = inspect::ReadFromVmo(std::move(vmo)).take_value();
EXPECT_THAT(hierarchy, (NodeMatches(NameMatches("root"))));
const auto& child = hierarchy.children();
EXPECT_THAT(child.size(), 2);
EXPECT_THAT(child.at(0), NodeMatches(NameMatches(kClipboardAgentUrl)));
EXPECT_THAT(child.at(1),
NodeMatches(NameMatches(modular_testing::kSessionAgentFakeInterceptionUrl)));
}
TEST_F(InspectSessionTest, CheckNodeHierarchyStartAndStopStory) {
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());
auto text_story_annotation_value = fuchsia::modular::AnnotationValue{};
text_story_annotation_value.set_text("test_value");
auto text_story_annotation = fuchsia::modular::Annotation{
.key = "test_key", .value = fidl::MakeOptional(std::move(text_story_annotation_value))};
std::vector<fuchsia::modular::Annotation> story_annotations;
story_annotations.push_back(std::move(text_story_annotation));
bool done{false};
story_master->Annotate(std::move(story_annotations),
[&](fuchsia::modular::StoryPuppetMaster_Annotate_Result result) {
EXPECT_FALSE(result.is_err());
done = true;
});
RunLoopUntil([&] { return done; });
// Watch for changes to the session.
TestStoryProviderWatcher story_provider_watcher;
story_provider_watcher.Watch(fake_session_shell_->story_provider());
// Keep track of the focus timestamp that we receive for the story.
std::vector<int64_t> last_focus_timestamps;
story_provider_watcher.OnChange2([&](fuchsia::modular::StoryInfo2 story_info) {
ASSERT_TRUE(story_info.has_id());
ASSERT_TRUE(story_info.has_last_focus_time());
ASSERT_EQ(kStoryId, story_info.id());
last_focus_timestamps.push_back(story_info.last_focus_time());
});
// Story doesn't start unless it has a mod, so add a mod.
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_called = false;
story_master->Execute(
[&execute_called](fuchsia::modular::ExecuteResult result) { execute_called = true; });
RunLoopUntil([&] { return execute_called; });
zx::vmo vmo_inspect;
ASSERT_EQ(ZX_OK, GetInspectVmo(&vmo_inspect));
auto hierarchy = inspect::ReadFromVmo(std::move(vmo_inspect)).take_value();
EXPECT_THAT(hierarchy, (NodeMatches(NameMatches("root"))));
const auto& children = hierarchy.children();
// Contains 2 agents and 1 story
EXPECT_THAT(children.size(), 3);
EXPECT_THAT(children, Contains(NodeMatches(NameMatches("my_story"))));
EXPECT_THAT(children.at(0), NodeMatches(AllOf(PropertyList(UnorderedElementsAre(
IntIs("last_focus_time", last_focus_timestamps.back()),
StringIs("annotation: test_key", "test_value"))))));
bool story_deleted = false;
puppet_master->DeleteStory(kStoryId, [&] { story_deleted = true; });
RunLoopUntil([&] { return story_deleted; });
// Check that a node is removed from the hierarchy when a story is removed.
ASSERT_EQ(ZX_OK, GetInspectVmo(&vmo_inspect));
hierarchy = inspect::ReadFromVmo(std::move(vmo_inspect)).take_value();
EXPECT_THAT(hierarchy, AllOf(NodeMatches(NameMatches("root"))));
}
TEST_F(InspectSessionTest, CheckNodeHierarchyMods) {
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";
auto initial_module_intent =
CreateIntent(kFakeModuleUrl, kIntentParameterName, kInitialIntentParameterData);
add_mod.intent = std::move(initial_module_intent);
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; });
// Annotate the module.
auto text_mod_annotation_value = fuchsia::modular::AnnotationValue{};
text_mod_annotation_value.set_bytes({01});
auto text_mod_annotation = fuchsia::modular::Annotation{
.key = "text_key", .value = fidl::MakeOptional(fidl::Clone(text_mod_annotation_value))};
std::vector<fuchsia::modular::Annotation> mod_annotations;
mod_annotations.push_back(fidl::Clone(text_mod_annotation));
bool annotate_done = false;
story_master->AnnotateModule(
"mod1", std::move(mod_annotations),
[&](fuchsia::modular::StoryPuppetMaster_AnnotateModule_Result result) {
EXPECT_FALSE(result.is_err());
annotate_done = true;
});
RunLoopUntil([&] { return annotate_done && execute_called; });
zx::vmo vmo_inspect;
ASSERT_EQ(ZX_OK, GetInspectVmo(&vmo_inspect));
auto hierarchy = inspect::ReadFromVmo(std::move(vmo_inspect)).take_value();
EXPECT_THAT(hierarchy, (NodeMatches(NameMatches("root"))));
const auto& child = hierarchy.children();
EXPECT_THAT(child.size(), 3);
EXPECT_THAT(child.at(2), NodeMatches(NameMatches("my_story")));
const auto& grandchild = child.at(2).children();
EXPECT_THAT(grandchild.size(), 1);
EXPECT_THAT(grandchild.at(0), NodeMatches(NameMatches(kFakeModuleUrl)));
EXPECT_THAT(grandchild.at(0),
NodeMatches(AllOf(PropertyList(UnorderedElementsAre(
StringIs(modular_config::kInspectIsEmbedded, "False"),
StringIs(modular_config::kInspectModuleSource, "EXTERNAL"),
StringIs(modular_config::kInspectIntentHandler, kFakeModuleUrl),
StringIs(modular_config::kInspectIntentAction, "action"),
StringIs(modular_config::kInspectIsDeleted, "False"),
StringIs(modular_config::kInspectIntentParams, "name : intent_parameter "),
StringIs(modular_config::kInspectSurfaceRelationArrangement, "NONE"),
StringIs(modular_config::kInspectSurfaceRelationDependency, "NONE"),
DoubleIs(modular_config::kInspectSurfaceRelationEmphasis, 1.0),
StringIs(modular_config::kInspectModulePath, "mod1"),
StringIs("annotation: text_key", "bytes"))))));
}
} // namespace