mirror of
https://github.com/sablierapp/sablier.git
synced 2025-12-21 13:23:03 +01:00
refactor: put acouvreur/tinykv inside the repository
This commit is contained in:
629
pkg/tinykv/tinykv_test.go
Normal file
629
pkg/tinykv/tinykv_test.go
Normal file
@@ -0,0 +1,629 @@
|
||||
package tinykv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"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)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1)
|
||||
v, ok := rg.Get("1")
|
||||
assert.True(ok)
|
||||
assert.Equal(1, v)
|
||||
|
||||
rg.Put("2", 2, ExpiresAfter(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)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1)
|
||||
rg.Put("2", 2)
|
||||
|
||||
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)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1)
|
||||
rg.Put("2", 2)
|
||||
|
||||
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)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1)
|
||||
rg.Put("2", 2)
|
||||
rg.Put("3", 3, ExpiresAfter(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) {
|
||||
assert := assert.New(t)
|
||||
rg := New[int](0)
|
||||
defer rg.Stop()
|
||||
|
||||
rg.Put("1", 1)
|
||||
rg.Put("2", 2)
|
||||
rg.Put("3", 3, ExpiresAfter(time.Minute*50))
|
||||
|
||||
jsonb, err := json.Marshal(rg)
|
||||
assert.Nil(err)
|
||||
json := string(jsonb)
|
||||
assert.Regexp("{\"1\":{\"value\":1},\"2\":{\"value\":2},\"3\":{\"value\":3,\"expiresAt\":\"\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\d\\d\\d\\d\\d\\dZ\",\"expiresAfter\":3000000000000,\"isSliding\":false}}", 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) + ",\"expiresAfter\":3000000000000,\"isSliding\":false}}"
|
||||
|
||||
rg := New[int](0)
|
||||
defer rg.Stop()
|
||||
|
||||
err = json.Unmarshal([]byte(jsons), &rg)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
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, ExpiresAfter(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 Test02(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rg := New[int](time.Millisecond * 30)
|
||||
|
||||
rg.Put("1", 1)
|
||||
v, ok := rg.Get("1")
|
||||
assert.True(ok)
|
||||
assert.Equal(1, v)
|
||||
|
||||
rg.Put("1", 1, ExpiresAfter(time.Millisecond*50), IsSliding(true))
|
||||
<-time.After(time.Millisecond * 40)
|
||||
v, ok = rg.Get("1")
|
||||
assert.True(ok)
|
||||
assert.Equal(1, v)
|
||||
<-time.After(time.Millisecond * 10)
|
||||
v, ok = rg.Get("1")
|
||||
assert.True(ok)
|
||||
assert.Equal(1, v)
|
||||
<-time.After(time.Millisecond * 10)
|
||||
v, ok = rg.Get("1")
|
||||
assert.True(ok)
|
||||
assert.Equal(1, v)
|
||||
|
||||
<-time.After(time.Millisecond * 100)
|
||||
|
||||
v, ok = rg.Get("1")
|
||||
assert.False(ok)
|
||||
assert.NotEqual(1, v)
|
||||
}
|
||||
|
||||
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, ExpiresAfter(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, ExpiresAfter(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),
|
||||
ExpiresAfter(
|
||||
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 Test06(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
kv := New(
|
||||
time.Millisecond,
|
||||
func(k string, v interface{}) {
|
||||
t.Fail()
|
||||
})
|
||||
|
||||
err := kv.Put("1", 1, ExpiresAfter(10*time.Millisecond), IsSliding(true))
|
||||
assert.NoError(err)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
_, ok := kv.Get("1")
|
||||
assert.True(ok)
|
||||
<-time.After(time.Millisecond)
|
||||
}
|
||||
kv.Delete("1")
|
||||
|
||||
<-time.After(time.Millisecond * 30)
|
||||
|
||||
_, ok := kv.Get("1")
|
||||
assert.False(ok)
|
||||
}
|
||||
|
||||
func Test07(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
kv := New[int](-1)
|
||||
kv.Put("1", 1)
|
||||
v, ok := kv.Take("1")
|
||||
assert.True(ok)
|
||||
assert.Equal(1, v)
|
||||
|
||||
_, ok = kv.Get("1")
|
||||
assert.False(ok)
|
||||
}
|
||||
|
||||
func Test08(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
kv := New[interface{}](-1)
|
||||
err := kv.Put(
|
||||
"QQG", "G",
|
||||
CAS(func(interface{}, bool) bool { return true }),
|
||||
ExpiresAfter(time.Millisecond))
|
||||
assert.NoError(err)
|
||||
|
||||
v, ok := kv.Take("QQG")
|
||||
assert.True(ok)
|
||||
assert.Equal("G", v)
|
||||
}
|
||||
|
||||
// ignore new timeouts when cas, and just use the old ones from the old value (if exists)
|
||||
func Test09IgnoreTimeoutParamsOnCAS(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key := "QQG"
|
||||
|
||||
kv := New[interface{}](time.Millisecond)
|
||||
err := kv.Put(
|
||||
key, "G",
|
||||
CAS(func(interface{}, bool) bool { return true }),
|
||||
ExpiresAfter(time.Millisecond*30))
|
||||
assert.NoError(err)
|
||||
|
||||
v, ok := kv.Get(key)
|
||||
assert.True(ok)
|
||||
assert.Equal("G", v)
|
||||
|
||||
<-time.After(time.Millisecond * 20)
|
||||
|
||||
err = kv.Put(key, "OK",
|
||||
CAS(func(currentValue interface{}, found bool) bool {
|
||||
assert.True(found)
|
||||
assert.Equal("G", currentValue)
|
||||
return true
|
||||
}))
|
||||
assert.NoError(err)
|
||||
|
||||
<-time.After(time.Millisecond * 12)
|
||||
_, ok = kv.Get(key)
|
||||
assert.False(ok)
|
||||
}
|
||||
|
||||
func Test10(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key := "QQG"
|
||||
|
||||
kv := New[interface{}](time.Millisecond)
|
||||
err := kv.Put(
|
||||
key, "G",
|
||||
CAS(func(interface{}, bool) bool { return true }),
|
||||
IsSliding(true),
|
||||
ExpiresAfter(time.Millisecond*15))
|
||||
assert.NoError(err)
|
||||
|
||||
<-time.After(time.Millisecond * 12)
|
||||
|
||||
v, ok := kv.Get(key)
|
||||
assert.True(ok)
|
||||
assert.Equal("G", v)
|
||||
|
||||
<-time.After(time.Millisecond * 12)
|
||||
|
||||
err = kv.Put(key, "OK",
|
||||
CAS(func(currentValue interface{}, found bool) bool {
|
||||
assert.True(found)
|
||||
assert.Equal("G", currentValue)
|
||||
return true
|
||||
}))
|
||||
assert.NoError(err)
|
||||
|
||||
<-time.After(time.Millisecond * 12)
|
||||
|
||||
_, ok = kv.Get(key)
|
||||
assert.True(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",
|
||||
ExpiresAfter(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",
|
||||
ExpiresAfter(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,
|
||||
ExpiresAfter(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, ExpiresAfter(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 TestCASOldFound(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
kv := New[interface{}](time.Millisecond * 10)
|
||||
key := "KEY01"
|
||||
value := "VALUE01"
|
||||
err := kv.Put(
|
||||
key, value,
|
||||
CAS(func(old interface{}, found bool) bool {
|
||||
assert.Nil(old)
|
||||
assert.False(found)
|
||||
return true
|
||||
}))
|
||||
assert.NoError(err)
|
||||
err = kv.Put(
|
||||
key, value,
|
||||
CAS(func(old interface{}, found bool) bool {
|
||||
assert.Equal(value, old)
|
||||
assert.True(found)
|
||||
return true
|
||||
}))
|
||||
assert.NoError(err)
|
||||
kv.Delete(key)
|
||||
err = kv.Put(
|
||||
key, value,
|
||||
CAS(func(old interface{}, found bool) bool {
|
||||
assert.Nil(old)
|
||||
assert.False(found)
|
||||
return true
|
||||
}))
|
||||
assert.NoError(err)
|
||||
v, ok := kv.Take(key)
|
||||
assert.True(ok)
|
||||
assert.Equal(value, v)
|
||||
err = kv.Put(
|
||||
key, value,
|
||||
CAS(func(old interface{}, found bool) bool {
|
||||
assert.Nil(old)
|
||||
assert.False(found)
|
||||
return true
|
||||
}))
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func ExampleNew() {
|
||||
key := "KEY"
|
||||
value := "VALUE"
|
||||
|
||||
kv := New[interface{}](time.Millisecond * 10)
|
||||
defer kv.Stop()
|
||||
kv.Put(key, value)
|
||||
v, ok := kv.Get(key)
|
||||
if !ok {
|
||||
// ...
|
||||
}
|
||||
fmt.Println(key, v)
|
||||
kv.Delete(key)
|
||||
_, ok = kv.Get(key)
|
||||
fmt.Println(ok)
|
||||
|
||||
// Output:
|
||||
// KEY VALUE
|
||||
// false
|
||||
}
|
||||
|
||||
func BenchmarkGetNoValue(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Get("1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetValue(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg.Put("1", 1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Get("1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetSlidingTimeout(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg.Put("1", 1, ExpiresAfter(time.Second*10))
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Get("1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPutOne(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Put("1", 1)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPutN(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
k := strconv.Itoa(n)
|
||||
rg.Put(k, n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPutExpire(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Put("1", 1, ExpiresAfter(time.Second*10))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCASTrue(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg.Put("1", 1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Put("1", 2, CAS(func(interface{}, bool) bool { return true }))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCASFalse(b *testing.B) {
|
||||
rg := New[interface{}](-1)
|
||||
rg.Put("1", 1)
|
||||
for n := 0; n < b.N; n++ {
|
||||
rg.Put("1", 2, CAS(func(interface{}, bool) bool { return false }))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user