github.com/MetalBlockchain/metalgo@v1.11.9/utils/window/window_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 window 5 6 import ( 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 13 ) 14 15 const ( 16 testTTL = 10 * time.Second 17 testMaxSize = 10 18 ) 19 20 // TestAdd tests that elements are populated as expected, ignoring 21 // any eviction. 22 func TestAdd(t *testing.T) { 23 tests := []struct { 24 name string 25 window []int 26 newlyAdded int 27 expectedOldest int 28 }{ 29 { 30 name: "empty", 31 window: []int{}, 32 newlyAdded: 1, 33 expectedOldest: 1, 34 }, 35 { 36 name: "populated", 37 window: []int{1, 2, 3, 4}, 38 newlyAdded: 5, 39 expectedOldest: 1, 40 }, 41 } 42 43 for _, test := range tests { 44 t.Run(test.name, func(t *testing.T) { 45 require := require.New(t) 46 47 clock := &mockable.Clock{} 48 clock.Set(time.Now()) 49 50 window := New[int]( 51 Config{ 52 Clock: clock, 53 MaxSize: testMaxSize, 54 TTL: testTTL, 55 }, 56 ) 57 for _, element := range test.window { 58 window.Add(element) 59 } 60 61 window.Add(test.newlyAdded) 62 63 require.Equal(len(test.window)+1, window.Length()) 64 oldest, ok := window.Oldest() 65 require.True(ok) 66 require.Equal(test.expectedOldest, oldest) 67 }) 68 } 69 } 70 71 // TestTTLAdd tests the case where an element is stale in the window 72 // and needs to be evicted on Add. 73 func TestTTLAdd(t *testing.T) { 74 require := require.New(t) 75 76 clock := &mockable.Clock{} 77 start := time.Now() 78 clock.Set(start) 79 80 window := New[int]( 81 Config{ 82 Clock: clock, 83 MaxSize: testMaxSize, 84 TTL: testTTL, 85 }, 86 ) 87 88 // Now the window looks like this: 89 // [1, 2, 3] 90 window.Add(1) 91 window.Add(2) 92 window.Add(3) 93 94 require.Equal(3, window.Length()) 95 oldest, ok := window.Oldest() 96 require.True(ok) 97 require.Equal(1, oldest) 98 // Now we're one second past the ttl of 10 seconds as defined in testTTL, 99 // so all existing elements need to be evicted. 100 clock.Set(start.Add(testTTL + time.Second)) 101 102 // Now the window should look like this: 103 // [4] 104 window.Add(4) 105 106 require.Equal(1, window.Length()) 107 oldest, ok = window.Oldest() 108 require.True(ok) 109 require.Equal(4, oldest) 110 // Now we're one second before the ttl of 10 seconds of when [4] was added, 111 // no element should be evicted 112 // [4, 5] 113 clock.Set(start.Add(2 * testTTL)) 114 window.Add(5) 115 require.Equal(2, window.Length()) 116 oldest, ok = window.Oldest() 117 require.True(ok) 118 require.Equal(4, oldest) 119 120 // Now the window is still containing 4: 121 // [4, 5] 122 // we only evict on Add method because the window is calculated in the last element added 123 require.Equal(2, window.Length()) 124 125 oldest, ok = window.Oldest() 126 require.True(ok) 127 require.Equal(4, oldest) 128 } 129 130 // TestTTLLength tests that elements are not evicted on Length 131 func TestTTLLength(t *testing.T) { 132 require := require.New(t) 133 134 clock := &mockable.Clock{} 135 start := time.Now() 136 clock.Set(start) 137 138 window := New[int]( 139 Config{ 140 Clock: clock, 141 MaxSize: testMaxSize, 142 TTL: testTTL, 143 }, 144 ) 145 146 // Now the window looks like this: 147 // [1, 2, 3] 148 window.Add(1) 149 window.Add(2) 150 window.Add(3) 151 152 require.Equal(3, window.Length()) 153 154 // Now we're one second past the ttl of 10 seconds as defined in testTTL, 155 // so all existing elements need to be evicted. 156 clock.Set(start.Add(testTTL + time.Second)) 157 158 // No more elements should be present in the window. 159 require.Equal(3, window.Length()) 160 } 161 162 // TestTTLOldest tests that stale elements are not evicted on calling Oldest 163 func TestTTLOldest(t *testing.T) { 164 require := require.New(t) 165 166 clock := &mockable.Clock{} 167 start := time.Now() 168 clock.Set(start) 169 170 windowIntf := New[int]( 171 Config{ 172 Clock: clock, 173 MaxSize: testMaxSize, 174 TTL: testTTL, 175 }, 176 ) 177 require.IsType(&window[int]{}, windowIntf) 178 window := windowIntf.(*window[int]) 179 180 // Now the window looks like this: 181 // [1, 2, 3] 182 window.Add(1) 183 window.Add(2) 184 window.Add(3) 185 186 oldest, ok := window.Oldest() 187 require.True(ok) 188 require.Equal(1, oldest) 189 require.Equal(3, window.elements.Len()) 190 191 // Now we're one second past the ttl of 10 seconds as defined in testTTL, 192 // so all existing elements shoud still exist. 193 clock.Set(start.Add(testTTL + time.Second)) 194 195 // Now there should be three elements in the window 196 oldest, ok = window.Oldest() 197 require.True(ok) 198 require.Equal(1, oldest) 199 require.Equal(3, window.elements.Len()) 200 } 201 202 // Tests that we bound the amount of elements in the window 203 func TestMaxCapacity(t *testing.T) { 204 require := require.New(t) 205 206 clock := &mockable.Clock{} 207 clock.Set(time.Now()) 208 209 window := New[int]( 210 Config{ 211 Clock: clock, 212 MaxSize: 3, 213 TTL: testTTL, 214 }, 215 ) 216 217 // Now the window looks like this: 218 // [1, 2, 3] 219 window.Add(1) 220 window.Add(2) 221 window.Add(3) 222 223 // We should evict 1 and replace it with 4. 224 // Now the window should look like this: 225 // [2, 3, 4] 226 window.Add(4) 227 // We should evict 2 and replace it with 5. 228 // Now the window should look like this: 229 // [3, 4, 5] 230 window.Add(5) 231 // We should evict 3 and replace it with 6. 232 // Now the window should look like this: 233 // [4, 5, 6] 234 window.Add(6) 235 236 require.Equal(3, window.Length()) 237 oldest, ok := window.Oldest() 238 require.True(ok) 239 require.Equal(4, oldest) 240 } 241 242 // Tests that we do not evict past the minimum window size 243 func TestMinCapacity(t *testing.T) { 244 require := require.New(t) 245 246 clock := &mockable.Clock{} 247 start := time.Now() 248 clock.Set(start) 249 250 window := New[int]( 251 Config{ 252 Clock: clock, 253 MaxSize: 3, 254 MinSize: 2, 255 TTL: testTTL, 256 }, 257 ) 258 259 // Now the window looks like this: 260 // [1, 2, 3] 261 window.Add(1) 262 window.Add(2) 263 window.Add(3) 264 265 clock.Set(start.Add(testTTL + time.Second)) 266 267 // All of [1, 2, 3] are past the ttl now, but we don't evict 3 because of 268 // the minimum length. 269 // Now the window should look like this: 270 // [3, 4] 271 window.Add(4) 272 273 require.Equal(2, window.Length()) 274 oldest, ok := window.Oldest() 275 require.True(ok) 276 require.Equal(3, oldest) 277 }