123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- /*
- *
- * Copyright 2021 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- package rls
- import (
- "testing"
- "time"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "google.golang.org/grpc/internal/backoff"
- )
- var (
- cacheKeys = []cacheKey{
- {path: "0", keys: "a"},
- {path: "1", keys: "b"},
- {path: "2", keys: "c"},
- {path: "3", keys: "d"},
- {path: "4", keys: "e"},
- }
- longDuration = 10 * time.Minute
- shortDuration = 1 * time.Millisecond
- cacheEntries []*cacheEntry
- )
- func initCacheEntries() {
- // All entries have a dummy size of 1 to simplify resize operations.
- cacheEntries = []*cacheEntry{
- {
- // Entry is valid and minimum expiry time has not expired.
- expiryTime: time.Now().Add(longDuration),
- earliestEvictTime: time.Now().Add(longDuration),
- size: 1,
- },
- {
- // Entry is valid and is in backoff.
- expiryTime: time.Now().Add(longDuration),
- backoffTime: time.Now().Add(longDuration),
- backoffState: &backoffState{timer: time.NewTimer(longDuration)},
- size: 1,
- },
- {
- // Entry is valid, and not in backoff.
- expiryTime: time.Now().Add(longDuration),
- size: 1,
- },
- {
- // Entry is invalid.
- expiryTime: time.Time{}.Add(shortDuration),
- size: 1,
- },
- {
- // Entry is invalid valid and backoff has expired.
- expiryTime: time.Time{}.Add(shortDuration),
- backoffExpiryTime: time.Time{}.Add(shortDuration),
- size: 1,
- },
- }
- }
- func (s) TestLRU_BasicOperations(t *testing.T) {
- initCacheEntries()
- // Create an LRU and add some entries to it.
- lru := newLRU()
- for _, k := range cacheKeys {
- lru.addEntry(k)
- }
- // Get the least recent entry. This should be the first entry we added.
- if got, want := lru.getLeastRecentlyUsed(), cacheKeys[0]; got != want {
- t.Fatalf("lru.getLeastRecentlyUsed() = %v, want %v", got, want)
- }
- // Iterate through the slice of keys we added earlier, making them the most
- // recent entry, one at a time. The least recent entry at that point should
- // be the next entry from our slice of keys.
- for i, k := range cacheKeys {
- lru.makeRecent(k)
- lruIndex := (i + 1) % len(cacheKeys)
- if got, want := lru.getLeastRecentlyUsed(), cacheKeys[lruIndex]; got != want {
- t.Fatalf("lru.getLeastRecentlyUsed() = %v, want %v", got, want)
- }
- }
- // Iterate through the slice of keys we added earlier, removing them one at
- // a time The least recent entry at that point should be the next entry from
- // our slice of keys, except for the last one because the lru will be empty.
- for i, k := range cacheKeys {
- lru.removeEntry(k)
- var want cacheKey
- if i < len(cacheKeys)-1 {
- want = cacheKeys[i+1]
- }
- if got := lru.getLeastRecentlyUsed(); got != want {
- t.Fatalf("lru.getLeastRecentlyUsed() = %v, want %v", got, want)
- }
- }
- }
- func (s) TestDataCache_BasicOperations(t *testing.T) {
- initCacheEntries()
- dc := newDataCache(5, nil)
- for i, k := range cacheKeys {
- dc.addEntry(k, cacheEntries[i])
- }
- for i, k := range cacheKeys {
- entry := dc.getEntry(k)
- if !cmp.Equal(entry, cacheEntries[i], cmp.AllowUnexported(cacheEntry{}, backoffState{}), cmpopts.IgnoreUnexported(time.Timer{})) {
- t.Fatalf("Data cache lookup for key %v returned entry %v, want %v", k, entry, cacheEntries[i])
- }
- }
- }
- func (s) TestDataCache_AddForcesResize(t *testing.T) {
- initCacheEntries()
- dc := newDataCache(1, nil)
- // The first entry in cacheEntries has a minimum expiry time in the future.
- // This entry would stop the resize operation since we do not evict entries
- // whose minimum expiration time is in the future. So, we do not use that
- // entry in this test. The entry being added has a running backoff timer.
- evicted, ok := dc.addEntry(cacheKeys[1], cacheEntries[1])
- if evicted || !ok {
- t.Fatalf("dataCache.addEntry() returned (%v, %v) want (false, true)", evicted, ok)
- }
- // Add another entry leading to the eviction of the above entry which has a
- // running backoff timer. The first return value is expected to be true.
- backoffCancelled, ok := dc.addEntry(cacheKeys[2], cacheEntries[2])
- if !backoffCancelled || !ok {
- t.Fatalf("dataCache.addEntry() returned (%v, %v) want (true, true)", backoffCancelled, ok)
- }
- // Add another entry leading to the eviction of the above entry which does not
- // have a running backoff timer. This should evict the above entry, but the
- // first return value is expected to be false.
- backoffCancelled, ok = dc.addEntry(cacheKeys[3], cacheEntries[3])
- if backoffCancelled || !ok {
- t.Fatalf("dataCache.addEntry() returned (%v, %v) want (false, true)", backoffCancelled, ok)
- }
- }
- func (s) TestDataCache_Resize(t *testing.T) {
- initCacheEntries()
- dc := newDataCache(5, nil)
- for i, k := range cacheKeys {
- dc.addEntry(k, cacheEntries[i])
- }
- // The first cache entry (with a key of cacheKeys[0]) that we added has an
- // earliestEvictTime in the future. As part of the resize operation, we
- // traverse the cache in least recently used order, and this will be first
- // entry that we will encounter. And since the earliestEvictTime is in the
- // future, the resize operation will stop, leaving the cache bigger than
- // what was asked for.
- if dc.resize(1) {
- t.Fatalf("dataCache.resize() returned true, want false")
- }
- if dc.currentSize != 5 {
- t.Fatalf("dataCache.size is %d, want 5", dc.currentSize)
- }
- // Remove the entry with earliestEvictTime in the future and retry the
- // resize operation.
- dc.removeEntryForTesting(cacheKeys[0])
- if !dc.resize(1) {
- t.Fatalf("dataCache.resize() returned false, want true")
- }
- if dc.currentSize != 1 {
- t.Fatalf("dataCache.size is %d, want 1", dc.currentSize)
- }
- }
- func (s) TestDataCache_EvictExpiredEntries(t *testing.T) {
- initCacheEntries()
- dc := newDataCache(5, nil)
- for i, k := range cacheKeys {
- dc.addEntry(k, cacheEntries[i])
- }
- // The last two entries in the cacheEntries list have expired, and will be
- // evicted. The first three should still remain in the cache.
- if !dc.evictExpiredEntries() {
- t.Fatal("dataCache.evictExpiredEntries() returned false, want true")
- }
- if dc.currentSize != 3 {
- t.Fatalf("dataCache.size is %d, want 3", dc.currentSize)
- }
- for i := 0; i < 3; i++ {
- entry := dc.getEntry(cacheKeys[i])
- if !cmp.Equal(entry, cacheEntries[i], cmp.AllowUnexported(cacheEntry{}, backoffState{}), cmpopts.IgnoreUnexported(time.Timer{})) {
- t.Fatalf("Data cache lookup for key %v returned entry %v, want %v", cacheKeys[i], entry, cacheEntries[i])
- }
- }
- }
- func (s) TestDataCache_ResetBackoffState(t *testing.T) {
- type fakeBackoff struct {
- backoff.Strategy
- }
- initCacheEntries()
- dc := newDataCache(5, nil)
- for i, k := range cacheKeys {
- dc.addEntry(k, cacheEntries[i])
- }
- newBackoffState := &backoffState{bs: &fakeBackoff{}}
- if updatePicker := dc.resetBackoffState(newBackoffState); !updatePicker {
- t.Fatal("dataCache.resetBackoffState() returned updatePicker is false, want true")
- }
- // Make sure that the entry with no backoff state was not touched.
- if entry := dc.getEntry(cacheKeys[0]); cmp.Equal(entry.backoffState, newBackoffState, cmp.AllowUnexported(backoffState{})) {
- t.Fatal("dataCache.resetBackoffState() touched entries without a valid backoffState")
- }
- // Make sure that the entry with a valid backoff state was reset.
- entry := dc.getEntry(cacheKeys[1])
- if diff := cmp.Diff(entry.backoffState, newBackoffState, cmp.AllowUnexported(backoffState{})); diff != "" {
- t.Fatalf("unexpected diff in backoffState for cache entry after dataCache.resetBackoffState(): %s", diff)
- }
- }
|