| // Copyright (C) 2018 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <sys/stat.h> |
| |
| #include <cstdio> |
| #include <iostream> |
| #include <memory> |
| #include <string_view> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <gtest/gtest.h> |
| #include <libsnapshot/cow_reader.h> |
| #include <libsnapshot/cow_writer.h> |
| |
| using testing::AssertionFailure; |
| using testing::AssertionResult; |
| using testing::AssertionSuccess; |
| |
| namespace android { |
| namespace snapshot { |
| |
| class CowTest : public ::testing::Test { |
| protected: |
| virtual void SetUp() override { |
| cow_ = std::make_unique<TemporaryFile>(); |
| ASSERT_GE(cow_->fd, 0) << strerror(errno); |
| } |
| |
| virtual void TearDown() override { cow_ = nullptr; } |
| |
| std::unique_ptr<TemporaryFile> cow_; |
| }; |
| |
| // Sink that always appends to the end of a string. |
| class StringSink : public IByteSink { |
| public: |
| void* GetBuffer(size_t requested, size_t* actual) override { |
| size_t old_size = stream_.size(); |
| stream_.resize(old_size + requested, '\0'); |
| *actual = requested; |
| return stream_.data() + old_size; |
| } |
| bool ReturnData(void*, size_t) override { return true; } |
| void Reset() { stream_.clear(); } |
| |
| std::string& stream() { return stream_; } |
| |
| private: |
| std::string stream_; |
| }; |
| |
| TEST_F(CowTest, ReadWrite) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| |
| ASSERT_TRUE(writer.AddCopy(10, 20)); |
| ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer.AddZeroBlocks(51, 2)); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| CowHeader header; |
| CowFooter footer; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_TRUE(reader.GetHeader(&header)); |
| ASSERT_TRUE(reader.GetFooter(&footer)); |
| ASSERT_EQ(header.magic, kCowMagicNumber); |
| ASSERT_EQ(header.major_version, kCowVersionMajor); |
| ASSERT_EQ(header.minor_version, kCowVersionMinor); |
| ASSERT_EQ(header.block_size, options.block_size); |
| ASSERT_EQ(footer.op.num_ops, 4); |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowCopyOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 0); |
| ASSERT_EQ(op->new_block, 10); |
| ASSERT_EQ(op->source, 20); |
| |
| StringSink sink; |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 4096); |
| ASSERT_EQ(op->new_block, 50); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| // Note: the zero operation gets split into two blocks. |
| ASSERT_EQ(op->type, kCowZeroOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 0); |
| ASSERT_EQ(op->new_block, 51); |
| ASSERT_EQ(op->source, 0); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowZeroOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 0); |
| ASSERT_EQ(op->new_block, 52); |
| ASSERT_EQ(op->source, 0); |
| |
| iter->Next(); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, ReadWriteXor) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| |
| ASSERT_TRUE(writer.AddCopy(10, 20)); |
| ASSERT_TRUE(writer.AddXorBlocks(50, data.data(), data.size(), 24, 10)); |
| ASSERT_TRUE(writer.AddZeroBlocks(51, 2)); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| CowHeader header; |
| CowFooter footer; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_TRUE(reader.GetHeader(&header)); |
| ASSERT_TRUE(reader.GetFooter(&footer)); |
| ASSERT_EQ(header.magic, kCowMagicNumber); |
| ASSERT_EQ(header.major_version, kCowVersionMajor); |
| ASSERT_EQ(header.minor_version, kCowVersionMinor); |
| ASSERT_EQ(header.block_size, options.block_size); |
| ASSERT_EQ(footer.op.num_ops, 4); |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowCopyOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 0); |
| ASSERT_EQ(op->new_block, 10); |
| ASSERT_EQ(op->source, 20); |
| |
| StringSink sink; |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowXorOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 4096); |
| ASSERT_EQ(op->new_block, 50); |
| ASSERT_EQ(op->source, 98314); // 4096 * 24 + 10 |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| // Note: the zero operation gets split into two blocks. |
| ASSERT_EQ(op->type, kCowZeroOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 0); |
| ASSERT_EQ(op->new_block, 51); |
| ASSERT_EQ(op->source, 0); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowZeroOp); |
| ASSERT_EQ(op->compression, kCowCompressNone); |
| ASSERT_EQ(op->data_length, 0); |
| ASSERT_EQ(op->new_block, 52); |
| ASSERT_EQ(op->source, 0); |
| |
| iter->Next(); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, CompressGz) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| options.compression = "gz"; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| |
| ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| |
| StringSink sink; |
| |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_EQ(op->compression, kCowCompressGz); |
| ASSERT_EQ(op->data_length, 56); // compressed! |
| ASSERT_EQ(op->new_block, 50); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data); |
| |
| iter->Next(); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, ClusterCompressGz) { |
| CowOptions options; |
| options.compression = "gz"; |
| options.cluster_ops = 2; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); |
| |
| std::string data2 = "More data!"; |
| data2.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer.AddRawBlocks(51, data2.data(), data2.size())); |
| |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| |
| StringSink sink; |
| |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_EQ(op->compression, kCowCompressGz); |
| ASSERT_EQ(op->data_length, 56); // compressed! |
| ASSERT_EQ(op->new_block, 50); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowClusterOp); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| sink.Reset(); |
| ASSERT_EQ(op->compression, kCowCompressGz); |
| ASSERT_EQ(op->data_length, 41); // compressed! |
| ASSERT_EQ(op->new_block, 51); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data2); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| |
| ASSERT_EQ(op->type, kCowClusterOp); |
| |
| iter->Next(); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, CompressTwoBlocks) { |
| CowOptions options; |
| options.compression = "gz"; |
| options.cluster_ops = 0; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size * 2, '\0'); |
| |
| ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| ASSERT_FALSE(iter->Done()); |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| |
| StringSink sink; |
| |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_EQ(op->compression, kCowCompressGz); |
| ASSERT_EQ(op->new_block, 51); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| } |
| |
| // Only return 1-byte buffers, to stress test the partial read logic in |
| // CowReader. |
| class HorribleStringSink : public StringSink { |
| public: |
| void* GetBuffer(size_t, size_t* actual) override { return StringSink::GetBuffer(1, actual); } |
| }; |
| |
| class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {}; |
| |
| TEST_P(CompressionTest, HorribleSink) { |
| CowOptions options; |
| options.compression = GetParam(); |
| options.cluster_ops = 0; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| |
| ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| ASSERT_FALSE(iter->Done()); |
| |
| HorribleStringSink sink; |
| auto op = &iter->Get(); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(CowApi, CompressionTest, testing::Values("none", "gz", "brotli")); |
| |
| TEST_F(CowTest, GetSize) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| CowWriter writer(options); |
| if (ftruncate(cow_->fd, 0) < 0) { |
| perror("Fails to set temp file size"); |
| FAIL(); |
| } |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| |
| ASSERT_TRUE(writer.AddCopy(10, 20)); |
| ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer.AddZeroBlocks(51, 2)); |
| auto size_before = writer.GetCowSize(); |
| ASSERT_TRUE(writer.Finalize()); |
| auto size_after = writer.GetCowSize(); |
| ASSERT_EQ(size_before, size_after); |
| struct stat buf; |
| |
| ASSERT_GE(fstat(cow_->fd, &buf), 0) << strerror(errno); |
| ASSERT_EQ(buf.st_size, writer.GetCowSize()); |
| } |
| |
| TEST_F(CowTest, AppendLabelSmall) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer->AddLabel(3)); |
| ASSERT_TRUE(writer->Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 3)); |
| |
| std::string data2 = "More data!"; |
| data2.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); |
| ASSERT_TRUE(writer->Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(cow_->fd, &buf), 0); |
| ASSERT_EQ(buf.st_size, writer->GetCowSize()); |
| |
| // Read back both operations, and label. |
| CowReader reader; |
| uint64_t label; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_TRUE(reader.GetLastLabel(&label)); |
| ASSERT_EQ(label, 3); |
| |
| StringSink sink; |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data); |
| |
| iter->Next(); |
| sink.Reset(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 3); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data2); |
| |
| iter->Next(); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, AppendLabelMissing) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer->AddLabel(0)); |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer->AddLabel(1)); |
| // Drop the tail end of the last op header, corrupting it. |
| ftruncate(cow_->fd, writer->GetCowSize() - sizeof(CowFooter) - 3); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_FALSE(writer->InitializeAppend(cow_->fd, 1)); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 0)); |
| |
| ASSERT_TRUE(writer->AddZeroBlocks(51, 1)); |
| ASSERT_TRUE(writer->Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(cow_->fd, &buf), 0); |
| ASSERT_EQ(buf.st_size, writer->GetCowSize()); |
| |
| // Read back both operations. |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| StringSink sink; |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 0); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowZeroOp); |
| |
| iter->Next(); |
| |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, AppendExtendedCorrupted) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer->AddLabel(5)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size * 2, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer->AddLabel(6)); |
| |
| // fail to write the footer. Cow Format does not know if Label 6 is valid |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| // Get the last known good label |
| CowReader label_reader; |
| uint64_t label; |
| ASSERT_TRUE(label_reader.Parse(cow_->fd, {5})); |
| ASSERT_TRUE(label_reader.GetLastLabel(&label)); |
| ASSERT_EQ(label, 5); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 5)); |
| |
| ASSERT_TRUE(writer->Finalize()); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(cow_->fd, &buf), 0); |
| ASSERT_EQ(buf.st_size, writer->GetCowSize()); |
| |
| // Read back all valid operations |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| StringSink sink; |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 5); |
| |
| iter->Next(); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, AppendbyLabel) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size * 2, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); |
| |
| ASSERT_TRUE(writer->AddLabel(4)); |
| |
| ASSERT_TRUE(writer->AddZeroBlocks(50, 2)); |
| |
| ASSERT_TRUE(writer->AddLabel(5)); |
| |
| ASSERT_TRUE(writer->AddCopy(5, 6)); |
| |
| ASSERT_TRUE(writer->AddLabel(6)); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_FALSE(writer->InitializeAppend(cow_->fd, 12)); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 5)); |
| |
| // This should drop label 6 |
| ASSERT_TRUE(writer->Finalize()); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(cow_->fd, &buf), 0); |
| ASSERT_EQ(buf.st_size, writer->GetCowSize()); |
| |
| // Read back all ops |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| StringSink sink; |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data.substr(0, options.block_size)); |
| |
| iter->Next(); |
| sink.Reset(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data.substr(options.block_size, 2 * options.block_size)); |
| |
| iter->Next(); |
| sink.Reset(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 4); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowZeroOp); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowZeroOp); |
| |
| iter->Next(); |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 5); |
| |
| iter->Next(); |
| |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, ClusterTest) { |
| CowOptions options; |
| options.cluster_ops = 4; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); |
| |
| ASSERT_TRUE(writer->AddLabel(4)); |
| |
| ASSERT_TRUE(writer->AddZeroBlocks(50, 2)); // Cluster split in middle |
| |
| ASSERT_TRUE(writer->AddLabel(5)); |
| |
| ASSERT_TRUE(writer->AddCopy(5, 6)); |
| |
| // Cluster split |
| |
| ASSERT_TRUE(writer->AddLabel(6)); |
| |
| ASSERT_TRUE(writer->Finalize()); // No data for cluster, so no cluster split needed |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| // Read back all ops |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| StringSink sink; |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data.substr(0, options.block_size)); |
| |
| iter->Next(); |
| sink.Reset(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 4); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowZeroOp); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowClusterOp); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowZeroOp); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 5); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowCopyOp); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowClusterOp); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 6); |
| |
| iter->Next(); |
| |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, ClusterAppendTest) { |
| CowOptions options; |
| options.cluster_ops = 3; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer->AddLabel(50)); |
| ASSERT_TRUE(writer->Finalize()); // Adds a cluster op, should be dropped on append |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 50)); |
| |
| std::string data2 = "More data!"; |
| data2.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); |
| ASSERT_TRUE(writer->Finalize()); // Adds a cluster op |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(cow_->fd, &buf), 0); |
| ASSERT_EQ(buf.st_size, writer->GetCowSize()); |
| |
| // Read back both operations, plus cluster op at end |
| CowReader reader; |
| uint64_t label; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_TRUE(reader.GetLastLabel(&label)); |
| ASSERT_EQ(label, 50); |
| |
| StringSink sink; |
| |
| auto iter = reader.GetOpIter(); |
| ASSERT_NE(iter, nullptr); |
| |
| ASSERT_FALSE(iter->Done()); |
| auto op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowLabelOp); |
| ASSERT_EQ(op->source, 50); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowReplaceOp); |
| ASSERT_TRUE(reader.ReadData(*op, &sink)); |
| ASSERT_EQ(sink.stream(), data2); |
| |
| iter->Next(); |
| |
| ASSERT_FALSE(iter->Done()); |
| op = &iter->Get(); |
| ASSERT_EQ(op->type, kCowClusterOp); |
| |
| iter->Next(); |
| |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, AppendAfterFinalize) { |
| CowOptions options; |
| options.cluster_ops = 0; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); |
| ASSERT_TRUE(writer->AddLabel(3)); |
| ASSERT_TRUE(writer->Finalize()); |
| |
| std::string data2 = "More data!"; |
| data2.resize(options.block_size, '\0'); |
| ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); |
| ASSERT_TRUE(writer->Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| // COW should be valid. |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| } |
| |
| AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) { |
| data.resize(writer->options().block_size, '\0'); |
| if (!writer->AddRawBlocks(new_block, data.data(), data.size())) { |
| return AssertionFailure() << "Failed to add raw block"; |
| } |
| return AssertionSuccess(); |
| } |
| |
| AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op, |
| const std::string& data) { |
| CowHeader header; |
| reader->GetHeader(&header); |
| |
| std::string cmp = data; |
| cmp.resize(header.block_size, '\0'); |
| |
| StringSink sink; |
| if (!reader->ReadData(op, &sink)) { |
| return AssertionFailure() << "Failed to read data block"; |
| } |
| if (cmp != sink.stream()) { |
| return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got " |
| << sink.stream(); |
| } |
| |
| return AssertionSuccess(); |
| } |
| |
| TEST_F(CowTest, ResumeMidCluster) { |
| CowOptions options; |
| options.cluster_ops = 7; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); |
| ASSERT_TRUE(writer->AddLabel(1)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); |
| ASSERT_TRUE(writer->AddLabel(2)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| size_t num_replace = 0; |
| size_t max_in_cluster = 0; |
| size_t num_in_cluster = 0; |
| size_t num_clusters = 0; |
| while (!iter->Done()) { |
| const auto& op = iter->Get(); |
| |
| num_in_cluster++; |
| max_in_cluster = std::max(max_in_cluster, num_in_cluster); |
| |
| if (op.type == kCowReplaceOp) { |
| num_replace++; |
| |
| ASSERT_EQ(op.new_block, num_replace); |
| ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace))); |
| } else if (op.type == kCowClusterOp) { |
| num_in_cluster = 0; |
| num_clusters++; |
| } |
| |
| iter->Next(); |
| } |
| ASSERT_EQ(num_replace, 8); |
| ASSERT_EQ(max_in_cluster, 7); |
| ASSERT_EQ(num_clusters, 2); |
| } |
| |
| TEST_F(CowTest, ResumeEndCluster) { |
| CowOptions options; |
| int cluster_ops = 5; |
| options.cluster_ops = cluster_ops; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); |
| ASSERT_TRUE(writer->AddLabel(1)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); |
| ASSERT_TRUE(writer->AddLabel(2)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| size_t num_replace = 0; |
| size_t max_in_cluster = 0; |
| size_t num_in_cluster = 0; |
| size_t num_clusters = 0; |
| while (!iter->Done()) { |
| const auto& op = iter->Get(); |
| |
| num_in_cluster++; |
| max_in_cluster = std::max(max_in_cluster, num_in_cluster); |
| |
| if (op.type == kCowReplaceOp) { |
| num_replace++; |
| |
| ASSERT_EQ(op.new_block, num_replace); |
| ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace))); |
| } else if (op.type == kCowClusterOp) { |
| num_in_cluster = 0; |
| num_clusters++; |
| } |
| |
| iter->Next(); |
| } |
| ASSERT_EQ(num_replace, 8); |
| ASSERT_EQ(max_in_cluster, cluster_ops); |
| ASSERT_EQ(num_clusters, 3); |
| } |
| |
| TEST_F(CowTest, DeleteMidCluster) { |
| CowOptions options; |
| options.cluster_ops = 7; |
| auto writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); |
| ASSERT_TRUE(writer->AddLabel(1)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); |
| ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| |
| auto iter = reader.GetOpIter(); |
| size_t num_replace = 0; |
| size_t max_in_cluster = 0; |
| size_t num_in_cluster = 0; |
| size_t num_clusters = 0; |
| while (!iter->Done()) { |
| const auto& op = iter->Get(); |
| |
| num_in_cluster++; |
| max_in_cluster = std::max(max_in_cluster, num_in_cluster); |
| if (op.type == kCowReplaceOp) { |
| num_replace++; |
| |
| ASSERT_EQ(op.new_block, num_replace); |
| ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace))); |
| } else if (op.type == kCowClusterOp) { |
| num_in_cluster = 0; |
| num_clusters++; |
| } |
| |
| iter->Next(); |
| } |
| ASSERT_EQ(num_replace, 3); |
| ASSERT_EQ(max_in_cluster, 5); // 3 data, 1 label, 1 cluster op |
| ASSERT_EQ(num_clusters, 1); |
| } |
| |
| TEST_F(CowTest, BigSeqOp) { |
| CowOptions options; |
| CowWriter writer(options); |
| const int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1; |
| uint32_t sequence[seq_len]; |
| for (int i = 0; i < seq_len; i++) { |
| sequence[i] = i + 1; |
| } |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence)); |
| ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len)); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| auto iter = reader.GetRevMergeOpIter(); |
| |
| for (int i = 0; i < seq_len; i++) { |
| ASSERT_TRUE(!iter->Done()); |
| const auto& op = iter->Get(); |
| |
| ASSERT_EQ(op.new_block, seq_len - i); |
| |
| iter->Next(); |
| } |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, MissingSeqOp) { |
| CowOptions options; |
| CowWriter writer(options); |
| const int seq_len = 10; |
| uint32_t sequence[seq_len]; |
| for (int i = 0; i < seq_len; i++) { |
| sequence[i] = i + 1; |
| } |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence)); |
| ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len - 1)); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_FALSE(reader.Parse(cow_->fd)); |
| } |
| |
| TEST_F(CowTest, ResumeSeqOp) { |
| CowOptions options; |
| auto writer = std::make_unique<CowWriter>(options); |
| const int seq_len = 10; |
| uint32_t sequence[seq_len]; |
| for (int i = 0; i < seq_len; i++) { |
| sequence[i] = i + 1; |
| } |
| |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence)); |
| ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2)); |
| ASSERT_TRUE(writer->AddLabel(1)); |
| ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, 1)); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| auto reader = std::make_unique<CowReader>(); |
| ASSERT_TRUE(reader->Parse(cow_->fd, 1)); |
| auto itr = reader->GetRevMergeOpIter(); |
| ASSERT_TRUE(itr->Done()); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); |
| ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2)); |
| ASSERT_TRUE(writer->Finalize()); |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| reader = std::make_unique<CowReader>(); |
| ASSERT_TRUE(reader->Parse(cow_->fd)); |
| |
| auto iter = reader->GetRevMergeOpIter(); |
| |
| uint64_t expected_block = 10; |
| while (!iter->Done() && expected_block > 0) { |
| ASSERT_FALSE(iter->Done()); |
| const auto& op = iter->Get(); |
| |
| ASSERT_EQ(op.new_block, expected_block); |
| |
| iter->Next(); |
| expected_block--; |
| } |
| ASSERT_EQ(expected_block, 0); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, RevMergeOpItrTest) { |
| CowOptions options; |
| options.cluster_ops = 5; |
| options.num_merge_ops = 1; |
| CowWriter writer(options); |
| uint32_t sequence[] = {2, 10, 6, 7, 3, 5}; |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer.AddSequenceData(6, sequence)); |
| ASSERT_TRUE(writer.AddCopy(6, 13)); |
| ASSERT_TRUE(writer.AddZeroBlocks(12, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(8, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(11, 1)); |
| ASSERT_TRUE(writer.AddCopy(3, 15)); |
| ASSERT_TRUE(writer.AddCopy(2, 11)); |
| ASSERT_TRUE(writer.AddZeroBlocks(4, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(9, 1)); |
| ASSERT_TRUE(writer.AddCopy(5, 16)); |
| ASSERT_TRUE(writer.AddZeroBlocks(1, 1)); |
| ASSERT_TRUE(writer.AddCopy(10, 12)); |
| ASSERT_TRUE(writer.AddCopy(7, 14)); |
| ASSERT_TRUE(writer.Finalize()); |
| |
| // New block in cow order is 6, 12, 8, 11, 3, 2, 4, 9, 5, 1, 10, 7 |
| // New block in merge order is 2, 10, 6, 7, 3, 5, 12, 11, 9, 8, 4, 1 |
| // RevMergeOrder is 1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10, 2 |
| // new block 2 is "already merged", so will be left out. |
| |
| std::vector<uint64_t> revMergeOpSequence = {1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10}; |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| auto iter = reader.GetRevMergeOpIter(); |
| auto expected_new_block = revMergeOpSequence.begin(); |
| |
| while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) { |
| const auto& op = iter->Get(); |
| |
| ASSERT_EQ(op.new_block, *expected_new_block); |
| |
| iter->Next(); |
| expected_new_block++; |
| } |
| ASSERT_EQ(expected_new_block, revMergeOpSequence.end()); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, LegacyRevMergeOpItrTest) { |
| CowOptions options; |
| options.cluster_ops = 5; |
| options.num_merge_ops = 1; |
| CowWriter writer(options); |
| |
| ASSERT_TRUE(writer.Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer.AddCopy(2, 11)); |
| ASSERT_TRUE(writer.AddCopy(10, 12)); |
| ASSERT_TRUE(writer.AddCopy(6, 13)); |
| ASSERT_TRUE(writer.AddCopy(7, 14)); |
| ASSERT_TRUE(writer.AddCopy(3, 15)); |
| ASSERT_TRUE(writer.AddCopy(5, 16)); |
| ASSERT_TRUE(writer.AddZeroBlocks(12, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(8, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(11, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(4, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(9, 1)); |
| ASSERT_TRUE(writer.AddZeroBlocks(1, 1)); |
| |
| ASSERT_TRUE(writer.Finalize()); |
| |
| // New block in cow order is 2, 10, 6, 7, 3, 5, 12, 8, 11, 4, 9, 1 |
| // New block in merge order is 2, 10, 6, 7, 3, 5, 12, 11, 9, 8, 4, 1 |
| // RevMergeOrder is 1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10, 2 |
| // new block 2 is "already merged", so will be left out. |
| |
| std::vector<uint64_t> revMergeOpSequence = {1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10}; |
| |
| ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); |
| |
| CowReader reader; |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| auto iter = reader.GetRevMergeOpIter(); |
| auto expected_new_block = revMergeOpSequence.begin(); |
| |
| while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) { |
| const auto& op = iter->Get(); |
| |
| ASSERT_EQ(op.new_block, *expected_new_block); |
| |
| iter->Next(); |
| expected_new_block++; |
| } |
| ASSERT_EQ(expected_new_block, revMergeOpSequence.end()); |
| ASSERT_TRUE(iter->Done()); |
| } |
| |
| TEST_F(CowTest, InvalidMergeOrderTest) { |
| CowOptions options; |
| options.cluster_ops = 5; |
| options.num_merge_ops = 1; |
| std::string data = "This is some data, believe it"; |
| data.resize(options.block_size, '\0'); |
| auto writer = std::make_unique<CowWriter>(options); |
| CowReader reader; |
| |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| |
| ASSERT_TRUE(writer->AddCopy(3, 2)); |
| ASSERT_TRUE(writer->AddCopy(2, 1)); |
| ASSERT_TRUE(writer->AddLabel(1)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_TRUE(reader.VerifyMergeOps()); |
| |
| ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); |
| ASSERT_TRUE(writer->AddCopy(4, 2)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_FALSE(reader.VerifyMergeOps()); |
| |
| writer = std::make_unique<CowWriter>(options); |
| ASSERT_TRUE(writer->Initialize(cow_->fd)); |
| ASSERT_TRUE(writer->AddCopy(2, 1)); |
| ASSERT_TRUE(writer->AddXorBlocks(3, &data, data.size(), 1, 1)); |
| ASSERT_TRUE(writer->Finalize()); |
| ASSERT_TRUE(reader.Parse(cow_->fd)); |
| ASSERT_FALSE(reader.VerifyMergeOps()); |
| } |
| |
| } // namespace snapshot |
| } // namespace android |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |