Some checks failed
Coverage / coverage (push) Failing after 6s
Tests / test (1.24.x, ubuntu-latest) (push) Failing after 42s
Tests / test (1.26.x, ubuntu-latest) (push) Failing after 38s
Tests / test (1.25.x, ubuntu-latest) (push) Failing after 35s
Tests / test (1.25.x, windows-latest) (push) Has been cancelled
Tests / test (1.26.x, macos-latest) (push) Has been cancelled
Tests / test (1.26.x, windows-latest) (push) Has been cancelled
Tests / test (1.24.x, macos-latest) (push) Has been cancelled
Tests / test (1.24.x, windows-latest) (push) Has been cancelled
Tests / test (1.25.x, macos-latest) (push) Has been cancelled
206 lines
5.2 KiB
Go
206 lines
5.2 KiB
Go
// Copyright (c) 2026 Couchbase, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package util
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/RoaringBitmap/roaring/v2"
|
|
)
|
|
|
|
func TestBitsetAddContains(t *testing.T) {
|
|
b := NewBitset(100, nil)
|
|
|
|
// values not yet added must not be present
|
|
for _, v := range []int{0, 1, 63, 64, 65, 100} {
|
|
if b.Contains(v) {
|
|
t.Fatalf("expected %d to be absent from an empty bitset", v)
|
|
}
|
|
}
|
|
|
|
// exercise word boundaries explicitly: 63 is the last bit of word 0,
|
|
// 64 is the first bit of word 1, 65 the second
|
|
added := []int{0, 1, 63, 64, 65, 100}
|
|
for _, v := range added {
|
|
b.Add(v)
|
|
}
|
|
for _, v := range added {
|
|
if !b.Contains(v) {
|
|
t.Fatalf("expected %d to be present after Add", v)
|
|
}
|
|
}
|
|
|
|
// a value between two set bits must remain absent
|
|
if b.Contains(50) {
|
|
t.Fatalf("expected 50 to be absent")
|
|
}
|
|
}
|
|
|
|
func TestBitsetAddDuplicate(t *testing.T) {
|
|
b := NewBitset(100, nil)
|
|
b.Add(42)
|
|
b.Add(42)
|
|
if got := b.Count(); got != 1 {
|
|
t.Fatalf("expected duplicate Add to keep count at 1, got %d", got)
|
|
}
|
|
if !b.Contains(42) {
|
|
t.Fatalf("expected 42 to be present")
|
|
}
|
|
}
|
|
|
|
func TestBitsetRemove(t *testing.T) {
|
|
b := NewBitset(100, nil)
|
|
b.Add(10)
|
|
b.Add(64)
|
|
b.Remove(10)
|
|
if b.Contains(10) {
|
|
t.Fatalf("expected 10 to be absent after Remove")
|
|
}
|
|
if !b.Contains(64) {
|
|
t.Fatalf("expected 64 to still be present")
|
|
}
|
|
// removing an absent value must be a no-op
|
|
b.Remove(99)
|
|
if b.Count() != 1 {
|
|
t.Fatalf("expected count 1 after removing an absent value, got %d", b.Count())
|
|
}
|
|
}
|
|
|
|
func TestBitsetCountAndClear(t *testing.T) {
|
|
b := NewBitset(200, nil)
|
|
for _, v := range []int{0, 5, 63, 64, 128, 200} {
|
|
b.Add(v)
|
|
}
|
|
if got := b.Count(); got != 6 {
|
|
t.Fatalf("expected count 6, got %d", got)
|
|
}
|
|
b.Clear()
|
|
if got := b.Count(); got != 0 {
|
|
t.Fatalf("expected count 0 after Clear, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestBitsetIterateAscending(t *testing.T) {
|
|
b := NewBitset(200, nil)
|
|
// add out of order to confirm Iterate returns ascending order
|
|
for _, v := range []int{130, 0, 64, 63, 7} {
|
|
b.Add(v)
|
|
}
|
|
var got []int
|
|
b.Iterate(func(v int) {
|
|
got = append(got, v)
|
|
})
|
|
want := []int{0, 7, 63, 64, 130}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("expected Iterate to yield %v in ascending order, got %v", want, got)
|
|
}
|
|
}
|
|
|
|
func TestBitsetExcludeBlocksAdd(t *testing.T) {
|
|
exclude := roaring.New()
|
|
exclude.AddInt(5)
|
|
exclude.AddInt(70)
|
|
|
|
b := NewBitset(100, exclude)
|
|
b.Add(5) // excluded, must be ignored
|
|
b.Add(6) // allowed
|
|
b.Add(70) // excluded, must be ignored
|
|
|
|
if b.Contains(5) {
|
|
t.Fatalf("expected excluded value 5 to be blocked by Add")
|
|
}
|
|
if b.Contains(70) {
|
|
t.Fatalf("expected excluded value 70 to be blocked by Add")
|
|
}
|
|
if !b.Contains(6) {
|
|
t.Fatalf("expected non-excluded value 6 to be present")
|
|
}
|
|
}
|
|
|
|
func TestBitsetInvertRespectsNumBits(t *testing.T) {
|
|
// numDocs = 10 means the valid doc IDs are 0..9. NewBitset allocates a
|
|
// full 64-bit word, so bits 10..63 are unused and must never surface.
|
|
numDocs := 10
|
|
b := NewBitset(numDocs, nil)
|
|
b.Add(3)
|
|
b.Add(7)
|
|
|
|
b.Invert()
|
|
|
|
var got []int
|
|
b.Iterate(func(v int) {
|
|
got = append(got, v)
|
|
})
|
|
|
|
// after inverting, exactly the doc IDs in [0, 10) that were NOT set
|
|
// should be present - and nothing >= 10
|
|
want := []int{0, 1, 2, 4, 5, 6, 8, 9}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("expected inverted bitset to yield %v, got %v", want, got)
|
|
}
|
|
if got := b.Count(); got != len(want) {
|
|
t.Fatalf("expected inverted count %d, got %d", len(want), got)
|
|
}
|
|
}
|
|
|
|
func TestBitsetInvertWordBoundaries(t *testing.T) {
|
|
// exercise numBits at and around 64-bit word boundaries to make sure
|
|
// the trailing-bit mask is computed correctly
|
|
for _, numDocs := range []int{1, 63, 64, 65, 128, 129} {
|
|
b := NewBitset(numDocs, nil)
|
|
b.Invert()
|
|
max := -1
|
|
b.Iterate(func(v int) {
|
|
if v > max {
|
|
max = v
|
|
}
|
|
})
|
|
// every valid doc ID [0, numDocs) should be present after inverting
|
|
// an empty bitset, and none at or beyond numDocs
|
|
if got := b.Count(); got != numDocs {
|
|
t.Fatalf("numDocs=%d: expected inverted-empty count %d, got %d",
|
|
numDocs, numDocs, got)
|
|
}
|
|
if max >= numDocs {
|
|
t.Fatalf("numDocs=%d: Invert surfaced out-of-range value %d",
|
|
numDocs, max)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBitsetInvertClearsExcluded(t *testing.T) {
|
|
// excluded docs must remain unset even after Invert, since they are
|
|
// never valid hits
|
|
exclude := roaring.New()
|
|
exclude.AddInt(2)
|
|
exclude.AddInt(8)
|
|
|
|
b := NewBitset(10, exclude)
|
|
b.Add(3)
|
|
b.Invert()
|
|
|
|
if b.Contains(2) || b.Contains(8) {
|
|
t.Fatalf("expected excluded docs to stay unset after Invert")
|
|
}
|
|
if b.Contains(3) {
|
|
t.Fatalf("expected the originally-set doc 3 to be cleared after Invert")
|
|
}
|
|
// a normal, non-excluded, originally-unset doc should now be set
|
|
if !b.Contains(0) {
|
|
t.Fatalf("expected doc 0 to be set after Invert")
|
|
}
|
|
}
|