aboutsummaryrefslogtreecommitdiff
path: root/internal/format/packidx/bloom/roundtrip_test.go
blob: 28db17a5888a87b882d472c1a54478098ff9c0bc (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package bloom_test

import (
	"bytes"
	"encoding/binary"
	"testing"

	"lindenii.org/go/furgit/internal/format/packidx/bloom"
	"lindenii.org/go/furgit/object/id"
)

func makeOID(size int, seed uint64) []byte {
	out := make([]byte, size)
	state := seed

	for i := 0; i < size; i += 8 {
		state = state*6364136223846793005 + 1442695040888963407

		var word [8]byte

		binary.BigEndian.PutUint64(word[:], state)
		copy(out[i:], word[:])
	}

	return out
}

func TestRoundTrip(t *testing.T) {
	t.Parallel()

	for _, format := range id.SupportedObjectFormats() {
		t.Run(format.String(), func(t *testing.T) {
			t.Parallel()

			const objects = 10000

			bucketCount, k, err := bloom.RecommendParams(format, objects)
			if err != nil {
				t.Fatal(err)
			}

			size := format.Size()
			packHash := makeOID(size, 0xC0FFEE)

			builder, err := bloom.NewBuilder(format, bucketCount, k, packHash)
			if err != nil {
				t.Fatal(err)
			}

			for i := range objects {
				builder.Add(makeOID(size, uint64(i)))
			}

			filter, err := bloom.Parse(builder.Bytes(), format)
			if err != nil {
				t.Fatal(err)
			}

			if !bytes.Equal(filter.PackHash(), packHash) {
				t.Fatalf("PackHash = %x, want %x", filter.PackHash(), packHash)
			}

			err = filter.Verify()
			if err != nil {
				t.Fatalf("Verify on a freshly built filter: %v", err)
			}

			for i := range objects {
				if !filter.MayContain(makeOID(size, uint64(i))) {
					t.Fatalf("false negative for added object %d", i)
				}
			}

			const probes = 10000

			falsePositives := 0

			for i := range probes {
				if filter.MayContain(makeOID(size, uint64(1)<<40+uint64(i))) {
					falsePositives++
				}
			}

			rate := float64(falsePositives) / float64(probes)
			if rate > 0.05 {
				t.Errorf("false positive rate %.4f exceeds 0.05", rate)
			}

			t.Logf("B=%d K=%d false positive rate %.4f", bucketCount, k, rate)
		})
	}
}