| // 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 "bsdiff/extents_file.h" |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <string> |
| #include <vector> |
| |
| #include "bsdiff/file_interface.h" |
| |
| using std::vector; |
| using testing::AnyNumber; |
| using testing::InSequence; |
| using testing::Return; |
| using testing::StrictMock; |
| using testing::_; |
| |
| namespace bsdiff { |
| |
| // Mock class for the underlying file interface. |
| class MockFile : public FileInterface { |
| public: |
| MOCK_METHOD3(Read, bool(void*, size_t, size_t*)); |
| MOCK_METHOD3(Write, bool(const void*, size_t, size_t*)); |
| MOCK_METHOD1(Seek, bool(off_t)); |
| MOCK_METHOD0(Close, bool()); |
| MOCK_METHOD1(GetSize, bool(uint64_t*)); |
| }; |
| |
| ACTION(SucceedIO) { |
| // Check that arg1 (count) can be converted |
| *arg2 = arg1; |
| return true; |
| } |
| |
| ACTION_P(SucceedPartialIO, bytes) { |
| // Check that arg1 (count) can be converted |
| *arg2 = bytes; |
| return true; |
| } |
| |
| class ExtentsFileTest : public testing::Test { |
| protected: |
| void SetUp() { |
| mock_file_ = new StrictMock<MockFile>(); |
| mock_file_ptr_.reset(mock_file_); |
| // The destructor of the ExtentsFile will call Close once. |
| EXPECT_CALL(*mock_file_, Close()).WillOnce(Return(true)); |
| } |
| |
| // Pointer to the underlying File owned by the ExtentsFile under test. This |
| // pointer is invalidated whenever the ExtentsFile is destroyed. |
| StrictMock<MockFile>* mock_file_; |
| std::unique_ptr<FileInterface> mock_file_ptr_; |
| }; |
| |
| TEST_F(ExtentsFileTest, DestructorCloses) { |
| ExtentsFile file(std::move(mock_file_ptr_), {}); |
| } |
| |
| TEST_F(ExtentsFileTest, CloseIsForwarded) { |
| ExtentsFile file(std::move(mock_file_ptr_), {}); |
| EXPECT_TRUE(file.Close()); |
| EXPECT_CALL(*mock_file_, Close()).WillOnce(Return(false)); |
| } |
| |
| TEST_F(ExtentsFileTest, GetSizeSumExtents) { |
| ExtentsFile file(std::move(mock_file_ptr_), |
| {ex_t{10, 5}, ex_t{20, 5}, {25, 2}}); |
| uint64_t size; |
| EXPECT_TRUE(file.GetSize(&size)); |
| EXPECT_EQ(12U, size); |
| } |
| |
| TEST_F(ExtentsFileTest, SeekToRightOffsets) { |
| ExtentsFile file(std::move(mock_file_ptr_), |
| {ex_t{10, 5}, ex_t{20, 5}, {25, 2}}); |
| vector<std::pair<off_t, off_t>> tests = { |
| // Seek to the beginning of the file. |
| {0, 10}, |
| // Seek to the middle of a extent. |
| {3, 13}, |
| {11, 26}, |
| // Seek to the extent boundary. |
| {5, 20}, // Seeks to the first byte in the second extent. |
| {10, 25}, |
| }; |
| for (const auto& offset_pair : tests) { |
| // We use a failing Read() call to trigger the actual seek call to the |
| // underlying file. |
| EXPECT_CALL(*mock_file_, Seek(offset_pair.second)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(_, _, _)).WillOnce(Return(false)); |
| |
| EXPECT_TRUE(file.Seek(offset_pair.first)); |
| size_t bytes_read; |
| EXPECT_FALSE(file.Read(nullptr, 1, &bytes_read)); |
| } |
| |
| // Seeking to the end of the file is ok, but not past it. |
| EXPECT_TRUE(file.Seek(12)); |
| EXPECT_FALSE(file.Seek(13)); |
| |
| EXPECT_FALSE(file.Seek(-1)); |
| } |
| |
| TEST_F(ExtentsFileTest, ReadAcrossAllExtents) { |
| ExtentsFile file(std::move(mock_file_ptr_), |
| {ex_t{10, 5}, ex_t{20, 7}, {27, 3}}); |
| InSequence s; |
| char* buf = reinterpret_cast<char*>(0x1234); |
| |
| EXPECT_CALL(*mock_file_, Seek(10)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf, 5, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(20)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf + 5, 7, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(27)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf + 12, 3, _)).WillOnce(SucceedIO()); |
| |
| // FileExtents::Read() should read everything in one shot, by reading all |
| // the little chunks. Note that it doesn't attempt to read past the end of the |
| // FileExtents. |
| size_t bytes_read = 0; |
| EXPECT_TRUE(file.Read(buf, 100, &bytes_read)); |
| EXPECT_EQ(15U, bytes_read); |
| } |
| |
| TEST_F(ExtentsFileTest, MultiReadAcrossAllExtents) { |
| ExtentsFile file(std::move(mock_file_ptr_), |
| {ex_t{10, 5}, ex_t{20, 7}, {27, 3}}); |
| InSequence s; |
| char* buf = reinterpret_cast<char*>(0x1234); |
| |
| EXPECT_CALL(*mock_file_, Seek(10)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf, 2, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(12)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf, 3, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(20)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf + 3, 5, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(25)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf, 2, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(27)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf + 2, 3, _)).WillOnce(SucceedIO()); |
| |
| size_t bytes_read = 0; |
| EXPECT_TRUE(file.Read(buf, 2, &bytes_read)); |
| EXPECT_EQ(2U, bytes_read); |
| EXPECT_TRUE(file.Read(buf, 8, &bytes_read)); |
| EXPECT_EQ(8U, bytes_read); |
| EXPECT_TRUE(file.Read(buf, 100, &bytes_read)); |
| EXPECT_EQ(5U, bytes_read); |
| } |
| |
| TEST_F(ExtentsFileTest, ReadSmallChunks) { |
| ExtentsFile file(std::move(mock_file_ptr_), {ex_t{10, 1}, ex_t{20, 10}}); |
| InSequence s; |
| char* buf = reinterpret_cast<char*>(0x1234); |
| |
| EXPECT_CALL(*mock_file_, Seek(10)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(buf, 1, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Seek(20)).WillOnce(Return(true)); |
| // We expect to read only part of the second extent. |
| EXPECT_CALL(*mock_file_, Read(buf + 1, 1, _)).WillOnce(SucceedIO()); |
| |
| size_t bytes_read = 0; |
| EXPECT_TRUE(file.Read(buf, 2, &bytes_read)); |
| EXPECT_EQ(2U, bytes_read); |
| } |
| |
| TEST_F(ExtentsFileTest, ReadFailureFails) { |
| ExtentsFile file(std::move(mock_file_ptr_), {ex_t{10, 1}, ex_t{20, 10}}); |
| EXPECT_CALL(*mock_file_, Seek(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(_, 1, _)).WillOnce(SucceedIO()); |
| // A second read that fails will succeed if there was partial data read. |
| EXPECT_CALL(*mock_file_, Read(_, 10, _)).WillOnce(Return(false)); |
| |
| char* buf = reinterpret_cast<char*>(0x1234); |
| size_t bytes_read = 0; |
| EXPECT_TRUE(file.Read(buf, 100, &bytes_read)); |
| EXPECT_EQ(1U, bytes_read); |
| } |
| |
| TEST_F(ExtentsFileTest, ReadFails) { |
| ExtentsFile file(std::move(mock_file_ptr_), {ex_t{10, 1}, ex_t{20, 10}}); |
| EXPECT_CALL(*mock_file_, Seek(10)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_file_, Read(_, 1, _)).WillOnce(Return(false)); |
| size_t bytes_read; |
| EXPECT_FALSE(file.Read(nullptr, 1, &bytes_read)); |
| } |
| |
| TEST_F(ExtentsFileTest, ReadPartialReadsAndEOF) { |
| ExtentsFile file(std::move(mock_file_ptr_), {ex_t{10, 1}, ex_t{20, 10}}); |
| EXPECT_CALL(*mock_file_, Seek(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(true)); |
| char* buf = reinterpret_cast<char*>(0x1234); |
| InSequence s; |
| EXPECT_CALL(*mock_file_, Read(buf, 1, _)).WillOnce(SucceedIO()); |
| EXPECT_CALL(*mock_file_, Read(buf + 1, _, _)).WillOnce(SucceedPartialIO(3)); |
| EXPECT_CALL(*mock_file_, Read(buf + 4, _, _)).WillOnce(SucceedPartialIO(0)); |
| |
| size_t bytes_read = 0; |
| EXPECT_TRUE(file.Read(buf, 100, &bytes_read)); |
| EXPECT_EQ(4U, bytes_read); |
| } |
| |
| } // namespace bsdiff |