1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00
any-sync/app/ocache/ocache_test.go
2023-01-05 15:34:09 +03:00

206 lines
5.2 KiB
Go

package ocache
import (
"context"
"errors"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testObject struct {
name string
closeErr error
closeCh chan struct{}
}
func NewTestObject(name string, closeCh chan struct{}) *testObject {
return &testObject{
name: name,
closeCh: closeCh,
}
}
func (t *testObject) Close() (err error) {
if t.closeCh != nil {
<-t.closeCh
}
return t.closeErr
}
func TestOCache_Get(t *testing.T) {
t.Run("successful", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
return &testObject{name: "test"}, nil
})
val, err := c.Get(context.TODO(), "test")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, "test", val.(*testObject).name)
assert.Equal(t, 1, c.Len())
assert.NoError(t, c.Close())
})
t.Run("error", func(t *testing.T) {
tErr := errors.New("err")
c := New(func(ctx context.Context, id string) (value Object, err error) {
return nil, tErr
})
val, err := c.Get(context.TODO(), "test")
require.Equal(t, tErr, err)
require.Nil(t, val)
assert.Equal(t, 0, c.Len())
assert.NoError(t, c.Close())
})
t.Run("parallel load", func(t *testing.T) {
var waitCh = make(chan struct{})
var obj = &testObject{
name: "test",
}
var calls uint32
c := New(func(ctx context.Context, id string) (value Object, err error) {
atomic.AddUint32(&calls, 1)
<-waitCh
return obj, nil
})
var l = 10
var res = make(chan struct{}, l)
for i := 0; i < l; i++ {
go func() {
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
assert.Equal(t, obj, val)
res <- struct{}{}
}()
}
time.Sleep(time.Millisecond * 10)
close(waitCh)
var timeout = time.After(time.Second)
for i := 0; i < l; i++ {
select {
case <-res:
case <-timeout:
require.True(t, false, "timeout")
}
}
assert.Equal(t, 1, c.Len())
assert.Equal(t, uint32(1), calls)
assert.NoError(t, c.Close())
})
t.Run("errClosed", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
return nil, errors.New("test")
})
require.NoError(t, c.Close())
_, err := c.Get(context.TODO(), "id")
assert.Equal(t, ErrClosed, err)
})
t.Run("context cancel", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
time.Sleep(time.Second / 3)
return &testObject{
name: "id",
}, nil
})
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := c.Get(ctx, "id")
assert.Equal(t, context.Canceled, err)
assert.NoError(t, c.Close())
})
}
func TestOCache_GC(t *testing.T) {
t.Run("test without close wait", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
return &testObject{name: id}, nil
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
c.GC()
assert.Equal(t, 1, c.Len())
time.Sleep(time.Millisecond * 30)
c.GC()
assert.Equal(t, 1, c.Len())
assert.True(t, c.Release("id"))
c.GC()
assert.Equal(t, 0, c.Len())
assert.False(t, c.Release("id"))
})
t.Run("test with close wait", func(t *testing.T) {
closeCh := make(chan struct{})
getCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, closeCh), nil
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
assert.True(t, c.Release("id"))
// making ttl pass
time.Sleep(time.Millisecond * 40)
// first gc will be run after 20 secs, so calling it manually
go c.GC()
// waiting until all objects are marked as closing
time.Sleep(time.Millisecond * 40)
var events []string
go func() {
_, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
events = append(events, "get")
close(getCh)
}()
events = append(events, "close")
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 40)
close(closeCh)
<-getCh
require.Equal(t, []string{"close", "get"}, events)
})
}
func Test_OCache_Remove(t *testing.T) {
closeCh := make(chan struct{})
getCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, closeCh), nil
}, WithTTL(time.Millisecond*10))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
// removing the object, so we will wait on closing
go func() {
_, err := c.Remove("id")
require.NoError(t, err)
}()
time.Sleep(time.Millisecond * 40)
var events []string
go func() {
_, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
events = append(events, "get")
close(getCh)
}()
events = append(events, "close")
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 40)
close(closeCh)
<-getCh
require.Equal(t, []string{"close", "get"}, events)
}