|  | // 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 "sdk/lib/virtualization/scenic_wayland_dispatcher.h" | 
|  |  | 
|  | #include <fuchsia/virtualization/cpp/fidl.h> | 
|  | #include <lib/async-loop/cpp/loop.h> | 
|  | #include <lib/async-loop/default.h> | 
|  | #include <lib/gtest/test_loop_fixture.h> | 
|  | #include <lib/sys/cpp/testing/component_context_provider.h> | 
|  | #include <lib/sys/cpp/testing/fake_component.h> | 
|  | #include <lib/sys/cpp/testing/fake_launcher.h> | 
|  |  | 
|  | namespace guest { | 
|  |  | 
|  | static constexpr const char* kWaylandDispatcherUrl = | 
|  | "fuchsia-pkg://fuchsia.com/wayland_bridge#meta/wayland_bridge.cmx"; | 
|  |  | 
|  | class FakeDispatcher : public fuchsia::virtualization::WaylandDispatcher, | 
|  | public fuchsia::wayland::ViewProducer { | 
|  | public: | 
|  | FakeDispatcher() { | 
|  | component_.AddPublicService(bindings_.GetHandler(this)); | 
|  | component_.AddPublicService(view_producer_bindings_.GetHandler(this)); | 
|  | } | 
|  |  | 
|  | // Register to be launched with a fake URL | 
|  | void Register(sys::testing::FakeLauncher& fake_launcher) { | 
|  | component_.Register(kWaylandDispatcherUrl, fake_launcher); | 
|  | } | 
|  |  | 
|  | size_t BindingCount() const { return bindings_.size(); } | 
|  | size_t ConnectionCount() const { return connections_.size(); } | 
|  |  | 
|  | // Simulates the bridge dying by clearing all state and closing any bindings. | 
|  | void Terminate() { | 
|  | bindings_.CloseAll(); | 
|  | view_producer_bindings_.CloseAll(); | 
|  | connections_.clear(); | 
|  | } | 
|  |  | 
|  | void SendOnNewView() { | 
|  | for (auto& binding : view_producer_bindings_.bindings()) { | 
|  | zx::channel c1, c2; | 
|  | zx::channel::create(0, &c1, &c2); | 
|  | binding->events().OnNewView( | 
|  | fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider>(std::move(c1))); | 
|  | new_view_channels_.push_back(std::move(c2)); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | // |fuchsia::virtualization::WaylandDispatcher| | 
|  | void OnNewConnection(zx::channel channel) { connections_.push_back(std::move(channel)); } | 
|  |  | 
|  | sys::testing::FakeComponent component_; | 
|  | fidl::BindingSet<fuchsia::virtualization::WaylandDispatcher> bindings_; | 
|  | fidl::BindingSet<fuchsia::wayland::ViewProducer> view_producer_bindings_; | 
|  | std::vector<zx::channel> connections_; | 
|  | std::vector<zx::channel> new_view_channels_; | 
|  | }; | 
|  |  | 
|  | class ScenicWaylandDispatcherTest : public gtest::TestLoopFixture { | 
|  | public: | 
|  | void SetUp() override { | 
|  | TestLoopFixture::SetUp(); | 
|  | dispatcher_.reset(new ScenicWaylandDispatcher( | 
|  | provider_.context(), fit::bind_member(this, &ScenicWaylandDispatcherTest::OnNewView))); | 
|  | provider_.service_directory_provider()->AddService(fake_launcher_.GetHandler()); | 
|  |  | 
|  | fake_dispatcher_impl_.reset(new FakeDispatcher()); | 
|  | fake_dispatcher_impl_->Register(fake_launcher_); | 
|  | } | 
|  |  | 
|  | void TearDown() override { TestLoopFixture::TearDown(); } | 
|  |  | 
|  | protected: | 
|  | ScenicWaylandDispatcher* dispatcher() const { return dispatcher_.get(); } | 
|  | FakeDispatcher* remote_dispatcher() const { return fake_dispatcher_impl_.get(); } | 
|  | std::vector<fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider>>* views() { return &views_; } | 
|  |  | 
|  | private: | 
|  | void OnNewView(fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider> view) { | 
|  | views_.push_back(std::move(view)); | 
|  | } | 
|  |  | 
|  | sys::testing::FakeLauncher fake_launcher_; | 
|  | std::unique_ptr<FakeDispatcher> fake_dispatcher_impl_; | 
|  | sys::testing::ComponentContextProvider provider_; | 
|  | std::unique_ptr<ScenicWaylandDispatcher> dispatcher_; | 
|  | std::vector<fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider>> views_; | 
|  | }; | 
|  |  | 
|  | // The |ScenicWaylandDispatcher| will simply spawn a new bridge process for each | 
|  | // connection and reuse that process for subsequent connections. | 
|  | // | 
|  | // Test that multiple connections are sent to the same bridge component. | 
|  | TEST_F(ScenicWaylandDispatcherTest, LaunchBridgeOnce) { | 
|  | zx::channel c1, c2; | 
|  | zx::channel::create(0, &c1, &c2); | 
|  | dispatcher()->OnNewConnection(std::move(c1)); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->BindingCount()); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->ConnectionCount()); | 
|  |  | 
|  | dispatcher()->OnNewConnection(std::move(c2)); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->BindingCount()); | 
|  | ASSERT_EQ(2u, remote_dispatcher()->ConnectionCount()); | 
|  | } | 
|  |  | 
|  | // When the remote wayland_bridge component dies, we restart it on the next | 
|  | // connection request. | 
|  | TEST_F(ScenicWaylandDispatcherTest, RelaunchBridgeWhenLost) { | 
|  | zx::channel c1, c2; | 
|  | zx::channel::create(0, &c1, &c2); | 
|  | dispatcher()->OnNewConnection(std::move(c1)); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->BindingCount()); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->ConnectionCount()); | 
|  |  | 
|  | remote_dispatcher()->Terminate(); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(0u, remote_dispatcher()->BindingCount()); | 
|  | ASSERT_EQ(0u, remote_dispatcher()->ConnectionCount()); | 
|  |  | 
|  | dispatcher()->OnNewConnection(std::move(c2)); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->BindingCount()); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->ConnectionCount()); | 
|  | } | 
|  |  | 
|  | // Verify we can correctly receive new ViewProviders from the remote bridge | 
|  | // process. | 
|  | TEST_F(ScenicWaylandDispatcherTest, ReceiveNewViewEvents) { | 
|  | zx::channel c1, c2; | 
|  | zx::channel::create(0, &c1, &c2); | 
|  | dispatcher()->OnNewConnection(std::move(c1)); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->BindingCount()); | 
|  | ASSERT_EQ(1u, remote_dispatcher()->ConnectionCount()); | 
|  | ASSERT_EQ(0u, views()->size()); | 
|  |  | 
|  | remote_dispatcher()->SendOnNewView(); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(1u, views()->size()); | 
|  | } | 
|  |  | 
|  | };  // namespace guest |