From 5e54f6b55f00fb6a41e232dd87509aefcd7c1bd2 Mon Sep 17 00:00:00 2001 From: Alexis Poyen <apoyen@mail.apoyen.fr> Date: Thu, 30 Apr 2020 16:46:46 +0200 Subject: [PATCH] Remove: useless package --- pkg/cache/LICENSE | 21 -- pkg/cache/cache.go | 297 -------------------- pkg/cache/cache_test.go | 481 -------------------------------- pkg/cache/memory/memory.go | 190 ------------- pkg/cache/memory/memory_test.go | 299 -------------------- 5 files changed, 1288 deletions(-) delete mode 100644 pkg/cache/LICENSE delete mode 100644 pkg/cache/cache.go delete mode 100644 pkg/cache/cache_test.go delete mode 100644 pkg/cache/memory/memory.go delete mode 100644 pkg/cache/memory/memory_test.go diff --git a/pkg/cache/LICENSE b/pkg/cache/LICENSE deleted file mode 100644 index bbe6990..0000000 --- a/pkg/cache/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Victor Springer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go deleted file mode 100644 index 0fc15c6..0000000 --- a/pkg/cache/cache.go +++ /dev/null @@ -1,297 +0,0 @@ -/* -MIT License - -Copyright (c) 2018 Victor Springer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package cache - -import ( - "bytes" - "encoding/gob" - "errors" - "fmt" - "hash/fnv" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "sort" - "strconv" - "strings" - "time" - - "github.com/nicolaspernoud/vestibule/pkg/glob" -) - -// Response is the cached response data structure. -type Response struct { - // Value is the cached response value. - Value []byte - - // Header is the cached response header. - Header http.Header - - // Expiration is the cached response expiration date. - Expiration time.Time - - // LastAccess is the last date a cached response was accessed. - // Used by LRU and MRU algorithms. - LastAccess time.Time - - // Frequency is the count of times a cached response is accessed. - // Used for LFU and MFU algorithms. - Frequency int -} - -// Client data structure for HTTP cache middleware. -type Client struct { - adapter Adapter - ttl time.Duration - refreshKey string - methods []string -} - -// ClientOption is used to set Client settings. -type ClientOption func(c *Client) error - -// Adapter interface for HTTP cache middleware client. -type Adapter interface { - // Get retrieves the cached response by a given key. It also - // returns true or false, whether it exists or not. - Get(key uint64) ([]byte, bool) - - // Set caches a response for a given key until an expiration date. - Set(key uint64, response []byte, expiration time.Time) - - // Release frees cache for a given key. - Release(key uint64) -} - -// Middleware is the HTTP cache middleware handler. -func (c *Client) Middleware(next http.Handler, patterns []string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Check pattern matching - var patternMatched bool - for _, p := range patterns { - if glob.Glob(p, r.URL.Path) { - patternMatched = true - break - } - } - if c.cacheableMethod(r.Method) && patternMatched { - sortURLParams(r.URL) - key := generateKey(r.URL.String()) - if r.Method == http.MethodPost && r.Body != nil { - body, err := ioutil.ReadAll(r.Body) - defer r.Body.Close() - if err != nil { - next.ServeHTTP(w, r) - return - } - reader := ioutil.NopCloser(bytes.NewBuffer(body)) - key = generateKeyWithBody(r.URL.String(), body) - r.Body = reader - } - - params := r.URL.Query() - if _, ok := params[c.refreshKey]; ok { - delete(params, c.refreshKey) - - r.URL.RawQuery = params.Encode() - key = generateKey(r.URL.String()) - - c.adapter.Release(key) - } else { - b, ok := c.adapter.Get(key) - response := BytesToResponse(b) - if ok { - if response.Expiration.After(time.Now()) { - response.LastAccess = time.Now() - response.Frequency++ - c.adapter.Set(key, response.Bytes(), response.Expiration) - - //w.WriteHeader(http.StatusNotModified) - for k, v := range response.Header { - w.Header().Set(k, strings.Join(v, ",")) - } - w.Write(response.Value) - return - } - - c.adapter.Release(key) - } - } - - rec := httptest.NewRecorder() - next.ServeHTTP(rec, r) - result := rec.Result() - - statusCode := result.StatusCode - value := rec.Body.Bytes() - if statusCode < 300 { - now := time.Now() - - response := Response{ - Value: value, - Header: result.Header, - Expiration: now.Add(c.ttl), - LastAccess: now, - Frequency: 1, - } - c.adapter.Set(key, response.Bytes(), response.Expiration) - } - for k, v := range result.Header { - w.Header().Set(k, strings.Join(v, ",")) - } - w.WriteHeader(statusCode) - w.Write(value) - return - } - next.ServeHTTP(w, r) - }) -} - -func (c *Client) cacheableMethod(method string) bool { - for _, m := range c.methods { - if method == m { - return true - } - } - return false -} - -// BytesToResponse converts bytes array into Response data structure. -func BytesToResponse(b []byte) Response { - var r Response - dec := gob.NewDecoder(bytes.NewReader(b)) - dec.Decode(&r) - - return r -} - -// Bytes converts Response data structure into bytes array. -func (r Response) Bytes() []byte { - var b bytes.Buffer - enc := gob.NewEncoder(&b) - enc.Encode(&r) - - return b.Bytes() -} - -func sortURLParams(URL *url.URL) { - params := URL.Query() - for _, param := range params { - sort.Slice(param, func(i, j int) bool { - return param[i] < param[j] - }) - } - URL.RawQuery = params.Encode() -} - -// KeyAsString can be used by adapters to convert the cache key from uint64 to string. -func KeyAsString(key uint64) string { - return strconv.FormatUint(key, 36) -} - -func generateKey(URL string) uint64 { - hash := fnv.New64a() - hash.Write([]byte(URL)) - - return hash.Sum64() -} - -func generateKeyWithBody(URL string, body []byte) uint64 { - hash := fnv.New64a() - body = append([]byte(URL), body...) - hash.Write(body) - - return hash.Sum64() -} - -// NewClient initializes the cache HTTP middleware client with the given -// options. -func NewClient(opts ...ClientOption) (*Client, error) { - c := &Client{} - - for _, opt := range opts { - if err := opt(c); err != nil { - return nil, err - } - } - - if c.adapter == nil { - return nil, errors.New("cache client adapter is not set") - } - if int64(c.ttl) < 1 { - return nil, errors.New("cache client ttl is not set") - } - if c.methods == nil { - c.methods = []string{http.MethodGet} - } - - return c, nil -} - -// ClientWithAdapter sets the adapter type for the HTTP cache -// middleware client. -func ClientWithAdapter(a Adapter) ClientOption { - return func(c *Client) error { - c.adapter = a - return nil - } -} - -// ClientWithTTL sets how long each response is going to be cached. -func ClientWithTTL(ttl time.Duration) ClientOption { - return func(c *Client) error { - if int64(ttl) < 1 { - return fmt.Errorf("cache client ttl %v is invalid", ttl) - } - - c.ttl = ttl - - return nil - } -} - -// ClientWithRefreshKey sets the parameter key used to free a request -// cached response. Optional setting. -func ClientWithRefreshKey(refreshKey string) ClientOption { - return func(c *Client) error { - c.refreshKey = refreshKey - return nil - } -} - -// ClientWithMethods sets the acceptable HTTP methods to be cached. -// Optional setting. If not set, default is "GET". -func ClientWithMethods(methods []string) ClientOption { - return func(c *Client) error { - for _, method := range methods { - if method != http.MethodGet && method != http.MethodPost { - return fmt.Errorf("invalid method %s", method) - } - } - c.methods = methods - return nil - } -} diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go deleted file mode 100644 index 8555ab9..0000000 --- a/pkg/cache/cache_test.go +++ /dev/null @@ -1,481 +0,0 @@ -package cache - -import ( - "bytes" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "reflect" - "sync" - "testing" - "time" -) - -type adapterMock struct { - sync.Mutex - store map[uint64][]byte -} - -type errReader int - -func (a *adapterMock) Get(key uint64) ([]byte, bool) { - a.Lock() - defer a.Unlock() - if _, ok := a.store[key]; ok { - return a.store[key], true - } - return nil, false -} - -func (a *adapterMock) Set(key uint64, response []byte, expiration time.Time) { - a.Lock() - defer a.Unlock() - a.store[key] = response -} - -func (a *adapterMock) Release(key uint64) { - a.Lock() - defer a.Unlock() - delete(a.store, key) -} - -func (errReader) Read(p []byte) (n int, err error) { - return 0, errors.New("readAll error") -} - -func TestMiddleware(t *testing.T) { - counter := 0 - httpTestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(fmt.Sprintf("new value %v", counter))) - }) - - adapter := &adapterMock{ - store: map[uint64][]byte{ - 14974843192121052621: Response{ - Value: []byte("value 1"), - Expiration: time.Now().Add(1 * time.Minute), - }.Bytes(), - 14974839893586167988: Response{ - Value: []byte("value 2"), - Expiration: time.Now().Add(1 * time.Minute), - }.Bytes(), - 14974840993097796199: Response{ - Value: []byte("value 3"), - Expiration: time.Now().Add(-1 * time.Minute), - }.Bytes(), - 10956846073361780255: Response{ - Value: []byte("value 4"), - Expiration: time.Now().Add(-1 * time.Minute), - }.Bytes(), - }, - } - - client, _ := NewClient( - ClientWithAdapter(adapter), - ClientWithTTL(1*time.Minute), - ClientWithRefreshKey("rk"), - ClientWithMethods([]string{http.MethodGet, http.MethodPost}), - ) - - handler := client.Middleware(httpTestHandler, []string{"*"}) - - tests := []struct { - name string - url string - method string - body []byte - wantBody string - wantCode int - }{ - { - "returns cached response", - "http://foo.bar/test-1", - "GET", - nil, - "value 1", - 200, - }, - { - "returns new response", - "http://foo.bar/test-2", - "PUT", - nil, - "new value 2", - 200, - }, - { - "returns cached response", - "http://foo.bar/test-2", - "GET", - nil, - "value 2", - 200, - }, - { - "returns new response", - "http://foo.bar/test-3?zaz=baz&baz=zaz", - "GET", - nil, - "new value 4", - 200, - }, - { - "returns cached response", - "http://foo.bar/test-3?baz=zaz&zaz=baz", - "GET", - nil, - "new value 4", - 200, - }, - { - "cache expired", - "http://foo.bar/test-3", - "GET", - nil, - "new value 6", - 200, - }, - { - "releases cached response and returns new response", - "http://foo.bar/test-2?rk=true", - "GET", - nil, - "new value 7", - 200, - }, - { - "returns new cached response", - "http://foo.bar/test-2", - "GET", - nil, - "new value 7", - 200, - }, - { - "returns new cached response", - "http://foo.bar/test-2", - "POST", - []byte(`{"foo": "bar"}`), - "new value 9", - 200, - }, - { - "returns new cached response", - "http://foo.bar/test-2", - "POST", - []byte(`{"foo": "bar"}`), - "new value 9", - 200, - }, - { - "ignores request body", - "http://foo.bar/test-2", - "GET", - []byte(`{"foo": "bar"}`), - "new value 7", - 200, - }, - { - "returns new response", - "http://foo.bar/test-2", - "POST", - []byte(`{"foo": "bar"}`), - "new value 12", - 200, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - counter++ - var r *http.Request - var err error - - if counter != 12 { - reader := bytes.NewReader(tt.body) - r, err = http.NewRequest(tt.method, tt.url, reader) - if err != nil { - t.Error(err) - return - } - } else { - r, err = http.NewRequest(tt.method, tt.url, errReader(0)) - if err != nil { - t.Error(err) - return - } - } - - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - - if !reflect.DeepEqual(w.Code, tt.wantCode) { - t.Errorf("*Client.Middleware() = %v, want %v", w.Code, tt.wantCode) - return - } - if !reflect.DeepEqual(w.Body.String(), tt.wantBody) { - t.Errorf("*Client.Middleware() = %v, want %v", w.Body.String(), tt.wantBody) - } - }) - } -} - -func TestBytesToResponse(t *testing.T) { - r := Response{ - Value: []byte("value 1"), - Expiration: time.Time{}, - Frequency: 0, - LastAccess: time.Time{}, - } - - tests := []struct { - name string - b []byte - wantValue string - }{ - - { - "convert bytes array to response", - r.Bytes(), - "value 1", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := BytesToResponse(tt.b) - if string(got.Value) != tt.wantValue { - t.Errorf("BytesToResponse() Value = %v, want %v", got, tt.wantValue) - return - } - }) - } -} - -func TestResponseToBytes(t *testing.T) { - r := Response{ - Value: nil, - Expiration: time.Time{}, - Frequency: 0, - LastAccess: time.Time{}, - } - - tests := []struct { - name string - response Response - }{ - { - "convert response to bytes array", - r, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := tt.response.Bytes() - if b == nil || len(b) == 0 { - t.Error("Bytes() failed to convert") - return - } - }) - } -} - -func TestSortURLParams(t *testing.T) { - u, _ := url.Parse("http://test.com?zaz=bar&foo=zaz&boo=foo&boo=baz") - tests := []struct { - name string - URL *url.URL - want string - }{ - { - "returns url with ordered querystring params", - u, - "http://test.com?boo=baz&boo=foo&foo=zaz&zaz=bar", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sortURLParams(tt.URL) - got := tt.URL.String() - if got != tt.want { - t.Errorf("sortURLParams() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGenerateKeyString(t *testing.T) { - urls := []string{ - "http://localhost:8080/category", - "http://localhost:8080/category/morisco", - "http://localhost:8080/category/mourisquinho", - } - - keys := make(map[string]string, len(urls)) - for _, u := range urls { - rawKey := generateKey(u) - key := KeyAsString(rawKey) - - if otherURL, found := keys[key]; found { - t.Fatalf("URLs %s and %s share the same key %s", u, otherURL, key) - } - keys[key] = u - } -} - -func TestGenerateKey(t *testing.T) { - tests := []struct { - name string - URL string - want uint64 - }{ - { - "get url checksum", - "http://foo.bar/test-1", - 14974843192121052621, - }, - { - "get url 2 checksum", - "http://foo.bar/test-2", - 14974839893586167988, - }, - { - "get url 3 checksum", - "http://foo.bar/test-3", - 14974840993097796199, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := generateKey(tt.URL); got != tt.want { - t.Errorf("generateKey() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGenerateKeyWithBody(t *testing.T) { - tests := []struct { - name string - URL string - body []byte - want uint64 - }{ - { - "get POST checksum", - "http://foo.bar/test-1", - []byte(`{"foo": "bar"}`), - 16224051135567554746, - }, - { - "get POST 2 checksum", - "http://foo.bar/test-1", - []byte(`{"bar": "foo"}`), - 3604153880186288164, - }, - { - "get POST 3 checksum", - "http://foo.bar/test-2", - []byte(`{"foo": "bar"}`), - 10956846073361780255, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := generateKeyWithBody(tt.URL, tt.body); got != tt.want { - t.Errorf("generateKeyWithBody() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestNewClient(t *testing.T) { - adapter := &adapterMock{} - - tests := []struct { - name string - opts []ClientOption - want *Client - wantErr bool - }{ - { - "returns new client", - []ClientOption{ - ClientWithAdapter(adapter), - ClientWithTTL(1 * time.Millisecond), - ClientWithMethods([]string{http.MethodGet, http.MethodPost}), - }, - &Client{ - adapter: adapter, - ttl: 1 * time.Millisecond, - refreshKey: "", - methods: []string{http.MethodGet, http.MethodPost}, - }, - false, - }, - { - "returns new client with refresh key", - []ClientOption{ - ClientWithAdapter(adapter), - ClientWithTTL(1 * time.Millisecond), - ClientWithRefreshKey("rk"), - }, - &Client{ - adapter: adapter, - ttl: 1 * time.Millisecond, - refreshKey: "rk", - methods: []string{http.MethodGet}, - }, - false, - }, - { - "returns error", - []ClientOption{ - ClientWithAdapter(adapter), - }, - nil, - true, - }, - { - "returns error", - []ClientOption{ - ClientWithTTL(1 * time.Millisecond), - ClientWithRefreshKey("rk"), - }, - nil, - true, - }, - { - "returns error", - []ClientOption{ - ClientWithAdapter(adapter), - ClientWithTTL(0), - ClientWithRefreshKey("rk"), - }, - nil, - true, - }, - { - "returns error", - []ClientOption{ - ClientWithAdapter(adapter), - ClientWithTTL(1 * time.Millisecond), - ClientWithMethods([]string{http.MethodGet, http.MethodPut}), - }, - nil, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewClient(tt.opts...) - if (err != nil) != tt.wantErr { - t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewClient() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/cache/memory/memory.go b/pkg/cache/memory/memory.go deleted file mode 100644 index 0d5e823..0000000 --- a/pkg/cache/memory/memory.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -MIT License - -Copyright (c) 2018 Victor Springer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package memory - -import ( - "errors" - "fmt" - "sync" - "time" - - cache "github.com/nicolaspernoud/vestibule/pkg/cache" -) - -// Algorithm is the string type for caching algorithms labels. -type Algorithm string - -const ( - // LRU is the constant for Least Recently Used. - LRU Algorithm = "LRU" - - // MRU is the constant for Most Recently Used. - MRU Algorithm = "MRU" - - // LFU is the constant for Least Frequently Used. - LFU Algorithm = "LFU" - - // MFU is the constant for Most Frequently Used. - MFU Algorithm = "MFU" -) - -// Adapter is the memory adapter data structure. -type Adapter struct { - mutex sync.RWMutex - capacity int - algorithm Algorithm - store map[uint64][]byte -} - -// AdapterOptions is used to set Adapter settings. -type AdapterOptions func(a *Adapter) error - -// Get implements the cache Adapter interface Get method. -func (a *Adapter) Get(key uint64) ([]byte, bool) { - a.mutex.RLock() - response, ok := a.store[key] - a.mutex.RUnlock() - - if ok { - return response, true - } - - return nil, false -} - -// Set implements the cache Adapter interface Set method. -func (a *Adapter) Set(key uint64, response []byte, expiration time.Time) { - a.mutex.RLock() - length := len(a.store) - a.mutex.RUnlock() - - if length > 0 && length == a.capacity { - a.evict() - } - - a.mutex.Lock() - a.store[key] = response - a.mutex.Unlock() -} - -// Release implements the Adapter interface Release method. -func (a *Adapter) Release(key uint64) { - a.mutex.RLock() - _, ok := a.store[key] - a.mutex.RUnlock() - - if ok { - a.mutex.Lock() - delete(a.store, key) - a.mutex.Unlock() - } -} - -func (a *Adapter) evict() { - selectedKey := uint64(0) - lastAccess := time.Now() - frequency := 2147483647 - - if a.algorithm == MRU { - lastAccess = time.Time{} - } else if a.algorithm == MFU { - frequency = 0 - } - - for k, v := range a.store { - r := cache.BytesToResponse(v) - switch a.algorithm { - case LRU: - if r.LastAccess.Before(lastAccess) { - selectedKey = k - lastAccess = r.LastAccess - } - case MRU: - if r.LastAccess.After(lastAccess) || - r.LastAccess.Equal(lastAccess) { - selectedKey = k - lastAccess = r.LastAccess - } - case LFU: - if r.Frequency < frequency { - selectedKey = k - frequency = r.Frequency - } - case MFU: - if r.Frequency >= frequency { - selectedKey = k - frequency = r.Frequency - } - } - } - - a.Release(selectedKey) -} - -// NewAdapter initializes memory adapter. -func NewAdapter(opts ...AdapterOptions) (cache.Adapter, error) { - a := &Adapter{} - - for _, opt := range opts { - if err := opt(a); err != nil { - return nil, err - } - } - - if a.capacity <= 1 { - return nil, errors.New("memory adapter capacity is not set") - } - - if a.algorithm == "" { - return nil, errors.New("memory adapter caching algorithm is not set") - } - - a.mutex = sync.RWMutex{} - a.store = make(map[uint64][]byte, a.capacity) - - return a, nil -} - -// AdapterWithAlgorithm sets the approach used to select a cached -// response to be evicted when the capacity is reached. -func AdapterWithAlgorithm(alg Algorithm) AdapterOptions { - return func(a *Adapter) error { - a.algorithm = alg - return nil - } -} - -// AdapterWithCapacity sets the maximum number of cached responses. -func AdapterWithCapacity(cap int) AdapterOptions { - return func(a *Adapter) error { - if cap <= 1 { - return fmt.Errorf("memory adapter requires a capacity greater than %v", cap) - } - - a.capacity = cap - - return nil - } -} diff --git a/pkg/cache/memory/memory_test.go b/pkg/cache/memory/memory_test.go deleted file mode 100644 index b0f2dbc..0000000 --- a/pkg/cache/memory/memory_test.go +++ /dev/null @@ -1,299 +0,0 @@ -package memory - -import ( - "reflect" - "sync" - "testing" - "time" - - cache "github.com/nicolaspernoud/vestibule/pkg/cache" -) - -func TestGet(t *testing.T) { - a := &Adapter{ - sync.RWMutex{}, - 2, - LRU, - map[uint64][]byte{ - 14974843192121052621: cache.Response{ - Value: []byte("value 1"), - Expiration: time.Now(), - LastAccess: time.Now(), - Frequency: 1, - }.Bytes(), - }, - } - - tests := []struct { - name string - key uint64 - want []byte - ok bool - }{ - { - "returns right response", - 14974843192121052621, - []byte("value 1"), - true, - }, - { - "not found", - 123, - nil, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b, ok := a.Get(tt.key) - if ok != tt.ok { - t.Errorf("memory.Get() ok = %v, tt.ok %v", ok, tt.ok) - return - } - got := cache.BytesToResponse(b).Value - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("memory.Get() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSet(t *testing.T) { - a := &Adapter{ - sync.RWMutex{}, - 2, - LRU, - make(map[uint64][]byte), - } - - tests := []struct { - name string - key uint64 - response cache.Response - }{ - { - "sets a response cache", - 1, - cache.Response{ - Value: []byte("value 1"), - Expiration: time.Now().Add(1 * time.Minute), - }, - }, - { - "sets a response cache", - 2, - cache.Response{ - Value: []byte("value 2"), - Expiration: time.Now().Add(1 * time.Minute), - }, - }, - { - "sets a response cache", - 3, - cache.Response{ - Value: []byte("value 3"), - Expiration: time.Now().Add(1 * time.Minute), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - a.Set(tt.key, tt.response.Bytes(), tt.response.Expiration) - if cache.BytesToResponse(a.store[tt.key]).Value == nil { - t.Errorf( - "memory.Set() error = store[%v] response is not %s", tt.key, tt.response.Value, - ) - } - }) - } -} - -func TestRelease(t *testing.T) { - a := &Adapter{ - sync.RWMutex{}, - 2, - LRU, - map[uint64][]byte{ - 14974843192121052621: cache.Response{ - Expiration: time.Now().Add(1 * time.Minute), - Value: []byte("value 1"), - }.Bytes(), - 14974839893586167988: cache.Response{ - Expiration: time.Now(), - Value: []byte("value 2"), - }.Bytes(), - 14974840993097796199: cache.Response{ - Expiration: time.Now(), - Value: []byte("value 3"), - }.Bytes(), - }, - } - - tests := []struct { - name string - key uint64 - storeLength int - wantErr bool - }{ - { - "removes cached response from store", - 14974843192121052621, - 2, - false, - }, - { - "removes cached response from store", - 14974839893586167988, - 1, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - a.Release(tt.key) - if len(a.store) > tt.storeLength { - t.Errorf("memory.Release() error; store length = %v, want 0", len(a.store)) - } - }) - } -} - -func TestEvict(t *testing.T) { - tests := []struct { - name string - algorithm Algorithm - }{ - { - "lru removes third cached response", - LRU, - }, - { - "mru removes first cached response", - MRU, - }, - { - "lfu removes second cached response", - LFU, - }, - { - "mfu removes third cached response", - MFU, - }, - } - count := 0 - for _, tt := range tests { - count++ - - a := &Adapter{ - sync.RWMutex{}, - 2, - tt.algorithm, - map[uint64][]byte{ - 14974843192121052621: cache.Response{ - Value: []byte("value 1"), - Expiration: time.Now().Add(1 * time.Minute), - LastAccess: time.Now().Add(-1 * time.Minute), - Frequency: 2, - }.Bytes(), - 14974839893586167988: cache.Response{ - Value: []byte("value 2"), - Expiration: time.Now().Add(1 * time.Minute), - LastAccess: time.Now().Add(-2 * time.Minute), - Frequency: 1, - }.Bytes(), - 14974840993097796199: cache.Response{ - Value: []byte("value 3"), - Expiration: time.Now().Add(1 * time.Minute), - LastAccess: time.Now().Add(-3 * time.Minute), - Frequency: 3, - }.Bytes(), - }, - } - t.Run(tt.name, func(t *testing.T) { - a.evict() - - if count == 1 { - if _, ok := a.store[14974840993097796199]; ok { - t.Errorf("lru is not working properly") - return - } - } else if count == 2 { - if _, ok := a.store[14974843192121052621]; ok { - t.Errorf("mru is not working properly") - return - } - } else if count == 3 { - if _, ok := a.store[14974839893586167988]; ok { - t.Errorf("lfu is not working properly") - return - } - } else { - if count == 4 { - if _, ok := a.store[14974840993097796199]; ok { - t.Errorf("mfu is not working properly") - } - } - } - }) - } -} - -func TestNewAdapter(t *testing.T) { - tests := []struct { - name string - opts []AdapterOptions - want cache.Adapter - wantErr bool - }{ - { - "returns new Adapter", - []AdapterOptions{ - AdapterWithCapacity(4), - AdapterWithAlgorithm(LRU), - }, - &Adapter{ - sync.RWMutex{}, - 4, - LRU, - make(map[uint64][]byte), - }, - false, - }, - { - "returns error", - []AdapterOptions{ - AdapterWithAlgorithm(LRU), - }, - nil, - true, - }, - { - "returns error", - []AdapterOptions{ - AdapterWithCapacity(4), - }, - nil, - true, - }, - { - "returns error", - []AdapterOptions{ - AdapterWithCapacity(1), - }, - nil, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewAdapter(tt.opts...) - if (err != nil) != tt.wantErr { - t.Errorf("NewAdapter() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewAdapter() = %v, want %v", got, tt.want) - } - }) - } -} -- GitLab