chromium/components/exo/wayland/test/wayland_server_test.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "components/exo/wayland/test/wayland_server_test.h"

#include <wayland-server-protocol-core.h>
#include <string>
#include <utility>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "components/exo/security_delegate.h"
#include "components/exo/wayland/server.h"
#include "components/exo/wayland/wayland_display_output.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace exo::wayland::test {

// Waits for the server to construct client objects on the server thread
// following a connection initiated on the client thread.
class ClientCreatedWaiter {
 public:
  explicit ClientCreatedWaiter(wl_display* display) {
    listener_.notify = [](wl_listener* listener_ptr, void* data) {
      ClientCreatedWaiter* created_waiter = wl_container_of(
          listener_ptr, /*sample=*/created_waiter, /*member=*/listener_);
      created_waiter->OnClientCreated(static_cast<wl_client*>(data));
    };
    wl_display_add_client_created_listener(display, &listener_);
  }
  ClientCreatedWaiter(const ClientCreatedWaiter&) = delete;
  ClientCreatedWaiter& operator=(const ClientCreatedWaiter&) = delete;
  ~ClientCreatedWaiter() = default;

  wl_client* Wait() { return future_.Get(); }

  void OnClientCreated(wl_client* client) {
    future_.SetValue(client);
    wl_list_remove(&listener_.link);
  }

 private:
  wl_listener listener_;
  base::test::TestFuture<wl_client*> future_;
};

// Waits for the destruction of a connected client to be processed on the server
// thread.
class ClientDestroyedWaiter {
 public:
  explicit ClientDestroyedWaiter(wl_client* client) {
    future_cb_ = future_.GetCallback();

    listener_.notify = [](wl_listener* listener_ptr, void* data) {
      ClientDestroyedWaiter* destroy_waiter = wl_container_of(
          listener_ptr, /*sample=*/destroy_waiter, /*member=*/listener_);
      destroy_waiter->OnClientDestroyed();
    };
    wl_client_add_destroy_listener(client, &listener_);
  }
  ClientDestroyedWaiter(const ClientDestroyedWaiter&) = delete;
  ClientDestroyedWaiter& operator=(const ClientDestroyedWaiter&) = delete;
  ~ClientDestroyedWaiter() = default;

  void Wait() {
    ASSERT_TRUE(future_.Wait()) << "Client was not destroyed by server.";
  }

  void OnClientDestroyed() { std::move(future_cb_).Run(); }

 private:
  wl_listener listener_;
  base::OnceClosure future_cb_;
  base::test::TestFuture<void> future_;
};

WaylandServerTest::WaylandServerTest()
    : WaylandServerTestBase(
          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
  Server::SetServerGetter(base::BindLambdaForTesting([&](wl_display* display) {
    // Currently tests run with a single Server instance.
    EXPECT_EQ(display, server_->GetWaylandDisplay());
    return server_.get();
  }));
}

WaylandServerTest::~WaylandServerTest() {
  Server::SetServerGetter(base::NullCallback());
}

void WaylandServerTest::SetUp() {
  WaylandServerTestBase::SetUp();

  socket_ = std::make_unique<ScopedTempSocket>();
  server_ = CreateServer();

  base::RunLoop loop;
  server_->StartWithFdAsync(socket_->TakeFd(),
                            base::BindLambdaForTesting([&](bool success) {
                              ASSERT_TRUE(success);
                              loop.Quit();
                            }));
  loop.Run();

  // Block on the successful construction of the client and server side objects.
  // TestWaylandClientThread::Start() will block until the initialization on the
  // client thread is done.
  ClientCreatedWaiter created_waiter(server_->GetWaylandDisplay());
  client_thread_ = std::make_unique<TestWaylandClientThread>("client");
  ASSERT_TRUE(client_thread_->Start(base::BindOnce(
      &WaylandServerTest::InitOnClientThread, base::Unretained(this))));
  client_resource_ = created_waiter.Wait();
}

void WaylandServerTest::TearDown() {
  // Force a cleanup of any remaining outputs, which are deleted on a delay to
  // give clients the opportunity to release the global.
  task_environment()->FastForwardBy(WaylandDisplayOutput::kDeleteTaskDelay *
                                    (WaylandDisplayOutput::kDeleteRetries + 1));

  client_resource_ = nullptr;
  client_thread_.reset();
  server_.reset();

  WaylandServerTestBase::TearDown();
}

void WaylandServerTest::PostToClientAndWait(
    base::OnceCallback<void(TestClient* client)> callback) {
  client_thread_->RunAndWait(std::move(callback));
}

void WaylandServerTest::PostToClientAndWait(base::OnceClosure closure) {
  client_thread_->RunAndWait(std::move(closure));
}

void WaylandServerTest::DisconnectClientAndWait() {
  ClientDestroyedWaiter waiter(client_resource_);
  client_resource_ = nullptr;
  client_thread_.reset();
  waiter.Wait();
}

std::unique_ptr<TestClient> WaylandServerTest::InitOnClientThread() {
  auto client = std::make_unique<TestClient>();
  if (!client->Init(socket_->server_path().value())) {
    return nullptr;
  }

  return client;
}

}  // namespace exo::wayland::test