Files
clice/tests/unit/support/context_bitmap_tests.cpp
ykiko 005fc9ac21 feat(index): optimize merge with ContextBitmap, hash caching, and selective merge
Introduce ContextBitmap (tagged-pointer inline bitmap for <63 ids) to
replace roaring::Roaring in MergedIndex hot paths, eliminating heap
allocations in the common case. Cache FileIndex SHA256 hashes to avoid
recomputation. Add selective ProjectIndex::merge that skips symbols not
referencing newly-merged files. MergedIndex::merge now returns bool to
indicate cache hits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-18 14:49:39 +08:00

255 lines
4.8 KiB
C++

#include "test/test.h"
#include "support/bitmap.h"
namespace clice::testing {
namespace {
TEST_SUITE(ContextBitmap) {
TEST_CASE(EmptyByDefault) {
ContextBitmap bm;
EXPECT_TRUE(bm.is_empty());
}
TEST_CASE(AddInline) {
ContextBitmap bm;
bm.add(0);
EXPECT_FALSE(bm.is_empty());
bm.add(62);
EXPECT_FALSE(bm.is_empty());
auto r = bm.to_roaring();
EXPECT_TRUE(r.contains(0));
EXPECT_TRUE(r.contains(62));
EXPECT_FALSE(r.contains(1));
EXPECT_EQ(r.cardinality(), 2U);
}
TEST_CASE(RemoveInline) {
ContextBitmap bm;
bm.add(5);
bm.add(10);
bm.remove(5);
auto r = bm.to_roaring();
EXPECT_FALSE(r.contains(5));
EXPECT_TRUE(r.contains(10));
EXPECT_EQ(r.cardinality(), 1U);
bm.remove(10);
EXPECT_TRUE(bm.is_empty());
}
TEST_CASE(UpgradeToHeap) {
ContextBitmap bm;
bm.add(5);
bm.add(63);
EXPECT_FALSE(bm.is_empty());
auto r = bm.to_roaring();
EXPECT_TRUE(r.contains(5));
EXPECT_TRUE(r.contains(63));
EXPECT_EQ(r.cardinality(), 2U);
}
TEST_CASE(HeapAddRemove) {
ContextBitmap bm;
bm.add(100);
bm.add(200);
bm.remove(100);
auto r = bm.to_roaring();
EXPECT_FALSE(r.contains(100));
EXPECT_TRUE(r.contains(200));
}
TEST_CASE(AnyNotInBothInline) {
ContextBitmap a, b;
a.add(1);
a.add(2);
b.add(1);
b.add(2);
EXPECT_FALSE(a.any_not_in(b));
a.add(3);
EXPECT_TRUE(a.any_not_in(b));
}
TEST_CASE(AnyNotInInlineVsHeap) {
ContextBitmap a, b;
a.add(5);
b.add(5);
b.add(100);
EXPECT_FALSE(a.any_not_in(b));
a.add(10);
EXPECT_TRUE(a.any_not_in(b));
}
TEST_CASE(AnyNotInHeapVsInline) {
ContextBitmap a, b;
a.add(100);
b.add(5);
// 100 >= 63 not in inline b
EXPECT_TRUE(a.any_not_in(b));
ContextBitmap c, d;
c.add(5);
c.add(100);
d.add(5);
// c has 100 which d doesn't have
EXPECT_TRUE(c.any_not_in(d));
}
TEST_CASE(AnyNotInBothHeap) {
ContextBitmap a, b;
a.add(100);
a.add(200);
b.add(100);
b.add(200);
EXPECT_FALSE(a.any_not_in(b));
a.add(300);
EXPECT_TRUE(a.any_not_in(b));
}
TEST_CASE(MoveConstruct) {
ContextBitmap a;
a.add(5);
a.add(10);
ContextBitmap b(std::move(a));
EXPECT_TRUE(a.is_empty());
EXPECT_FALSE(b.is_empty());
auto r = b.to_roaring();
EXPECT_TRUE(r.contains(5));
EXPECT_TRUE(r.contains(10));
}
TEST_CASE(MoveConstructHeap) {
ContextBitmap a;
a.add(100);
ContextBitmap b(std::move(a));
EXPECT_TRUE(a.is_empty());
auto r = b.to_roaring();
EXPECT_TRUE(r.contains(100));
}
TEST_CASE(MoveAssign) {
ContextBitmap a, b;
a.add(3);
b.add(7);
b = std::move(a);
EXPECT_TRUE(a.is_empty());
auto r = b.to_roaring();
EXPECT_TRUE(r.contains(3));
EXPECT_FALSE(r.contains(7));
}
TEST_CASE(FromRoaringInline) {
roaring::Roaring r;
r.add(0);
r.add(30);
r.add(62);
auto bm = ContextBitmap::from_roaring(r);
EXPECT_FALSE(bm.is_empty());
auto out = bm.to_roaring();
EXPECT_EQ(out.cardinality(), 3U);
EXPECT_TRUE(out.contains(0));
EXPECT_TRUE(out.contains(30));
EXPECT_TRUE(out.contains(62));
}
TEST_CASE(FromRoaringHeap) {
roaring::Roaring r;
r.add(5);
r.add(100);
auto bm = ContextBitmap::from_roaring(r);
auto out = bm.to_roaring();
EXPECT_EQ(out.cardinality(), 2U);
EXPECT_TRUE(out.contains(5));
EXPECT_TRUE(out.contains(100));
}
TEST_CASE(FromRoaringEmpty) {
roaring::Roaring r;
auto bm = ContextBitmap::from_roaring(r);
EXPECT_TRUE(bm.is_empty());
}
TEST_CASE(Equality) {
ContextBitmap a, b;
a.add(1);
a.add(2);
b.add(1);
b.add(2);
EXPECT_TRUE(a == b);
b.add(3);
EXPECT_FALSE(a == b);
}
TEST_CASE(BoundaryId62) {
ContextBitmap bm;
bm.add(62);
auto r = bm.to_roaring();
EXPECT_TRUE(r.contains(62));
EXPECT_EQ(r.cardinality(), 1U);
}
TEST_CASE(BoundaryId63) {
ContextBitmap bm;
bm.add(63);
auto r = bm.to_roaring();
EXPECT_TRUE(r.contains(63));
EXPECT_EQ(r.cardinality(), 1U);
}
TEST_CASE(MoveAssignHeap) {
ContextBitmap a, b;
a.add(100);
a.add(200);
b.add(300);
b.add(400);
b = std::move(a);
EXPECT_TRUE(a.is_empty());
auto r = b.to_roaring();
EXPECT_TRUE(r.contains(100));
EXPECT_TRUE(r.contains(200));
EXPECT_FALSE(r.contains(300));
EXPECT_FALSE(r.contains(400));
}
TEST_CASE(EqualityHeap) {
ContextBitmap a, b;
a.add(100);
a.add(200);
b.add(100);
b.add(200);
EXPECT_TRUE(a == b);
b.add(300);
EXPECT_FALSE(a == b);
}
TEST_CASE(AddSmallAfterUpgrade) {
ContextBitmap bm;
bm.add(100);
bm.add(5);
auto r = bm.to_roaring();
EXPECT_TRUE(r.contains(100));
EXPECT_TRUE(r.contains(5));
EXPECT_EQ(r.cardinality(), 2U);
}
}; // TEST_SUITE(ContextBitmap)
} // namespace
} // namespace clice::testing