| // 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 <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| |
| #include "puffin/src/bit_reader.h" |
| #include "puffin/src/bit_writer.h" |
| #include "puffin/src/include/puffin/common.h" |
| #include "puffin/src/include/puffin/huffer.h" |
| #include "puffin/src/include/puffin/puffer.h" |
| #include "puffin/src/include/puffin/utils.h" |
| #include "puffin/src/memory_stream.h" |
| #include "puffin/src/puff_reader.h" |
| #include "puffin/src/puff_writer.h" |
| #include "puffin/src/puffin_stream.h" |
| #include "puffin/src/sample_generator.h" |
| #include "puffin/src/set_errors.h" |
| #include "puffin/src/unittest_common.h" |
| |
| namespace puffin { |
| |
| using std::vector; |
| using std::string; |
| |
| class PuffinTest : public ::testing::Test { |
| public: |
| // Utility for decompressing a puff stream. |
| bool DecompressPuff(const uint8_t* puff_buf, |
| size_t* puff_size, |
| uint8_t* out_buf, |
| size_t* out_size) { |
| BufferPuffReader puff_reader(static_cast<const uint8_t*>(puff_buf), |
| *puff_size); |
| auto start = static_cast<uint8_t*>(out_buf); |
| |
| PuffData pd; |
| Error error; |
| while (puff_reader.BytesLeft() != 0) { |
| TEST_AND_RETURN_FALSE(puff_reader.GetNext(&pd, &error)); |
| switch (pd.type) { |
| case PuffData::Type::kLiteral: |
| *start = pd.byte; |
| start++; |
| |
| case PuffData::Type::kLiterals: |
| pd.read_fn(start, pd.length); |
| start += pd.length; |
| break; |
| |
| case PuffData::Type::kLenDist: { |
| while (pd.length-- > 0) { |
| *start = *(start - pd.distance); |
| start++; |
| } |
| break; |
| } |
| |
| case PuffData::Type::kBlockMetadata: |
| break; |
| |
| case PuffData::Type::kEndOfBlock: |
| break; |
| |
| default: |
| LOG(ERROR) << "Invalid block data type"; |
| break; |
| } |
| } |
| *out_size = start - static_cast<uint8_t*>(out_buf); |
| *puff_size = *puff_size - puff_reader.BytesLeft(); |
| return true; |
| } |
| |
| bool PuffDeflate(const uint8_t* comp_buf, |
| size_t comp_size, |
| uint8_t* puff_buf, |
| size_t puff_size, |
| Error* error) const { |
| BufferBitReader bit_reader(comp_buf, comp_size); |
| BufferPuffWriter puff_writer(puff_buf, puff_size); |
| |
| TEST_AND_RETURN_FALSE( |
| puffer_.PuffDeflate(&bit_reader, &puff_writer, nullptr, error)); |
| TEST_AND_RETURN_FALSE_SET_ERROR(comp_size == bit_reader.Offset(), |
| Error::kInvalidInput); |
| TEST_AND_RETURN_FALSE_SET_ERROR(puff_size = puff_writer.Size(), |
| Error::kInvalidInput); |
| return true; |
| } |
| |
| bool HuffDeflate(const uint8_t* puff_buf, |
| size_t puff_size, |
| uint8_t* comp_buf, |
| size_t comp_size, |
| Error* error) const { |
| BufferPuffReader puff_reader(puff_buf, puff_size); |
| BufferBitWriter bit_writer(comp_buf, comp_size); |
| |
| TEST_AND_RETURN_FALSE( |
| huffer_.HuffDeflate(&puff_reader, &bit_writer, error)); |
| TEST_AND_RETURN_FALSE_SET_ERROR(comp_size == bit_writer.Size(), |
| Error::kInvalidInput); |
| TEST_AND_RETURN_FALSE_SET_ERROR(puff_reader.BytesLeft() == 0, |
| Error::kInvalidInput); |
| return true; |
| } |
| |
| // Puffs |compressed| into |out_puff| and checks its equality with |
| // |expected_puff|. |
| void TestPuffDeflate(const Buffer& compressed, |
| const Buffer& expected_puff, |
| Buffer* out_puff) { |
| out_puff->resize(expected_puff.size()); |
| auto comp_size = compressed.size(); |
| auto puff_size = out_puff->size(); |
| Error error; |
| ASSERT_TRUE(PuffDeflate(compressed.data(), comp_size, out_puff->data(), |
| puff_size, &error)); |
| ASSERT_EQ(puff_size, expected_puff.size()); |
| out_puff->resize(puff_size); |
| ASSERT_EQ(expected_puff, *out_puff); |
| } |
| |
| // Should fail when trying to puff |compressed|. |
| void FailPuffDeflate(const Buffer& compressed, |
| Error expected_error, |
| Buffer* out_puff) { |
| out_puff->resize(compressed.size() * 2 + 10); |
| auto comp_size = compressed.size(); |
| auto puff_size = out_puff->size(); |
| Error error; |
| ASSERT_FALSE(PuffDeflate(compressed.data(), comp_size, out_puff->data(), |
| puff_size, &error)); |
| ASSERT_EQ(error, expected_error); |
| } |
| |
| // Huffs |puffed| into |out_huff| and checks its equality with |
| // |expected_huff|.| |
| void TestHuffDeflate(const Buffer& puffed, |
| const Buffer& expected_huff, |
| Buffer* out_huff) { |
| out_huff->resize(expected_huff.size()); |
| auto huff_size = out_huff->size(); |
| auto puffed_size = puffed.size(); |
| Error error; |
| ASSERT_TRUE(HuffDeflate(puffed.data(), puffed_size, out_huff->data(), |
| huff_size, &error)); |
| ASSERT_EQ(expected_huff, *out_huff); |
| } |
| |
| // Should fail while huffing |puffed| |
| void FailHuffDeflate(const Buffer& puffed, |
| Error expected_error, |
| Buffer* out_compress) { |
| out_compress->resize(puffed.size()); |
| auto comp_size = out_compress->size(); |
| auto puff_size = puffed.size(); |
| Error error; |
| ASSERT_TRUE(HuffDeflate(puffed.data(), puff_size, out_compress->data(), |
| comp_size, &error)); |
| ASSERT_EQ(error, expected_error); |
| } |
| |
| // Decompresses from |puffed| into |uncompress| and checks its equality with |
| // |original|. |
| void Decompress(const Buffer& puffed, |
| const Buffer& original, |
| Buffer* uncompress) { |
| uncompress->resize(original.size()); |
| auto uncomp_size = uncompress->size(); |
| auto puffed_size = puffed.size(); |
| ASSERT_TRUE(DecompressPuff( |
| puffed.data(), &puffed_size, uncompress->data(), &uncomp_size)); |
| ASSERT_EQ(puffed_size, puffed.size()); |
| ASSERT_EQ(uncomp_size, original.size()); |
| uncompress->resize(uncomp_size); |
| ASSERT_EQ(original, *uncompress); |
| } |
| |
| void CheckSample(const Buffer original, |
| const Buffer compressed, |
| const Buffer puffed) { |
| Buffer puff, uncompress, huff; |
| TestPuffDeflate(compressed, puffed, &puff); |
| TestHuffDeflate(puffed, compressed, &huff); |
| Decompress(puffed, original, &uncompress); |
| } |
| |
| void CheckBitExtentsPuffAndHuff(const Buffer& deflate_buffer, |
| const vector<BitExtent>& deflate_extents, |
| const Buffer& puff_buffer, |
| const vector<ByteExtent>& puff_extents) { |
| std::shared_ptr<Puffer> puffer(new Puffer()); |
| auto deflate_stream = MemoryStream::CreateForRead(deflate_buffer); |
| ASSERT_TRUE(deflate_stream->Seek(0)); |
| vector<ByteExtent> out_puff_extents; |
| size_t puff_size; |
| ASSERT_TRUE(FindPuffLocations(deflate_stream, deflate_extents, |
| &out_puff_extents, &puff_size)); |
| EXPECT_EQ(puff_size, puff_buffer.size()); |
| EXPECT_EQ(out_puff_extents, puff_extents); |
| |
| auto src_puffin_stream = |
| PuffinStream::CreateForPuff(std::move(deflate_stream), puffer, |
| puff_size, deflate_extents, puff_extents); |
| |
| Buffer out_puff_buffer(puff_buffer.size()); |
| ASSERT_TRUE(src_puffin_stream->Read(out_puff_buffer.data(), |
| out_puff_buffer.size())); |
| EXPECT_EQ(out_puff_buffer, puff_buffer); |
| |
| std::shared_ptr<Huffer> huffer(new Huffer()); |
| Buffer out_deflate_buffer; |
| deflate_stream = MemoryStream::CreateForWrite(&out_deflate_buffer); |
| |
| src_puffin_stream = |
| PuffinStream::CreateForHuff(std::move(deflate_stream), huffer, |
| puff_size, deflate_extents, puff_extents); |
| |
| ASSERT_TRUE( |
| src_puffin_stream->Write(puff_buffer.data(), puff_buffer.size())); |
| EXPECT_EQ(out_deflate_buffer, deflate_buffer); |
| } |
| |
| protected: |
| Puffer puffer_; |
| Huffer huffer_; |
| }; |
| |
| // Tests a simple buffer with uncompressed deflate block. |
| TEST_F(PuffinTest, UncompressedTest) { |
| CheckSample(kRaw1, kDeflate1, kPuff1); |
| } |
| |
| // Tests a simple buffer with uncompressed deflate block with length zero. |
| TEST_F(PuffinTest, ZeroLengthUncompressedTest) { |
| CheckSample(kRaw1_1, kDeflate1_1, kPuff1_1); |
| } |
| |
| // Tests a dynamically compressed buffer with only one literal. |
| TEST_F(PuffinTest, CompressedOneTest) { |
| CheckSample(kRaw2, kDeflate2, kPuff2); |
| } |
| |
| // Tests deflate of an empty buffer. |
| TEST_F(PuffinTest, EmptyTest) { |
| CheckSample(kRaw3, kDeflate3, kPuff3); |
| } |
| |
| // Tests a simple buffer with compress deflate block using fixed Huffman table. |
| TEST_F(PuffinTest, FixedCompressedTest) { |
| CheckSample(kRaw4, kDeflate4, kPuff4); |
| } |
| |
| // Tests a compressed deflate block using dynamic Huffman table. |
| TEST_F(PuffinTest, DynamicHuffmanTest) { |
| CheckSample(kRaw10, kDeflate10, kPuff10); |
| } |
| |
| // Tests an uncompressed deflate block with invalid LEN/NLEN. |
| TEST_F(PuffinTest, PuffDeflateFailedTest) { |
| Buffer puffed; |
| FailPuffDeflate(kDeflate5, Error::kInvalidInput, &puffed); |
| } |
| |
| // Tests puffing a block with invalid block header. |
| TEST_F(PuffinTest, PuffDeflateHeaderFailedTest) { |
| Buffer puffed; |
| FailPuffDeflate(kDeflate6, Error::kInvalidInput, &puffed); |
| } |
| |
| // Tests puffing a block with final block bit unset so it returns |
| // Error::kInsufficientInput. |
| TEST_F(PuffinTest, PuffDeflateNoFinalBlockBitTest) { |
| CheckSample(kRaw7, kDeflate7, kPuff7); |
| } |
| |
| TEST_F(PuffinTest, MultipleDeflateBufferNoFinabBitsTest) { |
| CheckSample(kRaw7_2, kDeflate7_2, kPuff7_2); |
| } |
| |
| TEST_F(PuffinTest, MultipleDeflateBufferOneFinalBitTest) { |
| CheckSample(kRaw7_3, kDeflate7_3, kPuff7_3); |
| } |
| |
| TEST_F(PuffinTest, MultipleDeflateBufferBothFinalBitTest) { |
| CheckSample(kRaw7_4, kDeflate7_4, kPuff7_4); |
| } |
| |
| // TODO(ahassani): Add unittests for Failhuff too. |
| |
| TEST_F(PuffinTest, BitExtentPuffAndHuffTest) { |
| CheckBitExtentsPuffAndHuff(kDeflate11, kSubblockDeflateExtents11, kPuff11, |
| kPuffExtents11); |
| } |
| |
| // TODO(ahassani): add tests for: |
| // TestPatchingEmptyTo9 |
| // TestPatchingNoDeflateTo9 |
| |
| // TODO(ahassani): Change tests data if you decided to compress the header of |
| // the patch. |
| |
| } // namespace puffin |