mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-15 08:41:02 +00:00
This commit refactors the roaring bitmaps to use uint64 values instead of uint32 values. This is required for the size of values we need in pilosa. This change is not backwards compatible with the previous data format so any existing data directories need to be removed before using this code.
231 lines
6.7 KiB
Go
231 lines
6.7 KiB
Go
package roaring_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"testing/quick"
|
|
|
|
"github.com/umbel/pilosa/roaring"
|
|
)
|
|
|
|
// Ensure an empty bitmap returns false if checking for existence.
|
|
func TestBitmap_Contains_Empty(t *testing.T) {
|
|
if roaring.NewBitmap().Contains(1000) {
|
|
t.Fatal("expected false")
|
|
}
|
|
}
|
|
|
|
// Ensure an empty bitmap does nothing when removing an element.
|
|
func TestBitmap_Remove_Empty(t *testing.T) {
|
|
roaring.NewBitmap().Remove(1000)
|
|
}
|
|
|
|
// Ensure a bitmap can return a slice of values.
|
|
func TestBitmap_Slice(t *testing.T) {
|
|
if a := roaring.NewBitmap(1, 2, 3).Slice(); !reflect.DeepEqual(a, []uint64{1, 2, 3}) {
|
|
t.Fatalf("unexpected slice: %+v", a)
|
|
}
|
|
}
|
|
|
|
// Ensure an empty bitmap returns an empty slice of values.
|
|
func TestBitmap_Slice_Empty(t *testing.T) {
|
|
if a := roaring.NewBitmap().Slice(); len(a) != 0 {
|
|
t.Fatalf("unexpected slice: %+v", a)
|
|
}
|
|
}
|
|
|
|
// Ensure a bitmap can return a slice of values within a range.
|
|
func TestBitmap_SliceRange(t *testing.T) {
|
|
if a := roaring.NewBitmap(0, 1000001, 1000002, 1000003).SliceRange(1, 1000003); !reflect.DeepEqual(a, []uint64{1000001, 1000002}) {
|
|
t.Fatalf("unexpected slice: %+v", a)
|
|
}
|
|
}
|
|
|
|
// Ensure a bitmap can loop over a set of values.
|
|
func TestBitmap_ForEach(t *testing.T) {
|
|
var a []uint64
|
|
roaring.NewBitmap(1, 2, 3).ForEach(func(v uint64) {
|
|
a = append(a, v)
|
|
})
|
|
if !reflect.DeepEqual(a, []uint64{1, 2, 3}) {
|
|
t.Fatalf("unexpected values: %+v", a)
|
|
}
|
|
}
|
|
|
|
// Ensure a bitmap can loop over a set of values in a range.
|
|
func TestBitmap_ForEachRange(t *testing.T) {
|
|
var a []uint64
|
|
roaring.NewBitmap(1, 2, 3, 4).ForEachRange(2, 4, func(v uint64) {
|
|
a = append(a, v)
|
|
})
|
|
if !reflect.DeepEqual(a, []uint64{2, 3}) {
|
|
t.Fatalf("unexpected values: %+v", a)
|
|
}
|
|
}
|
|
|
|
func TestBitmap_Quick_Array1(t *testing.T) { testBitmapQuick(t, 1000, 1000, 2000) }
|
|
func TestBitmap_Quick_Array2(t *testing.T) { testBitmapQuick(t, 10000, 0, 1000) }
|
|
func TestBitmap_Quick_Bitmap1(t *testing.T) { testBitmapQuick(t, 10000, 0, 10000) }
|
|
func TestBitmap_Quick_Bitmap2(t *testing.T) { testBitmapQuick(t, 10000, 10000, 20000) }
|
|
func TestBitmap_Quick_LargeValue(t *testing.T) { testBitmapQuick(t, 10000, 0, math.MaxInt64) }
|
|
|
|
// Ensure a bitmap can perform basic operations on randomly generated values.
|
|
func testBitmapQuick(t *testing.T, n int, min, max uint64) {
|
|
quick.Check(func(a []uint64) bool {
|
|
bm := roaring.NewBitmap()
|
|
m := make(map[uint64]struct{})
|
|
|
|
// Add values to the bitmap and set.
|
|
for _, v := range a {
|
|
bm.Add(v)
|
|
m[v] = struct{}{}
|
|
}
|
|
|
|
// Verify existence.
|
|
for _, v := range a {
|
|
// Check for individual value.
|
|
if !bm.Contains(v) {
|
|
t.Fatalf("expected bitmap to contain: %d", v)
|
|
}
|
|
|
|
// Check for next value (which may or may not exist).
|
|
if _, ok := m[v+1]; bm.Contains(v+1) != ok {
|
|
t.Fatalf("unexpected return from Contains(%d): %v", v+1, bm.Contains(v+1))
|
|
}
|
|
}
|
|
|
|
// Verify slices are equal.
|
|
if got, exp := bm.Slice(), uint64SetSlice(m); !reflect.DeepEqual(got, exp) {
|
|
t.Fatalf("unexpected values:\n\ngot=%+v\n\nexp=%+v\n\n", got, exp)
|
|
}
|
|
|
|
// Remove all values in random order.
|
|
for _, i := range rand.Perm(len(a)) {
|
|
bm.Remove(a[i])
|
|
}
|
|
|
|
// Verify all values have been removed.
|
|
if slice := bm.Slice(); len(slice) != 0 {
|
|
t.Fatalf("expected no values, got: %+v", slice)
|
|
}
|
|
|
|
return true
|
|
}, &quick.Config{
|
|
Values: func(values []reflect.Value, rand *rand.Rand) {
|
|
values[0] = reflect.ValueOf(GenerateUint64Slice(n, min, max, rand))
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBitmap_Marshal_Quick_Array1(t *testing.T) { testBitmapMarshalQuick(t, 1000, 1000, 2000) }
|
|
func TestBitmap_Marshal_Quick_Array2(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 1000) }
|
|
func TestBitmap_Marshal_Quick_Bitmap1(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 10000) }
|
|
func TestBitmap_Marshal_Quick_Bitmap2(t *testing.T) { testBitmapMarshalQuick(t, 10000, 10000, 20000) }
|
|
func TestBitmap_Marshal_Quick_LargeValue(t *testing.T) {
|
|
testBitmapMarshalQuick(t, 100, 0, math.MaxInt64)
|
|
}
|
|
|
|
// Ensure a bitmap can be marshaled and unmarshaled.
|
|
func testBitmapMarshalQuick(t *testing.T, n int, min, max uint64) {
|
|
if testing.Short() {
|
|
t.Skip("short")
|
|
}
|
|
|
|
quick.Check(func(a0, a1 []uint64) bool {
|
|
// Create bitmap with initial values set.
|
|
bm := roaring.NewBitmap(a0...)
|
|
|
|
set := make(map[uint64]struct{})
|
|
for _, v := range a0 {
|
|
set[v] = struct{}{}
|
|
}
|
|
|
|
// Write snapshot to buffer.
|
|
var buf bytes.Buffer
|
|
if n, err := bm.WriteTo(&buf); err != nil {
|
|
t.Fatal(err)
|
|
} else if n != int64(buf.Len()) {
|
|
t.Fatalf("size mismatch: %d != %d", n, buf.Len())
|
|
}
|
|
|
|
// Set buffer as the writer for the ops log.
|
|
bm.OpWriter = &buf
|
|
|
|
// Add more values to bitmap.
|
|
for _, v := range a1 {
|
|
set[v] = struct{}{}
|
|
if err := bm.Add(v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Extract buffer as a byte slice so it can be mapped.
|
|
data := buf.Bytes()
|
|
|
|
// Create new bitmap from ops log data.
|
|
bm2 := roaring.NewBitmap()
|
|
if err := bm2.UnmarshalBinary(data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify the original bitmap has the correct set of values.
|
|
if exp, got := uint64SetSlice(set), bm.Slice(); !reflect.DeepEqual(exp, got) {
|
|
t.Fatalf("mismatch: %s\n\nexp=%+v\n\ngot=%+v\n\n", diff(exp, got), exp, got)
|
|
}
|
|
|
|
// Verify the bitmap loaded with the ops log has the correct set of values.
|
|
if exp, got := uint64SetSlice(set), bm2.Slice(); !reflect.DeepEqual(exp, got) {
|
|
t.Fatalf("mismatch: %s\n\nexp=%+v\n\ngot=%+v\n\n", diff(exp, got), exp, got)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}, &quick.Config{
|
|
Values: func(values []reflect.Value, rand *rand.Rand) {
|
|
values[0] = reflect.ValueOf(GenerateUint64Slice(n, min, max, rand))
|
|
values[1] = reflect.ValueOf(GenerateUint64Slice(100, min, max, rand))
|
|
},
|
|
})
|
|
}
|
|
|
|
// GenerateUint64Slice generates between [0, n) random uint64 numbers between min and max.
|
|
func GenerateUint64Slice(n int, min, max uint64, rand *rand.Rand) []uint64 {
|
|
a := make([]uint64, rand.Intn(n))
|
|
for i := range a {
|
|
a[i] = min + uint64(rand.Int63n(int64(max-min)))
|
|
}
|
|
return a
|
|
}
|
|
|
|
// uint64SetSlice returns the values in a uint64 set.
|
|
func uint64SetSlice(m map[uint64]struct{}) []uint64 {
|
|
a := make([]uint64, 0, len(m))
|
|
for v := range m {
|
|
a = append(a, v)
|
|
}
|
|
sort.Sort(uint64Slice(a))
|
|
return a
|
|
}
|
|
|
|
// uint64Slice represents a sortable slice of uint64 numbers.
|
|
type uint64Slice []uint64
|
|
|
|
func (p uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p uint64Slice) Len() int { return len(p) }
|
|
func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
|
|
|
func diff(a, b []uint64) string {
|
|
if len(a) != len(b) {
|
|
return fmt.Sprintf("len: %d != %d", len(a), len(b))
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return fmt.Sprintf("index %d: %d != %d", i, a[i], b[i])
|
|
}
|
|
}
|
|
return ""
|
|
}
|