blob: 1a135ef35c87ff5aac6c5553e39d505fa5f2c937 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 "src/storage/volume_image/utils/lz4_decompressor.h"
#include <array>
#include <cstdint>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <lz4/lz4.h>
#include "src/storage/volume_image/options.h"
#include "src/storage/volume_image/utils/lz4_result.h"
namespace storage::volume_image {
namespace {
constexpr std::array<uint8_t, 4096> kData = {0};
fpromise::result<std::vector<uint8_t>, std::string> GetCompressedData() {
size_t max_size = LZ4F_compressFrameBound(kData.size(), nullptr);
std::vector<uint8_t> compressed_data(max_size, 0);
Lz4Result result = LZ4F_compressFrame(compressed_data.data(), compressed_data.size(),
kData.data(), kData.size(), nullptr);
if (result.is_error()) {
return fpromise::error("Failed to compress |kData|. LZ4 Error: " + std::string(result.error()));
}
compressed_data.resize(result.byte_count());
return fpromise::ok(std::move(compressed_data));
}
TEST(Lz4DecompressorTest, CreateWithWrongSchemaIsError) {
CompressionOptions options;
options.schema = CompressionSchema::kNone;
ASSERT_TRUE(Lz4Decompressor::Create(options).is_error());
}
TEST(Lz4DecompressorTest, CreateWithCompressionSchemaLz4IsOk) {
CompressionOptions options;
options.schema = CompressionSchema::kLz4;
ASSERT_TRUE(Lz4Decompressor::Create(options).is_ok());
}
TEST(Lz4DecompressorTest, PrepareAfterConstructionIsOk) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto decompressor = decompressor_or.take_value();
EXPECT_TRUE(decompressor.Prepare([](cpp20::span<const uint8_t> buffer) { return fpromise::ok(); })
.is_ok());
}
TEST(Lz4DecompressorTest, DecompressWithoutPrepareIsError) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto decompressor = decompressor_or.take_value();
EXPECT_TRUE(decompressor.Decompress(cpp20::span<const uint8_t>()).is_error());
}
TEST(Lz4DecompressorTest, FinalizeWithoutPrepareIsError) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto decompressor = decompressor_or.take_value();
EXPECT_TRUE(decompressor.Finalize().is_error());
}
TEST(Lz4DecompressorTest, DecompressWithPrepareAndSizeHintIsOk) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto data_or = GetCompressedData();
ASSERT_TRUE(data_or.is_ok()) << data_or.error();
auto decompressor = decompressor_or.take_value();
ASSERT_TRUE(decompressor
.Prepare([](cpp20::span<const uint8_t> buffer) {
EXPECT_THAT(buffer, testing::ElementsAreArray(kData));
return fpromise::ok();
})
.is_ok());
// This should allow us to decompress in one pass.
decompressor.ProvideSizeHint(kData.size());
auto decompressor_result = decompressor.Decompress(data_or.value());
ASSERT_TRUE(decompressor_result.is_ok()) << decompressor_result.error();
auto [hint, consumed_bytes] = decompressor_result.take_value();
EXPECT_EQ(hint, static_cast<size_t>(0));
EXPECT_EQ(consumed_bytes, data_or.value().size());
}
TEST(Lz4DecompressorTest, DecompressOnMultipleStepsIsOk) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto data_or = GetCompressedData();
ASSERT_TRUE(data_or.is_ok()) << data_or.error();
auto decompressor = decompressor_or.take_value();
size_t total_consumed_bytes = 0;
size_t decompressed_data_offset = 0;
ASSERT_TRUE(decompressor
.Prepare([&](cpp20::span<const uint8_t> buffer) {
EXPECT_THAT(buffer,
testing::ElementsAreArray(cpp20::span<const uint8_t>(kData).subspan(
decompressed_data_offset, buffer.size())));
decompressed_data_offset += buffer.size();
return fpromise::ok();
})
.is_ok());
decompressor.ProvideSizeHint(kData.size() / 4);
bool is_decompression_done = false;
while (!is_decompression_done) {
auto decompression_result = decompressor.Decompress(
cpp20::span<uint8_t>(data_or.value()).subspan(total_consumed_bytes));
ASSERT_TRUE(decompression_result.is_ok()) << decompression_result.is_error();
auto [hint, consumed_bytes] = decompression_result.take_value();
total_consumed_bytes += consumed_bytes;
is_decompression_done = hint == 0;
}
EXPECT_EQ(total_consumed_bytes, data_or.value().size());
}
TEST(Lz4DecompressorTest, FinalizeWithPrepareIsOk) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto decompressor = decompressor_or.take_value();
ASSERT_TRUE(decompressor.Prepare([](cpp20::span<const uint8_t> buffer) { return fpromise::ok(); })
.is_ok());
EXPECT_TRUE(decompressor.Finalize().is_ok());
}
TEST(Lz4DecompressorTest, PrepareAfterFinalizeIsOk) {
CompressionOptions options = {.schema = CompressionSchema::kLz4};
auto decompressor_or = Lz4Decompressor::Create(options);
ASSERT_TRUE(decompressor_or.is_ok()) << decompressor_or.error();
auto decompressor = decompressor_or.take_value();
ASSERT_TRUE(decompressor.Prepare([](cpp20::span<const uint8_t> buffer) { return fpromise::ok(); })
.is_ok());
EXPECT_TRUE(decompressor.Finalize().is_ok());
ASSERT_TRUE(decompressor.Prepare([](cpp20::span<const uint8_t> buffer) { return fpromise::ok(); })
.is_ok());
}
} // namespace
} // namespace storage::volume_image