blob: 4691fa5e899eb2007f857731fc64fe1f79bc13f5 [file] [log] [blame]
// Copyright 2021 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/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <gtest/gtest.h>
// This test exercises the fuchsia.ui.views.ViewRefInstalled protocol implemented by Scenic
// in the context of the GFX compositor interface.
// The geometry is not important in this test, so we use the following minimal two-node
// (plus a scene node) tree topology:
// (scene)
// |
// parent
// |
// child
namespace integration_tests {
const std::map<std::string, std::string> kServices = {
{"fuchsia.scenic.allocation.Allocator", "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"},
{"fuchsia.ui.scenic.Scenic", "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"},
{"fuchsia.ui.views.ViewRefInstalled", "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"},
{"fuchsia.hardware.display.Provider",
"fuchsia-pkg://fuchsia.com/fake-hardware-display-controller-provider#meta/hdcp.cmx"},
};
// Allow these global services.
const std::string kParentServices[] = {"fuchsia.vulkan.loader.Loader", "fuchsia.sysmem.Allocator"};
// "Long enough" time to wait before assuming a FIDL message won't arrive.
// Should not be used when actually expecting an update to occur, to avoid flakiness.
const zx::duration kWaitTime = zx::msec(2);
using fuchsia::ui::views::ViewRef;
using WatchResult = fuchsia::ui::views::ViewRefInstalled_Watch_Result;
scenic::Session CreateSession(fuchsia::ui::scenic::Scenic* scenic,
fuchsia::ui::scenic::SessionEndpoints endpoints) {
FX_DCHECK(scenic);
FX_DCHECK(!endpoints.has_session());
FX_DCHECK(!endpoints.has_session_listener());
fuchsia::ui::scenic::SessionPtr session_ptr;
fuchsia::ui::scenic::SessionListenerHandle listener_handle;
auto listener_request = listener_handle.NewRequest();
endpoints.set_session(session_ptr.NewRequest());
endpoints.set_session_listener(std::move(listener_handle));
scenic->CreateSessionT(std::move(endpoints), [] {});
return scenic::Session(std::move(session_ptr), std::move(listener_request));
}
// Sets up the root of a scene.
// Present() must be called separately by the creator, since this does not have access to the
// looper.
struct RootSession {
RootSession(fuchsia::ui::scenic::Scenic* scenic, fuchsia::ui::scenic::SessionEndpoints endpoints)
: session(CreateSession(scenic, std::move(endpoints))),
compositor(&session),
layer_stack(&session),
layer(&session),
renderer(&session),
scene(&session),
camera(scene) {
compositor.SetLayerStack(layer_stack);
layer_stack.AddLayer(layer);
layer.SetRenderer(renderer);
renderer.SetCamera(camera);
}
scenic::Session session;
scenic::DisplayCompositor compositor;
scenic::LayerStack layer_stack;
scenic::Layer layer;
scenic::Renderer renderer;
scenic::Scene scene;
scenic::Camera camera;
std::unique_ptr<scenic::ViewHolder> view_holder;
};
// Test fixture that sets up an environment with a Scenic we can connect to.
class GfxViewRefInstalledIntegrationTest : public sys::testing::TestWithEnvironment {
public:
fuchsia::ui::scenic::Scenic* scenic() { return scenic_.get(); }
void SetUp() override {
TestWithEnvironment::SetUp();
environment_ = CreateNewEnclosingEnvironment(
"gfx_view_ref_installed_integration_test_environment", CreateServices());
environment_->ConnectToService(scenic_.NewRequest());
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status);
});
// Set up root view.
root_session_ =
std::make_unique<RootSession>(scenic(), fuchsia::ui::scenic::SessionEndpoints{});
root_session_->session.set_error_handler([](auto) { FAIL() << "Root session terminated."; });
BlockingPresent(root_session_->session);
environment_->ConnectToService(view_ref_installed_ptr_.NewRequest());
}
void BlockingPresent(scenic::Session& session) {
bool presented = false;
session.set_on_frame_presented_handler([&presented](auto) { presented = true; });
fuchsia::ui::scenic::Present2Args args;
session.Present2(0, 0, [](auto) {});
RunLoopUntil([&presented] { return presented; });
}
void AttachToScene(fuchsia::ui::views::ViewHolderToken token) {
root_session_->view_holder =
std::make_unique<scenic::ViewHolder>(&root_session_->session, std::move(token), "holder");
root_session_->scene.AddChild(*root_session_->view_holder);
BlockingPresent(root_session_->session);
}
// Configures services available to the test environment. This method is called by |SetUp()|. It
// shadows but calls |TestWithEnvironment::CreateServices()|.
std::unique_ptr<sys::testing::EnvironmentServices> CreateServices() {
auto services = TestWithEnvironment::CreateServices();
for (const auto& [name, url] : kServices) {
const zx_status_t is_ok = services->AddServiceWithLaunchInfo({.url = url}, name);
FX_CHECK(is_ok == ZX_OK) << "Failed to add service " << name;
}
for (const auto& service : kParentServices) {
const zx_status_t is_ok = services->AllowParentService(service);
FX_CHECK(is_ok == ZX_OK) << "Failed to add service " << service;
}
return services;
}
protected:
std::unique_ptr<RootSession> root_session_;
fuchsia::ui::views::ViewRefInstalledPtr view_ref_installed_ptr_;
private:
std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
fuchsia::ui::scenic::ScenicPtr scenic_;
};
TEST_F(GfxViewRefInstalledIntegrationTest, InvalidatedViewRef_ShouldReturnError) {
std::optional<WatchResult> result;
{
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
view_ref_installed_ptr_->Watch(std::move(view_ref), [&result](auto watch_result) {
result.emplace(std::move(watch_result));
});
RunLoopWithTimeout(kWaitTime);
EXPECT_FALSE(result.has_value());
} // |control_ref| goes out of scope. This will invalidate the ViewRef.
RunLoopUntil([&result] { return result.has_value(); }); // Succeeds or times out.
EXPECT_TRUE(result->is_err());
}
TEST_F(GfxViewRefInstalledIntegrationTest, InstalledViewRef_ShouldReturnImmediately) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
ViewRef view_ref_copy;
fidl::Clone(view_ref, &view_ref_copy);
scenic::View view(&root_session_->session, std::move(view_token), std::move(control_ref),
std::move(view_ref_copy), "root_view");
AttachToScene(std::move(view_holder_token));
BlockingPresent(root_session_->session);
std::optional<WatchResult> result;
view_ref_installed_ptr_->Watch(std::move(view_ref), [&result](auto watch_result) {
result.emplace(std::move(watch_result));
});
RunLoopUntil([&result] { return result.has_value(); }); // Succeeds or times out.
EXPECT_TRUE(result->is_response());
}
TEST_F(GfxViewRefInstalledIntegrationTest, WaitedOnViewRef_ShouldReturnWhenInstalled) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
ViewRef view_ref_copy;
fidl::Clone(view_ref, &view_ref_copy);
std::optional<WatchResult> result;
view_ref_installed_ptr_->Watch(std::move(view_ref), [&result](auto watch_result) {
result.emplace(std::move(watch_result));
});
// Not installed; should not return yet.
RunLoopWithTimeout(kWaitTime);
EXPECT_FALSE(result.has_value());
// Install it.
scenic::View view(&root_session_->session, std::move(view_token), std::move(control_ref),
std::move(view_ref_copy), "root_view");
AttachToScene(std::move(view_holder_token));
BlockingPresent(root_session_->session);
RunLoopUntil([&result] { return result.has_value(); }); // Succeeds or times out.
EXPECT_TRUE(result->is_response());
}
TEST_F(GfxViewRefInstalledIntegrationTest, InstalledAndDisconnectedViewRef_ShouldReturnResponse) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
ViewRef view_ref_copy;
fidl::Clone(view_ref, &view_ref_copy);
scenic::View view(&root_session_->session, std::move(view_token), std::move(control_ref),
std::move(view_ref_copy), "root_view");
AttachToScene(std::move(view_holder_token));
BlockingPresent(root_session_->session);
// Disconnect the view from the scene.
root_session_->scene.DetachChildren();
BlockingPresent(root_session_->session);
// Watch should still return true, since the view has been previously installed.
std::optional<WatchResult> result;
view_ref_installed_ptr_->Watch(std::move(view_ref), [&result](auto watch_result) {
result.emplace(std::move(watch_result));
});
RunLoopUntil([&result] { return result.has_value(); }); // Succeeds or times out.
EXPECT_TRUE(result->is_response());
}
TEST_F(GfxViewRefInstalledIntegrationTest, InstalledAndDestroyedViewRef_ShouldReturnError) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
ViewRef view_ref_copy;
fidl::Clone(view_ref, &view_ref_copy);
{
scenic::View view(&root_session_->session, std::move(view_token), std::move(control_ref),
std::move(view_ref_copy), "root_view");
AttachToScene(std::move(view_holder_token));
BlockingPresent(root_session_->session);
} // view out of scope here
BlockingPresent(root_session_->session);
std::optional<WatchResult> result;
view_ref_installed_ptr_->Watch(std::move(view_ref), [&result](auto watch_result) {
result.emplace(std::move(watch_result));
});
RunLoopUntil([&result] { return result.has_value(); }); // Succeeds or times out.
EXPECT_TRUE(result->is_err());
}
// Check that transative connections are installed correctly.
TEST_F(GfxViewRefInstalledIntegrationTest, TransitiveConnection_ShouldReturnResponse) {
// Create the root View.
auto [root_view_token, root_view_holder_token] = scenic::ViewTokenPair::New();
auto [root_control_ref, root_view_ref] = scenic::ViewRefPair::New();
ViewRef root_view_ref_copy;
fidl::Clone(root_view_ref, &root_view_ref_copy);
scenic::View root_view(&root_session_->session, std::move(root_view_token),
std::move(root_control_ref), std::move(root_view_ref_copy), "root_view");
// Create the child view and connect it to the parent, but don't attach to the scene yet.
scenic::Session child_session = CreateSession(scenic(), {});
auto [child_view_token, child_view_holder_token] = scenic::ViewTokenPair::New();
auto [child_control_ref, child_view_ref] = scenic::ViewRefPair::New();
ViewRef child_view_ref_copy;
fidl::Clone(child_view_ref, &child_view_ref_copy);
scenic::View child_view(&child_session, std::move(child_view_token), std::move(child_control_ref),
std::move(child_view_ref_copy), "child_view");
scenic::ViewHolder child_view_holder(&root_session_->session, std::move(child_view_holder_token),
"child_holder");
root_view.AddChild(child_view_holder);
BlockingPresent(child_session);
BlockingPresent(root_session_->session);
std::optional<WatchResult> result;
view_ref_installed_ptr_->Watch(std::move(child_view_ref), [&result](auto watch_result) {
result.emplace(std::move(watch_result));
});
// Not installed; should not return yet.
RunLoopWithTimeout(kWaitTime);
EXPECT_FALSE(result.has_value());
// Now attach the whole thing to the scene and observe that the child view ref is installed.
AttachToScene(std::move(root_view_holder_token));
RunLoopUntil([&result] { return result.has_value(); }); // Succeeds or times out.
EXPECT_TRUE(result->is_response());
}
} // namespace integration_tests