libdpf/test/tests/bit_array_test.cpp
Ryan Henry e4e666f459 Initial import of libdpf.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 14:08:32 -06:00

410 lines
13 KiB
C++

#include <gtest/gtest.h>
#include <algorithm>
#include <cstdint>
#include <type_traits>
#include <utility>
#include "dpf/bit_array.hpp"
#include "dpf/bitstring.hpp"
using namespace dpf::literals;
namespace
{
template <typename Array>
std::size_t ref_count(const Array &a, std::size_t lo, std::size_t hi)
{
std::size_t n = 0;
for (std::size_t i = lo; i < hi; ++i)
n += static_cast<bool>(a[i]) ? 1u : 0u;
return n;
}
template <typename Array>
int ref_parity(const Array &a, std::size_t lo, std::size_t hi)
{
int p = 0;
for (std::size_t i = lo; i < hi; ++i)
p ^= static_cast<bool>(a[i]) ? 1 : 0;
return p;
}
template <typename Array>
bool ref_all(const Array &a, std::size_t lo, std::size_t hi)
{
for (std::size_t i = lo; i < hi; ++i)
if (!static_cast<bool>(a[i])) return false;
return true;
}
template <typename Array>
bool ref_any(const Array &a, std::size_t lo, std::size_t hi)
{
for (std::size_t i = lo; i < hi; ++i)
if (static_cast<bool>(a[i])) return true;
return false;
}
template <typename Array>
void expect_reductions(Array &a)
{
const std::size_t n = a.size();
EXPECT_EQ(a.count(), ref_count(a, 0, n));
EXPECT_EQ(static_cast<int>(a.any()), static_cast<int>(ref_any(a, 0, n)));
EXPECT_EQ(static_cast<int>(a.none()), static_cast<int>(!ref_any(a, 0, n)));
EXPECT_EQ(static_cast<int>(a.all()), static_cast<int>(ref_all(a, 0, n)));
EXPECT_EQ(a.parity(), static_cast<std::size_t>(ref_parity(a, 0, n)));
const std::size_t cuts[][2] = {
{0, 0},
{0, n},
{0, std::min<std::size_t>(n, 1)},
{0, std::min<std::size_t>(n, 3)},
{std::min<std::size_t>(n, 2), std::min<std::size_t>(n, 10)},
{n > 60 ? n - 60 : 0, n},
{n > 70 ? std::size_t{60} : std::size_t{0}, std::min<std::size_t>(n, 70)},
{n / 2, std::min(n, n / 2 + 1)},
};
for (const auto &c : cuts)
{
if (c[0] > c[1] || c[1] > n) continue;
auto first = a.begin() + static_cast<std::ptrdiff_t>(c[0]);
auto last = a.begin() + static_cast<std::ptrdiff_t>(c[1]);
EXPECT_EQ(a.count(first, last), ref_count(a, c[0], c[1]));
EXPECT_EQ(static_cast<int>(a.any(first, last)), static_cast<int>(ref_any(a, c[0], c[1])));
EXPECT_EQ(static_cast<int>(a.all(first, last)), static_cast<int>(ref_all(a, c[0], c[1])));
EXPECT_EQ(static_cast<int>(a.none(first, last)), static_cast<int>(!ref_any(a, c[0], c[1])));
EXPECT_EQ(a.parity(first, last), static_cast<std::size_t>(ref_parity(a, c[0], c[1])));
}
}
template <typename Array>
void exercise(Array a)
{
const std::size_t n = a.size();
a.unset();
expect_reductions(a);
if (n > 0)
{
a.set(n / 2, true);
if (n > 2) a.set(0, true);
expect_reductions(a);
}
a.unset();
for (std::size_t i = 0; i < n; ++i) a.set(i, true);
expect_reductions(a);
EXPECT_EQ(a.count(), n);
if (n > 0) EXPECT_TRUE(a.all());
a.set();
expect_reductions(a);
EXPECT_EQ(a.count(), n);
if (n > 0) EXPECT_TRUE(a.all());
EXPECT_EQ(a.parity(), n % 2);
a.unset();
for (std::size_t i = 0; i < n; ++i) a.set(i, (i % 3) != 0);
expect_reductions(a);
if (n > 1)
{
EXPECT_EQ(a.end() - static_cast<std::ptrdiff_t>(n), a.begin());
EXPECT_EQ(a.begin() + static_cast<std::ptrdiff_t>(n), a.end());
EXPECT_EQ(5 + a.begin(), a.begin() + 5);
auto back = a.begin() + static_cast<std::ptrdiff_t>(std::min<std::size_t>(n, 64));
const auto dest = static_cast<std::ptrdiff_t>(std::min<std::size_t>(n, 64)) - 3;
if (dest >= 0)
{
back += -3;
EXPECT_EQ(back, a.begin() + dest);
EXPECT_EQ(static_cast<bool>(*back), static_cast<bool>(a[static_cast<std::size_t>(dest)]));
}
}
}
} // namespace
constexpr auto kLiteral = 10101001_bitstring;
static_assert(kLiteral.size() == 8);
static_assert(kLiteral == dpf::bitstring<8>(0b10101001));
static_assert(dpf::utils::quotient_ceiling<std::size_t>(0, 64) == 0);
static_assert(dpf::utils::quotient_ceiling<std::size_t>(1, 64) == 1);
static_assert(dpf::utils::quotient_ceiling<std::size_t>(64, 64) == 1);
static_assert(dpf::utils::quotient_ceiling<std::size_t>(65, 64) == 2);
TEST(BitArray, QuotientCeilingZero)
{
EXPECT_EQ(dpf::utils::quotient_ceiling<std::size_t>(0, 8), 0u);
EXPECT_EQ(dpf::utils::quotient_ceiling<std::size_t>(9, 8), 2u);
}
TEST(BitArray, StaticPayloadIsZero)
{
dpf::static_bit_array<80> a;
EXPECT_EQ(a.data()[0], 0u);
EXPECT_EQ(a.data()[1], 0u);
EXPECT_EQ(a.count(), 0u);
EXPECT_FALSE(a.any());
EXPECT_TRUE(a.none());
EXPECT_FALSE(a.all());
dpf::static_bit_array<80> seeded(0x11);
EXPECT_EQ(seeded.data()[0], 0x11u);
EXPECT_EQ(seeded.data()[1], 0u);
EXPECT_EQ(seeded.count(), 2u);
dpf::static_bit_array<80> wide_val(0x1FFFF);
EXPECT_EQ(wide_val.data()[0], 0x1FFFFu);
EXPECT_EQ(wide_val.data()[1], 0u);
EXPECT_EQ(wide_val.count(), 17u);
dpf::static_bit_array<4> narrow(0xFF);
EXPECT_EQ(narrow.count(), 4u);
EXPECT_TRUE(narrow.all());
}
TEST(BitArray, DynamicPayloadIsZeroAndEmptyIsEmpty)
{
dpf::dynamic_bit_array<> a(130);
ASSERT_NE(a.data(), nullptr);
for (std::size_t i = 0; i < a.data_length(); ++i)
EXPECT_EQ(a.data(i), 0u);
dpf::dynamic_bit_array<> empty(0);
EXPECT_EQ(empty.size(), 0u);
EXPECT_EQ(empty.data_length(), 0u);
EXPECT_TRUE(empty.all());
EXPECT_FALSE(empty.any());
EXPECT_EQ(empty.count(), 0u);
EXPECT_EQ(empty.parity(), 0u);
EXPECT_EQ(empty.begin(), empty.end());
}
TEST(BitArray, DynamicCopyAndMove)
{
dpf::dynamic_bit_array<> a(70);
a.set(3, true);
a.set(69, true);
dpf::dynamic_bit_array<> b = a;
b.set(3, false);
EXPECT_TRUE(static_cast<bool>(a[3]));
EXPECT_FALSE(static_cast<bool>(b[3]));
EXPECT_TRUE(static_cast<bool>(b[69]));
dpf::dynamic_bit_array<> c(8);
c = a;
EXPECT_TRUE(static_cast<bool>(c[69]));
EXPECT_EQ(c.size(), 70u);
auto moved = std::move(a);
EXPECT_EQ(a.size(), 0u);
EXPECT_EQ(a.data(), nullptr);
EXPECT_TRUE(static_cast<bool>(moved[3]));
EXPECT_TRUE(static_cast<bool>(moved[69]));
}
TEST(BitArray, TailBitsAreIgnored)
{
exercise(dpf::dynamic_bit_array<>(1));
exercise(dpf::dynamic_bit_array<>(7));
exercise(dpf::dynamic_bit_array<>(8));
exercise(dpf::dynamic_bit_array<>(9));
exercise(dpf::dynamic_bit_array<>(63));
exercise(dpf::dynamic_bit_array<>(64));
exercise(dpf::dynamic_bit_array<>(65));
exercise(dpf::dynamic_bit_array<>(130));
exercise(dpf::static_bit_array<1>{});
exercise(dpf::static_bit_array<10>{});
exercise(dpf::static_bit_array<64>{});
exercise(dpf::static_bit_array<70>{});
exercise(dpf::static_bit_array<40, std::uint8_t>{});
exercise(dpf::static_bit_array<400, std::uint8_t>{});
exercise(dpf::static_bit_array<20, std::uint16_t>{});
exercise(dpf::bitstring<1>{});
exercise(dpf::bitstring<9>{});
exercise(dpf::bitstring<10>{});
exercise(dpf::bitstring<64>{});
exercise(dpf::bitstring<65>{});
exercise(dpf::bitstring<70, std::uint8_t>{});
}
TEST(BitArray, SameWordRangeDoesNotRunOff)
{
dpf::dynamic_bit_array<> a(32);
a.unset();
a.set(0, true);
a.set(10, true);
EXPECT_FALSE(a.any(a.begin() + 1, a.begin() + 10));
EXPECT_TRUE(a.any(a.begin(), a.begin() + 1));
EXPECT_EQ(a.count(a.begin(), a.begin() + 11), 2u);
EXPECT_FALSE(a.all(a.begin(), a.begin() + 11));
EXPECT_TRUE(a.all(a.begin() + 3, a.begin() + 3));
EXPECT_FALSE(a.any(a.begin() + 3, a.begin() + 3));
EXPECT_EQ(a.count(a.begin() + 3, a.begin() + 3), 0u);
EXPECT_EQ(a.parity(a.begin() + 3, a.begin() + 3), 0u);
a.set();
EXPECT_TRUE(a.all(a.begin() + 2, a.begin() + 9));
EXPECT_EQ(a.count(a.begin() + 2, a.begin() + 9), 7u);
EXPECT_EQ(a.parity(a.begin() + 2, a.begin() + 9), 1u);
}
TEST(BitArray, CrossWordRangeAndFullBitstring)
{
dpf::dynamic_bit_array<> a(130);
a.unset();
a.set(63, true);
a.set(64, true);
a.set(100, true);
EXPECT_EQ(a.count(a.begin() + 60, a.begin() + 70), 2u);
EXPECT_TRUE(a.any(a.begin() + 60, a.begin() + 70));
EXPECT_FALSE(a.all(a.begin() + 60, a.begin() + 70));
EXPECT_EQ(a.parity(a.begin() + 60, a.begin() + 70), 0u);
dpf::bitstring<64> aligned;
aligned.set(1, true);
EXPECT_TRUE(aligned.any(aligned.begin(), aligned.end()));
EXPECT_EQ(aligned.count(aligned.begin(), aligned.end()), 1u);
EXPECT_FALSE(aligned.all(aligned.begin(), aligned.end()));
aligned.set();
EXPECT_TRUE(aligned.all(aligned.begin(), aligned.end()));
EXPECT_EQ(aligned.count(aligned.begin(), aligned.end()), 64u);
dpf::bitstring<128> wide;
wide.set();
EXPECT_TRUE(wide.all(wide.begin(), wide.end()));
EXPECT_EQ(wide.count(wide.begin(), wide.end()), 128u);
EXPECT_EQ(wide.parity(wide.begin(), wide.end()), 0u);
}
TEST(BitArray, NarrowWordCountCompilesAndFits)
{
dpf::static_bit_array<400, std::uint8_t> wide;
wide.set();
EXPECT_EQ(wide.count(), 400u);
EXPECT_TRUE(wide.all());
EXPECT_EQ(wide.parity(), 0u);
dpf::bitstring<10> partial;
partial.set(0, true);
partial.set(3, true);
EXPECT_EQ(partial.count(partial.begin(), partial.begin() + 4), 2u);
EXPECT_EQ(partial.count(partial.begin(), partial.end()), 2u);
partial.set();
EXPECT_EQ(partial.count(), 10u);
EXPECT_EQ(partial.count(partial.begin(), partial.end()), 10u);
}
TEST(BitString, SetMatchesFlipAndIgnoresPadding)
{
dpf::bitstring<65> set_bits;
set_bits.set();
dpf::bitstring<65> flipped = ~dpf::bitstring<65>{};
EXPECT_EQ(set_bits, flipped);
EXPECT_EQ(set_bits.count(), 65u);
EXPECT_EQ(flipped.count(), 65u);
EXPECT_EQ(set_bits.parity(), 1u);
dpf::bitstring<10> dirty;
using word = std::remove_reference_t<decltype(dirty.data(0))>;
dirty.data(0) = static_cast<word>(word{1} << 10);
dpf::bitstring<10> clean;
EXPECT_EQ(dirty, clean);
EXPECT_FALSE(dirty.any());
EXPECT_TRUE(dirty.none());
EXPECT_EQ(dirty.count(), 0u);
EXPECT_FALSE(dirty.all());
const auto clz = dpf::utils::countl_zero_symmetric_difference<dpf::bitstring<10>>{};
EXPECT_EQ(clz(dirty, clean), 10u);
dpf::bitstring<10> ones = ~dpf::bitstring<10>{};
dpf::bitstring<10> also_ones;
also_ones.set();
EXPECT_EQ(clz(also_ones, ones), 10u);
dpf::bitstring<1> one_set;
one_set.set();
EXPECT_EQ(one_set.count(), 1u);
EXPECT_EQ(dpf::utils::countl_zero_symmetric_difference<dpf::bitstring<1>>{}(
one_set, ~dpf::bitstring<1>{}), 1u);
}
TEST(BitString, OrderingXorAndIncrement)
{
dpf::bitstring<12> lo(1);
dpf::bitstring<12> hi;
hi.set(11, true);
EXPECT_LT(lo, hi);
EXPECT_GT(hi, lo);
EXPECT_NE(lo, hi);
EXPECT_EQ(lo + dpf::bitstring<12>(1), dpf::bitstring<12>{});
auto inc = lo;
++inc;
EXPECT_EQ(inc, dpf::bitstring<12>(2));
dpf::bitstring<80> wrapped = ~dpf::bitstring<80>{};
++wrapped;
EXPECT_EQ(wrapped, dpf::bitstring<80>{});
--wrapped;
EXPECT_EQ(wrapped, ~dpf::bitstring<80>{});
}
TEST(BitString, MaskShiftPastZeroDoesNotIndex)
{
dpf::bitstring<8> b(1);
auto mask = dpf::utils::msb_of_v<dpf::bitstring<8>>;
mask >>= 8;
EXPECT_FALSE(static_cast<bool>(mask));
EXPECT_FALSE(mask & b);
EXPECT_FALSE(b & mask);
}
TEST(BitString, ModPow2WideShiftIsDefined)
{
dpf::bitstring<80> b;
b.set(3, true);
b.set(70, true);
const auto mod = dpf::utils::mod_pow_2<dpf::bitstring<80>>{};
EXPECT_EQ(mod(b, 4), 8u);
EXPECT_EQ(mod(b, 70), 8u);
EXPECT_EQ(mod(b, 0), 0u);
}
TEST(BitArray, IteratorArithmeticAndAlgorithms)
{
using iter = dpf::dynamic_bit_array<>::iterator;
EXPECT_TRUE(std::is_default_constructible_v<iter>);
iter singular_a, singular_b;
EXPECT_EQ(singular_a, singular_b);
dpf::static_bit_array<40, std::uint8_t> narrow;
narrow.unset();
narrow.set(7, true);
auto it = narrow.begin() + 8;
it += -1;
EXPECT_EQ(it, narrow.begin() + 7);
EXPECT_TRUE(static_cast<bool>(*it));
EXPECT_EQ(narrow.end() - static_cast<std::ptrdiff_t>(narrow.size()), narrow.begin());
dpf::static_bit_array<16> bits;
bits.unset();
for (std::size_t i = 0; i < 16; ++i) bits.set(i, i < 4);
using std::swap;
swap(bits[0], bits[15]);
EXPECT_FALSE(static_cast<bool>(bits[0]));
EXPECT_TRUE(static_cast<bool>(bits[15]));
std::reverse(bits.begin(), bits.end());
EXPECT_TRUE(static_cast<bool>(bits[0]));
EXPECT_FALSE(static_cast<bool>(bits[15]));
}
TEST(BitString, LiteralMatchesIntegerCtor)
{
EXPECT_EQ(kLiteral, dpf::bitstring<8>(0b10101001));
EXPECT_EQ(kLiteral.to_string(), "10101001");
}