diff --git a/huffman/test/decode_test.cpp b/huffman/test/decode_test.cpp index 01b4985..5efa953 100644 --- a/huffman/test/decode_test.cpp +++ b/huffman/test/decode_test.cpp @@ -2,70 +2,277 @@ #include +#include #include +#include #include #include #include -constexpr auto reverse_bits(int b) -> std::byte +namespace { + +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) { - std::byte result{}; - for (auto i = 0; i < CHAR_BIT; ++i) { - result <<= 1; - result |= std::byte{(b & 1) == 1}; - b >>= 1; + 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{}; + + 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{}; + + auto buf = std::array{}; + + 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{}; + + 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{}; + std::ranges::fill(arr, 'e'); + return arr; + }(); + + constexpr auto decoded = [] { + constexpr auto encoded = std::array{}; + + auto buf = std::array{}; + + 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{}; + 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{}; + + 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{}; + + 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{}; + + 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{}; + + 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{}; + + 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 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{}; + + 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)); }; }