Skip to content

Commit

Permalink
fix: add fifo tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shaj13 committed May 23, 2020
1 parent d872e60 commit 1fd9228
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 7 deletions.
10 changes: 8 additions & 2 deletions auth/strategies/bearer/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func ExampleNewStatic() {
}

func ExampleNew() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

authFunc := Authenticate(func(ctx context.Context, r *http.Request, token string) (auth.Info, error) {
fmt.Print("authFunc called ")
if token == "90d64460d14870c08c81352a05dedd3465940a7" {
Expand All @@ -56,7 +59,7 @@ func ExampleNew() {
return nil, fmt.Errorf("Invalid user token")
})

cache := store.NewFIFO(time.Minute * 5)
cache := store.NewFIFO(ctx, time.Minute*5)
strategy := New(authFunc, cache)

r, _ := http.NewRequest("GET", "/", nil)
Expand All @@ -75,7 +78,10 @@ func ExampleNew() {
}

func ExampleNoOpAuthenticate() {
cache := store.NewFIFO(time.Microsecond * 500)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

cache := store.NewFIFO(ctx, time.Microsecond*500)
strategy := New(NoOpAuthenticate, cache)

// demonstrate a user attempt to login
Expand Down
23 changes: 18 additions & 5 deletions store/cache.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package store

import (
"context"
"errors"
"net/http"
"sync"
Expand Down Expand Up @@ -32,7 +33,8 @@ type Cache interface {
// Otherwise, wait for the next record.
// When the all expired record collected the garbage collector will be blocked,
// until new record stored to repeat the process.
func NewFIFO(ttl time.Duration) Cache {
// The context will be Passed to garbage collector
func NewFIFO(ctx context.Context, ttl time.Duration) Cache {
queue := &queue{
notify: make(chan struct{}, 1),
mu: &sync.Mutex{},
Expand All @@ -45,7 +47,7 @@ func NewFIFO(ttl time.Duration) Cache {
mu: &sync.Mutex{},
}

go gc(queue, cache)
go gc(ctx, queue, cache)

return cache
}
Expand Down Expand Up @@ -145,8 +147,13 @@ func (q *queue) push(r *record) {
q.tail = q.tail.next
}

func gc(queue *queue, cache *fifo) {
func gc(ctx context.Context, queue *queue, cache *fifo) {
for {

if ctx.Err() != nil {
return
}

record := queue.next()

if record == nil {
Expand All @@ -158,9 +165,15 @@ func gc(queue *queue, cache *fifo) {
// check if the record exist then wait until it expired
if ok {
d := record.exp.Sub(time.Now().UTC())
<-time.After(d)
select {
case <-time.After(d):
case <-ctx.Done():
return
}
}

_ = cache.Delete(record.key, nil)
// invoke Load to expire the record, the load method used over delete
// cause the record may be renewed, and the cache queued the record again in another node.
_, _, _ = cache.Load(record.key, nil)
}
}
190 changes: 190 additions & 0 deletions store/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package store

import (
"context"
"net/http"
"sync"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

// nolint:goconst
func TestFIFO(t *testing.T) {
table := []struct {
name string
key string
value interface{}
op string
expectedErr bool
found bool
}{
{
name: "it return err when key expired",
op: "load",
key: "expired",
expectedErr: true,
found: true,
},
{
name: "it return false when key does not exist",
op: "load",
key: "key",
found: false,
},
{
name: "it return true and value when exist",
op: "load",
key: "test",
value: "test",
found: true,
},
{
name: "it overwrite exist key and value when store",
op: "store",
key: "test",
value: "test2",
found: true,
},
{
name: "it create new record when store",
op: "store",
key: "key",
value: "value",
found: true,
},
{
name: "it's not crash when trying to delete a non exist record",
key: "key",
found: false,
},
{
name: "it delete a exist record",
op: "delete",
key: "test",
found: false,
},
}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {

queue := &queue{
notify: make(chan struct{}, 10),
mu: &sync.Mutex{},
}

cache := &fifo{
queue: queue,
ttl: time.Second,
records: map[string]*record{
"test": {
value: "test",
exp: time.Now().Add(time.Hour),
},
"expired": {
value: "expired",
exp: time.Now().Add(-time.Hour),
},
},
mu: &sync.Mutex{},
}

r, _ := http.NewRequest("GET", "/", nil)
var err error

switch tt.op {
case "load":
v, ok, err := cache.Load(tt.key, r)
assert.Equal(t, tt.value, v)
assert.Equal(t, tt.found, ok)
assert.Equal(t, tt.expectedErr, err != nil)
return
case "store":
err = cache.Store(tt.key, tt.value, r)
assert.Equal(t, tt.key, queue.next().key)
case "delete":
err = cache.Delete(tt.key, r)
}

assert.Equal(t, tt.expectedErr, err != nil)
v, ok := cache.records[tt.key]
assert.Equal(t, tt.found, ok)

if tt.value != nil {
assert.Equal(t, tt.value, v.value)
}
})
}
}

func TestQueue(t *testing.T) {
queue := &queue{
notify: make(chan struct{}, 10),
mu: &sync.Mutex{},
}

for i := 0; i < 5; i++ {
queue.push(
&record{
key: "any",
value: i,
})
}

for i := 0; i < 5; i++ {
r := queue.next()
assert.Equal(t, i, r.value)
}
}

func TestQueueNotify(t *testing.T) {
done := make(chan struct{})

queue := &queue{
notify: make(chan struct{}),
mu: &sync.Mutex{},
}

go func() {
counter := 0
for {
<-queue.notify
counter++
if counter == 5 {
done <- struct{}{}
return
}

}
}()

for i := 0; i < 5; i++ {
queue.push(nil)
queue.next()
}

select {
case <-done:
case <-time.After(time.Second):
t.Fatal("Something wrong expected test to send done")
}
}

func TestGC(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

cache := NewFIFO(ctx, time.Nanosecond*50)

cache.Store("1", 1, nil)
cache.Store("2", 2, nil)

time.Sleep(time.Millisecond)
_, ok, _ := cache.Load("1", nil)
assert.False(t, ok)

_, ok, _ = cache.Load("2", nil)
assert.False(t, ok)
}

0 comments on commit 1fd9228

Please sign in to comment.