// 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.
#include "components/exo/wayland/test/test_wayland_client_thread.h"
#include <utility>
#include <poll.h>
#include <wayland-client-core.h>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/current_thread.h"
namespace exo::wayland::test {
TestWaylandClientThread::TestWaylandClientThread(const std::string& name)
: Thread(name), controller_(FROM_HERE) {}
TestWaylandClientThread::~TestWaylandClientThread() {
// Guarantee that no new events will come during or after the destruction of
// the display.
stopped_ = true;
task_runner()->PostTask(FROM_HERE,
base::BindOnce(&TestWaylandClientThread::DoCleanUp,
base::Unretained(this)));
// Ensure the task above is run.
FlushForTesting();
}
bool TestWaylandClientThread::Start(
base::OnceCallback<std::unique_ptr<TestClient>()> init_callback) {
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
CHECK(base::Thread::StartWithOptions(std::move(options)));
RunAndWait(base::BindOnce(&TestWaylandClientThread::DoInit,
base::Unretained(this), std::move(init_callback)));
return !!client_;
}
void TestWaylandClientThread::RunAndWait(
base::OnceCallback<void(TestClient*)> callback) {
base::OnceClosure closure =
base::BindOnce(std::move(callback), base::Unretained(client_.get()));
RunAndWait(std::move(closure));
}
void TestWaylandClientThread::RunAndWait(base::OnceClosure closure) {
base::RunLoop run_loop;
task_runner()->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&TestWaylandClientThread::DoRun, base::Unretained(this),
std::move(closure)),
run_loop.QuitClosure());
// TODO(crbug.com/40260645): Use busy loop to workaround RunLoop::Run()
// erroneously advancing mock time.
while (!run_loop.AnyQuitCalled()) {
run_loop.RunUntilIdle();
}
}
void TestWaylandClientThread::OnFileCanReadWithoutBlocking(int fd) {
if (stopped_) {
return;
}
if (wl_display_prepare_read(client_->display()) != 0) {
return;
}
// Disconnect can be seen as a read event, and wl_display_prepare_read_queue()
// is used to prevent read from other thread and does not actually check the
// `fd`'s state. Make sure that `fd` has indeed has data to read.
struct pollfd fds;
fds.fd = fd;
fds.events = POLLIN;
fds.revents = 0;
auto ret = poll(&fds, 1, -1);
if (ret != POLLIN) {
wl_display_cancel_read(client_->display());
return;
}
wl_display_read_events(client_->display());
wl_display_dispatch_pending(client_->display());
wl_display_flush(client_->display());
}
void TestWaylandClientThread::OnFileCanWriteWithoutBlocking(int fd) {}
void TestWaylandClientThread::DoInit(
TestWaylandClientThread::InitCallback init_callback) {
client_ = std::move(init_callback).Run();
if (!client_)
return;
const bool result = base::CurrentIOThread::Get().WatchFileDescriptor(
wl_display_get_fd(client_->display()), /*persistent=*/true,
base::MessagePumpEpoll::WATCH_READ, &controller_, this);
if (!result)
client_.reset();
}
void TestWaylandClientThread::DoRun(base::OnceClosure closure) {
std::move(closure).Run();
wl_display_flush(client_->display());
wl_display_roundtrip(client_->display());
}
void TestWaylandClientThread::DoCleanUp() {
controller_.StopWatchingFileDescriptor();
client_.reset();
}
} // namespace exo::wayland::test