package keyset
import (
"bytes"
"fmt"
"math/rand"
"strconv"
"testing"
"github.com/segmentio/asm/internal/buffer"
)
func TestKeySet(t *testing.T) {
const max = 23
keys := make([][]byte, max)
for i := range keys {
keys[i] = []byte(strconv.Itoa(max - i))
}
for i := 0; i < max; i++ {
subset := keys[:i]
keyset := New(subset)
if keyset == nil {
t.Skip("not implemented")
}
for j := range subset {
if n := Lookup(keyset, subset[j]); n != j {
t.Errorf("unexpected index for known key: %d, expected %d", n, j)
}
}
if n := Lookup(keyset, []byte(fmt.Sprintf("key-%d", i+1))); n != len(subset) {
t.Errorf("unexpected index for unknown key: %d", n)
}
}
for i := 0; i < max; i++ {
for j := 0; j <= 16; j++ {
key := bytes.Repeat([]byte("x"), j)
keyset := New(append(keys[:i:i], key))
if n := Lookup(keyset, key); n != i {
t.Errorf("unexpected index for known key: %d", n)
}
if j > 0 {
if n := Lookup(keyset, key[:j-1]); n != i+1 {
t.Errorf("unexpected match: %d", n)
}
}
if n := Lookup(keyset, append(key, 'x')); n != i+1 {
t.Errorf("unexpected match: %d", n)
}
}
}
if New([][]byte{[]byte("foo\x00bar")}) != nil {
t.Error("keyset was created when key contained null byte")
}
if New([][]byte{bytes.Repeat([]byte{'x'}, 17)}) != nil {
t.Error("keyset was created when key was longer than 16 bytes")
}
}
const hex = "0123456789abcdef"
func TestPageBoundary(t *testing.T) {
buf, err := buffer.New(16)
if err != nil {
t.Fatal(err)
}
defer buf.Release()
head := buf.ProtectHead()
tail := buf.ProtectTail()
copy(head, hex)
copy(tail, hex)
for i := 0; i <= 16; i++ {
key := head[:i]
keyset := New([][]byte{[]byte("foo"), []byte("bar"), key})
if keyset == nil {
t.Skip("not implemented")
}
if n := Lookup(keyset, key); n != 2 {
t.Errorf("unexpected lookup result %d", n)
}
}
for i := 0; i <= 16; i++ {
key := tail[i:]
keyset := New([][]byte{[]byte("foo"), []byte("bar"), key})
if n := Lookup(keyset, key); n != 2 {
t.Errorf("unexpected lookup result for i=%d: %d", i, n)
}
}
}
func BenchmarkKeySet(b *testing.B) {
keys := make([][]byte, 32)
m := map[string]int{}
for i := range keys {
k := "key-" + strconv.Itoa(i)
// k := strings.Repeat(strconv.Itoa(i), i)
if len(k) > 16 {
k = k[:16]
}
keys[i] = []byte(k)
m[k] = i
}
prng := rand.New(rand.NewSource(0))
const permutations = 1000 // enough to throw off the branch predictor hopeully
r := make([]int, len(keys)*permutations)
for i := 0; i < permutations; i++ {
x := r[i*len(keys):][:len(keys)]
for j := range x {
x[j] = j
}
prng.Shuffle(len(keys), func(a, b int) {
x[a], x[b] = x[b], x[a]
})
}
keyset := New(keys)
if keyset == nil {
b.Skip("not implemented")
}
b.Run("map-lookup-first", func(b *testing.B) {
first := keys[0]
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[string(first)]
}
})
b.Run("keyset-lookup-first", func(b *testing.B) {
first := keys[0]
b.ResetTimer()
for i := 0; i < b.N; i++ {
Lookup(keyset, first)
}
})
b.Run("map-lookup-last", func(b *testing.B) {
last := keys[len(keys)-1]
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[string(last)]
}
})
b.Run("keyset-lookup-last", func(b *testing.B) {
last := keys[len(keys)-1]
b.ResetTimer()
for i := 0; i < b.N; i++ {
Lookup(keyset, last)
}
})
b.Run("map-ordered-iteration", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, k := range keys {
_ = m[string(k)]
}
}
})
b.Run("keyset-ordered-iteration", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, k := range keys {
Lookup(keyset, k)
}
}
})
b.Run("map-random-iteration", func(b *testing.B) {
prng := rand.New(rand.NewSource(0))
b.ResetTimer()
for i := 0; i < b.N; i++ {
p := prng.Intn(permutations)
permutation := r[p*len(keys):][:len(keys)]
for _, i := range permutation {
_ = m[string(keys[i])]
}
}
})
b.Run("keyset-random-iteration", func(b *testing.B) {
prng := rand.New(rand.NewSource(0))
b.ResetTimer()
for i := 0; i < b.N; i++ {
p := prng.Intn(permutations)
permutation := r[p*len(keys):][:len(keys)]
for _, i := range permutation {
Lookup(keyset, keys[i])
}
}
})
}