blob: 69682a272d5982420e846735c829691b6099193c [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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/containers/span.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_pump_type.h"
#include "base/posix/eintr_wrapper.h"
#include "base/run_loop.h"
#include "base/test/task_environment.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(const Mock&) = delete;
Mock& operator=(const Mock&) = delete;
MOCK_METHOD0(ReadableCallback, void());
MOCK_METHOD0(WritableCallback, void());
MOCK_METHOD0(ReadableCallback2, void());
MOCK_METHOD0(WritableCallback2, void());
};
enum class FileDescriptorWatcherTestType {
MESSAGE_PUMP_FOR_IO_ON_MAIN_THREAD,
MESSAGE_PUMP_FOR_IO_ON_OTHER_THREAD,
};
class FileDescriptorWatcherTest
: public testing::TestWithParam<FileDescriptorWatcherTestType> {
public:
FileDescriptorWatcherTest()
: task_environment_(std::make_unique<test::TaskEnvironment>(
GetParam() == FileDescriptorWatcherTestType::
MESSAGE_PUMP_FOR_IO_ON_MAIN_THREAD
? test::TaskEnvironment::MainThreadType::IO
: test::TaskEnvironment::MainThreadType::DEFAULT)),
other_thread_("FileDescriptorWatcherTest_OtherThread") {}
FileDescriptorWatcherTest(const FileDescriptorWatcherTest&) = delete;
FileDescriptorWatcherTest& operator=(const FileDescriptorWatcherTest&) =
delete;
~FileDescriptorWatcherTest() override = default;
void SetUp() override {
ASSERT_EQ(0, pipe(pipe_fds_));
ASSERT_EQ(0, pipe(pipe_fds2_));
scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner;
if (GetParam() ==
FileDescriptorWatcherTestType::MESSAGE_PUMP_FOR_IO_ON_OTHER_THREAD) {
Thread::Options options;
options.message_pump_type = MessagePumpType::IO;
ASSERT_TRUE(other_thread_.StartWithOptions(std::move(options)));
file_descriptor_watcher_ =
std::make_unique<FileDescriptorWatcher>(other_thread_.task_runner());
}
}
void TearDown() override {
if (GetParam() ==
FileDescriptorWatcherTestType::MESSAGE_PUMP_FOR_IO_ON_MAIN_THREAD &&
task_environment_) {
// 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])));
// These fds may be destroyed during tests.
if (pipe_fds2_[0] != -1) {
EXPECT_EQ(0, IGNORE_EINTR(close(pipe_fds2_[0])));
}
if (pipe_fds2_[1] != -1) {
EXPECT_EQ(0, IGNORE_EINTR(close(pipe_fds2_[1])));
}
}
protected:
int read_file_descriptor() const { return pipe_fds_[0]; }
int write_file_descriptor() const { return pipe_fds_[1]; }
int read_file_descriptor2() const { return pipe_fds2_[0]; }
int write_file_descriptor2() const { return pipe_fds2_[1]; }
int& read_file_descriptor2_ref() { return pipe_fds2_[0]; }
int& write_file_descriptor2_ref() { return pipe_fds2_[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(),
BindRepeating(&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(),
BindRepeating(&Mock::WritableCallback, Unretained(&mock_)));
EXPECT_TRUE(controller);
return controller;
}
// Registers ReadableCallback2() to be called on |mock_| when
// read_file_descriptor2() is readable without blocking.
std::unique_ptr<FileDescriptorWatcher::Controller> WatchReadable2() {
std::unique_ptr<FileDescriptorWatcher::Controller> controller =
FileDescriptorWatcher::WatchReadable(
read_file_descriptor2(),
BindRepeating(&Mock::ReadableCallback2, 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 WritableCallback2() to be called on |mock_| when
// write_file_descriptor2() is writable without blocking.
std::unique_ptr<FileDescriptorWatcher::Controller> WatchWritable2() {
std::unique_ptr<FileDescriptorWatcher::Controller> controller =
FileDescriptorWatcher::WatchWritable(
write_file_descriptor2(),
BindRepeating(&Mock::WritableCallback2, Unretained(&mock_)));
EXPECT_TRUE(controller);
return controller;
}
void WriteByte() {
constexpr char kByte = '!';
ASSERT_TRUE(WriteFileDescriptor(write_file_descriptor(),
byte_span_from_ref(kByte)));
}
void ReadByte() {
// This is always called in ReadableCallback(), which should run on the main
// thread.
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
char buffer;
ASSERT_TRUE(ReadFromFD(read_file_descriptor(), span_from_ref(buffer)));
}
void WriteByte2() {
constexpr char kByte = '!';
ASSERT_TRUE(WriteFileDescriptor(write_file_descriptor2(),
byte_span_from_ref(kByte)));
}
void ReadByte2() {
// This is always called in ReadableCallback2(), which should run on the
// main thread.
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
char buffer;
ASSERT_TRUE(ReadFromFD(read_file_descriptor2(), span_from_ref(buffer)));
}
void CloseWriteFd2() {
ASSERT_EQ(0, IGNORE_EINTR(close(write_file_descriptor2())));
write_file_descriptor2_ref() = -1;
}
// Mock on wich callbacks are invoked.
testing::StrictMock<Mock> mock_;
// Task environment bound to the main thread.
std::unique_ptr<test::TaskEnvironment> task_environment_;
// Thread running an IO message pump. Used when the test type is
// MESSAGE_PUMP_FOR_IO_ON_OTHER_THREAD.
Thread other_thread_;
private:
// Used to listen for file descriptor events on |other_thread_|. The scoped
// task environment implements a watcher for the main thread case.
std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher_;
// Watched file descriptors.
int pipe_fds_[2];
int pipe_fds2_[2];
// Used to verify that callbacks run on the thread on which they are
// registered.
ThreadCheckerImpl thread_checker_;
};
} // 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, DeleteControllerAfterDeleteMessagePumpForIO) {
auto controller = WatchReadable();
// Delete the task environment.
if (GetParam() ==
FileDescriptorWatcherTestType::MESSAGE_PUMP_FOR_IO_ON_MAIN_THREAD) {
task_environment_.reset();
} else {
other_thread_.Stop();
}
// Deleting |controller| shouldn't crash even though that causes a task to be
// posted to the message pump thread.
controller = nullptr;
}
TEST_P(FileDescriptorWatcherTest,
WatchReadableOneByteAndWatchReadableTwoBytesOnFd2ClosedInTheMiddle) {
auto controller = WatchReadable();
auto controller2 = WatchReadable2();
// Write 1 byte to the pipe 1 and 2 bytes to the pipe 2. The write fd of the
// pipe 2 will be closed before finishing to read 2 bytes. Expect 1 call to
// ReadableCallback() and 2 calls to ReadableCallback2() which will each read
// 1 byte from the pipes.
WriteByte();
WriteByte2();
WriteByte2();
RunLoop run_loop;
EXPECT_CALL(mock_, ReadableCallback())
.WillOnce(testing::Invoke([this, &controller] {
ReadByte();
CloseWriteFd2();
controller.reset();
}));
EXPECT_CALL(mock_, ReadableCallback2())
.WillOnce(testing::Invoke([this] { ReadByte2(); }))
.WillOnce(testing::Invoke([this, &controller2, &run_loop] {
ReadByte2();
controller2.reset();
run_loop.Quit();
}));
run_loop.Run();
testing::Mock::VerifyAndClear(&mock_);
// No more call to ReadableCallback() or ReadableCallback2() is expected.
WaitAndRunPendingTasks();
}
INSTANTIATE_TEST_SUITE_P(
MessagePumpForIOOnMainThread,
FileDescriptorWatcherTest,
::testing::Values(
FileDescriptorWatcherTestType::MESSAGE_PUMP_FOR_IO_ON_MAIN_THREAD));
INSTANTIATE_TEST_SUITE_P(
MessagePumpForIOOnOtherThread,
FileDescriptorWatcherTest,
::testing::Values(
FileDescriptorWatcherTestType::MESSAGE_PUMP_FOR_IO_ON_OTHER_THREAD));
} // namespace base