// Copyright 2016 The Chromium 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 "base/files/file_descriptor_watcher_posix.h"
#include <unistd.h>
#include <memory>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/eintr_wrapper.h"
#include "base/run_loop.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker_impl.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
class Mock {
public:
Mock() = default;
MOCK_METHOD0(ReadableCallback, void());
MOCK_METHOD0(WritableCallback, void());
private:
DISALLOW_COPY_AND_ASSIGN(Mock);
};
enum class FileDescriptorWatcherTestType {
MESSAGE_LOOP_FOR_IO_ON_MAIN_THREAD,
MESSAGE_LOOP_FOR_IO_ON_OTHER_THREAD,
};
class FileDescriptorWatcherTest
: public testing::TestWithParam<FileDescriptorWatcherTestType> {
public:
FileDescriptorWatcherTest()
: message_loop_(GetParam() == FileDescriptorWatcherTestType::
MESSAGE_LOOP_FOR_IO_ON_MAIN_THREAD
? new MessageLoopForIO
: new MessageLoop),
other_thread_("FileDescriptorWatcherTest_OtherThread") {}
~FileDescriptorWatcherTest() override = default;
void SetUp() override {
ASSERT_EQ(0, pipe(pipe_fds_));
MessageLoop* message_loop_for_io;
if (GetParam() ==
FileDescriptorWatcherTestType::MESSAGE_LOOP_FOR_IO_ON_OTHER_THREAD) {
Thread::Options options;
options.message_loop_type = MessageLoop::TYPE_IO;
ASSERT_TRUE(other_thread_.StartWithOptions(options));
message_loop_for_io = other_thread_.message_loop();
} else {
message_loop_for_io = message_loop_.get();
}
ASSERT_TRUE(message_loop_for_io->IsType(MessageLoop::TYPE_IO));
file_descriptor_watcher_ = std::make_unique<FileDescriptorWatcher>(
static_cast<MessageLoopForIO*>(message_loop_for_io));
}
void TearDown() override {
if (GetParam() ==
FileDescriptorWatcherTestType::MESSAGE_LOOP_FOR_IO_ON_MAIN_THREAD &&
message_loop_) {
// Allow the delete task posted by the Controller's destructor to run.
base::RunLoop().RunUntilIdle();
}
// Ensure that OtherThread is done processing before closing fds.
other_thread_.Stop();
EXPECT_EQ(0, IGNORE_EINTR(close(pipe_fds_[0])));
EXPECT_EQ(0, IGNORE_EINTR(close(pipe_fds_[1])));
}
protected:
int read_file_descriptor() const { return pipe_fds_[0]; }
int write_file_descriptor() const { return pipe_fds_[1]; }
// Waits for a short delay and run pending tasks.
void WaitAndRunPendingTasks() {
PlatformThread::Sleep(TestTimeouts::tiny_timeout());
RunLoop().RunUntilIdle();
}
// Registers ReadableCallback() to be called on |mock_| when
// read_file_descriptor() is readable without blocking.
std::unique_ptr<FileDescriptorWatcher::Controller> WatchReadable() {
std::unique_ptr<FileDescriptorWatcher::Controller> controller =
FileDescriptorWatcher::WatchReadable(
read_file_descriptor(),
Bind(&Mock::ReadableCallback, Unretained(&mock_)));
EXPECT_TRUE(controller);
// Unless read_file_descriptor() was readable before the callback was
// registered, this shouldn't do anything.
WaitAndRunPendingTasks();
return controller;
}
// Registers WritableCallback() to be called on |mock_| when
// write_file_descriptor() is writable without blocking.
std::unique_ptr<FileDescriptorWatcher::Controller> WatchWritable() {
std::unique_ptr<FileDescriptorWatcher::Controller> controller =
FileDescriptorWatcher::WatchWritable(
write_file_descriptor(),
Bind(&Mock::WritableCallback, Unretained(&mock_)));
EXPECT_TRUE(controller);
return controller;
}
void WriteByte() {
constexpr char kByte = '!';
ASSERT_TRUE(
WriteFileDescriptor(write_file_descriptor(), &kByte, sizeof(kByte)));
}
void ReadByte() {
// This is always called as part of the WatchReadable() callback, which
// should run on the main thread.
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
char buffer;
ASSERT_TRUE(ReadFromFD(read_file_descriptor(), &buffer, sizeof(buffer)));
}
// Mock on wich callbacks are invoked.
testing::StrictMock<Mock> mock_;
// MessageLoop bound to the main thread.
std::unique_ptr<MessageLoop> message_loop_;
// Thread running a MessageLoopForIO. Used when the test type is
// MESSAGE_LOOP_FOR_IO_ON_OTHER_THREAD.
Thread other_thread_;
private:
// Determines which MessageLoopForIO is used to watch file descriptors for
// which callbacks are registered on the main thread.
std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher_;
// Watched file descriptors.
int pipe_fds_[2];
// Used to verify that callbacks run on the thread on which they are
// registered.
ThreadCheckerImpl thread_checker_;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcherTest);
};
} // namespace
TEST_P(FileDescriptorWatcherTest, WatchWritable) {
auto controller = WatchWritable();
// The write end of a newly created pipe is immediately writable.
RunLoop run_loop;
EXPECT_CALL(mock_, WritableCallback())
.WillOnce(testing::Invoke(&run_loop, &RunLoop::Quit));
run_loop.Run();
}
TEST_P(FileDescriptorWatcherTest, WatchReadableOneByte) {
auto controller = WatchReadable();
// Write 1 byte to the pipe, making it readable without blocking. Expect one
// call to ReadableCallback() which will read 1 byte from the pipe.
WriteByte();
RunLoop run_loop;
EXPECT_CALL(mock_, ReadableCallback())
.WillOnce(testing::Invoke([this, &run_loop]() {
ReadByte();
run_loop.Quit();
}));
run_loop.Run();
testing::Mock::VerifyAndClear(&mock_);
// No more call to ReadableCallback() is expected.
WaitAndRunPendingTasks();
}
TEST_P(FileDescriptorWatcherTest, WatchReadableTwoBytes) {
auto controller = WatchReadable();
// Write 2 bytes to the pipe. Expect two calls to ReadableCallback() which
// will each read 1 byte from the pipe.
WriteByte();
WriteByte();
RunLoop run_loop;
EXPECT_CALL(mock_, ReadableCallback())
.WillOnce(testing::Invoke([this]() { ReadByte(); }))
.WillOnce(testing::Invoke([this, &run_loop]() {
ReadByte();
run_loop.Quit();
}));
run_loop.Run();
testing::Mock::VerifyAndClear(&mock_);
// No more call to ReadableCallback() is expected.
WaitAndRunPendingTasks();
}
TEST_P(FileDescriptorWatcherTest, WatchReadableByteWrittenFromCallback) {
auto controller = WatchReadable();
// Write 1 byte to the pipe. Expect one call to ReadableCallback() from which
// 1 byte is read and 1 byte is written to the pipe. Then, expect another call
// to ReadableCallback() from which the remaining byte is read from the pipe.
WriteByte();
RunLoop run_loop;
EXPECT_CALL(mock_, ReadableCallback())
.WillOnce(testing::Invoke([this]() {
ReadByte();
WriteByte();
}))
.WillOnce(testing::Invoke([this, &run_loop]() {
ReadByte();
run_loop.Quit();
}));
run_loop.Run();
testing::Mock::VerifyAndClear(&mock_);
// No more call to ReadableCallback() is expected.
WaitAndRunPendingTasks();
}
TEST_P(FileDescriptorWatcherTest, DeleteControllerFromCallback) {
auto controller = WatchReadable();
// Write 1 byte to the pipe. Expect one call to ReadableCallback() from which
// |controller| is deleted.
WriteByte();
RunLoop run_loop;
EXPECT_CALL(mock_, ReadableCallback())
.WillOnce(testing::Invoke([&run_loop, &controller]() {
controller = nullptr;
run_loop.Quit();
}));
run_loop.Run();
testing::Mock::VerifyAndClear(&mock_);
// Since |controller| has been deleted, no call to ReadableCallback() is
// expected even though the pipe is still readable without blocking.
WaitAndRunPendingTasks();
}
TEST_P(FileDescriptorWatcherTest,
DeleteControllerBeforeFileDescriptorReadable) {
auto controller = WatchReadable();
// Cancel the watch.
controller = nullptr;
// Write 1 byte to the pipe to make it readable without blocking.
WriteByte();
// No call to ReadableCallback() is expected.
WaitAndRunPendingTasks();
}
TEST_P(FileDescriptorWatcherTest, DeleteControllerAfterFileDescriptorReadable) {
auto controller = WatchReadable();
// Write 1 byte to the pipe to make it readable without blocking.
WriteByte();
// Cancel the watch.
controller = nullptr;
// No call to ReadableCallback() is expected.
WaitAndRunPendingTasks();
}
TEST_P(FileDescriptorWatcherTest, DeleteControllerAfterDeleteMessageLoopForIO) {
auto controller = WatchReadable();
// Delete the MessageLoopForIO.
if (GetParam() ==
FileDescriptorWatcherTestType::MESSAGE_LOOP_FOR_IO_ON_MAIN_THREAD) {
message_loop_ = nullptr;
} else {
other_thread_.Stop();
}
// Deleting |controller| shouldn't crash even though that causes a task to be
// posted to the MessageLoopForIO thread.
controller = nullptr;
}
INSTANTIATE_TEST_CASE_P(
MessageLoopForIOOnMainThread,
FileDescriptorWatcherTest,
::testing::Values(
FileDescriptorWatcherTestType::MESSAGE_LOOP_FOR_IO_ON_MAIN_THREAD));
INSTANTIATE_TEST_CASE_P(
MessageLoopForIOOnOtherThread,
FileDescriptorWatcherTest,
::testing::Values(
FileDescriptorWatcherTestType::MESSAGE_LOOP_FOR_IO_ON_OTHER_THREAD));
} // namespace base