blob: b9b33b6a0a0abb34de549f40fdbec6742b5577f9 [file]
// 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/sys/cpp/fidl.h>
#include <lib/fidl/cpp/vector.h>
#include <lib/sys/cpp/testing/component_interceptor.h>
#include <lib/sys/cpp/testing/enclosing_environment.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <memory>
#include <string>
#include <variant>
#include <vector>
#include <gtest/gtest.h>
#include "src/lib/fsl/handles/object_info.h"
#include "src/ui/tools/present_view/testing/fake_integration_test_view.h"
#include "src/ui/tools/present_view/testing/fake_intl_manager.h"
#include "src/ui/tools/present_view/testing/fake_presenter.h"
namespace present_view::test {
constexpr char kEnvironment[] = "present_view_integration_tests";
constexpr char kPresentViewComponentUri[] =
"fuchsia-pkg://fuchsia.com/present_view_tests#meta/present_view.cmx";
constexpr char kIntlPropertyProviderUri[] =
"fuchsia-pkg://fuchsia.com/intl_property_manager#meta/intl_property_manager_without_flags.cmx";
// This test fixture tests the full present_view component running as a standalone process.
//
// The test fixture provides fake |fuchsia.ui.policy.Presenter| and |fuchsia.ui.app.ViewProvider|
// implementations and services them on its main loop.
//
// Each test creates a hermetic environment and launches a present_view component as a separate
// process inside of it.
class PresentViewIntegrationTest : public sys::testing::TestWithEnvironment {
protected:
// This type encapsulates the possible "return values" that an executing component can produce.
// The first type is |std::nullptr_t| to allow indicating a component that is still executing.
using ComponentReturn =
std::variant<std::nullptr_t, sys::testing::TerminationResult, zx_status_t>;
// This type encpsulates a component that is "Running". This means it is currently running or has
// stopped at some point in the past.
struct RunningComponent {
bool terminated() const { return !std::holds_alternative<std::nullptr_t>(return_val); }
fuchsia::sys::ComponentControllerPtr controller;
ComponentReturn return_val;
};
PresentViewIntegrationTest()
: interceptor_(sys::testing::ComponentInterceptor::CreateWithEnvironmentLoader(real_env())),
fake_presenter_(std::make_unique<testing::FakePresenter>()) {
// We want to inject our fake components and services into the environment.
auto services = interceptor_.MakeEnvironmentServices(real_env());
services->AddService(fake_presenter_->GetHandler());
// Hook various types of component launches in order to deliver pre-programmed behaviors.
EXPECT_TRUE(interceptor_.InterceptURL(
testing::kNonexistentViewUri, "",
[](fuchsia::sys::StartupInfo /*startup_info*/,
std::unique_ptr<sys::testing::InterceptedComponent> intercepted_component) {
// Simulate a failure to find the package.
intercepted_component->Exit(-1, fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND);
}));
EXPECT_TRUE(interceptor_.InterceptURL(
testing::kFakeViewUri, "",
[this](fuchsia::sys::StartupInfo startup_info,
std::unique_ptr<sys::testing::InterceptedComponent> intercepted_component) {
fake_view_ = std::make_unique<testing::FakeIntegrationTestView>(
std::move(startup_info), std::move(intercepted_component));
}));
EXPECT_TRUE(interceptor_.InterceptURL(
kIntlPropertyProviderUri, "",
[this](fuchsia::sys::StartupInfo startup_info,
std::unique_ptr<sys::testing::InterceptedComponent> intercepted_component) {
fake_intl_manager_ = std::make_unique<testing::FakeIntlManager>(
std::move(startup_info), std::move(intercepted_component));
}));
// Create the environment used in the test.
environment_ = CreateNewEnclosingEnvironment(kEnvironment, std::move(services));
WaitForEnclosingEnvToStart(environment_.get());
}
void RunUntilTerminated(RunningComponent* component) {
RunLoopUntil([&component] { return component->terminated(); });
}
void TerminateComponent(RunningComponent* component) {
component->controller->Kill();
RunUntilTerminated(component);
}
std::unique_ptr<RunningComponent> LaunchComponent(std::string url,
std::vector<std::string> args) {
fuchsia::sys::LaunchInfo launch_info{
.url = std::move(url),
.arguments = fidl::VectorPtr{std::move(args)},
};
auto component = std::make_unique<RunningComponent>();
auto& component_controller = component->controller;
component_controller.events().OnTerminated =
[c = component.get()](int64_t return_code, fuchsia::sys::TerminationReason reason) {
// Don't listen for the PEER_DISCONNECTED error at the end when the channel closes.
//
// PEER_DISCONNECTED always occurs at the end of a channel session, even when it closes
// normally. Listening for it in this case would stomp the |TerminationResult| with a
// ZX_OK.
c->controller.set_error_handler(nullptr);
c->return_val.emplace<sys::testing::TerminationResult>(sys::testing::TerminationResult{
.return_code = return_code,
.reason = reason,
});
};
component_controller.set_error_handler(
[c = component.get()](zx_status_t status) { c->return_val.emplace<zx_status_t>(status); });
// Now that event handlers are established, launch the component in the
// hermetic environment.
environment_->CreateComponent(std::move(launch_info), component_controller.NewRequest());
return component;
}
std::unique_ptr<RunningComponent> RunComponentUntilTerminated(std::string url,
std::vector<std::string> args) {
std::unique_ptr<RunningComponent> present_view =
LaunchComponent(kPresentViewComponentUri, std::move(args));
RunUntilTerminated(present_view.get());
return present_view;
}
std::unique_ptr<RunningComponent> LaunchPresentView(std::vector<std::string> args) {
return LaunchComponent(kPresentViewComponentUri, std::move(args));
}
std::unique_ptr<RunningComponent> RunPresentViewUntilTerminated(std::vector<std::string> args) {
return RunComponentUntilTerminated(kPresentViewComponentUri, std::move(args));
}
sys::testing::ComponentInterceptor interceptor_;
std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
std::unique_ptr<testing::FakeIntlManager> fake_intl_manager_;
std::unique_ptr<testing::FakePresenter> fake_presenter_;
std::unique_ptr<testing::FakeIntegrationTestView> fake_view_;
};
TEST_F(PresentViewIntegrationTest, NoParams) {
// Passing no parameters does nothing (but prints a warning).
//
// present_view should exit, and neither create a token pair nor connect to
// either of the FIDL interfaces.
std::unique_ptr<RunningComponent> present_view = RunPresentViewUntilTerminated({});
auto* result = std::get_if<sys::testing::TerminationResult>(&present_view->return_val);
ASSERT_NE(result, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_EQ(fake_view_, nullptr);
EXPECT_FALSE(fake_presenter_->bound());
EXPECT_FALSE(fake_presenter_->presentation());
EXPECT_EQ(fuchsia::sys::TerminationReason::EXITED, result->reason);
EXPECT_EQ(1, result->return_code);
}
TEST_F(PresentViewIntegrationTest, NoPositionalParams) {
// Passing no *positional* parameters does nothing, even with otherwise valid
// parameters specified.
//
// present_view should exit, and neither create a token pair nor connect to
// either of the FIDL interfaces.
std::unique_ptr<RunningComponent> present_view =
RunPresentViewUntilTerminated({{"--locale=en-US"}});
auto* result = std::get_if<sys::testing::TerminationResult>(&present_view->return_val);
ASSERT_NE(result, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_EQ(fake_view_, nullptr);
EXPECT_FALSE(fake_presenter_->bound());
EXPECT_FALSE(fake_presenter_->presentation());
EXPECT_EQ(fuchsia::sys::TerminationReason::EXITED, result->reason);
EXPECT_EQ(1, result->return_code);
}
TEST_F(PresentViewIntegrationTest, NonexistentComponentURI) {
// Non-existing component URIs are invalid and cause present_view to fail.
//
// present_view should create a token pair and pass one end to |Presenter|,
// but terminate itself once the specified component fails to launch.
std::unique_ptr<RunningComponent> present_view =
RunPresentViewUntilTerminated({{testing::kNonexistentViewUri}});
auto* result = std::get_if<sys::testing::TerminationResult>(&present_view->return_val);
ASSERT_NE(result, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_TRUE(fake_presenter_->presentation());
ASSERT_EQ(fake_view_, nullptr);
EXPECT_FALSE(fake_presenter_->bound());
EXPECT_TRUE(fake_presenter_->presentation()->token().value);
EXPECT_TRUE(fake_presenter_->presentation()->peer_disconnected());
EXPECT_EQ(fuchsia::sys::TerminationReason::EXITED, result->reason);
EXPECT_EQ(1, result->return_code);
}
TEST_F(PresentViewIntegrationTest, Launch) {
// present_view should create a token pair and launch the specified component,
// passing one end to |Presenter| and the other end to a |ViewProvider| from
// the component.
std::unique_ptr<RunningComponent> present_view = LaunchPresentView({{testing::kFakeViewUri}});
// Run the loop until either both tokens have been created or present_view terminates.
RunLoopUntil([this, &present_view] {
const bool presenter_bound = fake_presenter_->bound();
const bool presentation_bound = presenter_bound && fake_presenter_->presentation();
const bool presentation_has_token =
presentation_bound && fake_presenter_->presentation()->token().value;
const bool view_created = (fake_view_ != nullptr);
const bool view_bound = view_created && fake_view_->bound();
const bool view_has_token = view_bound && fake_view_->token().value;
return (presentation_has_token && view_has_token) || present_view->terminated();
});
auto* running_result = std::get_if<std::nullptr_t>(&present_view->return_val);
ASSERT_NE(running_result, nullptr);
ASSERT_NE(fake_view_, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_TRUE(fake_presenter_->presentation());
EXPECT_FALSE(fake_presenter_->presentation()->peer_disconnected());
EXPECT_FALSE(fake_view_->killed());
EXPECT_FALSE(fake_view_->peer_disconnected());
auto& view_holder_token = fake_presenter_->presentation()->token();
auto& view_token = fake_view_->token();
EXPECT_TRUE(view_holder_token.value);
EXPECT_TRUE(view_token.value);
EXPECT_EQ(fsl::GetKoid(view_token.value.get()),
fsl::GetRelatedKoid(view_holder_token.value.get()));
EXPECT_EQ(fsl::GetKoid(view_holder_token.value.get()),
fsl::GetRelatedKoid(view_token.value.get()));
TerminateComponent(present_view.get());
auto* killed_result = std::get_if<sys::testing::TerminationResult>(&present_view->return_val);
ASSERT_NE(killed_result, nullptr);
ASSERT_NE(fake_view_, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_TRUE(fake_presenter_->presentation());
EXPECT_FALSE(fake_presenter_->bound());
EXPECT_FALSE(fake_presenter_->presentation()->bound());
EXPECT_TRUE(fake_presenter_->presentation()->peer_disconnected());
EXPECT_FALSE(fake_view_->bound());
EXPECT_TRUE(fake_view_->killed());
EXPECT_FALSE(fake_view_->peer_disconnected()); // Wait was cancelled by Kill()
EXPECT_EQ(fuchsia::sys::TerminationReason::EXITED, killed_result->reason);
EXPECT_EQ(ZX_TASK_RETCODE_SYSCALL_KILL, killed_result->return_code);
}
TEST_F(PresentViewIntegrationTest, LaunchWithLocale) {
// present_view should create a token pair and launch the specified component,
// passing one end to |Presenter| and the other end to a |ViewProvider| from
// the component.
std::unique_ptr<RunningComponent> present_view =
LaunchPresentView({{"--locale=en-US"}, {testing::kFakeViewUri}});
// Run the loop until either both tokens have been created or present_view terminates.
RunLoopUntil([this, &present_view] {
const bool presenter_bound = fake_presenter_->bound();
const bool presentation_bound = presenter_bound && fake_presenter_->presentation();
const bool presentation_has_token =
presentation_bound && fake_presenter_->presentation()->token().value;
const bool view_created = (fake_view_ != nullptr);
const bool view_bound = view_created && fake_view_->bound();
const bool view_has_token = view_bound && fake_view_->token().value;
return (presentation_has_token && view_has_token) || present_view->terminated();
});
auto* running_result = std::get_if<std::nullptr_t>(&present_view->return_val);
ASSERT_NE(running_result, nullptr);
ASSERT_NE(fake_view_, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_TRUE(fake_presenter_->presentation());
EXPECT_FALSE(fake_presenter_->presentation()->peer_disconnected());
EXPECT_FALSE(fake_view_->killed());
EXPECT_FALSE(fake_view_->peer_disconnected());
auto& view_holder_token = fake_presenter_->presentation()->token();
auto& view_token = fake_view_->token();
EXPECT_TRUE(view_holder_token.value);
EXPECT_TRUE(view_token.value);
EXPECT_EQ(fsl::GetKoid(view_token.value.get()),
fsl::GetRelatedKoid(view_holder_token.value.get()));
EXPECT_EQ(fsl::GetKoid(view_holder_token.value.get()),
fsl::GetRelatedKoid(view_token.value.get()));
TerminateComponent(present_view.get());
auto* killed_result = std::get_if<sys::testing::TerminationResult>(&present_view->return_val);
ASSERT_NE(killed_result, nullptr);
ASSERT_NE(fake_view_, nullptr);
ASSERT_NE(fake_presenter_, nullptr);
ASSERT_TRUE(fake_presenter_->presentation());
EXPECT_FALSE(fake_presenter_->bound());
EXPECT_FALSE(fake_presenter_->presentation()->bound());
EXPECT_TRUE(fake_presenter_->presentation()->peer_disconnected());
EXPECT_FALSE(fake_view_->bound());
EXPECT_TRUE(fake_view_->killed());
EXPECT_FALSE(fake_view_->peer_disconnected()); // Wait was cancelled by Kill()
EXPECT_EQ(fuchsia::sys::TerminationReason::EXITED, killed_result->reason);
EXPECT_EQ(ZX_TASK_RETCODE_SYSCALL_KILL, killed_result->return_code);
}
} // namespace present_view::test