blob: 87dbfab7495f5ae3f4651a1eea473331117a3cf8 [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS 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 <vector>
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include "base/logging.h"
#include "window_manager/callback.h"
#include "window_manager/event_loop.h"
#include "window_manager/test_lib.h"
#include "window_manager/x11/mock_x_connection.h"
DEFINE_bool(logtostderr, false,
"Print debugging messages to stderr (suppressed otherwise)");
using std::vector;
using window_manager::EventLoop;
namespace window_manager {
class EventLoopTest : public ::testing::Test {};
// Helper class that receives X events and uses them to manipulate the
// event loop. See the comment near the end of the "Basic" test for
// details about what's going on here.
class TestEventLoopSubscriber {
public:
TestEventLoopSubscriber(EventLoop* event_loop, MockXConnection* xconn)
: event_loop_(event_loop),
xconn_(xconn),
timeout_id_(EventLoop::kUnsetTimeoutId),
num_times_timeout_invoked_(0) {
}
void ProcessPendingEvents() {
while (xconn_->IsEventPending()) {
XEvent event;
xconn_->GetNextEvent(&event);
switch (event.type) {
case ButtonPress: {
// Make HandleTimeout() get run every five milliseconds.
timeout_id_ = event_loop_->AddTimeout(
NewPermanentCallback(
this, &TestEventLoopSubscriber::HandleTimeout),
5, 5);
break;
}
case ButtonRelease: {
event_loop_->Exit();
break;
}
default:
CHECK(false) << "Got unexpected event of type " << event.type;
}
}
}
private:
void HandleTimeout() {
num_times_timeout_invoked_++;
if (num_times_timeout_invoked_ > 1) {
// The second time that we're called, remove our timeout and put a
// button release event on the queue.
event_loop_->RemoveTimeout(timeout_id_);
XEvent event;
memset(&event, 0, sizeof(event));
event.type = ButtonRelease;
// Intentionally don't make the FD readable here, to simulate the
// case where Xlib pulls an event into its queue before we see that
// it's readable.
xconn_->AppendEventToQueue(event, false); // write_to_fd=false
}
}
EventLoop* event_loop_; // not owned
MockXConnection* xconn_; // not owned
// ID for a recurring timeout that invokes HandleTimeout().
int timeout_id_;
// Number of times that HandleTimeout() has been called.
int num_times_timeout_invoked_;
};
// Data used for the RemoveScheduledTimeout test.
struct RemoveScheduledTimeoutData {
RemoveScheduledTimeoutData(EventLoop* event_loop)
: event_loop(event_loop),
timeout_id_to_remove(EventLoop::kUnsetTimeoutId),
called(false) {
}
void RemoveTimeout() {
event_loop->RemoveTimeout(timeout_id_to_remove);
timeout_id_to_remove = EventLoop::kUnsetTimeoutId;
called = true;
event_loop->Exit();
}
EventLoop* event_loop;
int timeout_id_to_remove;
bool called;
};
// Data used for the PostTask test.
struct PostTaskData {
PostTaskData(EventLoop* event_loop)
: event_loop(event_loop),
prepoll_called(false),
timeout_called(false) {
event_loop->AddPrePollCallback(
NewPermanentCallback(this, &PostTaskData::HandlePrePollCallback));
first_timeout_id = event_loop->AddTimeout(
NewPermanentCallback(this, &PostTaskData::HandleTimeout), 0, 0);
second_timeout_id = event_loop->AddTimeout(
NewPermanentCallback(this, &PostTaskData::HandleTimeout), 0, 0);
}
~PostTaskData() {
event_loop->RemoveTimeout(first_timeout_id);
event_loop->RemoveTimeout(second_timeout_id);
}
// These values represent the various Handle*() methods defined below.
// We use them to record the order in which the callbacks were invoked.
enum CallbackType {
PRE_POLL_CALLBACK = 0,
TIMEOUT,
TASK_PRE_POLL,
TASK_TIMEOUT_A,
TASK_TIMEOUT_B,
TASK_REPOSTED_A,
TASK_REPOSTED_B,
};
// Post HandlePrePollTask() the first time and make the event loop exit
// the second.
void HandlePrePollCallback() {
called_types.push_back(PRE_POLL_CALLBACK);
if (prepoll_called) {
event_loop->Exit();
return;
}
event_loop->PostTask(
NewPermanentCallback(this, &PostTaskData::HandlePrePollTask));
prepoll_called = true;
}
// Post HandleTimeoutTaskA() the first time and HandleTimeoutTaskB() the
// second.
void HandleTimeout() {
called_types.push_back(TIMEOUT);
event_loop->PostTask(
NewPermanentCallback(this, !timeout_called ?
&PostTaskData::HandleTimeoutTaskA :
&PostTaskData::HandleTimeoutTaskB));
timeout_called = true;
}
// Post HandleRepostedTaskA() and HandleRepostedTaskB().
void HandleTimeoutTaskA() {
called_types.push_back(TASK_TIMEOUT_A);
event_loop->PostTask(
NewPermanentCallback(this, &PostTaskData::HandleRepostedTaskA));
event_loop->PostTask(
NewPermanentCallback(this, &PostTaskData::HandleRepostedTaskB));
}
// These methods just record that they were called.
void HandlePrePollTask() { called_types.push_back(TASK_PRE_POLL); }
void HandleTimeoutTaskB() { called_types.push_back(TASK_TIMEOUT_B); }
void HandleRepostedTaskA() { called_types.push_back(TASK_REPOSTED_A); }
void HandleRepostedTaskB() { called_types.push_back(TASK_REPOSTED_B); }
EventLoop* event_loop; // not owned
// The order in which various callbacks were executed.
vector<CallbackType> called_types;
// Have HandlePrePollCallback() and HandleTimeout() been called yet?
bool prepoll_called;
bool timeout_called;
// IDs of the two timeouts that we register.
int first_timeout_id;
int second_timeout_id;
};
// Perform a somewhat-tricky test of the event loop.
TEST_F(EventLoopTest, Basic) {
EventLoop event_loop;
MockXConnection xconn;
TestEventLoopSubscriber subscriber(&event_loop, &xconn);
event_loop.AddFileDescriptor(
xconn.GetConnectionFileDescriptor(),
NewPermanentCallback(
&subscriber, &TestEventLoopSubscriber::ProcessPendingEvents));
event_loop.AddPrePollCallback(
NewPermanentCallback(
&subscriber, &TestEventLoopSubscriber::ProcessPendingEvents));
// Add a button press event to the X connection's event queue.
XEvent event;
memset(&event, 0, sizeof(event));
event.type = ButtonPress;
xconn.AppendEventToQueue(event, true); // write_to_fd=true
// Now start the event loop. The subscriber's button press handler will
// register a recurring timeout with the event loop. The second time
// that the timeout is invoked, it will enqueue a button release event.
// The button release handler tells the event loop to exit. If all goes
// well, we should return in about 10 milliseconds! If it doesn't, we
// will hang forever. :-(
event_loop.Run();
}
// Test that if two timeouts are scheduled in the same poll cycle and one
// of them removes the other, the second one doesn't get invoked.
TEST_F(EventLoopTest, RemoveScheduledTimeout) {
EventLoop event_loop;
RemoveScheduledTimeoutData first_timeout_data(&event_loop);
RemoveScheduledTimeoutData second_timeout_data(&event_loop);
// We don't know which timeout's callback will be invoked first, so we
// make each remove the other.
second_timeout_data.timeout_id_to_remove = event_loop.AddTimeout(
NewPermanentCallback(&first_timeout_data,
&RemoveScheduledTimeoutData::RemoveTimeout), 0, 0);
first_timeout_data.timeout_id_to_remove = event_loop.AddTimeout(
NewPermanentCallback(&second_timeout_data,
&RemoveScheduledTimeoutData::RemoveTimeout), 0, 0);
event_loop.Run();
// At the end, exactly one of the callbacks should've been called.
EXPECT_TRUE(first_timeout_data.called ^ second_timeout_data.called)
<< "first=" << first_timeout_data.called
<< " second=" << second_timeout_data.called;
event_loop.RemoveTimeoutIfSet(&first_timeout_data.timeout_id_to_remove);
event_loop.RemoveTimeoutIfSet(&second_timeout_data.timeout_id_to_remove);
}
// Test that tasks posted via the PostTask() method always get called as
// soon as control is returned to the event loop.
TEST_F(EventLoopTest, PostTask) {
EventLoop event_loop;
PostTaskData data(&event_loop);
event_loop.Run();
// The pre-poll callback should run first and post a task that will get
// called immediately afterwards.
ASSERT_EQ(static_cast<size_t>(9), data.called_types.size());
EXPECT_EQ(PostTaskData::PRE_POLL_CALLBACK, data.called_types[0]);
EXPECT_EQ(PostTaskData::TASK_PRE_POLL, data.called_types[1]);
// The timeout that gets called first should post two more tasks, which
// should be run in the order that they were posted.
EXPECT_EQ(PostTaskData::TIMEOUT, data.called_types[2]);
EXPECT_EQ(PostTaskData::TASK_TIMEOUT_A, data.called_types[3]);
EXPECT_EQ(PostTaskData::TASK_REPOSTED_A, data.called_types[4]);
EXPECT_EQ(PostTaskData::TASK_REPOSTED_B, data.called_types[5]);
// The second timeout should post another task, which should also be
// called immediately.
EXPECT_EQ(PostTaskData::TIMEOUT, data.called_types[6]);
EXPECT_EQ(PostTaskData::TASK_TIMEOUT_B, data.called_types[7]);
// When the pre-poll callback is called for a second time, it should exit.
EXPECT_EQ(PostTaskData::PRE_POLL_CALLBACK, data.called_types[8]);
}
} // namespace window_manager
int main(int argc, char** argv) {
if (!EventLoop::IsTimerFdSupported()) {
LOG(ERROR) << "timerfd isn't supported on this system; skipping tests";
return 0;
}
return window_manager::InitAndRunTests(&argc, argv, &FLAGS_logtostderr);
}