| // Copyright 2015 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 <brillo/message_loops/message_loop.h> |
| |
| // These are the common tests for all the brillo::MessageLoop implementations |
| // that should conform to this interface's contracts. For extra |
| // implementation-specific tests see the particular implementation unittests in |
| // the *_unittest.cc files. |
| |
| #include <fcntl.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/location.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <gtest/gtest.h> |
| |
| #include <brillo/bind_lambda.h> |
| #include <brillo/message_loops/base_message_loop.h> |
| #include <brillo/message_loops/glib_message_loop.h> |
| #include <brillo/message_loops/message_loop_utils.h> |
| |
| using base::Bind; |
| using base::TimeDelta; |
| |
| namespace { |
| // Helper class to create and close a unidirectional pipe. Used to provide valid |
| // file descriptors when testing watching for a file descriptor. |
| class ScopedPipe { |
| public: |
| // The internal pipe size. |
| static const int kPipeSize; |
| |
| ScopedPipe() { |
| int fds[2]; |
| if (pipe(fds) != 0) { |
| PLOG(FATAL) << "Creating a pipe()"; |
| } |
| reader = fds[0]; |
| writer = fds[1]; |
| EXPECT_EQ(kPipeSize, fcntl(writer, F_SETPIPE_SZ, kPipeSize)); |
| } |
| ~ScopedPipe() { |
| if (reader != -1) |
| close(reader); |
| if (writer != -1) |
| close(writer); |
| } |
| |
| // The reader and writer end of the pipe. |
| int reader{-1}; |
| int writer{-1}; |
| }; |
| |
| const int ScopedPipe::kPipeSize = 4096; |
| |
| class ScopedSocketPair { |
| public: |
| ScopedSocketPair() { |
| int fds[2]; |
| if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) { |
| PLOG(FATAL) << "Creating a socketpair()"; |
| } |
| left = fds[0]; |
| right = fds[1]; |
| } |
| ~ScopedSocketPair() { |
| if (left != -1) |
| close(left); |
| if (right != -1) |
| close(right); |
| } |
| |
| // The left and right sockets are bi-directional connected and |
| // indistinguishable file descriptor. We named them left/right for easier |
| // reading. |
| int left{-1}; |
| int right{-1}; |
| }; |
| } // namespace |
| |
| namespace brillo { |
| |
| using TaskId = MessageLoop::TaskId; |
| |
| template <typename T> |
| class MessageLoopTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| MessageLoopSetUp(); |
| EXPECT_TRUE(this->loop_.get()); |
| } |
| |
| std::unique_ptr<base::MessageLoopForIO> base_loop_; |
| |
| std::unique_ptr<MessageLoop> loop_; |
| |
| private: |
| // These MessageLoopSetUp() methods are used to setup each MessageLoop |
| // according to its constructor requirements. |
| void MessageLoopSetUp(); |
| }; |
| |
| template <> |
| void MessageLoopTest<GlibMessageLoop>::MessageLoopSetUp() { |
| loop_.reset(new GlibMessageLoop()); |
| } |
| |
| template <> |
| void MessageLoopTest<BaseMessageLoop>::MessageLoopSetUp() { |
| base_loop_.reset(new base::MessageLoopForIO()); |
| loop_.reset(new BaseMessageLoop(base::MessageLoopForIO::current())); |
| } |
| |
| // This setups gtest to run each one of the following TYPED_TEST test cases on |
| // on each implementation. |
| typedef ::testing::Types< |
| GlibMessageLoop, |
| BaseMessageLoop> MessageLoopTypes; |
| TYPED_TEST_CASE(MessageLoopTest, MessageLoopTypes); |
| |
| |
| TYPED_TEST(MessageLoopTest, CancelTaskInvalidValuesTest) { |
| EXPECT_FALSE(this->loop_->CancelTask(MessageLoop::kTaskIdNull)); |
| EXPECT_FALSE(this->loop_->CancelTask(1234)); |
| } |
| |
| TYPED_TEST(MessageLoopTest, PostTaskTest) { |
| bool called = false; |
| TaskId task_id = this->loop_->PostTask(FROM_HERE, |
| Bind([&called]() { called = true; })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| MessageLoopRunMaxIterations(this->loop_.get(), 100); |
| EXPECT_TRUE(called); |
| } |
| |
| // Tests that we can cancel tasks right after we schedule them. |
| TYPED_TEST(MessageLoopTest, PostTaskCancelledTest) { |
| bool called = false; |
| TaskId task_id = this->loop_->PostTask(FROM_HERE, |
| Bind([&called]() { called = true; })); |
| EXPECT_TRUE(this->loop_->CancelTask(task_id)); |
| MessageLoopRunMaxIterations(this->loop_.get(), 100); |
| EXPECT_FALSE(called); |
| // Can't remove a task you already removed. |
| EXPECT_FALSE(this->loop_->CancelTask(task_id)); |
| } |
| |
| TYPED_TEST(MessageLoopTest, PostDelayedTaskRunsEventuallyTest) { |
| bool called = false; |
| TaskId task_id = this->loop_->PostDelayedTask( |
| FROM_HERE, |
| Bind([&called]() { called = true; }), |
| TimeDelta::FromMilliseconds(50)); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| MessageLoopRunUntil(this->loop_.get(), |
| TimeDelta::FromSeconds(10), |
| Bind([&called]() { return called; })); |
| // Check that the main loop finished before the 10 seconds timeout, so it |
| // finished due to the callback being called and not due to the timeout. |
| EXPECT_TRUE(called); |
| } |
| |
| // Test that you can call the overloaded version of PostDelayedTask from |
| // MessageLoop. This is important because only one of the two methods is |
| // virtual, so you need to unhide the other when overriding the virtual one. |
| TYPED_TEST(MessageLoopTest, PostDelayedTaskWithoutLocation) { |
| this->loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta()); |
| EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); |
| } |
| |
| TYPED_TEST(MessageLoopTest, WatchForInvalidFD) { |
| bool called = false; |
| EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor( |
| FROM_HERE, -1, MessageLoop::kWatchRead, true, |
| Bind([&called] { called = true; }))); |
| EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor( |
| FROM_HERE, -1, MessageLoop::kWatchWrite, true, |
| Bind([&called] { called = true; }))); |
| EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100)); |
| EXPECT_FALSE(called); |
| } |
| |
| TYPED_TEST(MessageLoopTest, CancelWatchedFileDescriptor) { |
| ScopedPipe pipe; |
| bool called = false; |
| TaskId task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, |
| Bind([&called] { called = true; })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| // The reader end is blocked because we didn't write anything to the writer |
| // end. |
| EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100)); |
| EXPECT_FALSE(called); |
| EXPECT_TRUE(this->loop_->CancelTask(task_id)); |
| } |
| |
| // When you watch a file descriptor for reading, the guaranties are that a |
| // blocking call to read() on that file descriptor will not block. This should |
| // include the case when the other end of a pipe is closed or the file is empty. |
| TYPED_TEST(MessageLoopTest, WatchFileDescriptorTriggersWhenPipeClosed) { |
| ScopedPipe pipe; |
| bool called = false; |
| EXPECT_EQ(0, HANDLE_EINTR(close(pipe.writer))); |
| pipe.writer = -1; |
| TaskId task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, |
| Bind([&called] { called = true; })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| // The reader end is not blocked because we closed the writer end so a read on |
| // the reader end would return 0 bytes read. |
| EXPECT_NE(0, MessageLoopRunMaxIterations(this->loop_.get(), 10)); |
| EXPECT_TRUE(called); |
| EXPECT_TRUE(this->loop_->CancelTask(task_id)); |
| } |
| |
| // When a WatchFileDescriptor task is scheduled with |persistent| = true, we |
| // should keep getting a call whenever the file descriptor is ready. |
| TYPED_TEST(MessageLoopTest, WatchFileDescriptorPersistently) { |
| ScopedPipe pipe; |
| EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1))); |
| |
| int called = 0; |
| TaskId task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, |
| Bind([&called] { called++; })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| // We let the main loop run for 20 iterations to give it enough iterations to |
| // verify that our callback was called more than one. We only check that our |
| // callback is called more than once. |
| EXPECT_EQ(20, MessageLoopRunMaxIterations(this->loop_.get(), 20)); |
| EXPECT_LT(1, called); |
| EXPECT_TRUE(this->loop_->CancelTask(task_id)); |
| } |
| |
| TYPED_TEST(MessageLoopTest, WatchFileDescriptorNonPersistent) { |
| ScopedPipe pipe; |
| EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1))); |
| |
| int called = 0; |
| TaskId task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipe.reader, MessageLoop::kWatchRead, false, |
| Bind([&called] { called++; })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| // We let the main loop run for 20 iterations but we just expect it to run |
| // at least once. The callback should be called exactly once since we |
| // scheduled it non-persistently. After it ran, we shouldn't be able to cancel |
| // this task. |
| EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20)); |
| EXPECT_EQ(1, called); |
| EXPECT_FALSE(this->loop_->CancelTask(task_id)); |
| } |
| |
| TYPED_TEST(MessageLoopTest, WatchFileDescriptorForReadAndWriteSimultaneously) { |
| ScopedSocketPair socks; |
| EXPECT_EQ(1, HANDLE_EINTR(write(socks.right, "a", 1))); |
| // socks.left should be able to read this "a" and should also be able to write |
| // without blocking since the kernel has some buffering for it. |
| |
| TaskId read_task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, socks.left, MessageLoop::kWatchRead, true, |
| Bind([this, &read_task_id] { |
| EXPECT_TRUE(this->loop_->CancelTask(read_task_id)) |
| << "task_id" << read_task_id; |
| })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, read_task_id); |
| |
| TaskId write_task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, socks.left, MessageLoop::kWatchWrite, true, |
| Bind([this, &write_task_id] { |
| EXPECT_TRUE(this->loop_->CancelTask(write_task_id)); |
| })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, write_task_id); |
| |
| EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20)); |
| |
| EXPECT_FALSE(this->loop_->CancelTask(read_task_id)); |
| EXPECT_FALSE(this->loop_->CancelTask(write_task_id)); |
| } |
| |
| // Test that we can cancel the task we are running, and should just fail. |
| TYPED_TEST(MessageLoopTest, DeleteTaskFromSelf) { |
| bool cancel_result = true; // We would expect this to be false. |
| MessageLoop* loop_ptr = this->loop_.get(); |
| TaskId task_id; |
| task_id = this->loop_->PostTask( |
| FROM_HERE, |
| Bind([&cancel_result, loop_ptr, &task_id]() { |
| cancel_result = loop_ptr->CancelTask(task_id); |
| })); |
| EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); |
| EXPECT_FALSE(cancel_result); |
| } |
| |
| // Test that we can cancel a non-persistent file descriptor watching callback, |
| // which should fail. |
| TYPED_TEST(MessageLoopTest, DeleteNonPersistenIOTaskFromSelf) { |
| ScopedPipe pipe; |
| TaskId task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, false /* persistent */, |
| Bind([this, &task_id] { |
| EXPECT_FALSE(this->loop_->CancelTask(task_id)); |
| task_id = MessageLoop::kTaskIdNull; |
| })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); |
| EXPECT_EQ(MessageLoop::kTaskIdNull, task_id); |
| } |
| |
| // Test that we can cancel a persistent file descriptor watching callback from |
| // the same callback. |
| TYPED_TEST(MessageLoopTest, DeletePersistenIOTaskFromSelf) { |
| ScopedPipe pipe; |
| TaskId task_id = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, true /* persistent */, |
| Bind([this, &task_id] { |
| EXPECT_TRUE(this->loop_->CancelTask(task_id)); |
| task_id = MessageLoop::kTaskIdNull; |
| })); |
| EXPECT_NE(MessageLoop::kTaskIdNull, task_id); |
| EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); |
| EXPECT_EQ(MessageLoop::kTaskIdNull, task_id); |
| } |
| |
| // Test that we can cancel several persistent file descriptor watching callbacks |
| // from a scheduled callback. In the BaseMessageLoop implementation, this code |
| // will cause us to cancel an IOTask that has a pending delayed task, but |
| // otherwise is a valid test case on all implementations. |
| TYPED_TEST(MessageLoopTest, DeleteAllPersistenIOTaskFromSelf) { |
| const int kNumTasks = 5; |
| ScopedPipe pipes[kNumTasks]; |
| TaskId task_ids[kNumTasks]; |
| |
| for (int i = 0; i < kNumTasks; ++i) { |
| task_ids[i] = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipes[i].writer, MessageLoop::kWatchWrite, |
| true /* persistent */, |
| Bind([this, kNumTasks, &task_ids] { |
| for (int j = 0; j < kNumTasks; ++j) { |
| // Once we cancel all the tasks, none should run, so this code runs |
| // only once from one callback. |
| EXPECT_TRUE(this->loop_->CancelTask(task_ids[j])); |
| task_ids[j] = MessageLoop::kTaskIdNull; |
| } |
| })); |
| } |
| MessageLoopRunMaxIterations(this->loop_.get(), 100); |
| for (int i = 0; i < kNumTasks; ++i) { |
| EXPECT_EQ(MessageLoop::kTaskIdNull, task_ids[i]); |
| } |
| } |
| |
| // Test that if there are several tasks watching for file descriptors to be |
| // available or simply waiting in the message loop are fairly scheduled to run. |
| // In other words, this test ensures that having a file descriptor always |
| // available doesn't prevent other file descriptors watching tasks or delayed |
| // tasks to be dispatched, causing starvation. |
| TYPED_TEST(MessageLoopTest, AllTasksAreEqual) { |
| int total_calls = 0; |
| |
| // First, schedule a repeating timeout callback to run from the main loop. |
| int timeout_called = 0; |
| base::Closure timeout_callback; |
| MessageLoop::TaskId timeout_task; |
| timeout_callback = base::Bind( |
| [this, &timeout_called, &total_calls, &timeout_callback, &timeout_task] { |
| timeout_called++; |
| total_calls++; |
| timeout_task = this->loop_->PostTask(FROM_HERE, Bind(timeout_callback)); |
| if (total_calls > 100) |
| this->loop_->BreakLoop(); |
| }); |
| timeout_task = this->loop_->PostTask(FROM_HERE, timeout_callback); |
| |
| // Second, schedule several file descriptor watchers. |
| const int kNumTasks = 3; |
| ScopedPipe pipes[kNumTasks]; |
| MessageLoop::TaskId tasks[kNumTasks]; |
| |
| int reads[kNumTasks] = {}; |
| auto fd_callback = [this, &pipes, &reads, &total_calls](int i) { |
| reads[i]++; |
| total_calls++; |
| char c; |
| EXPECT_EQ(1, HANDLE_EINTR(read(pipes[i].reader, &c, 1))); |
| if (total_calls > 100) |
| this->loop_->BreakLoop(); |
| }; |
| |
| for (int i = 0; i < kNumTasks; ++i) { |
| tasks[i] = this->loop_->WatchFileDescriptor( |
| FROM_HERE, pipes[i].reader, MessageLoop::kWatchRead, |
| true /* persistent */, |
| Bind(fd_callback, i)); |
| // Make enough bytes available on each file descriptor. This should not |
| // block because we set the size of the file descriptor buffer when |
| // creating it. |
| std::vector<char> blob(1000, 'a'); |
| EXPECT_EQ(blob.size(), |
| HANDLE_EINTR(write(pipes[i].writer, blob.data(), blob.size()))); |
| } |
| this->loop_->Run(); |
| EXPECT_GT(total_calls, 100); |
| // We run the loop up 100 times and expect each callback to run at least 10 |
| // times. A good scheduler should balance these callbacks. |
| EXPECT_GE(timeout_called, 10); |
| EXPECT_TRUE(this->loop_->CancelTask(timeout_task)); |
| for (int i = 0; i < kNumTasks; ++i) { |
| EXPECT_GE(reads[i], 10) << "Reading from pipes[" << i << "], fd " |
| << pipes[i].reader; |
| EXPECT_TRUE(this->loop_->CancelTask(tasks[i])); |
| } |
| } |
| |
| } // namespace brillo |