// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bloom import ( "fmt" "math" "testing" "github.com/stretchr/testify/require" ) const largestFloat64LessThan1 float64 = 1 - 1e-16 func TestOptimalHashes(t *testing.T) { tests := []struct { numEntries int count int expectedHashes int }{ { // invalid params numEntries: 0, count: 1024, expectedHashes: minHashes, }, { // invalid params numEntries: 1024, count: 0, expectedHashes: maxHashes, }, { numEntries: math.MaxInt, count: 1, expectedHashes: maxHashes, }, { numEntries: 1, count: math.MaxInt, expectedHashes: minHashes, }, { numEntries: 1024, count: 1024, expectedHashes: 6, }, } for _, test := range tests { t.Run(fmt.Sprintf("%d_%d", test.numEntries, test.count), func(t *testing.T) { hashes := OptimalHashes(test.numEntries, test.count) require.Equal(t, test.expectedHashes, hashes) }) } } func TestOptimalEntries(t *testing.T) { tests := []struct { count int falsePositiveProbability float64 expectedEntries int }{ { // invalid params count: 0, falsePositiveProbability: .5, expectedEntries: minEntries, }, { // invalid params count: 1, falsePositiveProbability: 0, expectedEntries: math.MaxInt, }, { // invalid params count: 1, falsePositiveProbability: 1, expectedEntries: minEntries, }, { count: math.MaxInt, falsePositiveProbability: math.SmallestNonzeroFloat64, expectedEntries: math.MaxInt, }, { count: 1024, falsePositiveProbability: largestFloat64LessThan1, expectedEntries: minEntries, }, { count: 1024, falsePositiveProbability: .01, expectedEntries: 1227, }, } for _, test := range tests { t.Run(fmt.Sprintf("%d_%f", test.count, test.falsePositiveProbability), func(t *testing.T) { entries := OptimalEntries(test.count, test.falsePositiveProbability) require.Equal(t, test.expectedEntries, entries) }) } } func TestEstimateEntries(t *testing.T) { tests := []struct { numHashes int numEntries int falsePositiveProbability float64 expectedEntries int }{ { // invalid params numHashes: 0, numEntries: 2_048, falsePositiveProbability: .5, expectedEntries: 0, }, { // invalid params numHashes: 1, numEntries: 0, falsePositiveProbability: .5, expectedEntries: 0, }, { // invalid params numHashes: 1, numEntries: 1, falsePositiveProbability: 2, expectedEntries: math.MaxInt, }, { // invalid params numHashes: 1, numEntries: 1, falsePositiveProbability: -1, expectedEntries: 0, }, { numHashes: 8, numEntries: 2_048, falsePositiveProbability: 0, expectedEntries: 0, }, { // params from OptimalParameters(10_000, .01) numHashes: 7, numEntries: 11_982, falsePositiveProbability: .01, expectedEntries: 9_993, }, { // params from OptimalParameters(100_000, .001) numHashes: 10, numEntries: 179_720, falsePositiveProbability: .001, expectedEntries: 100_000, }, { // params from OptimalParameters(10_000, .01) numHashes: 7, numEntries: 11_982, falsePositiveProbability: .05, expectedEntries: 14_449, }, { // params from OptimalParameters(10_000, .01) numHashes: 7, numEntries: 11_982, falsePositiveProbability: 1, expectedEntries: math.MaxInt, }, { // params from OptimalParameters(10_000, .01) numHashes: 7, numEntries: 11_982, falsePositiveProbability: math.SmallestNonzeroFloat64, expectedEntries: 0, }, { // params from OptimalParameters(10_000, .01) numHashes: 7, numEntries: 11_982, falsePositiveProbability: largestFloat64LessThan1, expectedEntries: math.MaxInt, }, } for _, test := range tests { t.Run(fmt.Sprintf("%d_%d_%f", test.numHashes, test.numEntries, test.falsePositiveProbability), func(t *testing.T) { entries := EstimateCount(test.numHashes, test.numEntries, test.falsePositiveProbability) require.Equal(t, test.expectedEntries, entries) }) } } func FuzzOptimalHashes(f *testing.F) { f.Fuzz(func(t *testing.T, numEntries, count int) { hashes := OptimalHashes(numEntries, count) require.GreaterOrEqual(t, hashes, minHashes) require.LessOrEqual(t, hashes, maxHashes) }) } func FuzzOptimalEntries(f *testing.F) { f.Fuzz(func(t *testing.T, count int, falsePositiveProbability float64) { entries := OptimalEntries(count, falsePositiveProbability) require.GreaterOrEqual(t, entries, minEntries) }) } func FuzzEstimateEntries(f *testing.F) { f.Fuzz(func(t *testing.T, numHashes, numEntries int, falsePositiveProbability float64) { entries := EstimateCount(numHashes, numEntries, falsePositiveProbability) require.GreaterOrEqual(t, entries, 0) }) }