1 package gcs_test
2 3 import (
4 "bytes"
5 "encoding/binary"
6 "math/rand"
7 "testing"
8 9 "github.com/p9c/p9/pkg/gcs"
10 )
11 12 var (
13 // No need to allocate an e variable in every test
14 e error
15 // Collision probability for the tests (1/2**19)
16 P = uint8(19)
17 // Modulus value for the tests.
18 M uint64 = 784931
19 // Filters are conserved between tests but we must define with an interface which functions we're testing because
20 // the gcsFilter type isn't exported
21 filter, filter2, filter3 /*, filter4, filter5*/ *gcs.Filter
22 // We need to use the same key for building and querying the filters
23 key [gcs.KeySize]byte
24 // List of values for building a filter
25 contents = [][]byte{
26 []byte("Alex"),
27 []byte("Bob"),
28 []byte("Charlie"),
29 []byte("Dick"),
30 []byte("Ed"),
31 []byte("Frank"),
32 []byte("George"),
33 []byte("Harry"),
34 []byte("Ilya"),
35 []byte("John"),
36 []byte("Kevin"),
37 []byte("Larry"),
38 []byte("Michael"),
39 []byte("Nate"),
40 []byte("Owen"),
41 []byte("Paul"),
42 []byte("Quentin"),
43 }
44 // List of values for querying a filter using MatchAny()
45 contents2 = [][]byte{
46 []byte("Alice"),
47 []byte("Betty"),
48 []byte("Charmaine"),
49 []byte("Donna"),
50 []byte("Edith"),
51 []byte("Faina"),
52 []byte("Georgia"),
53 []byte("Hannah"),
54 []byte("Ilsbeth"),
55 []byte("Jennifer"),
56 []byte("Kayla"),
57 []byte("Lena"),
58 []byte("Michelle"),
59 []byte("Natalie"),
60 []byte("Ophelia"),
61 []byte("Peggy"),
62 []byte("Queenie"),
63 }
64 )
65 66 // TestGCSFilterBuild builds a test filter with a randomized key. For Bitcoin use, deterministic filter generation is
67 // desired. Therefore, a key that's derived deterministically would be required.
68 func TestGCSFilterBuild(t *testing.T) {
69 for i := 0; i < gcs.KeySize; i += 4 {
70 binary.BigEndian.PutUint32(key[i:], rand.Uint32())
71 }
72 filter, e = gcs.BuildGCSFilter(P, M, key, contents)
73 if e != nil {
74 t.Fatalf("Filter podbuild failed: %s", e.Error())
75 }
76 }
77 78 // TestGCSFilterCopy deserializes and serializes a filter to create a copy.
79 func TestGCSFilterCopy(t *testing.T) {
80 var serialized2 []byte
81 serialized2, e = filter.Bytes()
82 if e != nil {
83 t.Fatalf("Filter Hash() failed: %v", e)
84 }
85 filter2, e = gcs.FromBytes(filter.N(), P, M, serialized2)
86 if e != nil {
87 t.Fatalf("Filter copy failed: %s", e.Error())
88 }
89 var serialized3 []byte
90 serialized3, e = filter.NBytes()
91 if e != nil {
92 t.Fatalf("Filter NBytes() failed: %v", e)
93 }
94 filter3, e = gcs.FromNBytes(filter.P(), M, serialized3)
95 if e != nil {
96 t.Fatalf("Filter copy failed: %s", e.Error())
97 }
98 }
99 100 // TestGCSFilterMetadata checks that the filter metadata is built and copied correctly.
101 func TestGCSFilterMetadata(t *testing.T) {
102 if filter.P() != P {
103 t.Fatal("P not correctly stored in filter metadata")
104 }
105 if filter.N() != uint32(len(contents)) {
106 t.Fatal("N not correctly stored in filter metadata")
107 }
108 if filter.P() != filter2.P() {
109 t.Fatal("P doesn't match between copied filters")
110 }
111 if filter.P() != filter3.P() {
112 t.Fatal("P doesn't match between copied filters")
113 }
114 if filter.N() != filter2.N() {
115 t.Fatal("N doesn't match between copied filters")
116 }
117 if filter.N() != filter3.N() {
118 t.Fatal("N doesn't match between copied filters")
119 }
120 var serialized []byte
121 serialized, e = filter.Bytes()
122 if e != nil {
123 t.Fatalf("Filter Hash() failed: %v", e)
124 }
125 var serialized2 []byte
126 serialized2, e = filter2.Bytes()
127 if e != nil {
128 t.Fatalf("Filter Hash() failed: %v", e)
129 }
130 if !bytes.Equal(serialized, serialized2) {
131 t.Fatal("Hash don't match between copied filters")
132 }
133 var serialized3 []byte
134 serialized3, e = filter3.Bytes()
135 if e != nil {
136 t.Fatalf("Filter Hash() failed: %v", e)
137 }
138 if !bytes.Equal(serialized, serialized3) {
139 t.Fatal("Hash don't match between copied filters")
140 }
141 var serialized4 []byte
142 serialized4, e = filter3.Bytes()
143 if e != nil {
144 t.Fatalf("Filter Hash() failed: %v", e)
145 }
146 if !bytes.Equal(serialized, serialized4) {
147 t.Fatal("Hash don't match between copied filters")
148 }
149 }
150 151 // TestGCSFilterMatch checks that both the built and copied filters match correctly, logging any false positives without
152 // failing on them.
153 func TestGCSFilterMatch(t *testing.T) {
154 match, e = filter.Match(key, []byte("Nate"))
155 if e != nil {
156 t.Fatalf("Filter match failed: %s", e.Error())
157 }
158 if !match {
159 t.Fatal("Filter didn't match when it should have!")
160 }
161 match, e = filter2.Match(key, []byte("Nate"))
162 if e != nil {
163 t.Fatalf("Filter match failed: %s", e.Error())
164 }
165 if !match {
166 t.Fatal("Filter didn't match when it should have!")
167 }
168 match, e = filter.Match(key, []byte("Quentin"))
169 if e != nil {
170 t.Fatalf("Filter match failed: %s", e.Error())
171 }
172 if !match {
173 t.Fatal("Filter didn't match when it should have!")
174 }
175 match, e = filter2.Match(key, []byte("Quentin"))
176 if e != nil {
177 t.Fatalf("Filter match failed: %s", e.Error())
178 }
179 if !match {
180 t.Fatal("Filter didn't match when it should have!")
181 }
182 match, e = filter.Match(key, []byte("Nates"))
183 if e != nil {
184 t.Fatalf("Filter match failed: %s", e.Error())
185 }
186 if match {
187 t.Logf("False positive match, should be 1 in 2**%d!", P)
188 }
189 match, e = filter2.Match(key, []byte("Nates"))
190 if e != nil {
191 t.Fatalf("Filter match failed: %s", e.Error())
192 }
193 if match {
194 t.Logf("False positive match, should be 1 in 2**%d!", P)
195 }
196 match, e = filter.Match(key, []byte("Quentins"))
197 if e != nil {
198 t.Fatalf("Filter match failed: %s", e.Error())
199 }
200 if match {
201 t.Logf("False positive match, should be 1 in 2**%d!", P)
202 }
203 match, e = filter2.Match(key, []byte("Quentins"))
204 if e != nil {
205 t.Fatalf("Filter match failed: %s", e.Error())
206 }
207 if match {
208 t.Logf("False positive match, should be 1 in 2**%d!", P)
209 }
210 }
211 212 // TestGCSFilterMatchAny checks that both the built and copied filters match a list correctly, logging any false
213 // positives without failing on them.
214 func TestGCSFilterMatchAny(t *testing.T) {
215 match, e = filter.MatchAny(key, contents2)
216 if e != nil {
217 t.Fatalf("Filter match any failed: %s", e.Error())
218 }
219 if match {
220 t.Logf("False positive match, should be 1 in 2**%d!", P)
221 }
222 match, e = filter2.MatchAny(key, contents2)
223 if e != nil {
224 t.Fatalf("Filter match any failed: %s", e.Error())
225 }
226 if match {
227 t.Logf("False positive match, should be 1 in 2**%d!", P)
228 }
229 contents2 = append(contents2, []byte("Nate"))
230 match, e = filter.MatchAny(key, contents2)
231 if e != nil {
232 t.Fatalf("Filter match any failed: %s", e.Error())
233 }
234 if !match {
235 t.Fatal("Filter didn't match any when it should have!")
236 }
237 match, e = filter2.MatchAny(key, contents2)
238 if e != nil {
239 t.Fatalf("Filter match any failed: %s", e.Error())
240 }
241 if !match {
242 t.Fatal("Filter didn't match any when it should have!")
243 }
244 }
245