gcs_test.go raw

   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