gcs: Overhaul tests and benchmarks.

This rewrites the tests to make them more consistent with the rest of
the code base and significantly increases their coverage of the code.

It also reworks the benchmarks to actually benchmark what their names
claim, renames them for consistency, and make them more stable by
ensuring the same prng seed is used each run to eliminate variance
introduced by different values.

Finally, it removes an impossible to hit condition from the bit reader
and adds a couple of additional checks to harden the filters against
potential misuse.

This is part of the ongoing process to cleanup and improve the gcs
module to the quality level required by consensus code for ultimate
inclusion in header commitments.
This commit is contained in:
Dave Collins 2019-08-19 14:57:06 -05:00
parent 468f3287c2
commit 2a8856d026
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
5 changed files with 451 additions and 307 deletions

127
gcs/bench_test.go Normal file
View File

@ -0,0 +1,127 @@
// Copyright (c) 2018-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package gcs
import (
"math/rand"
"testing"
)
var (
// globalMatch is used to ensure the benchmarks do not elide code.
globalMatch bool
// Collision probability for the benchmarks (1/2**20).
P = uint8(20)
)
// genFilterElements generates the given number of elements using the provided
// prng. This allows a prng with a fixed seed to be provided so the same values
// are produced for each benchmark iteration.
func genFilterElements(numElements uint, prng *rand.Rand) ([][]byte, error) {
result := make([][]byte, numElements)
for i := uint(0); i < numElements; i++ {
randElem := make([]byte, 32)
if _, err := prng.Read(randElem); err != nil {
return nil, err
}
result[i] = randElem
}
return result, nil
}
// BenchmarkFilterBuild benchmarks building a filter with 50000 elements.
func BenchmarkFilterBuild50000(b *testing.B) {
// Use a fixed prng seed for stable benchmarks.
prng := rand.New(rand.NewSource(0))
contents, err := genFilterElements(50000, prng)
if err != nil {
b.Fatalf("unable to generate random item: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
var key [KeySize]byte
for i := 0; i < b.N; i++ {
_, err := NewFilter(P, key, contents)
if err != nil {
b.Fatalf("unable to generate filter: %v", err)
}
}
}
// BenchmarkFilterBuild benchmarks building a filter with 100000 elements.
func BenchmarkFilterBuild100000(b *testing.B) {
// Use a fixed prng seed for stable benchmarks.
prng := rand.New(rand.NewSource(0))
contents, err := genFilterElements(100000, prng)
if err != nil {
b.Fatalf("unable to generate random item: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
var key [KeySize]byte
for i := 0; i < b.N; i++ {
_, err := NewFilter(P, key, contents)
if err != nil {
b.Fatalf("unable to generate filter: %v", err)
}
}
}
// BenchmarkFilterMatch benchmarks querying a filter for a single value.
func BenchmarkFilterMatch(b *testing.B) {
// Use a fixed prng seed for stable benchmarks.
prng := rand.New(rand.NewSource(0))
contents, err := genFilterElements(20, prng)
if err != nil {
b.Fatalf("unable to generate random item: %v", err)
}
var key [KeySize]byte
filter, err := NewFilter(P, key, contents)
if err != nil {
b.Fatalf("Failed to build filter")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
globalMatch = filter.Match(key, []byte("Nate"))
globalMatch = filter.Match(key, []byte("Nates"))
}
}
// BenchmarkFilterMatchAny benchmarks querying a filter for a list of values.
func BenchmarkFilterMatchAny(b *testing.B) {
// Generate elements for filter.
prng1 := rand.New(rand.NewSource(0))
contents, err := genFilterElements(20, prng1)
if err != nil {
b.Fatalf("unable to generate random item: %v", err)
}
// Generate matches using a separate prng seed so they're very likely all
// misses.
prng2 := rand.New(rand.NewSource(1))
matchList, err := genFilterElements(20, prng2)
if err != nil {
b.Fatalf("unable to generate random item: %v", err)
}
var key [KeySize]byte
filter, err := NewFilter(P, key, contents)
if err != nil {
b.Fatalf("Failed to build filter")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
globalMatch = filter.MatchAny(key, matchList)
}
}

View File

@ -127,6 +127,9 @@ func (b *bitReader) readNBits(n uint) (uint64, error) {
if n > 64 {
panic("gcs: cannot read more than 64 bits as a uint64")
}
if n == 0 {
return 0, nil
}
if len(b.bytes) == 0 {
return 0, io.EOF
@ -175,14 +178,6 @@ func (b *bitReader) readNBits(n uint) (uint64, error) {
// Read the remaining bits.
for n > 0 {
if b.next == 0 {
b.bytes = b.bytes[1:]
if len(b.bytes) == 0 {
return 0, io.EOF
}
b.next = 1 << 7
}
n--
if b.bytes[0]&b.next != 0 {
value |= 1 << n

View File

@ -161,11 +161,13 @@ func FromBytes(N uint32, P uint8, d []byte) (*Filter, error) {
// FromNBytes deserializes a GCS filter from a known P, and serialized N and
// filter as returned by NBytes().
func FromNBytes(P uint8, d []byte) (*Filter, error) {
if len(d) < 4 {
var n uint32
if len(d) >= 4 {
n = binary.BigEndian.Uint32(d[:4])
} else if len(d) < 4 && len(d) != 0 {
return nil, ErrMisserialized
}
n := binary.BigEndian.Uint32(d[:4])
f := &Filter{
n: n,
p: P,

View File

@ -1,6 +1,4 @@
// Copyright (c) 2016-2017 The btcsuite developers
// Copyright (c) 2016-2017 The Lightning Network Developers
// Copyright (c) 2018 The Decred developers
// Copyright (c) 2018-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -9,218 +7,340 @@ package gcs
import (
"bytes"
"encoding/binary"
"encoding/hex"
"math/rand"
"testing"
"time"
"github.com/decred/dcrd/chaincfg/chainhash"
)
var (
// No need to allocate an err variable in every test
err error
// Collision probability for the tests (1/2**20)
P = uint8(20)
// Filters are conserved between tests but we must define with an
// interface which functions we're testing because the gcsFilter type
// isn't exported
filter, filter2, filter3 *Filter
// We need to use the same key for building and querying the filters
key [KeySize]byte
// List of values for building a filter
contents = [][]byte{
[]byte("Alex"),
[]byte("Bob"),
[]byte("Charlie"),
[]byte("Dick"),
[]byte("Ed"),
[]byte("Frank"),
[]byte("George"),
[]byte("Harry"),
[]byte("Ilya"),
[]byte("John"),
[]byte("Kevin"),
[]byte("Larry"),
[]byte("Michael"),
[]byte("Nate"),
[]byte("Owen"),
[]byte("Paul"),
[]byte("Quentin"),
}
// List of values for querying a filter using MatchAny()
contents2 = [][]byte{
[]byte("Alice"),
[]byte("Betty"),
[]byte("Charmaine"),
[]byte("Donna"),
[]byte("Edith"),
[]byte("Faina"),
[]byte("Georgia"),
[]byte("Hannah"),
[]byte("Ilsbeth"),
[]byte("Jennifer"),
[]byte("Kayla"),
[]byte("Lena"),
[]byte("Michelle"),
[]byte("Natalie"),
[]byte("Ophelia"),
[]byte("Peggy"),
[]byte("Queenie"),
}
)
// TestGCSFilterBuild builds a test filter with a randomized key. For Bitcoin
// use, deterministic filter generation is desired. Therefore, a key that's
// derived deterministically would be required.
func TestGCSFilterBuild(t *testing.T) {
// TestFilter ensures the filters and all associated methods work as expected by
// using various known parameters and contents along with random keys for
// matching purposes.
func TestFilter(t *testing.T) {
// Use a random key for each test instance and log it if the tests fail.
rng := rand.New(rand.NewSource(time.Now().Unix()))
var randKey [KeySize]byte
for i := 0; i < KeySize; i += 4 {
binary.BigEndian.PutUint32(key[i:], rand.Uint32())
binary.BigEndian.PutUint32(randKey[i:], rng.Uint32())
}
filter, err = NewFilter(P, key, contents)
if err != nil {
t.Fatalf("Filter build failed: %s", err.Error())
defer func(t *testing.T, randKey [KeySize]byte) {
if t.Failed() {
t.Logf("random key: %x", randKey)
}
}(t, randKey)
// contents1 defines a set of known elements for use in the tests below.
contents1 := [][]byte{[]byte("Alex"), []byte("Bob"), []byte("Charlie"),
[]byte("Dick"), []byte("Ed"), []byte("Frank"), []byte("George"),
[]byte("Harry"), []byte("Ilya"), []byte("John"), []byte("Kevin"),
[]byte("Larry"), []byte("Michael"), []byte("Nate"), []byte("Owen"),
[]byte("Paul"), []byte("Quentin"),
}
// contents2 defines a separate set of known elements for use in the tests
// below.
contents2 := [][]byte{[]byte("Alice"), []byte("Betty"),
[]byte("Charmaine"), []byte("Donna"), []byte("Edith"), []byte("Faina"),
[]byte("Georgia"), []byte("Hannah"), []byte("Ilsbeth"),
[]byte("Jennifer"), []byte("Kayla"), []byte("Lena"), []byte("Michelle"),
[]byte("Natalie"), []byte("Ophelia"), []byte("Peggy"), []byte("Queenie"),
}
tests := []struct {
name string // test description
p uint8 // collision probability
matchKey [KeySize]byte // random filter key for matches
contents [][]byte // data to include in the filter
wantMatches [][]byte // expected matches
fixedKey [KeySize]byte // fixed filter key for testing serialization
wantBytes string // expected serialized bytes
wantNBytes string // expected serialized bytes with N param
wantHash string // expected filter hash
}{{
name: "empty filter",
p: 20,
matchKey: randKey,
contents: nil,
wantMatches: nil,
fixedKey: [KeySize]byte{},
wantBytes: "",
wantNBytes: "",
wantHash: "0000000000000000000000000000000000000000000000000000000000000000",
}, {
name: "contents1 with P=20",
p: 20,
matchKey: randKey,
contents: contents1,
wantMatches: contents1,
fixedKey: [KeySize]byte{},
wantBytes: "ce76b76760b54096a233d504ce55b80600fb072c74893cf306eb0c050f0b3c32e8c23436f8f5e67a986a46470790",
wantNBytes: "00000011ce76b76760b54096a233d504ce55b80600fb072c74893cf306eb0c050f0b3c32e8c23436f8f5e67a986a46470790",
wantHash: "a802fbe6f06991877cde8f3d770d8da8cf195816f04874cab045ffccaddd880d",
}, {
name: "contents1 with P=19",
p: 19,
matchKey: randKey,
contents: contents1,
wantMatches: contents1,
fixedKey: [KeySize]byte{},
wantBytes: "2375937586050f0e9e19689983a3ab9b6f8f0cbc2f204b5233d5099ca0c9fbe9ec6a1f60e76fba3ad6835a28",
wantNBytes: "000000112375937586050f0e9e19689983a3ab9b6f8f0cbc2f204b5233d5099ca0c9fbe9ec6a1f60e76fba3ad6835a28",
wantHash: "be9ba34f03ced957e6f5c4d583ddfd34c136b486fbec2a42b4c7588a2d7813c1",
}, {
name: "contents2 with P=19",
p: 19,
matchKey: randKey,
contents: contents2,
wantMatches: contents2,
fixedKey: [KeySize]byte{},
wantBytes: "4306259e36131a6c9bbd968a6c61dc110804d5ac91d20d6e9314a50332bffed877657c004e2366fcd34cda60",
wantNBytes: "000000114306259e36131a6c9bbd968a6c61dc110804d5ac91d20d6e9314a50332bffed877657c004e2366fcd34cda60",
wantHash: "dcbaf452f6de4c82ea506fa551d75876c4979ef388f785509b130de62eeaec23",
}, {
name: "contents2 with P=10",
p: 10,
matchKey: randKey,
contents: contents2,
wantMatches: contents2,
fixedKey: [KeySize]byte{},
wantBytes: "1ca3aafb023074dc5bf2498df791b7d6e846e9f5016006d600",
wantNBytes: "000000111ca3aafb023074dc5bf2498df791b7d6e846e9f5016006d600",
wantHash: "afa181cd5c4b08eb9c16d1c97c95df1ca7b82e5e444a396cec5e02f2804fbd1a",
}}
for _, test := range tests {
// Create a filter with the match key for all tests not related to
// testing serialization.
f, err := NewFilter(test.p, test.matchKey, test.contents)
if err != nil {
t.Errorf("%q: unexpected err: %v", test.name, err)
continue
}
// Ensure the parameter values are returned properly.
resultP := f.P()
if resultP != test.p {
t.Errorf("%q: unexpected P -- got %d, want %d", test.name,
resultP, test.p)
continue
}
resultN := f.N()
if resultN != uint32(len(test.contents)) {
t.Errorf("%q: unexpected N -- got %d, want %d", test.name,
resultN, uint32(len(test.contents)))
continue
}
// Ensure empty data never matches.
if f.Match(test.matchKey, nil) {
t.Errorf("%q: unexpected match of nil data", test.name)
continue
}
if f.MatchAny(test.matchKey, nil) {
t.Errorf("%q: unexpected match any of nil data", test.name)
continue
}
// Ensure empty filter never matches data.
if len(test.contents) == 0 {
wantMiss := []byte("test")
if f.Match(test.matchKey, wantMiss) {
t.Errorf("%q: unexpected match of %q on empty filter",
test.name, wantMiss)
continue
}
if f.MatchAny(test.matchKey, [][]byte{wantMiss}) {
t.Errorf("%q: unexpected match any of %q on empty filter",
test.name, wantMiss)
continue
}
}
// Ensure all of the expected matches occur individually.
for _, wantMatch := range test.wantMatches {
if !f.Match(test.matchKey, wantMatch) {
t.Errorf("%q: failed match for %q", test.name, wantMatch)
continue
}
}
// Ensure a subset of the expected matches works in various orders when
// matching any.
if len(test.contents) > 0 {
// Create set of data to attempt to match such that only the final
// item is an element in the filter.
matches := make([][]byte, 0, len(test.contents))
for _, data := range test.contents {
mutated := make([]byte, len(data))
copy(mutated, data)
mutated[0] ^= 0x55
matches = append(matches, mutated)
}
matches[len(matches)-1] = test.contents[len(test.contents)-1]
if !f.MatchAny(test.matchKey, matches) {
t.Errorf("%q: failed match for %q", test.name, matches)
continue
}
// Fisher-Yates shuffle the match set and test for matches again.
for i := 0; i < len(matches); i++ {
// Pick a number between current index and the end.
j := rand.Intn(len(matches)-i) + i
matches[i], matches[j] = matches[j], matches[i]
}
if !f.MatchAny(test.matchKey, matches) {
t.Errorf("%q: failed match for %q", test.name, matches)
continue
}
}
// Recreate the filter with a fixed key for serialization testing.
fixedFilter, err := NewFilter(test.p, test.fixedKey, test.contents)
if err != nil {
t.Errorf("%q: unexpected err: %v", test.name, err)
continue
}
// Parse the expected serialized bytes and ensure they match.
wantBytes, err := hex.DecodeString(test.wantBytes)
if err != nil {
t.Errorf("%q: unexpected err parsing want bytes hex: %v", test.name,
err)
continue
}
resultBytes := fixedFilter.Bytes()
if !bytes.Equal(resultBytes, wantBytes) {
t.Errorf("%q: mismatched bytes -- got %x, want %x", test.name,
resultBytes, wantBytes)
continue
}
// Parse the expected serialized bytes that include the N parameter and
// ensure they match.
wantNBytes, err := hex.DecodeString(test.wantNBytes)
if err != nil {
t.Errorf("%q: unexpected err parsing want nbytes hex: %v", test.name,
err)
continue
}
resultNBytes := fixedFilter.NBytes()
if !bytes.Equal(resultNBytes, wantNBytes) {
t.Errorf("%q: mismatched bytes -- got %x, want %x", test.name,
resultNBytes, wantNBytes)
continue
}
// Parse the expected hash and ensure it matches.
wantHash, err := chainhash.NewHashFromStr(test.wantHash)
if err != nil {
t.Errorf("%q: unexpected err parsing want hash hex: %v", test.name,
err)
continue
}
resultHash := fixedFilter.Hash()
if resultHash != *wantHash {
t.Errorf("%q: mismatched hash -- got %v, want %v", test.name,
resultHash, *wantHash)
continue
}
// Deserialize the filter from bytes.
f2, err := FromBytes(uint32(len(test.contents)), test.p, wantBytes)
if err != nil {
t.Errorf("%q: unexpected err: %v", test.name, err)
continue
}
// Ensure all of the expected matches occur on the deserialized filter.
for _, wantMatch := range test.wantMatches {
if !f2.Match(test.fixedKey, wantMatch) {
t.Errorf("%q: failed match for %q", test.name, wantMatch)
continue
}
}
// Deserialize the filter from bytes with N parameter.
f3, err := FromNBytes(test.p, wantNBytes)
if err != nil {
t.Errorf("%q: unexpected err: %v", test.name, err)
continue
}
// Ensure all of the expected matches occur on the deserialized filter.
for _, wantMatch := range test.wantMatches {
if !f3.Match(test.fixedKey, wantMatch) {
t.Errorf("%q: failed match for %q", test.name, wantMatch)
continue
}
}
}
}
// TestGCSFilterCopy deserializes and serializes a filter to create a copy.
func TestGCSFilterCopy(t *testing.T) {
filter2, err = FromBytes(filter.N(), P, filter.Bytes())
if err != nil {
t.Fatalf("Filter copy failed: %s", err.Error())
}
filter3, err = FromNBytes(filter.P(), filter.NBytes())
if err != nil {
t.Fatalf("Filter copy failed: %s", err.Error())
}
}
// TestGCSFilterMetadata checks that the filter metadata is built and copied
// correctly.
func TestGCSFilterMetadata(t *testing.T) {
if filter.P() != P {
t.Fatal("P not correctly stored in filter metadata")
}
if filter.N() != uint32(len(contents)) {
t.Fatal("N not correctly stored in filter metadata")
}
if filter.P() != filter2.P() {
t.Fatal("P doesn't match between copied filters")
}
if filter.P() != filter3.P() {
t.Fatal("P doesn't match between copied filters")
}
if filter.N() != filter2.N() {
t.Fatal("N doesn't match between copied filters")
}
if filter.N() != filter3.N() {
t.Fatal("N doesn't match between copied filters")
}
if !bytes.Equal(filter.Bytes(), filter2.Bytes()) {
t.Fatal("Bytes don't match between copied filters")
}
if !bytes.Equal(filter.Bytes(), filter3.Bytes()) {
t.Fatal("Bytes don't match between copied filters")
}
}
// TestGCSFilterMatch checks that both the built and copied filters match
// correctly, logging any false positives without failing on them.
func TestGCSFilterMatch(t *testing.T) {
if !filter.Match(key, []byte("Nate")) {
t.Fatal("Filter didn't match when it should have!")
}
if !filter2.Match(key, []byte("Nate")) {
t.Fatal("Filter didn't match when it should have!")
}
if !filter.Match(key, []byte("Quentin")) {
t.Fatal("Filter didn't match when it should have!")
}
if !filter2.Match(key, []byte("Quentin")) {
t.Fatal("Filter didn't match when it should have!")
}
if filter.Match(key, []byte("Nates")) {
t.Logf("False positive match, should be 1 in 2**%d!", P)
}
if filter2.Match(key, []byte("Nates")) {
t.Logf("False positive match, should be 1 in 2**%d!", P)
}
if filter.Match(key, []byte("Quentins")) {
t.Logf("False positive match, should be 1 in 2**%d!", P)
}
if filter2.Match(key, []byte("Quentins")) {
t.Logf("False positive match, should be 1 in 2**%d!", P)
}
}
// TestGCSFilterMatchAny checks that both the built and copied filters match a
// list correctly, logging any false positives without failing on them.
func TestGCSFilterMatchAny(t *testing.T) {
if filter.MatchAny(key, contents2) {
t.Logf("False positive match, should be 1 in 2**%d!", P)
}
if filter2.MatchAny(key, contents2) {
t.Logf("False positive match, should be 1 in 2**%d!", P)
}
contents2 = append(contents2, []byte("Nate"))
if !filter.MatchAny(key, contents2) {
t.Fatal("Filter didn't match any when it should have!")
}
if !filter2.MatchAny(key, contents2) {
t.Fatal("Filter didn't match any when it should have!")
}
}
// TestEmptyFilter ensures that empty filters are handled properly.
func TestEmptyFilter(t *testing.T) {
// Ensure an empty filter can be constructed without error.
f, err := NewFilter(P, key, nil)
// TestFilterMisses ensures the filter does not match entries with a rate that
// far exceeds the false positive rate.
func TestFilterMisses(t *testing.T) {
// Create a filter with the lowest supported false positive rate to reduce
// the chances of a false positive as much as possible.
var key [KeySize]byte
f, err := NewFilter(32, key, [][]byte{[]byte("entry")})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
// Ensure empty filters do not have any serialization.
gotBytes := f.Bytes()
if gotBytes != nil {
t.Fatalf("filter bytes not empty -- got %x", gotBytes)
// Since the filter may have false positives, try several queries and track
// how many matches there are. Something is very wrong if the filter
// matched multiple queries for data that are not in the filter with such a
// low false positive rate.
const numTries = 5
var numMatches int
for i := uint8(0); i < numTries; i++ {
data := [1]byte{i}
if f.Match(key, data[:]) {
numMatches++
}
}
gotBytes = f.NBytes()
if gotBytes != nil {
t.Fatalf("filter nbytes not empty -- got %x", gotBytes)
if numMatches == numTries {
t.Fatalf("filter matched non-existing entries %d times", numMatches)
}
// Ensure the hash of empty filters is all zeroes.
gotHash := f.Hash()
expectedHash := chainhash.Hash{}
if gotHash != expectedHash {
t.Fatalf("unexpected filter hash -- got %s, want %s", gotHash,
expectedHash)
// Try again with multi match.
numMatches = 0
for i := uint8(0); i < numTries; i++ {
searchEntry := [1]byte{i}
data := [][]byte{searchEntry[:]}
if f.MatchAny(key, data[:]) {
numMatches++
}
}
// Ensure an empty filter does not match empty data or arbitrary data.
if f.Match(key, nil) {
t.Fatal("unexpected match of nil data")
}
if f.Match(key, []byte("test")) {
t.Fatal("unexpected match of data")
}
if f.MatchAny(key, nil) {
t.Fatal("unexpected match of nil data")
}
if f.MatchAny(key, [][]byte{[]byte("test")}) {
t.Fatal("unexpected match of data")
}
// Ensure empty filter returns correct parameters.
gotN := f.N()
if gotN != 0 {
t.Fatalf("unexpected N -- got %d, want %d", gotN, 0)
}
gotP := f.P()
if gotP != P {
t.Fatalf("unexpected P -- got %d, want %d", gotP, P)
if numMatches == numTries {
t.Fatalf("filter matched non-existing entries %d times", numMatches)
}
}
// TestFilterCorners ensures a few negative corner cases such as specifying
// parameters that are too large behave as expected.
func TestFilterCorners(t *testing.T) {
// Attempt to construct filter with parameters too large.
const largeP = 33
var key [KeySize]byte
_, err := NewFilter(largeP, key, nil)
if err != ErrPTooBig {
t.Fatalf("did not receive expected err for P too big -- got %v, want %v",
err, ErrPTooBig)
}
_, err = FromBytes(0, largeP, nil)
if err != ErrPTooBig {
t.Fatalf("did not receive expected err for P too big -- got %v, want %v",
err, ErrPTooBig)
}
// Attempt to decode a filter without the N value serialized properly.
_, err = FromNBytes(20, []byte{0x00})
if err != ErrMisserialized {
t.Fatalf("did not receive expected err -- got %v, want %v",
err, ErrMisserialized)
}
}

View File

@ -1,100 +0,0 @@
// Copyright (c) 2016-2017 The btcsuite developers
// Copyright (c) 2016-2017 The Lightning Network Developers
// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package gcs
import (
"encoding/binary"
"math/rand"
"testing"
)
var globalMatch bool
func genRandFilterElements(numElements uint) ([][]byte, error) {
testContents := make([][]byte, numElements)
for i := range contents {
randElem := make([]byte, 32)
if _, err := rand.Read(randElem); err != nil {
return nil, err
}
testContents[i] = randElem
}
return testContents, nil
}
// BenchmarkGCSFilterBuild benchmarks building a filter.
func BenchmarkGCSFilterBuild50000(b *testing.B) {
b.StopTimer()
var testKey [KeySize]byte
for i := 0; i < KeySize; i += 4 {
binary.BigEndian.PutUint32(testKey[i:], rand.Uint32())
}
randFilterElems, genErr := genRandFilterElements(50000)
if err != nil {
b.Fatalf("unable to generate random item: %v", genErr)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := NewFilter(P, key, randFilterElems)
if err != nil {
b.Fatalf("unable to generate filter: %v", err)
}
}
}
// BenchmarkGCSFilterBuild benchmarks building a filter.
func BenchmarkGCSFilterBuild100000(b *testing.B) {
var testKey [KeySize]byte
for i := 0; i < KeySize; i += 4 {
binary.BigEndian.PutUint32(testKey[i:], rand.Uint32())
}
randFilterElems, genErr := genRandFilterElements(100000)
if err != nil {
b.Fatalf("unable to generate random item: %v", genErr)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := NewFilter(P, key, randFilterElems)
if err != nil {
b.Fatalf("unable to generate filter: %v", err)
}
}
}
// BenchmarkGCSFilterMatch benchmarks querying a filter for a single value.
func BenchmarkGCSFilterMatch(b *testing.B) {
filter, err := NewFilter(P, key, contents)
if err != nil {
b.Fatalf("Failed to build filter")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
globalMatch = filter.Match(key, []byte("Nate"))
globalMatch = filter.Match(key, []byte("Nates"))
}
}
// BenchmarkGCSFilterMatchAny benchmarks querying a filter for a list of
// values.
func BenchmarkGCSFilterMatchAny(b *testing.B) {
filter, err := NewFilter(P, key, contents)
if err != nil {
b.Fatalf("Failed to build filter")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
globalMatch = filter.MatchAny(key, contents2)
}
}