// 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/include/puffin/huffer.h" #include "puffin/src/include/puffin/puffer.h" #include "puffin/src/extent_stream.h" #include "puffin/src/file_stream.h" #include "puffin/src/memory_stream.h" #include "puffin/src/puffin_stream.h" #include "puffin/src/unittest_common.h" namespace puffin { using std::string; using std::shared_ptr; using std::vector; 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("/tmp/test_filepath"); 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(kDeflates8), puffer, kPuffs8.size(), kSubblockDeflateExtents8, kPuffExtents8); TestRead(read_stream.get(), kPuffs8); TestSeek(read_stream.get(), false); TestClose(read_stream.get()); // Test the stream with puff cache. read_stream = PuffinStream::CreateForPuff( MemoryStream::CreateForRead(kDeflates8), puffer, kPuffs8.size(), kSubblockDeflateExtents8, kPuffExtents8, 8 /* max_cache_size */); TestRead(read_stream.get(), kPuffs8); TestSeek(read_stream.get(), false); TestClose(read_stream.get()); Buffer buf(kDeflates8.size()); shared_ptr<Huffer> huffer(new Huffer()); auto write_stream = PuffinStream::CreateForHuff( MemoryStream::CreateForWrite(&buf), huffer, kPuffs8.size(), kSubblockDeflateExtents8, kPuffExtents8); ASSERT_TRUE(write_stream->Seek(0)); for (size_t idx = 0; idx < kPuffs8.size(); idx++) { ASSERT_TRUE(write_stream->Write(&kPuffs8[idx], 1)); } // Make sure the write works ASSERT_EQ(buf, kDeflates8); std::fill(buf.begin(), buf.end(), 0); ASSERT_TRUE(write_stream->Seek(0)); ASSERT_TRUE(write_stream->Write(kPuffs8.data(), kPuffs8.size())); // Check its correctness. ASSERT_EQ(buf, kDeflates8); // 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 : kPuffs8) { ASSERT_TRUE(write_stream->Write(&byte, 1)); } // Check its correctness. ASSERT_EQ(buf, kDeflates8); // 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