blob: 4c9974ae67866be0509d23646a43f91af3066596 [file] [log] [blame]
// Copyright 2018 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 <stdlib.h>
#include <zircon/assert.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <blobfs/format.h>
#include <zxtest/zxtest.h>
#include "compression/blob-compressor.h"
#include "compression/chunked.h"
#include "compression/compressor.h"
#include "compression/seekable-decompressor.h"
#include "compression/zstd-seekable.h"
#include "zircon/errors.h"
namespace blobfs {
namespace {
enum class DataType {
Compressible,
Random,
};
std::unique_ptr<char[]> GenerateInput(DataType data_type, unsigned* seed, size_t size) {
std::unique_ptr<char[]> input(new char[size]);
switch (data_type) {
case DataType::Compressible: {
size_t i = 0;
while (i < size) {
size_t run_length = 1 + (rand_r(seed) % (size - i));
char value = static_cast<char>(rand_r(seed) % std::numeric_limits<char>::max());
memset(input.get() + i, value, run_length);
i += run_length;
}
break;
}
case DataType::Random:
for (size_t i = 0; i < size; i++) {
input[i] = static_cast<char>(rand_r(seed));
}
break;
default:
ADD_FAILURE("Bad Data Type");
}
return input;
}
void CompressionHelper(CompressionAlgorithm algorithm, const char* input, size_t size, size_t step,
std::optional<BlobCompressor>* out) {
auto compressor = BlobCompressor::Create(algorithm, size);
ASSERT_TRUE(compressor);
size_t offset = 0;
while (offset != size) {
const void* data = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(input) + offset);
const size_t incremental_size = std::min(step, size - offset);
ASSERT_OK(compressor->Update(data, incremental_size));
offset += incremental_size;
}
ASSERT_OK(compressor->End());
EXPECT_GT(compressor->Size(), 0);
*out = std::move(compressor);
}
void DecompressAndVerifyMapping(SeekableDecompressor* decompressor, const uint8_t* compressed_buf,
size_t compressed_size, const uint8_t* expected,
size_t expected_size, const CompressionMapping& mapping) {
ASSERT_LE(mapping.decompressed_offset + mapping.decompressed_length, expected_size);
ASSERT_LE(mapping.compressed_offset + mapping.compressed_length, compressed_size);
fbl::Array<uint8_t> buf(new uint8_t[mapping.decompressed_length], mapping.decompressed_length);
size_t sz = mapping.decompressed_length;
ASSERT_OK(decompressor->DecompressRange(buf.get(), &sz,
compressed_buf + mapping.compressed_offset,
mapping.compressed_length, mapping.decompressed_offset));
EXPECT_EQ(mapping.decompressed_length, sz);
EXPECT_BYTES_EQ(expected + mapping.decompressed_offset, buf.get(), sz);
}
void DecompressionHelper(SeekableDecompressor* decompressor, unsigned* seed,
const void* compressed_buf, size_t compressed_size, const void* expected,
size_t expected_size) {
// 1. Sequential decompression of each range
size_t offset = 0;
std::optional<CompressionMapping> mapping;
while ((mapping = decompressor->MappingForDecompressedAddress(offset)) != std::nullopt) {
DecompressAndVerifyMapping(decompressor, static_cast<const uint8_t*>(compressed_buf),
compressed_size, static_cast<const uint8_t*>(expected),
expected_size, *mapping);
offset += mapping->decompressed_length;
}
// 2. Random offsets
for (int i = 0; i < 100; ++i) {
offset = rand_r(seed) % expected_size;
std::optional<CompressionMapping> mapping = decompressor->MappingForDecompressedAddress(offset);
ASSERT_TRUE(mapping);
DecompressAndVerifyMapping(decompressor, static_cast<const uint8_t*>(compressed_buf),
compressed_size, static_cast<const uint8_t*>(expected),
expected_size, *mapping);
}
}
// Tests a contained case of compression and decompression.
//
// size: The size of the input buffer.
// step: The step size of updating the compression buffer.
void RunCompressDecompressTest(CompressionAlgorithm algorithm, DataType data_type, size_t size,
size_t step) {
ASSERT_LE(step, size, "Step size too large");
unsigned seed = ::zxtest::Runner::GetInstance()->random_seed();
// Generate input.
std::unique_ptr<char[]> input(GenerateInput(data_type, &seed, size));
// Compress a buffer.
std::optional<BlobCompressor> compressor;
ASSERT_NO_FAILURES(CompressionHelper(algorithm, input.get(), size, step, &compressor));
ASSERT_TRUE(compressor);
// Decompress the buffer.
std::unique_ptr<SeekableDecompressor> decompressor;
switch (algorithm) {
case CompressionAlgorithm::CHUNKED: {
ASSERT_OK(SeekableChunkedDecompressor::CreateDecompressor(
compressor->Data(), compressor->Size(), compressor->Size(), &decompressor));
break;
}
default:
ASSERT_TRUE(false);
}
ASSERT_NO_FAILURES(DecompressionHelper(decompressor.get(), &seed, compressor->Data(),
compressor->Size(), input.get(), size));
}
TEST(SeekableCompressorTest, CompressDecompressChunkCompressible1) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 0, 1 << 0);
}
TEST(SeekableCompressorTest, CompressDecompressChunkCompressible2) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 1, 1 << 0);
}
TEST(SeekableCompressorTest, CompressDecompressChunkCompressible3) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 10, 1 << 5);
}
TEST(SeekableCompressorTest, CompressDecompressChunkCompressible4) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 15, 1 << 10);
}
TEST(SeekableCompressorTest, CompressDecompressChunkRandom1) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 0, 1 << 0);
}
TEST(SeekableCompressorTest, CompressDecompressChunkRandom2) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 1, 1 << 0);
}
TEST(SeekableCompressorTest, CompressDecompressChunkRandom3) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 10, 1 << 5);
}
TEST(SeekableCompressorTest, CompressDecompressChunkRandom4) {
RunCompressDecompressTest(CompressionAlgorithm::CHUNKED, DataType::Random, 1 << 15, 1 << 10);
}
} // namespace
} // namespace blobfs