github.com/vc42/parquet-go@v0.0.0-20240320194221-1a9adb5f23f5/bloom/filter_test.go (about)

     1  package bloom_test
     2  
     3  import (
     4  	"bytes"
     5  	"math/rand"
     6  	"testing"
     7  
     8  	"github.com/vc42/parquet-go/bloom"
     9  )
    10  
    11  func TestSplitBlockFilter(t *testing.T) {
    12  	const N = 1000
    13  	const S = 3
    14  	f := make(bloom.SplitBlockFilter, bloom.NumSplitBlocksOf(N, 10))
    15  	p := rand.New(rand.NewSource(S))
    16  
    17  	// Half of the values are inserted individually.
    18  	for i := 0; i < N/2; i++ {
    19  		f.Insert(p.Uint64())
    20  	}
    21  	// The other half is inserted as a bulk operation.
    22  	b := make([]uint64, N/2)
    23  	for i := range b {
    24  		b[i] = p.Uint64()
    25  	}
    26  	f.InsertBulk(b)
    27  
    28  	if f.Block(0) == nil {
    29  		t.Fatal("looking up filter block returned impossible nil value")
    30  	}
    31  
    32  	for _, test := range []struct {
    33  		scenario string
    34  		filter   bloom.Filter
    35  	}{
    36  		{scenario: "filter", filter: f},
    37  		{scenario: "reader", filter: newSerializedFilter(f.Bytes())},
    38  	} {
    39  		t.Run(test.scenario, func(t *testing.T) {
    40  			p.Seed(S)
    41  			falsePositives := 0
    42  
    43  			for i := 0; i < N; i++ {
    44  				x := p.Uint64()
    45  
    46  				if !test.filter.Check(x) {
    47  					t.Fatalf("bloom filter block does not contain the value #%d that was inserted: %d", i, x)
    48  				}
    49  				if test.filter.Check(^x) {
    50  					falsePositives++
    51  				}
    52  			}
    53  
    54  			if r := (float64(falsePositives) / N); r > 0.01 {
    55  				t.Fatalf("bloom filter triggered too many false positives: %g%%", r*100)
    56  			}
    57  		})
    58  	}
    59  
    60  	t.Run("Reset", func(t *testing.T) {
    61  		allZeros := true
    62  		for _, b := range f.Bytes() {
    63  			if b != 0 {
    64  				allZeros = false
    65  				break
    66  			}
    67  		}
    68  		if allZeros {
    69  			t.Fatal("bloom filter bytes were all zero after inserting keys")
    70  		}
    71  		f.Reset()
    72  		for i, b := range f.Bytes() {
    73  			if b != 0 {
    74  				t.Fatalf("bloom filter byte at index %d was not zero after resetting the filter: %02X", i, b)
    75  			}
    76  		}
    77  	})
    78  }
    79  
    80  func TestSplitBlockFilterBug1(t *testing.T) {
    81  	// This test exercises the case where we bulk insert a single key in the
    82  	// filter, which skips the core of the optimized assembly routines and runs
    83  	// through the loop handling tails of remaining keys after consuming groups
    84  	// of two or more.
    85  	//
    86  	// The use of quick.Check in bloom filter tests of the parquet package had
    87  	// uncovered a bug which was reproduced here in isolation when debugging.
    88  	h := [1]uint64{0b1000101001000001001001111000000100011011001000011110011100110000}
    89  	f := make(bloom.SplitBlockFilter, 1)
    90  	f.InsertBulk(h[:])
    91  	if !f.Check(h[0]) {
    92  		t.Error("value inserted in the filter was not found")
    93  	}
    94  }
    95  
    96  type serializedFilter struct {
    97  	bytes.Reader
    98  }
    99  
   100  func (f *serializedFilter) Check(x uint64) bool {
   101  	ok, _ := bloom.CheckSplitBlock(&f.Reader, f.Size(), x)
   102  	return ok
   103  }
   104  
   105  func newSerializedFilter(b []byte) *serializedFilter {
   106  	f := new(serializedFilter)
   107  	f.Reset(b)
   108  	return f
   109  }
   110  
   111  func BenchmarkFilterInsertBulk(b *testing.B) {
   112  	f := make(bloom.SplitBlockFilter, 99)
   113  	x := make([]uint64, 16)
   114  	r := rand.NewSource(0).(rand.Source64)
   115  
   116  	for i := range x {
   117  		x[i] = r.Uint64()
   118  	}
   119  
   120  	for i := 0; i < b.N; i++ {
   121  		f.InsertBulk(x)
   122  	}
   123  
   124  	b.SetBytes(bloom.BlockSize * int64(len(x)))
   125  }
   126  
   127  func BenchmarkFilterInsert(b *testing.B) {
   128  	f := make(bloom.SplitBlockFilter, 1)
   129  	for i := 0; i < b.N; i++ {
   130  		f.Insert(uint64(i))
   131  	}
   132  	b.SetBytes(bloom.BlockSize)
   133  }
   134  
   135  func BenchmarkFilterCheck(b *testing.B) {
   136  	f := make(bloom.SplitBlockFilter, 1)
   137  	f.Insert(42)
   138  	for i := 0; i < b.N; i++ {
   139  		f.Check(42)
   140  	}
   141  	b.SetBytes(bloom.BlockSize)
   142  }