github.com/MetalBlockchain/metalgo@v1.11.9/x/merkledb/cache_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package merkledb 5 6 import ( 7 "errors" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 ) 12 13 var errTest = errors.New("test error") 14 15 func TestNewOnEvictCache(t *testing.T) { 16 require := require.New(t) 17 18 called := false 19 size := func(int, int) int { 20 return 1 21 } 22 onEviction := func(int, int) error { 23 called = true 24 return nil 25 } 26 maxSize := 10 27 28 cache := newOnEvictCache(maxSize, size, onEviction) 29 require.Equal(maxSize, cache.maxSize) 30 require.NotNil(cache.fifo) 31 require.Zero(cache.fifo.Len()) 32 // Can't test function equality directly so do this 33 // to make sure it was assigned correctly 34 require.NoError(cache.onEviction(0, 0)) 35 require.True(called) 36 } 37 38 // Test the functionality of the cache when the onEviction function 39 // never returns an error. 40 // Note this test assumes the internal cache is a FIFO cache 41 func TestOnEvictCacheNoOnEvictionError(t *testing.T) { 42 require := require.New(t) 43 44 evictedKey := []int{} 45 evictedValue := []int{} 46 size := func(int, int) int { 47 return 1 48 } 49 onEviction := func(k, n int) error { 50 evictedKey = append(evictedKey, k) 51 evictedValue = append(evictedValue, n) 52 return nil 53 } 54 maxSize := 3 55 56 cache := newOnEvictCache(maxSize, size, onEviction) 57 58 // Get non-existent key 59 _, ok := cache.Get(0) 60 require.False(ok) 61 62 // Put key 63 require.NoError(cache.Put(0, 0)) 64 require.Equal(1, cache.fifo.Len()) 65 66 // Get key 67 val, ok := cache.Get(0) 68 require.True(ok) 69 require.Zero(val) 70 71 // Get non-existent key 72 _, ok = cache.Get(1) 73 require.False(ok) 74 75 // Fill the cache 76 for i := 1; i < maxSize; i++ { 77 require.NoError(cache.Put(i, i)) 78 require.Equal(i+1, cache.fifo.Len()) 79 } 80 require.Empty(evictedKey) 81 require.Empty(evictedValue) 82 // Cache has [0,1,2] 83 84 // Put another key. This should evict the oldest inserted key (0). 85 require.NoError(cache.Put(maxSize, maxSize)) 86 require.Equal(maxSize, cache.fifo.Len()) 87 require.Len(evictedKey, 1) 88 require.Zero(evictedKey[0]) 89 require.Len(evictedValue, 1) 90 require.Zero(evictedValue[0]) 91 92 // Cache has [1,2,3] 93 iter := cache.fifo.NewIterator() 94 require.True(iter.Next()) 95 require.Equal(1, iter.Key()) 96 require.Equal(1, iter.Value()) 97 require.True(iter.Next()) 98 require.Equal(2, iter.Key()) 99 require.Equal(2, iter.Value()) 100 require.True(iter.Next()) 101 require.Equal(3, iter.Key()) 102 require.Equal(3, iter.Value()) 103 require.False(iter.Next()) 104 105 // 0 should no longer be in the cache 106 _, ok = cache.Get(0) 107 require.False(ok) 108 109 // Other keys should still be in the cache 110 for i := maxSize; i >= 1; i-- { 111 val, ok := cache.Get(i) 112 require.True(ok) 113 require.Equal(i, val) 114 } 115 116 // Cache has [1,2,3] 117 iter = cache.fifo.NewIterator() 118 require.True(iter.Next()) 119 require.Equal(1, iter.Key()) 120 require.Equal(1, iter.Value()) 121 require.True(iter.Next()) 122 require.Equal(2, iter.Key()) 123 require.Equal(2, iter.Value()) 124 require.True(iter.Next()) 125 require.Equal(3, iter.Key()) 126 require.Equal(3, iter.Value()) 127 require.False(iter.Next()) 128 129 // Put another key to evict the oldest inserted key (1). 130 require.NoError(cache.Put(maxSize+1, maxSize+1)) 131 require.Equal(maxSize, cache.fifo.Len()) 132 require.Len(evictedKey, 2) 133 require.Equal(1, evictedKey[1]) 134 require.Len(evictedValue, 2) 135 require.Equal(1, evictedValue[1]) 136 137 // Cache has [2,3,4] 138 iter = cache.fifo.NewIterator() 139 require.True(iter.Next()) 140 require.Equal(2, iter.Key()) 141 require.Equal(2, iter.Value()) 142 require.True(iter.Next()) 143 require.Equal(3, iter.Key()) 144 require.Equal(3, iter.Value()) 145 require.True(iter.Next()) 146 require.Equal(4, iter.Key()) 147 require.Equal(4, iter.Value()) 148 require.False(iter.Next()) 149 150 // 1 should no longer be in the cache 151 _, ok = cache.Get(1) 152 require.False(ok) 153 154 require.NoError(cache.Flush()) 155 156 // Cache should be empty 157 require.Zero(cache.fifo.Len()) 158 require.Len(evictedKey, 5) 159 require.Equal([]int{0, 1, 2, 3, 4}, evictedKey) 160 require.Len(evictedValue, 5) 161 require.Equal([]int{0, 1, 2, 3, 4}, evictedValue) 162 require.Zero(cache.fifo.Len()) 163 require.Equal(maxSize, cache.maxSize) // Should be unchanged 164 } 165 166 // Test the functionality of the cache when the onEviction function 167 // returns an error. 168 // Note this test assumes the cache is FIFO. 169 func TestOnEvictCacheOnEvictionError(t *testing.T) { 170 var ( 171 require = require.New(t) 172 evicted = []int{} 173 size = func(int, int) int { 174 return 1 175 } 176 onEviction = func(_, n int) error { 177 // Evicting even keys errors 178 evicted = append(evicted, n) 179 if n%2 == 0 { 180 return errTest 181 } 182 return nil 183 } 184 maxSize = 2 185 ) 186 187 cache := newOnEvictCache(maxSize, size, onEviction) 188 189 // Fill the cache 190 for i := 0; i < maxSize; i++ { 191 require.NoError(cache.Put(i, i)) 192 require.Equal(i+1, cache.fifo.Len()) 193 } 194 195 // Cache has [0,1] 196 197 // Put another key. This should evict the first key (0) 198 // and return an error since 0 is even. 199 err := cache.Put(maxSize, maxSize) 200 require.ErrorIs(err, errTest) 201 202 // Cache has [1,2] 203 require.Equal([]int{0}, evicted) 204 require.Equal(maxSize, cache.fifo.Len()) 205 _, ok := cache.Get(0) 206 require.False(ok) 207 _, ok = cache.Get(1) 208 require.True(ok) 209 _, ok = cache.Get(2) 210 require.True(ok) 211 212 // Flush the cache. Should error on last element (2). 213 err = cache.Flush() 214 require.ErrorIs(err, errTest) 215 216 // Should still be empty. 217 require.Zero(cache.fifo.Len()) 218 require.Equal([]int{0, 1, 2}, evicted) 219 _, ok = cache.Get(0) 220 require.False(ok) 221 _, ok = cache.Get(1) 222 require.False(ok) 223 _, ok = cache.Get(2) 224 require.False(ok) 225 }