| // Copyright 2017 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 <numeric> |
| |
| #include "gtest/gtest.h" |
| |
| #include "puffin/src/extent_stream.h" |
| #include "puffin/src/file_stream.h" |
| #include "puffin/src/include/puffin/huffer.h" |
| #include "puffin/src/include/puffin/puffer.h" |
| #include "puffin/src/memory_stream.h" |
| #include "puffin/src/puffin_stream.h" |
| #include "puffin/src/unittest_common.h" |
| |
| using std::shared_ptr; |
| using std::string; |
| using std::vector; |
| |
| namespace puffin { |
| |
| class StreamTest : public ::testing::Test { |
| public: |
| // |data| is the content of stream as a buffer. |
| void TestRead(StreamInterface* stream, const Buffer& data) { |
| // Test read |
| Buffer buf(data.size(), 0); |
| |
| ASSERT_TRUE(stream->Seek(0)); |
| ASSERT_TRUE(stream->Read(buf.data(), buf.size())); |
| for (size_t idx = 0; idx < buf.size(); idx++) { |
| ASSERT_EQ(buf[idx], data[idx]); |
| } |
| |
| // No reading out of data boundary. |
| Buffer tmp(100); |
| uint64_t size; |
| ASSERT_TRUE(stream->GetSize(&size)); |
| ASSERT_TRUE(stream->Seek(size)); |
| ASSERT_TRUE(stream->Read(tmp.data(), 0)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 1)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 2)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 3)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 100)); |
| |
| ASSERT_TRUE(stream->Seek(size - 1)); |
| ASSERT_TRUE(stream->Read(tmp.data(), 0)); |
| ASSERT_TRUE(stream->Read(tmp.data(), 1)); |
| |
| ASSERT_TRUE(stream->Seek(size - 1)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 2)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 3)); |
| ASSERT_FALSE(stream->Read(tmp.data(), 100)); |
| |
| // Read the entire buffer one byte at a time. |
| ASSERT_TRUE(stream->Seek(0)); |
| for (size_t idx = 0; idx < size; idx++) { |
| uint8_t u; |
| ASSERT_TRUE(stream->Read(&u, 1)); |
| ASSERT_EQ(u, buf[idx]); |
| } |
| |
| // Read the entire buffer one byte at a time and set offset for each read. |
| for (size_t idx = 0; idx < size; idx++) { |
| uint8_t u; |
| ASSERT_TRUE(stream->Seek(idx)); |
| ASSERT_TRUE(stream->Read(&u, 1)); |
| ASSERT_EQ(u, buf[idx]); |
| } |
| |
| // Read random lengths from random offsets. |
| tmp.resize(buf.size()); |
| srand(time(nullptr)); |
| uint32_t rand_seed; |
| for (size_t idx = 0; idx < 10000; idx++) { |
| // zero to full size available. |
| size_t size = rand_r(&rand_seed) % (buf.size() + 1); |
| uint64_t max_start = buf.size() - size; |
| uint64_t start = rand_r(&rand_seed) % (max_start + 1); |
| ASSERT_TRUE(stream->Seek(start)); |
| ASSERT_TRUE(stream->Read(tmp.data(), size)); |
| for (size_t idx = 0; idx < size; idx++) { |
| ASSERT_EQ(tmp[idx], buf[start + idx]); |
| } |
| } |
| } |
| |
| void TestWriteBoundary(StreamInterface* stream) { |
| Buffer buf(10); |
| // Writing out of boundary is fine. |
| uint64_t size; |
| ASSERT_TRUE(stream->GetSize(&size)); |
| ASSERT_TRUE(stream->Seek(size)); |
| ASSERT_TRUE(stream->Write(buf.data(), 0)); |
| ASSERT_TRUE(stream->Write(buf.data(), 1)); |
| ASSERT_TRUE(stream->Write(buf.data(), 2)); |
| ASSERT_TRUE(stream->Write(buf.data(), 3)); |
| ASSERT_TRUE(stream->Write(buf.data(), 10)); |
| |
| ASSERT_TRUE(stream->GetSize(&size)); |
| ASSERT_TRUE(stream->Seek(size - 1)); |
| ASSERT_TRUE(stream->Write(buf.data(), 0)); |
| ASSERT_TRUE(stream->Write(buf.data(), 1)); |
| |
| ASSERT_TRUE(stream->GetSize(&size)); |
| ASSERT_TRUE(stream->Seek(size - 1)); |
| ASSERT_TRUE(stream->Write(buf.data(), 2)); |
| ASSERT_TRUE(stream->Write(buf.data(), 3)); |
| ASSERT_TRUE(stream->Write(buf.data(), 10)); |
| } |
| |
| void TestWrite(StreamInterface* write_stream, StreamInterface* read_stream) { |
| uint64_t size; |
| ASSERT_TRUE(read_stream->GetSize(&size)); |
| Buffer buf1(size); |
| Buffer buf2(size); |
| std::iota(buf1.begin(), buf1.end(), 0); |
| |
| // Make sure the write works. |
| ASSERT_TRUE(write_stream->Seek(0)); |
| ASSERT_TRUE(write_stream->Write(buf1.data(), buf1.size())); |
| ASSERT_TRUE(read_stream->Seek(0)); |
| ASSERT_TRUE(read_stream->Read(buf2.data(), buf2.size())); |
| ASSERT_EQ(buf1, buf2); |
| |
| std::fill(buf2.begin(), buf2.end(), 0); |
| |
| // Write entire buffer one byte at a time. (all zeros). |
| ASSERT_TRUE(write_stream->Seek(0)); |
| for (size_t idx = 0; idx < buf2.size(); idx++) { |
| ASSERT_TRUE(write_stream->Write(&buf2[idx], 1)); |
| } |
| |
| ASSERT_TRUE(read_stream->Seek(0)); |
| ASSERT_TRUE(read_stream->Read(buf1.data(), buf1.size())); |
| ASSERT_EQ(buf1, buf2); |
| } |
| |
| // Call this at the end before |TestClose|. |
| void TestSeek(StreamInterface* stream, bool seek_end_is_fine) { |
| uint64_t size, offset; |
| ASSERT_TRUE(stream->GetSize(&size)); |
| ASSERT_TRUE(stream->Seek(size)); |
| ASSERT_TRUE(stream->GetOffset(&offset)); |
| ASSERT_EQ(offset, size); |
| ASSERT_TRUE(stream->Seek(10)); |
| ASSERT_TRUE(stream->GetOffset(&offset)); |
| ASSERT_EQ(offset, 10); |
| ASSERT_TRUE(stream->Seek(0)); |
| ASSERT_TRUE(stream->GetOffset(&offset)); |
| ASSERT_EQ(offset, 0); |
| // Test end of stream offset. |
| ASSERT_EQ(stream->Seek(size + 1), seek_end_is_fine); |
| } |
| |
| void TestClose(StreamInterface* stream) { ASSERT_TRUE(stream->Close()); } |
| }; |
| |
| TEST_F(StreamTest, MemoryStreamTest) { |
| Buffer buf(105); |
| std::iota(buf.begin(), buf.end(), 0); |
| |
| auto read_stream = MemoryStream::CreateForRead(buf); |
| TestRead(read_stream.get(), buf); |
| TestSeek(read_stream.get(), false); |
| |
| auto write_stream = MemoryStream::CreateForWrite(&buf); |
| TestWrite(write_stream.get(), read_stream.get()); |
| TestWriteBoundary(write_stream.get()); |
| TestSeek(write_stream.get(), false); |
| |
| TestClose(read_stream.get()); |
| TestClose(write_stream.get()); |
| } |
| |
| TEST_F(StreamTest, FileStreamTest) { |
| string filepath; |
| ASSERT_TRUE(MakeTempFile(&filepath, nullptr)); |
| ScopedPathUnlinker scoped_unlinker(filepath); |
| ASSERT_FALSE(FileStream::Open(filepath, false, false)); |
| |
| auto stream = FileStream::Open(filepath, true, true); |
| ASSERT_TRUE(stream.get() != nullptr); |
| // Doesn't matter if it is not initialized. I will be overridden. |
| Buffer buf(105); |
| std::iota(buf.begin(), buf.end(), 0); |
| |
| ASSERT_TRUE(stream->Write(buf.data(), buf.size())); |
| |
| TestRead(stream.get(), buf); |
| TestWrite(stream.get(), stream.get()); |
| TestWriteBoundary(stream.get()); |
| TestSeek(stream.get(), true); |
| TestClose(stream.get()); |
| } |
| |
| TEST_F(StreamTest, PuffinStreamTest) { |
| shared_ptr<Puffer> puffer(new Puffer()); |
| auto read_stream = PuffinStream::CreateForPuff( |
| MemoryStream::CreateForRead(kDeflatesSample1), puffer, |
| kPuffsSample1.size(), kSubblockDeflateExtentsSample1, |
| kPuffExtentsSample1); |
| TestRead(read_stream.get(), kPuffsSample1); |
| TestSeek(read_stream.get(), false); |
| TestClose(read_stream.get()); |
| |
| // Test the stream with puff cache. |
| read_stream = PuffinStream::CreateForPuff( |
| MemoryStream::CreateForRead(kDeflatesSample1), puffer, |
| kPuffsSample1.size(), kSubblockDeflateExtentsSample1, kPuffExtentsSample1, |
| 8 /* max_cache_size */); |
| TestRead(read_stream.get(), kPuffsSample1); |
| TestSeek(read_stream.get(), false); |
| TestClose(read_stream.get()); |
| |
| Buffer buf(kDeflatesSample1.size()); |
| shared_ptr<Huffer> huffer(new Huffer()); |
| auto write_stream = PuffinStream::CreateForHuff( |
| MemoryStream::CreateForWrite(&buf), huffer, kPuffsSample1.size(), |
| kSubblockDeflateExtentsSample1, kPuffExtentsSample1); |
| |
| ASSERT_TRUE(write_stream->Seek(0)); |
| for (size_t idx = 0; idx < kPuffsSample1.size(); idx++) { |
| ASSERT_TRUE(write_stream->Write(&kPuffsSample1[idx], 1)); |
| } |
| // Make sure the write works |
| ASSERT_EQ(buf, kDeflatesSample1); |
| |
| std::fill(buf.begin(), buf.end(), 0); |
| ASSERT_TRUE(write_stream->Seek(0)); |
| ASSERT_TRUE(write_stream->Write(kPuffsSample1.data(), kPuffsSample1.size())); |
| // Check its correctness. |
| ASSERT_EQ(buf, kDeflatesSample1); |
| |
| // Write entire buffer one byte at a time. (all zeros). |
| std::fill(buf.begin(), buf.end(), 0); |
| ASSERT_TRUE(write_stream->Seek(0)); |
| for (const auto& byte : kPuffsSample1) { |
| ASSERT_TRUE(write_stream->Write(&byte, 1)); |
| } |
| // Check its correctness. |
| ASSERT_EQ(buf, kDeflatesSample1); |
| |
| // No TestSeek is needed as PuffinStream is not supposed to seek to anywhere |
| // except 0. |
| TestClose(write_stream.get()); |
| } |
| |
| TEST_F(StreamTest, ExtentStreamTest) { |
| Buffer buf(100); |
| std::iota(buf.begin(), buf.end(), 0); |
| |
| vector<ByteExtent> extents = {{10, 10}, {25, 0}, {30, 10}}; |
| Buffer data = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
| 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; |
| |
| auto read_stream = |
| ExtentStream::CreateForRead(MemoryStream::CreateForRead(buf), extents); |
| TestSeek(read_stream.get(), false); |
| TestRead(read_stream.get(), data); |
| TestClose(read_stream.get()); |
| |
| auto buf2 = buf; |
| std::fill(data.begin(), data.end(), 3); |
| for (const auto& extent : extents) { |
| std::fill(buf.begin() + extent.offset, |
| buf.begin() + (extent.offset + extent.length), 3); |
| } |
| auto write_stream = ExtentStream::CreateForWrite( |
| MemoryStream::CreateForWrite(&buf2), extents); |
| ASSERT_TRUE(write_stream->Seek(0)); |
| ASSERT_TRUE(write_stream->Write(data.data(), data.size())); |
| EXPECT_EQ(buf2, buf); |
| |
| TestSeek(write_stream.get(), false); |
| TestClose(write_stream.get()); |
| } |
| |
| } // namespace puffin |