Skip to content

Commit

Permalink
fix decode test after always using DEFLATE canonicalization
Browse files Browse the repository at this point in the history
Update decode test to construct a table in canonical form and fix the
associated test. This commit also adds additional decode test cases.

Change-Id: Ic46cbcee812f5812ab55f446c7d05c0e9e9d3e8a
  • Loading branch information
oliverlee committed Nov 22, 2023
1 parent 5482af4 commit 081149b
Showing 1 changed file with 268 additions and 47 deletions.
315 changes: 268 additions & 47 deletions huffman/test/decode_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,291 @@

#include <boost/ut.hpp>

#include <algorithm>
#include <array>
#include <climits>
#include <cstddef>
#include <stdexcept>
#include <utility>

constexpr auto reverse_bits(int b) -> std::byte
// TODO(move a test utility?)
struct byte_printer
{
std::byte result{};
for (auto i = 0; i < CHAR_BIT; ++i) {
result <<= 1;
result |= std::byte{(b & 1) == 1};
b >>= 1;
std::uint8_t value;

byte_printer(std::byte b) : value{static_cast<std::uint8_t>(b)} {}
};

auto operator<<(std::ostream& os, byte_printer bp) -> std::ostream&
{
for (auto mask = std::uint8_t{0x80U}; mask != std::uint8_t{}; mask >>= 1U) {
const auto on = (bp.value & mask) == std::uint8_t{1};
os << (on ? '1' : '0');
}

return os;
}

static constexpr auto eot = '\4';

static constexpr auto code_table = [] {
using namespace ::starflate::huffman::literals;

// clang-format off
return ::starflate::huffman::table{
::starflate::huffman::table_contents,
{
std::pair{0_c, 'e'},
{10_c, 'i'},
{110_c, 'n'},
{1110_c, 'q'},
{11110_c, eot},
{11111_c, 'x'},
}};
// clang-format on
}();

namespace test {

constexpr auto verify(bool cond)
{
if (not cond) {
throw std::runtime_error{""};
}
return result;
}

} // namespace test

auto main() -> int
{
using ::boost::ut::eq;
using ::boost::ut::expect;
using ::boost::ut::test;

namespace huffman = ::starflate::huffman;
using namespace huffman::literals;

// FIXME table contents are not in canonical form
test("basic") = [] {
// encoded data from soxofaan/dahuffman readme.rst.
// We reverse the bits in each byte to match the encoding used in DEFLATE.
constexpr std::array encoded_bytes = {
reverse_bits(134),
reverse_bits(124),
reverse_bits(37),
reverse_bits(19),
reverse_bits(105),
reverse_bits(64)};

constexpr char eot = {'\4'};
static constexpr auto code_table = // clang-format off
huffman::table{
huffman::table_contents,
{std::pair{1_c, 'e'},
{01_c, 'i'},
{001_c, 'n'},
{0001_c, 'q'},
{00000_c, eot},
{00001_c, 'x'}
}
}; // clang-format on

constexpr std::array expected = {

test("empty") = [] {
constexpr auto result = [] {
auto buf = std::array<char, 1>{};

auto it = huffman::decode(code_table, huffman::bit_span{}, buf.begin());

return it == buf.begin();
}();

static_assert(result);
};

test("just `e` - beginning not byte aligned") = [] {
static constexpr auto expected = std::array{'e'};

constexpr auto decoded = [] {
constexpr auto encoded = std::array<std::byte, 1>{};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(
code_table,
huffman::bit_span{encoded}.consume(CHAR_BIT - 1),
buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("just `e` - beginning not byte aligned, different padding") = [] {
static constexpr auto expected = std::array{'e'};

constexpr auto decoded = [] {
constexpr auto encoded = std::array{
//
std::byte{0b0111'1111}
// ^^^ ^^^^
// padding
};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(
code_table,
huffman::bit_span{encoded}.consume(CHAR_BIT - 1),
buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("`e` x8") = [] {
static constexpr auto expected = [] {
auto arr = std::array<char, 8>{};
std::ranges::fill(arr, 'e');
return arr;
}();

constexpr auto decoded = [] {
constexpr auto encoded = std::array<std::byte, 1>{};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(code_table, encoded, buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("invalid code") = [] {
static constexpr auto different_table = [] {
using namespace ::starflate::huffman::literals;

// clang-format off
return ::starflate::huffman::table{
::starflate::huffman::table_contents,
{
std::pair{0_c, 'e'},
}};
// clang-format on
}();

static constexpr auto expected = [] {
auto arr = std::array<char, 8>{};
std::ranges::fill(arr, 'e');
return arr;
}();

static constexpr auto encoded =
std::array{std::byte{0x00U}, std::byte{0xFFU}};

constexpr auto decoded = [] {
auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(different_table, encoded, buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("`nx`") = [] {
static constexpr auto expected = std::array{'n', 'x'};

constexpr auto decoded = [] {
constexpr auto encoded = std::array{std::byte{0b1111'1011}};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(code_table, encoded, buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("`nxqiee`") = [] {
static constexpr auto expected = std::array{'n', 'x', 'q', 'i', 'e', 'e'};

constexpr auto decoded = [] {
constexpr auto encoded = std::array{
std::byte{0b1111'1011}, //
std::byte{0b0001'0111}};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(code_table, encoded, buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("`nxqi` - end is not byte aligned") = [] {
static constexpr auto expected = std::array{'n', 'x', 'q', 'i'};

constexpr auto decoded = [] {
constexpr auto encoded = std::array{
std::byte{0b1111'1011}, //
std::byte{0b0001'0111}
// ^^
// padding
};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(
code_table,
huffman::bit_span{encoded.data(), encoded.size() * CHAR_BIT - 2},
buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("`exeneeeexni`") = [] {
static constexpr auto expected =
std::array{'e', 'x', 'e', 'n', 'e', 'e', 'e', 'e', 'x', 'n', 'i'};

constexpr auto decoded = [] {
constexpr auto encoded = std::array{
std::byte{0b1011'1110},
std::byte{0b1100'0001},
std::byte{0b0101'1111}};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(code_table, encoded, buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(eq(expected, decoded));
};

test("`exeneeeexniqneiein`") = [] {
static constexpr auto expected = std::array{
'e', 'x', 'e', 'n', 'e', 'e', 'e', 'e', 'x', 'n',
'i', 'q', 'n', 'e', 'i', 'e', 'i', 'n', 'i', eot,
};

const auto output_buf = [&] {
std::array<char, expected.size()> output_buf{};
auto result = decode(code_table, encoded_bytes, output_buf.begin());
// result should point to the back of output_buf.
if (output_buf.end() != result) {
throw std::runtime_error("assertion failed");
}
return output_buf;
'i', 'q', 'n', 'e', 'i', 'e', 'i', 'n', 'i', eot};

constexpr auto decoded = [] {
constexpr auto encoded = std::array{
std::byte{0b1011'1110},
std::byte{0b1100'0001},
std::byte{0b0101'1111},
std::byte{0b0011'0111},
std::byte{0b0110'1001},
std::byte{0b0011'1101}
// ^
// padding
};

auto buf = std::array<char, expected.size()>{};

auto it = huffman::decode(
code_table,
huffman::bit_span{encoded.data(), encoded.size() * CHAR_BIT - 1},
buf.begin());

::test::verify(it == buf.end());
return buf;
}();

expect(output_buf == expected);
expect(eq(expected, decoded));
};
}

0 comments on commit 081149b

Please sign in to comment.