@@ -83,6 +83,22 @@ func TestM_JSON(t *testing.T) {
8383 t .Errorf ("FAILED !AddIfNotHas = %v; want %v" , cnt2 , shallBe )
8484 }
8585}
86+
87+ func TestSipHashLowAlwaysOdd (t * testing.T ) {
88+ bf , err := New (float64 (1 << 20 ), float64 (7 ))
89+ if err != nil {
90+ t .Fatal (err )
91+ }
92+
93+ for i := range 10000 {
94+ entry := []byte ("entry-" + strconv .Itoa (i ))
95+ l , _ := bf .sipHash (entry )
96+ if l % 2 == 0 {
97+ t .Fatalf ("l is even for entry %q: l=%d" , entry , l )
98+ }
99+ }
100+ }
101+
86102func TestJSON_ElementsRoundTrip (t * testing.T ) {
87103 bf , err := New (float64 (n * 10 ), float64 (7 ))
88104 if err != nil {
@@ -176,6 +192,39 @@ func TestNewWithKeys(t *testing.T) {
176192 }
177193}
178194
195+ func TestJSONBackwardCompatV0 (t * testing.T ) {
196+ // simulate a filter created with the legacy hash (version 0)
197+ bf , err := New (float64 (n * 10 ), float64 (7 ))
198+ if err != nil {
199+ t .Fatal (err )
200+ }
201+ bf .hashVersion = 0 // legacy
202+
203+ entries := wordlist1 [:1000 ]
204+ for _ , e := range entries {
205+ bf .Add (e )
206+ }
207+
208+ // serialize (will have Version:0 which is omitted from JSON)
209+ data := bf .JSONMarshal ()
210+
211+ // deserialize -- should restore version 0 and use legacy hash
212+ bf2 , err := JSONUnmarshal (data )
213+ if err != nil {
214+ t .Fatal (err )
215+ }
216+
217+ if bf2 .hashVersion != 0 {
218+ t .Fatalf ("expected hashVersion 0, got %d" , bf2 .hashVersion )
219+ }
220+
221+ for _ , e := range entries {
222+ if ! bf2 .Has (e ) {
223+ t .Fatalf ("v0 filter lost entry %q after JSON round-trip" , e )
224+ }
225+ }
226+ }
227+
179228func TestNewWithKeysJSON (t * testing.T ) {
180229 k0 := uint64 (0x0123456789abcdef )
181230 k1 := uint64 (0xfedcba9876543210 )
@@ -209,6 +258,49 @@ func TestNewWithKeysJSON(t *testing.T) {
209258 }
210259}
211260
261+ func TestJSONRoundTripV1 (t * testing.T ) {
262+ bf , err := New (float64 (n * 10 ), float64 (7 ))
263+ if err != nil {
264+ t .Fatal (err )
265+ }
266+
267+ entries := wordlist1 [:1000 ]
268+ for _ , e := range entries {
269+ bf .Add (e )
270+ }
271+
272+ data := bf .JSONMarshal ()
273+
274+ bf2 , err := JSONUnmarshal (data )
275+ if err != nil {
276+ t .Fatal (err )
277+ }
278+
279+ if bf2 .hashVersion != 1 {
280+ t .Fatalf ("expected hashVersion 1, got %d" , bf2 .hashVersion )
281+ }
282+
283+ for _ , e := range entries {
284+ if ! bf2 .Has (e ) {
285+ t .Fatalf ("v1 filter lost entry %q after JSON round-trip" , e )
286+ }
287+ }
288+ }
289+
290+ func TestJSONUnmarshalPartialKeys (t * testing.T ) {
291+ // Only K0 present, K1 absent -- should error, not silently fall back.
292+ jsonK0Only := []byte (`{"FilterSet":"AAAAAAAAAA==","SetLocs":3,"K0":42}` )
293+ if _ , err := JSONUnmarshal (jsonK0Only ); err == nil {
294+ t .Fatal ("expected error for JSON with K0 but no K1" )
295+ }
296+
297+ // Only K1 present, K0 absent.
298+ jsonK1Only := []byte (`{"FilterSet":"AAAAAAAAAA==","SetLocs":3,"K1":99}` )
299+ if _ , err := JSONUnmarshal (jsonK1Only ); err == nil {
300+ t .Fatal ("expected error for JSON with K1 but no K0" )
301+ }
302+ }
303+
212304func TestDefaultKeysOmittedFromJSON (t * testing.T ) {
213305 bf , err := New (float64 (512 ), float64 (3 ))
214306 if err != nil {
@@ -236,6 +328,69 @@ func TestDefaultKeysOmittedFromJSON(t *testing.T) {
236328 }
237329}
238330
331+ func TestNewWithBoolsetAndKeys (t * testing.T ) {
332+ k0 := uint64 (0x0123456789abcdef )
333+ k1 := uint64 (0xfedcba9876543210 )
334+ entries := wordlist1 [:1000 ]
335+
336+ // Build a reference filter with custom keys and populate it.
337+ ref , err := NewWithKeys (k0 , k1 , float64 (n * 10 ), float64 (7 ))
338+ if err != nil {
339+ t .Fatal (err )
340+ }
341+ for _ , e := range entries {
342+ ref .Add (e )
343+ }
344+
345+ // Export the raw bitset so we can reconstruct with NewWithBoolsetAndKeys.
346+ rawBitset := ref .JSONMarshal ()
347+ refImport , err := JSONUnmarshal (rawBitset )
348+ if err != nil {
349+ t .Fatal (err )
350+ }
351+
352+ t .Run ("keys are stored" , func (t * testing.T ) {
353+ // NewWithBoolsetAndKeys must propagate k0/k1 into the Bloom struct,
354+ // otherwise all lookups will use the wrong hash positions.
355+ got := NewWithBoolsetAndKeys (make ([]byte , 64 ), 7 , k0 , k1 )
356+ if got .k0 != k0 || got .k1 != k1 {
357+ t .Fatalf ("keys not set: got k0=%x k1=%x, want k0=%x k1=%x" ,
358+ got .k0 , got .k1 , k0 , k1 )
359+ }
360+ })
361+
362+ t .Run ("entries survive bitset round-trip" , func (t * testing.T ) {
363+ // A filter rebuilt from the same bitset and keys must recognize
364+ // every entry that was added to the original.
365+ for _ , e := range entries {
366+ if ! refImport .Has (e ) {
367+ t .Fatalf ("entry %q lost after round-trip" , e )
368+ }
369+ }
370+ })
371+
372+ t .Run ("wrong keys miss entries" , func (t * testing.T ) {
373+ // Unmarshal the custom-key filter, then force default keys.
374+ // Lookups must fail, proving the keys actually affect hashing.
375+ wrong , err := JSONUnmarshal (ref .JSONMarshal ())
376+ if err != nil {
377+ t .Fatal (err )
378+ }
379+ wrong .k0 = defaultK0
380+ wrong .k1 = defaultK1
381+
382+ misses := 0
383+ for _ , e := range entries {
384+ if ! wrong .Has (e ) {
385+ misses ++
386+ }
387+ }
388+ if misses == 0 {
389+ t .Fatal ("default keys matched every entry; custom keys had no effect" )
390+ }
391+ })
392+ }
393+
239394func TestFillRatio (t * testing.T ) {
240395 bf , err := New (float64 (512 ), float64 (7 ))
241396 if err != nil {
0 commit comments