blob: 4422f45aeaeb7166a419af7540b18ca574e216d9 [file] [log] [blame]
// Copyright 2020 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/testing/modular/cpp/fidl.h>
#include <lib/inspect/contrib/cpp/archive_reader.h>
#include <lib/modular/testing/cpp/fake_component.h>
#include <lib/stdcompat/optional.h>
#include <gmock/gmock.h>
#include <sdk/lib/modular/testing/cpp/fake_agent.h>
#include "src/lib/fsl/vmo/strings.h"
#include "src/modular/lib/fidl/clone.h"
#include "src/modular/lib/modular_config/modular_config.h"
#include "src/modular/lib/modular_config/modular_config_constants.h"
#include "src/modular/lib/modular_test_harness/cpp/fake_session_launcher_component.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/pseudo_dir/pseudo_dir_server.h"
namespace {
using ::testing::HasSubstr;
constexpr char kBasemgrSelector[] = "*_inspect/basemgr.cmx:root";
constexpr char kBasemgrComponentName[] = "basemgr.cmx";
class BasemgrTest : public modular_testing::TestHarnessFixture {
public:
BasemgrTest() : executor_(dispatcher()) {}
fit::result<inspect::contrib::DiagnosticsData> GetInspectDiagnosticsData() {
auto archive = real_services()->Connect<fuchsia::diagnostics::ArchiveAccessor>();
inspect::contrib::ArchiveReader reader(std::move(archive), {kBasemgrSelector});
fit::result<std::vector<inspect::contrib::DiagnosticsData>, std::string> result;
executor_.schedule_task(
reader.SnapshotInspectUntilPresent({kBasemgrComponentName})
.then([&](fit::result<std::vector<inspect::contrib::DiagnosticsData>, std::string>&
snapshot_result) { result = std::move(snapshot_result); }));
RunLoopUntil([&] { return result.is_ok() || result.is_error(); });
if (result.is_error()) {
EXPECT_FALSE(result.is_error()) << "Error was " << result.error();
return fit::error();
}
if (result.value().size() != 1) {
EXPECT_EQ(1u, result.value().size()) << "Expected only one component";
return fit::error();
}
return fit::ok(std::move(result.value()[0]));
}
async::Executor executor_;
};
// Tests that when multiple session shell are provided the first is picked
TEST_F(BasemgrTest, StartFirstShellWhenMultiple) {
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
// Session shells used in list
auto session_shell = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
auto session_shell2 = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
// Create session shell list (appended in order)
builder.InterceptSessionShell(session_shell->BuildInterceptOptions());
builder.InterceptSessionShell(session_shell2->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
// Run until one is started
RunLoopUntil([&] { return session_shell->is_running() || session_shell2->is_running(); });
// Assert only first one is started
EXPECT_TRUE(session_shell->is_running());
EXPECT_FALSE(session_shell2->is_running());
}
// Tests that basemgr starts the configured session launcher component when basemgr starts.
TEST_F(BasemgrTest, StartsSessionComponent) {
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
EXPECT_TRUE(session_launcher_component->is_running());
}
// Tests that basemgr starts the configured session launcher component with the given args.
TEST_F(BasemgrTest, StartsSessionComponentWithArgs) {
static const std::string kTestArg = "--foo";
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
fidl::VectorPtr<std::string> startup_args = std::nullopt;
builder.InterceptSessionLauncherComponent(
{.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl(),
.launch_handler = [&startup_args](
fuchsia::sys::StartupInfo startup_info,
fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>
/* unused */) { startup_args = startup_info.launch_info.arguments; }},
/*args=*/cpp17::make_optional<std::vector<std::string>>({kTestArg}));
builder.BuildAndRun(test_harness());
// Run until the session launcher component is started.
RunLoopUntil([&] { return startup_args.has_value(); });
ASSERT_TRUE(startup_args.has_value());
ASSERT_EQ(1u, startup_args->size());
EXPECT_EQ(kTestArg, startup_args->at(0));
}
// Tests that basemgr starts a session with the given configuration when instructed by
// the session launcher component.
TEST_F(BasemgrTest, StartsSessionWithConfig) {
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
auto session_shell = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
// The session shell is specified in the configuration generated by the session launcher
// component, so avoid InterceptSessionShell(), which adds it to the configuration in |builder|.
builder.InterceptComponent(session_shell->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
EXPECT_TRUE(!session_shell->is_running());
// Create the configuration that the session launcher component passes to basemgr.
fuchsia::modular::session::SessionShellMapEntry entry;
entry.mutable_config()->mutable_app_config()->set_url(session_shell->url());
fuchsia::modular::session::ModularConfig config;
config.mutable_basemgr_config()->mutable_session_shell_map()->push_back(std::move(entry));
fuchsia::mem::Buffer config_buf;
ASSERT_TRUE(fsl::VmoFromString(modular::ConfigToJsonString(config), &config_buf));
// Launch the session.
session_launcher_component->launcher()->LaunchSessionmgr(std::move(config_buf));
// The configured session shell should start.
RunLoopUntil([&] { return session_shell->is_running(); });
EXPECT_TRUE(session_shell->is_running());
}
// Tests that the session launcher component can also provide services to sessionmgr's children.
TEST_F(BasemgrTest, SessionLauncherCanOfferServices) {
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
auto session_shell = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
auto agent = modular_testing::FakeAgent::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
// The following components are specified in the configuration generated by the session launcher.
builder.InterceptComponent(session_shell->BuildInterceptOptions());
auto agent_options = agent->BuildInterceptOptions();
agent_options.sandbox_services.push_back(fuchsia::testing::modular::TestProtocol::Name_);
builder.InterceptComponent(std::move(agent_options));
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
EXPECT_TRUE(!session_shell->is_running());
// Create the configuration that the session launcher component passes to basemgr.
fuchsia::modular::session::SessionShellMapEntry entry;
entry.mutable_config()->mutable_app_config()->set_url(session_shell->url());
fuchsia::modular::session::ModularConfig config;
config.mutable_basemgr_config()->mutable_session_shell_map()->push_back(std::move(entry));
config.mutable_sessionmgr_config()->mutable_session_agents()->push_back(agent->url());
fuchsia::mem::Buffer config_buf;
ASSERT_TRUE(fsl::VmoFromString(modular::ConfigToJsonString(config), &config_buf));
// Build a directory to serve services from the session launcher component.
int connect_count = 0;
auto dir = std::make_unique<vfs::PseudoDir>();
dir->AddEntry(
fuchsia::testing::modular::TestProtocol::Name_,
std::make_unique<vfs::Service>([&](zx::channel, async_dispatcher_t*) { ++connect_count; }));
auto dir_server = std::make_unique<modular::PseudoDirServer>(std::move(dir));
// Construct a ServiceList with the above dir server.
fuchsia::sys::ServiceList service_list;
service_list.names.push_back(fuchsia::testing::modular::TestProtocol::Name_);
service_list.host_directory = dir_server->Serve().Unbind().TakeChannel();
// Launch the session.
session_launcher_component->launcher()->LaunchSessionmgrWithServices(std::move(config_buf),
std::move(service_list));
// The configured session shell and agent should start.
RunLoopUntil([&] { return session_shell->is_running() && agent->is_running(); });
// Connect to the provided service from the agent.
auto test_ptr =
agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>();
RunLoopUntil([&] { return connect_count > 0; });
EXPECT_EQ(1, connect_count);
// Test that the provided services can still be reached if the session is restarted
// (fxbug.dev/61680).
session_shell->Exit(1);
RunLoopUntil([&] { return !session_shell->is_running() && !agent->is_running(); });
RunLoopUntil([&] { return session_shell->is_running() && agent->is_running(); });
auto test_ptr2 =
agent->component_context()->svc()->Connect<fuchsia::testing::modular::TestProtocol>();
RunLoopUntil([&] { return connect_count > 1; });
EXPECT_EQ(2, connect_count);
}
// Tests that basemgr starts a new session with a new configuration, and stops the existing one
// when instructed to launch a new session by the session launcher component.
TEST_F(BasemgrTest, LaunchSessionmgrReplacesExistingSession) {
// Instructs the session launcher component to launches a session with a configuration with the
// given session shell URL, and waits until it's launched.
auto launch_session_with_session_shell =
[](modular_testing::FakeSessionLauncherComponent& session_launcher_component,
std::string session_shell_url) {
fuchsia::modular::session::SessionShellMapEntry entry;
entry.mutable_config()->mutable_app_config()->set_url(std::move(session_shell_url));
fuchsia::modular::session::ModularConfig config;
config.mutable_basemgr_config()->mutable_session_shell_map()->push_back(std::move(entry));
fuchsia::mem::Buffer config_buf;
ASSERT_TRUE(fsl::VmoFromString(modular::ConfigToJsonString(config), &config_buf));
session_launcher_component.launcher()->LaunchSessionmgr(std::move(config_buf));
};
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
auto session_shell = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
auto session_shell_2 = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
builder.InterceptComponent(session_shell->BuildInterceptOptions());
builder.InterceptComponent(session_shell_2->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
EXPECT_TRUE(!session_shell->is_running());
// Launch the first session.
launch_session_with_session_shell(*session_launcher_component, session_shell->url());
// The first session shell should start.
RunLoopUntil([&] { return session_shell->is_running(); });
EXPECT_TRUE(session_shell->is_running());
// Launch the second session.
launch_session_with_session_shell(*session_launcher_component, session_shell_2->url());
// The second session shell should start, and the first shell should stop.
RunLoopUntil([&] { return !session_shell->is_running() && session_shell_2->is_running(); });
EXPECT_FALSE(session_shell->is_running());
EXPECT_TRUE(session_shell_2->is_running());
}
// Tests that LaunchSessionmgr closes the channel with an ZX_ERR_INVALID_ARGS epitaph if the
// config buffer is not readable.
TEST_F(BasemgrTest, LaunchSessionmgrFailsGivenUnreadableBuffer) {
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
// Launch the session with a configuration Buffer that has an incorrect size.
fuchsia::mem::Buffer config_buf;
ASSERT_TRUE(fsl::VmoFromString("", &config_buf));
config_buf.size = 1;
// Connect to Launcher with a handler that lets us capture the error.
fuchsia::modular::session::LauncherPtr launcher;
session_launcher_component->component_context()->svc()->Connect(launcher.NewRequest());
bool error_handler_called{false};
zx_status_t error_status{ZX_OK};
launcher.set_error_handler([&](zx_status_t status) {
error_handler_called = true;
error_status = status;
});
launcher->LaunchSessionmgr(std::move(config_buf));
RunLoopUntil([&] { return error_handler_called; });
EXPECT_EQ(ZX_ERR_INVALID_ARGS, error_status);
}
// Tests that LaunchSessionmgr closes the channel with an ZX_ERR_INVALID_ARGS epitaph if the
// config buffer does not contain valid Modular configuration JSON.
TEST_F(BasemgrTest, LaunchSessionmgrFailsGivenInvalidConfigJson) {
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
// Launch the session with a configuration that is not valid JSON.
fuchsia::mem::Buffer config_buf;
ASSERT_TRUE(fsl::VmoFromString("this is not valid json", &config_buf));
// Connect to Launcher with a handler that lets us capture the error.
fuchsia::modular::session::LauncherPtr launcher;
session_launcher_component->component_context()->svc()->Connect(launcher.NewRequest());
bool error_handler_called{false};
zx_status_t error_status{ZX_OK};
launcher.set_error_handler([&](zx_status_t status) {
error_handler_called = true;
error_status = status;
});
launcher->LaunchSessionmgr(std::move(config_buf));
RunLoopUntil([&] { return error_handler_called; });
EXPECT_EQ(ZX_ERR_INVALID_ARGS, error_status);
}
// Tests that LaunchSessionmgr closes the channel with an ZX_ERR_INVALID_ARGS epitaph if the
// config includes a session launcher component.
TEST_F(BasemgrTest, LaunchSessionmgrFailsGivenConfigWithSessionLauncher) {
static constexpr auto kTestSessionLauncherUrl =
"fuchsia-pkg://fuchsia.com/test_session_launcher#meta/test_session_launcher.cmx";
fuchsia::modular::testing::TestHarnessSpec spec;
modular_testing::TestHarnessBuilder builder(std::move(spec));
auto session_launcher_component =
modular_testing::FakeSessionLauncherComponent::CreateWithDefaultOptions();
builder.InterceptSessionLauncherComponent(session_launcher_component->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_launcher_component->is_running(); });
// Launch the session with a valid configuration that has `session_launcher` set.
fuchsia::modular::session::ModularConfig config;
config.mutable_basemgr_config()->mutable_session_launcher()->set_url(kTestSessionLauncherUrl);
fuchsia::mem::Buffer config_buf;
ASSERT_TRUE(fsl::VmoFromString(modular::ConfigToJsonString(config), &config_buf));
// Connect to Launcher with a handler that lets us capture the error.
fuchsia::modular::session::LauncherPtr launcher;
session_launcher_component->component_context()->svc()->Connect(launcher.NewRequest());
bool error_handler_called{false};
zx_status_t error_status{ZX_OK};
launcher.set_error_handler([&](zx_status_t status) {
error_handler_called = true;
error_status = status;
});
launcher->LaunchSessionmgr(std::move(config_buf));
RunLoopUntil([&] { return error_handler_called; });
EXPECT_EQ(ZX_ERR_INVALID_ARGS, error_status);
}
// Tests that basemgr exposes its configuration in Inspect.
TEST_F(BasemgrTest, ExposesConfigInInspect) {
auto session_shell = modular_testing::FakeSessionShell::CreateWithDefaultOptions();
fuchsia::modular::testing::TestHarnessSpec spec;
spec.set_environment_suffix("inspect");
modular_testing::TestHarnessBuilder builder(std::move(spec));
builder.InterceptSessionShell(session_shell->BuildInterceptOptions());
builder.BuildAndRun(test_harness());
RunLoopUntil([&] { return session_shell->is_running(); });
auto inspect_result = GetInspectDiagnosticsData();
ASSERT_TRUE(inspect_result.is_ok());
auto inspect_data = inspect_result.take_value();
// The inspect property should contain configuration that uses |session_shell|.
const auto& config_value = inspect_data.GetByPath({"root", modular_config::kInspectConfig});
ASSERT_TRUE(config_value.IsString());
EXPECT_THAT(config_value.GetString(), HasSubstr(session_shell->url()));
}
} // namespace