Files
sablier/pkg/tinykv/tinykv_test.go
Alexis Couvreur f29b13a55a refactor(storage): add store.Store interface
There is a first implementation with ValKey that will allow to use redis APIs as a backend for Sablier with Hight Availability
2025-02-02 18:13:45 -05:00

402 lines
7.7 KiB
Go

package tinykv
import (
"encoding/json"
"fmt"
"math/rand"
"os"
"strconv"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTimeoutHeap(t *testing.T) {
assert := assert.New(t)
now := time.Now()
r := rand.New(rand.NewSource(now.Unix()))
n := r.Intn(10000) + 10
var h th = []*timeout{}
for i := 0; i < n; i++ {
to := &timeout{expiresAt: now.Add(time.Duration(r.Intn(100000)) * time.Second)}
timeheapPush(&h, to)
}
var prev *timeout
// t.Log(h[0].expiresAt, h[len(h)-1].expiresAt)
for len(h) > 0 {
ito := timeheapPop(&h)
if prev != nil {
assert.Condition(func() bool { return !prev.expiresAt.After(ito.expiresAt) })
}
prev = ito
}
assert.Equal(0, len(h))
}
var _ KV[int] = &store[int]{}
func TestGetPut(t *testing.T) {
assert := assert.New(t)
rg := New[int](0, nil)
defer rg.Stop()
rg.Put("1", 1, time.Minute*50)
v, ok := rg.Get("1")
assert.True(ok)
assert.Equal(1, v)
rg.Put("2", 2, time.Millisecond*50)
v, ok = rg.Get("2")
assert.True(ok)
assert.Equal(2, v)
<-time.After(time.Millisecond * 100)
v, ok = rg.Get("2")
assert.False(ok)
assert.NotEqual(2, v)
}
func TestKeys(t *testing.T) {
assert := assert.New(t)
rg := New[int](0, nil)
defer rg.Stop()
rg.Put("1", 1, time.Minute*50)
rg.Put("2", 2, time.Minute*50)
keys := rg.Keys()
assert.NotEmpty(keys)
assert.Contains(keys, "1")
assert.Contains(keys, "2")
}
func TestValues(t *testing.T) {
assert := assert.New(t)
rg := New[int](0, nil)
defer rg.Stop()
rg.Put("1", 1, time.Minute*50)
rg.Put("2", 2, time.Minute*50)
values := rg.Values()
assert.NotEmpty(values)
assert.Contains(values, 1)
assert.Contains(values, 2)
}
func TestEntries(t *testing.T) {
assert := assert.New(t)
rg := New[int](0, nil)
defer rg.Stop()
rg.Put("1", 1, time.Minute*50)
rg.Put("2", 2, time.Minute*50)
rg.Put("3", 3, time.Minute*50)
entries := rg.Entries()
assert.NotEmpty(entries)
assert.NotNil(entries["1"])
assert.NotNil(entries["2"])
assert.NotNil(entries["3"])
}
func TestMarshalJSON(t *testing.T) {
os.Setenv("TZ", "")
assert := assert.New(t)
rg := New[int](0, nil)
defer rg.Stop()
rg.Put("3", 3, time.Minute*50)
jsonb, err := json.Marshal(rg)
assert.Nil(err)
json := string(jsonb)
assert.Regexp(`{"3":{"value":3,"expiresAt":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z"}}`, json)
}
func TestUnmarshalJSON(t *testing.T) {
assert := assert.New(t)
in5Minutes := time.Now().Add(time.Minute * 5)
in5MinutesJson, err := json.Marshal(in5Minutes)
assert.Nil(err)
jsons := `{"1":{"value":1},"2":{"value":2},"3":{"value":3,"expiresAt":` + string(in5MinutesJson) + `}}`
rg := New[int](0, nil)
defer rg.Stop()
err = json.Unmarshal([]byte(jsons), &rg)
assert.Nil(err)
assert.Len(rg.Entries(), 1)
}
func TestUnmarshalJSONExpired(t *testing.T) {
assert := assert.New(t)
since5Minutes := time.Now().Add(-time.Minute * 5)
since5MinutesJson, err := json.Marshal(since5Minutes)
assert.Nil(err)
jsons := `{"1":{"value":1},"2":{"value":2},"3":{"value":3,"expiresAt":` + string(since5MinutesJson) + `}}`
rg := New[int](0, nil)
defer rg.Stop()
err = json.Unmarshal([]byte(jsons), &rg)
assert.Nil(err)
assert.Empty(rg.Entries())
}
func TestTimeout(t *testing.T) {
assert := assert.New(t)
rcvd := make(chan string, 100)
notify := func(k string, v interface{}) {
rcvd <- k
}
rg := New(time.Millisecond*10, notify)
n := 1000
for i := n; i < 2*n; i++ {
rg.Put(strconv.Itoa(i), i, time.Millisecond*10)
}
got := make([]string, n)
OUT01:
for {
select {
case v := <-rcvd:
i, err := strconv.Atoi(v)
assert.NoError(err)
i = i - n
if i < 0 || i >= n {
t.Fail()
}
got[i] = v
case <-time.After(time.Millisecond * 100):
break OUT01
}
}
assert.Equal(len(got), n)
for i := 0; i < n; i++ {
if got[i] != "" {
continue
}
assert.Fail("should have value", i, got[i])
}
}
func Test03(t *testing.T) {
assert := assert.New(t)
var putAt time.Time
elapsed := make(chan time.Duration, 1)
kv := New(
time.Millisecond*50,
func(k string, v interface{}) {
elapsed <- time.Since(putAt)
})
putAt = time.Now()
kv.Put("1", 1, time.Millisecond*10)
<-time.After(time.Millisecond * 100)
assert.WithinDuration(putAt, putAt.Add(<-elapsed), time.Millisecond*60)
}
func Test04(t *testing.T) {
assert := assert.New(t)
kv := New(
time.Millisecond*10,
func(k string, v interface{}) {
t.Fatal(k, v)
})
err := kv.Put("1", 1, time.Millisecond*10000)
assert.NoError(err)
<-time.After(time.Millisecond * 50)
kv.Delete("1")
kv.Delete("1")
<-time.After(time.Millisecond * 100)
_, ok := kv.Get("1")
assert.False(ok)
}
func Test05(t *testing.T) {
assert := assert.New(t)
N := 10000
var cnt int64
kv := New(
time.Millisecond*10,
func(k string, v interface{}) {
atomic.AddInt64(&cnt, 1)
})
src := rand.NewSource(time.Now().Unix())
rnd := rand.New(src)
for i := 0; i < N; i++ {
k := fmt.Sprintf("%d", i)
kv.Put(k, fmt.Sprintf("VAL::%v", k),
time.Millisecond*time.Duration(rnd.Intn(10)+1))
}
<-time.After(time.Millisecond * 100)
for i := 0; i < N; i++ {
k := fmt.Sprintf("%d", i)
_, ok := kv.Get(k)
assert.False(ok)
}
}
func Test11(t *testing.T) {
assert := assert.New(t)
key := "QQG"
var expiredKey = make(chan string, 100)
onExpired := func(k string, v interface{}) { expiredKey <- k }
kv := New(time.Millisecond*100, onExpired)
err := kv.Put(
key, "G",
time.Millisecond*15)
assert.NoError(err)
<-time.After(time.Millisecond * 10)
v, ok := kv.Get(key)
assert.True(ok)
assert.Equal("G", v)
<-time.After(time.Millisecond * 10)
_, ok = kv.Get(key)
assert.False(ok)
<-time.After(time.Millisecond)
assert.Equal(key, <-expiredKey)
<-time.After(time.Millisecond * 110)
_, ok = kv.Get(key)
assert.False(ok)
}
func Test12(t *testing.T) {
assert := assert.New(t)
key := "QQG"
onExpired := func(k string, v interface{}) {}
kv := New(time.Millisecond*100, onExpired)
err := kv.Put(
key, "G",
time.Millisecond)
assert.NoError(err)
<-time.After(time.Millisecond * 10)
v, ok := kv.Get(key)
assert.False(ok)
assert.Equal(nil, v)
}
func Test13(t *testing.T) {
assert := assert.New(t)
got := make(chan interface{}, 10)
onExpired := func(k string, v interface{}) {
got <- v
}
kv := New(time.Millisecond*10, onExpired)
err := kv.Put(
"1", 123,
time.Millisecond)
assert.NoError(err)
<-time.After(time.Millisecond * 50)
v, ok := kv.Get("1")
assert.False(ok)
assert.Equal(nil, v)
v = <-got
assert.Equal(123, v)
}
func TestOrdering(t *testing.T) {
assert := assert.New(t)
type data struct {
key string
value interface{}
}
got := make(chan data, 100)
onExpired := func(k string, v interface{}) {
got <- data{k, v}
}
kv := New(time.Millisecond*5, onExpired)
for i := 1; i <= 10; i++ {
k := strconv.Itoa(i)
v := i
kv.Put(k, v, time.Millisecond*time.Duration(i)*50)
}
var order = make([]int, 10)
done := make(chan struct{})
go func() {
defer close(done)
for {
select {
case v := <-got:
i, _ := strconv.Atoi(v.key)
i--
val := v.value.(int)
val--
order[i] = val
case <-time.After(time.Millisecond * 100):
return
}
}
}()
<-done
for k, v := range order {
assert.Equal(k, v)
}
assert.Equal(1, 1)
}
func BenchmarkGetNoValue(b *testing.B) {
rg := New[interface{}](-1, nil)
for n := 0; n < b.N; n++ {
rg.Get("1")
}
}
func BenchmarkGetValue(b *testing.B) {
rg := New[interface{}](-1, nil)
rg.Put("1", 1, time.Minute*50)
for n := 0; n < b.N; n++ {
rg.Get("1")
}
}
func BenchmarkGetSlidingTimeout(b *testing.B) {
rg := New[interface{}](-1, nil)
rg.Put("1", 1, time.Second*10)
for n := 0; n < b.N; n++ {
rg.Get("1")
}
}
func BenchmarkPutExpire(b *testing.B) {
rg := New[interface{}](-1, nil)
for n := 0; n < b.N; n++ {
rg.Put("1", 1, time.Second*10)
}
}