diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-06-19 12:01:48 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-06-19 12:01:48 +0000 |
commit | 9063677c3726d766de3c5fc3a2cd3feabd840d08 (patch) | |
tree | 8f6ec66173bb45a55a65c8449f382c2c16b3b13a /brillo/streams/file_stream_test.cc | |
parent | 9e260fa59a43bc04701216c51053922d445fefd8 (diff) | |
parent | 6901182adc9a85d7f514df4d68605a179c195f75 (diff) | |
download | libbrillo-c8fb7718ed046a13722df2c09e11b77a85e8535f.tar.gz |
Snap for 7474514 from 6901182adc9a85d7f514df4d68605a179c195f75 to mainline-media-releaseandroid-mainline-12.0.0_r89android-mainline-12.0.0_r74android-mainline-12.0.0_r62android-mainline-12.0.0_r46android-mainline-12.0.0_r29android-mainline-12.0.0_r12android-mainline-12.0.0_r119android-mainline-12.0.0_r104android12-mainline-media-release
Change-Id: I1756a28b97a568bbb2c02438730597bb2aa59efe
Diffstat (limited to 'brillo/streams/file_stream_test.cc')
-rw-r--r-- | brillo/streams/file_stream_test.cc | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/brillo/streams/file_stream_test.cc b/brillo/streams/file_stream_test.cc new file mode 100644 index 0000000..36bad07 --- /dev/null +++ b/brillo/streams/file_stream_test.cc @@ -0,0 +1,1134 @@ +// 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/streams/file_stream.h> + +#include <sys/stat.h> + +#include <limits> +#include <numeric> +#include <string> +#include <utility> +#include <vector> + +#include <base/bind.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <base/message_loop/message_loop.h> +#include <base/rand_util.h> +#include <base/run_loop.h> +#include <brillo/errors/error_codes.h> +#include <brillo/message_loops/base_message_loop.h> +#include <brillo/message_loops/message_loop_utils.h> +#include <brillo/streams/stream_errors.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using testing::DoAll; +using testing::InSequence; +using testing::Return; +using testing::ReturnArg; +using testing::SaveArg; +using testing::SetErrnoAndReturn; +using testing::_; + +namespace brillo { + +namespace { + +// gmock action that would return a blocking situation from a read() or write(). +ACTION(ReturnWouldBlock) { + errno = EWOULDBLOCK; + return -1; +} + +// Helper function to read one byte from the stream. +inline int ReadByte(Stream* stream) { + uint8_t byte = 0; + return stream->ReadAllBlocking(&byte, sizeof(byte), nullptr) ? byte : -1; +} + +// Helper function to write one byte from the stream. +inline bool WriteByte(Stream* stream, uint8_t byte) { + return stream->WriteAllBlocking(&byte, sizeof(byte), nullptr); +} + +// Helper function to test file stream workflow on newly created file. +void TestCreateFile(Stream* stream) { + ASSERT_TRUE(stream->IsOpen()); + + // Set up a sample data buffer. + std::vector<uint8_t> in_buffer(256); + std::iota(in_buffer.begin(), in_buffer.end(), 0); + + // Initial assumptions about empty file stream. + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(0, stream->GetSize()); + + // Write sample data. + EXPECT_TRUE(stream->WriteAllBlocking(in_buffer.data(), in_buffer.size(), + nullptr)); + EXPECT_EQ(in_buffer.size(), stream->GetPosition()); + EXPECT_EQ(in_buffer.size(), stream->GetSize()); + + // Rewind the stream to the beginning. + uint64_t pos = 0; + EXPECT_TRUE(stream->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(0, pos); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(in_buffer.size(), stream->GetSize()); + + // Read the file contents back. + std::vector<uint8_t> out_buffer(256); + EXPECT_TRUE(stream->ReadAllBlocking(out_buffer.data(), out_buffer.size(), + nullptr)); + EXPECT_EQ(out_buffer.size(), stream->GetPosition()); + EXPECT_EQ(out_buffer.size(), stream->GetSize()); + + // Make sure the data read matches those written. + EXPECT_EQ(in_buffer, out_buffer); + + // Random read/write + EXPECT_TRUE(stream->Seek(10, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(10, pos); + + // Since our data buffer contained values from 0 to 255, the byte at position + // 10 will contain the value of 10. + EXPECT_EQ(10, ReadByte(stream)); + EXPECT_EQ(11, ReadByte(stream)); + EXPECT_EQ(12, ReadByte(stream)); + EXPECT_TRUE(stream->Seek(7, Stream::Whence::FROM_CURRENT, nullptr, nullptr)); + EXPECT_EQ(20, ReadByte(stream)); + + EXPECT_EQ(21, stream->GetPosition()); + EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(19, pos); + EXPECT_TRUE(WriteByte(stream, 100)); + EXPECT_EQ(20, ReadByte(stream)); + EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, nullptr, nullptr)); + EXPECT_EQ(100, ReadByte(stream)); + EXPECT_EQ(20, ReadByte(stream)); + EXPECT_TRUE(stream->Seek(-1, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(255, pos); + EXPECT_EQ(255, ReadByte(stream)); + EXPECT_EQ(-1, ReadByte(stream)); +} + +// Helper functions for base::Bind. +void SetSizeT(size_t* target, size_t source) { + *target = source; +} + +void SetToTrue(bool* target, const Error* /* error */) { + *target = true; +} + +} // anonymous namespace + +// A mock file descriptor wrapper to test low-level file API used by FileStream. +class MockFileDescriptor : public FileStream::FileDescriptorInterface { + public: + MOCK_METHOD(bool, IsOpen, (), (const, override)); + MOCK_METHOD(ssize_t, Read, (void*, size_t), (override)); + MOCK_METHOD(ssize_t, Write, (const void*, size_t), (override)); + MOCK_METHOD(off64_t, Seek, (off64_t, int), (override)); + MOCK_METHOD(mode_t, GetFileMode, (), (const, override)); + MOCK_METHOD(uint64_t, GetSize, (), (const, override)); + MOCK_METHOD(int, Truncate, (off64_t), (const, override)); + MOCK_METHOD(int, Close, (), (override)); + MOCK_METHOD(bool, + WaitForData, + (Stream::AccessMode, const DataCallback&, ErrorPtr*), + (override)); + MOCK_METHOD(int, + WaitForDataBlocking, + (Stream::AccessMode, base::TimeDelta, Stream::AccessMode*), + (override)); + MOCK_METHOD(void, CancelPendingAsyncOperations, (), (override)); +}; + +class FileStreamTest : public testing::Test { + public: + void SetUp() override { + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + } + + MockFileDescriptor& fd_mock() { + return *static_cast<MockFileDescriptor*>(stream_->fd_interface_.get()); + } + + void CreateStream(mode_t file_mode, Stream::AccessMode access_mode) { + std::unique_ptr<MockFileDescriptor> fd{new MockFileDescriptor{}}; + EXPECT_CALL(*fd, GetFileMode()).WillOnce(Return(file_mode)); + stream_.reset(new FileStream(std::move(fd), access_mode)); + EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(true)); + } + + void ExpectStreamClosed(const ErrorPtr& error) const { + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode()); + EXPECT_EQ("Stream is closed", error->GetMessage()); + } + + void ExpectStreamOffsetTooLarge(const ErrorPtr& error) const { + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); + EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); + } + + inline static char* IntToPtr(int addr) { + return reinterpret_cast<char*>(addr); + } + + inline static const char* IntToConstPtr(int addr) { + return reinterpret_cast<const char*>(addr); + } + + bool CallWaitForData(Stream::AccessMode mode, ErrorPtr* error) { + return stream_->WaitForData(mode, {}, error); + } + + std::unique_ptr<FileStream> stream_; + + const uint64_t kMaxSize = std::numeric_limits<int64_t>::max(); + const uint64_t kTooLargeSize = std::numeric_limits<uint64_t>::max(); + + // Dummy buffer pointer values to make sure that input pointer values + // are delegated to the file interface without a change. + char* const test_read_buffer_ = IntToPtr(12345); + const char* const test_write_buffer_ = IntToConstPtr(67890); +}; + +TEST_F(FileStreamTest, IsOpen) { + EXPECT_TRUE(stream_->IsOpen()); + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_FALSE(stream_->IsOpen()); +} + +TEST_F(FileStreamTest, CanRead) { + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanRead()); + EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false)); + EXPECT_FALSE(stream_->CanRead()); + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_TRUE(stream_->CanRead()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_FALSE(stream_->CanRead()); +} + +TEST_F(FileStreamTest, CanWrite) { + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanWrite()); + EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false)); + EXPECT_FALSE(stream_->CanWrite()); + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_FALSE(stream_->CanWrite()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->CanWrite()); +} + +TEST_F(FileStreamTest, CanSeek) { + CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanSeek()); + CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanSeek()); + CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanSeek()); + + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->CanSeek()); +} + +TEST_F(FileStreamTest, CanGetSize) { + CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanGetSize()); + CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanGetSize()); + CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanGetSize()); + + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->CanGetSize()); +} + +TEST_F(FileStreamTest, GetSize) { + EXPECT_CALL(fd_mock(), GetSize()).WillRepeatedly(Return(12345)); + EXPECT_EQ(12345u, stream_->GetSize()); + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_EQ(0u, stream_->GetSize()); +} + +TEST_F(FileStreamTest, SetSizeBlocking) { + EXPECT_CALL(fd_mock(), Truncate(0)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->SetSizeBlocking(0, nullptr)); + + EXPECT_CALL(fd_mock(), Truncate(123)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->SetSizeBlocking(123, nullptr)); + + EXPECT_CALL(fd_mock(), Truncate(kMaxSize)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->SetSizeBlocking(kMaxSize, nullptr)); +} + +TEST_F(FileStreamTest, SetSizeBlocking_Fail) { + brillo::ErrorPtr error; + + EXPECT_CALL(fd_mock(), Truncate(1235)).WillOnce(SetErrnoAndReturn(EIO, -1)); + EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EIO", error->GetCode()); + + error.reset(); + EXPECT_FALSE(stream_->SetSizeBlocking(kTooLargeSize, &error)); + ExpectStreamOffsetTooLarge(error); + + error.reset(); + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error)); + ExpectStreamClosed(error); +} + +TEST_F(FileStreamTest, GetRemainingSize) { + EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(234)); + EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1234)); + EXPECT_EQ(1000u, stream_->GetRemainingSize()); + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(1234)); + EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1000)); + EXPECT_EQ(0u, stream_->GetRemainingSize()); +} + +TEST_F(FileStreamTest, Seek_Set) { + uint64_t pos = 0; + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(0u, pos); + + EXPECT_CALL(fd_mock(), Seek(123456, SEEK_SET)).WillOnce(Return(123456)); + EXPECT_TRUE(stream_->Seek(123456, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(123456u, pos); + + EXPECT_CALL(fd_mock(), Seek(kMaxSize, SEEK_SET)) + .WillRepeatedly(Return(kMaxSize)); + EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, &pos, + nullptr)); + EXPECT_EQ(kMaxSize, pos); + EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, nullptr, + nullptr)); +} + +TEST_F(FileStreamTest, Seek_Cur) { + uint64_t pos = 0; + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(100)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(100u, pos); + + EXPECT_CALL(fd_mock(), Seek(234, SEEK_CUR)).WillOnce(Return(1234)); + EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(1234u, pos); + + EXPECT_CALL(fd_mock(), Seek(-100, SEEK_CUR)).WillOnce(Return(900)); + EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(900u, pos); +} + +TEST_F(FileStreamTest, Seek_End) { + uint64_t pos = 0; + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_END)).WillOnce(Return(1000)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(1000u, pos); + + EXPECT_CALL(fd_mock(), Seek(234, SEEK_END)).WillOnce(Return(10234)); + EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(10234u, pos); + + EXPECT_CALL(fd_mock(), Seek(-100, SEEK_END)).WillOnce(Return(9900)); + EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(9900u, pos); +} + +TEST_F(FileStreamTest, Seek_Fail) { + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)) + .WillOnce(SetErrnoAndReturn(EPIPE, -1)); + EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EPIPE", error->GetCode()); +} + +TEST_F(FileStreamTest, ReadAsync) { + size_t read_size = 0; + bool failed = false; + FileStream::FileDescriptorInterface::DataCallback data_callback; + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)) + .WillOnce(ReturnWouldBlock()); + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_->ReadAsync(test_read_buffer_, 100, + base::Bind(&SetSizeT, &read_size), + base::Bind(&SetToTrue, &failed), + nullptr)); + EXPECT_EQ(0u, read_size); + EXPECT_FALSE(failed); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(83)); + data_callback.Run(Stream::AccessMode::READ); + EXPECT_EQ(83u, read_size); + EXPECT_FALSE(failed); +} + +TEST_F(FileStreamTest, ReadNonBlocking) { + size_t size = 0; + bool eos = false; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) + .WillRepeatedly(ReturnArg<1>()); + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, + nullptr)); + EXPECT_EQ(100u, size); + EXPECT_FALSE(eos); + + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 0, &size, &eos, + nullptr)); + EXPECT_EQ(0u, size); + EXPECT_FALSE(eos); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, + nullptr)); + EXPECT_EQ(0u, size); + EXPECT_TRUE(eos); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, + nullptr)); + EXPECT_EQ(0u, size); + EXPECT_FALSE(eos); +} + +TEST_F(FileStreamTest, ReadNonBlocking_Fail) { + size_t size = 0; + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EACCES, -1)); + EXPECT_FALSE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, nullptr, + &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EACCES", error->GetCode()); +} + +TEST_F(FileStreamTest, ReadBlocking) { + size_t size = 0; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); + EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 100, &size, nullptr)); + EXPECT_EQ(20u, size); + + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)).WillOnce(Return(45)); + } + EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 80, &size, nullptr)); + EXPECT_EQ(45u, size); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 50)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 50, &size, nullptr)); + EXPECT_EQ(0u, size); +} + +TEST_F(FileStreamTest, ReadBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(SetErrnoAndReturn(EBADF, -1)); + } + brillo::ErrorPtr error; + size_t size = 0; + EXPECT_FALSE(stream_->ReadBlocking(test_read_buffer_, 80, &size, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EBADF", error->GetCode()); +} + +TEST_F(FileStreamTest, ReadAllBlocking) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) + .WillOnce(Return(45)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) + .WillOnce(Return(35)); + } + EXPECT_TRUE(stream_->ReadAllBlocking(test_read_buffer_, 100, nullptr)); +} + +TEST_F(FileStreamTest, ReadAllBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) + .WillOnce(Return(0)); + } + brillo::ErrorPtr error; + EXPECT_FALSE(stream_->ReadAllBlocking(test_read_buffer_, 100, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); + EXPECT_EQ("Reading past the end of stream", error->GetMessage()); +} + +TEST_F(FileStreamTest, WriteAsync) { + size_t write_size = 0; + bool failed = false; + FileStream::FileDescriptorInterface::DataCallback data_callback; + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)) + .WillOnce(ReturnWouldBlock()); + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_->WriteAsync(test_write_buffer_, 100, + base::Bind(&SetSizeT, &write_size), + base::Bind(&SetToTrue, &failed), + nullptr)); + EXPECT_EQ(0u, write_size); + EXPECT_FALSE(failed); + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(87)); + data_callback.Run(Stream::AccessMode::WRITE); + EXPECT_EQ(87u, write_size); + EXPECT_FALSE(failed); +} + +TEST_F(FileStreamTest, WriteNonBlocking) { + size_t size = 0; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) + .WillRepeatedly(ReturnArg<1>()); + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + nullptr)); + EXPECT_EQ(100u, size); + + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 0, &size, nullptr)); + EXPECT_EQ(0u, size); + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + nullptr)); + EXPECT_EQ(0u, size); + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + nullptr)); + EXPECT_EQ(0u, size); +} + +TEST_F(FileStreamTest, WriteNonBlocking_Fail) { + size_t size = 0; + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EACCES, -1)); + EXPECT_FALSE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EACCES", error->GetCode()); +} + +TEST_F(FileStreamTest, WriteBlocking) { + size_t size = 0; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20)); + EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 100, &size, nullptr)); + EXPECT_EQ(20u, size); + + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)).WillOnce(Return(45)); + } + EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 80, &size, nullptr)); + EXPECT_EQ(45u, size); + + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(0)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(1)); + } + EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 50, &size, nullptr)); + EXPECT_EQ(1u, size); +} + +TEST_F(FileStreamTest, WriteBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(SetErrnoAndReturn(EBADF, -1)); + } + brillo::ErrorPtr error; + size_t size = 0; + EXPECT_FALSE(stream_->WriteBlocking(test_write_buffer_, 80, &size, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EBADF", error->GetCode()); +} + +TEST_F(FileStreamTest, WriteAllBlocking) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80)) + .WillOnce(Return(45)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) + .WillOnce(Return(35)); + } + EXPECT_TRUE(stream_->WriteAllBlocking(test_write_buffer_, 100, nullptr)); +} + +TEST_F(FileStreamTest, WriteAllBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(SetErrnoAndReturn(EBADF, -1)); + } + brillo::ErrorPtr error; + EXPECT_FALSE(stream_->WriteAllBlocking(test_write_buffer_, 80, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EBADF", error->GetCode()); +} + +TEST_F(FileStreamTest, WaitForDataBlocking_Timeout) { + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(0)); + brillo::ErrorPtr error; + EXPECT_FALSE(stream_->WaitForDataBlocking(Stream::AccessMode::WRITE, {}, + nullptr, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kTimeout, error->GetCode()); +} + +TEST_F(FileStreamTest, FlushBlocking) { + EXPECT_TRUE(stream_->FlushBlocking(nullptr)); +} + +TEST_F(FileStreamTest, CloseBlocking) { + EXPECT_CALL(fd_mock(), Close()).WillOnce(Return(0)); + EXPECT_TRUE(stream_->CloseBlocking(nullptr)); + + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_TRUE(stream_->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, CloseBlocking_Fail) { + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Close()).WillOnce(SetErrnoAndReturn(EFBIG, -1)); + EXPECT_FALSE(stream_->CloseBlocking(&error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EFBIG", error->GetCode()); +} + +TEST_F(FileStreamTest, WaitForData) { + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ, nullptr)); + + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(CallWaitForData(Stream::AccessMode::WRITE, nullptr)); + + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr)); + + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _)) + .WillOnce(Return(false)); + EXPECT_FALSE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr)); +} + +TEST_F(FileStreamTest, CreateTemporary) { + StreamPtr stream = FileStream::CreateTemporary(nullptr); + ASSERT_NE(nullptr, stream.get()); + TestCreateFile(stream.get()); +} + +TEST_F(FileStreamTest, OpenRead) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + std::vector<char> buffer(1024 * 1024); + base::RandBytes(buffer.data(), buffer.size()); + int file_size = buffer.size(); // Stupid base::WriteFile taking "int" size. + ASSERT_EQ(file_size, base::WriteFile(path, buffer.data(), file_size)); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ, + FileStream::Disposition::OPEN_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream.get()); + ASSERT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_FALSE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(buffer.size(), stream->GetSize()); + + std::vector<char> buffer2(buffer.size()); + EXPECT_TRUE(stream->ReadAllBlocking(buffer2.data(), buffer2.size(), nullptr)); + EXPECT_EQ(buffer2, buffer); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, OpenWrite) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + std::vector<char> buffer(1024 * 1024); + base::RandBytes(buffer.data(), buffer.size()); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::WRITE, + FileStream::Disposition::CREATE_ALWAYS, + nullptr); + ASSERT_NE(nullptr, stream.get()); + ASSERT_TRUE(stream->IsOpen()); + EXPECT_FALSE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + + EXPECT_TRUE(stream->WriteAllBlocking(buffer.data(), buffer.size(), nullptr)); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + std::vector<char> buffer2(buffer.size()); + int file_size = buffer2.size(); // Stupid base::ReadFile taking "int" size. + ASSERT_EQ(file_size, base::ReadFile(path, buffer2.data(), file_size)); + EXPECT_EQ(buffer2, buffer); +} + +TEST_F(FileStreamTest, Open_OpenExisting) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::OPEN_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(data.size(), stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_OpenExisting_Fail) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + + ErrorPtr error; + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::OPEN_EXISTING, + &error); + ASSERT_EQ(nullptr, stream.get()); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("ENOENT", error->GetCode()); +} + +TEST_F(FileStreamTest, Open_CreateAlways_New) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_ALWAYS, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_CreateAlways_Existing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_ALWAYS, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_CreateNewOnly_New) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_NEW_ONLY, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_CreateNewOnly_Existing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + ErrorPtr error; + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_NEW_ONLY, + &error); + ASSERT_EQ(nullptr, stream.get()); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EEXIST", error->GetCode()); +} + +TEST_F(FileStreamTest, Open_TruncateExisting_New) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + + ErrorPtr error; + StreamPtr stream = FileStream::Open( + path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::TRUNCATE_EXISTING, + &error); + ASSERT_EQ(nullptr, stream.get()); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("ENOENT", error->GetCode()); +} + +TEST_F(FileStreamTest, Open_TruncateExisting_Existing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + StreamPtr stream = FileStream::Open( + path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::TRUNCATE_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_StdIn) { + StreamPtr stream = + FileStream::FromFileDescriptor(STDIN_FILENO, false, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); +} + +TEST_F(FileStreamTest, FromFileDescriptor_StdOut) { + StreamPtr stream = + FileStream::FromFileDescriptor(STDOUT_FILENO, false, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); +} + +TEST_F(FileStreamTest, FromFileDescriptor_StdErr) { + StreamPtr stream = + FileStream::FromFileDescriptor(STDERR_FILENO, false, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); +} + +TEST_F(FileStreamTest, FromFileDescriptor_ReadNonBlocking) { + int fds[2] = {-1, -1}; + ASSERT_EQ(0, pipe(fds)); + + StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_FALSE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); + + char buf[10]; + size_t read = 0; + bool eos = true; + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(0, read); + EXPECT_FALSE(eos); + + std::string data{"foo_bar"}; + EXPECT_TRUE(base::WriteFileDescriptor(fds[1], data.data(), data.size())); + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(data.size(), read); + EXPECT_FALSE(eos); + EXPECT_EQ(data, (std::string{buf, read})); + + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(0, read); + EXPECT_FALSE(eos); + + close(fds[1]); + + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(0, read); + EXPECT_TRUE(eos); + + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_WriteNonBlocking) { + int fds[2] = {-1, -1}; + ASSERT_EQ(0, pipe(fds)); + + StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_FALSE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); + + // Pipe buffer is generally 64K, so 128K should be more than enough. + std::vector<char> buffer(128 * 1024); + base::RandBytes(buffer.data(), buffer.size()); + size_t written = 0; + size_t total_size = 0; + + // Fill the output buffer of the pipe until we can no longer write any data + // to it. + do { + ASSERT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, + nullptr)); + total_size += written; + } while (written == buffer.size()); + + EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, + nullptr)); + EXPECT_EQ(0, written); + + std::vector<char> out_buffer(total_size); + EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size())); + + EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, + nullptr)); + EXPECT_GT(written, 0); + out_buffer.resize(written); + EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size())); + EXPECT_TRUE(std::equal(out_buffer.begin(), out_buffer.end(), buffer.begin())); + + close(fds[0]); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_ReadAsync) { + int fds[2] = {-1, -1}; + bool succeeded = false; + bool failed = false; + char buffer[100]; + base::MessageLoopForIO base_loop; + BaseMessageLoop brillo_loop{&base_loop}; + brillo_loop.SetAsCurrent(); + + auto success_callback = [](bool* succeeded, char* buffer, size_t size) { + std::string data{buffer, buffer + size}; + ASSERT_EQ("abracadabra", data); + *succeeded = true; + }; + + auto write_data_callback = [](int write_fd) { + std::string data{"abracadabra"}; + EXPECT_TRUE(base::WriteFileDescriptor(write_fd, data.data(), data.size())); + }; + + ASSERT_EQ(0, pipe(fds)); + + StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr); + + // Write to the pipe with a bit of delay. + brillo_loop.PostDelayedTask( + FROM_HERE, + base::Bind(write_data_callback, fds[1]), + base::TimeDelta::FromMilliseconds(10)); + + EXPECT_TRUE(stream->ReadAsync( + buffer, 100, base::Bind(success_callback, &succeeded, buffer), + base::Bind(&SetToTrue, &failed), nullptr)); + + auto end_condition = [](bool* failed, bool* succeeded) { + return *failed || *succeeded; + }; + MessageLoopRunUntil(&brillo_loop, + base::TimeDelta::FromSeconds(1), + base::Bind(end_condition, + base::Unretained(&failed), + base::Unretained(&succeeded))); + + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); + + close(fds[1]); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_WriteAsync) { + int fds[2] = {-1, -1}; + bool succeeded = false; + bool failed = false; + const std::string data{"abracadabra"}; + base::MessageLoopForIO base_loop; + BaseMessageLoop brillo_loop{&base_loop}; + brillo_loop.SetAsCurrent(); + + ASSERT_EQ(0, pipe(fds)); + + auto success_callback = [](bool* succeeded, + const std::string& data, + int read_fd, + size_t /* size */) { + char buffer[100]; + EXPECT_TRUE(base::ReadFromFD(read_fd, buffer, data.size())); + EXPECT_EQ(data, (std::string{buffer, buffer + data.size()})); + *succeeded = true; + }; + + StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr); + + EXPECT_TRUE(stream->WriteAsync( + data.data(), + data.size(), + base::Bind(success_callback, base::Unretained(&succeeded), data, fds[0]), + base::Bind(&SetToTrue, &failed), + nullptr)); + + auto end_condition = [](bool* failed, bool* succeeded) { + return *failed || *succeeded; + }; + MessageLoopRunUntil(&brillo_loop, + base::TimeDelta::FromSeconds(1), + base::Bind(end_condition, + base::Unretained(&failed), + base::Unretained(&succeeded))); + + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); + + close(fds[0]); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +} // namespace brillo |