mirror of
https://github.com/luxfi/container.git
synced 2026-09-24 00:24:34 +00:00
288 lines
6.1 KiB
Go
288 lines
6.1 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package window
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/timer/mockable"
|
|
)
|
|
|
|
const (
|
|
testTTL = 10 * time.Second
|
|
testMaxSize = 10
|
|
)
|
|
|
|
// TestAdd tests that elements are populated as expected, ignoring
|
|
// any eviction.
|
|
func TestAdd(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
window []int
|
|
newlyAdded int
|
|
expectedOldest int
|
|
}{
|
|
{
|
|
name: "empty",
|
|
window: []int{},
|
|
newlyAdded: 1,
|
|
expectedOldest: 1,
|
|
},
|
|
{
|
|
name: "populated",
|
|
window: []int{1, 2, 3, 4},
|
|
newlyAdded: 5,
|
|
expectedOldest: 1,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
clock := &mockable.Clock{}
|
|
clock.Set(time.Now())
|
|
|
|
window := New[int](
|
|
Config{
|
|
Clock: clock,
|
|
MaxSize: testMaxSize,
|
|
TTL: testTTL,
|
|
},
|
|
)
|
|
for _, element := range test.window {
|
|
window.Add(element)
|
|
}
|
|
|
|
window.Add(test.newlyAdded)
|
|
|
|
require.Equal(len(test.window)+1, window.Length())
|
|
oldest, ok := window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(test.expectedOldest, oldest)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestTTLAdd tests the case where an element is stale in the window
|
|
// and needs to be evicted on Add.
|
|
func TestTTLAdd(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
clock := &mockable.Clock{}
|
|
start := time.Now()
|
|
clock.Set(start)
|
|
|
|
window := New[int](
|
|
Config{
|
|
Clock: clock,
|
|
MaxSize: testMaxSize,
|
|
TTL: testTTL,
|
|
},
|
|
)
|
|
|
|
// Now the window looks like this:
|
|
// [1, 2, 3]
|
|
window.Add(1)
|
|
window.Add(2)
|
|
window.Add(3)
|
|
|
|
require.Equal(3, window.Length())
|
|
oldest, ok := window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(1, oldest)
|
|
// Now we're one second past the ttl of 10 seconds as defined in testTTL,
|
|
// so all existing elements need to be evicted.
|
|
clock.Set(start.Add(testTTL + time.Second))
|
|
|
|
// Now the window should look like this:
|
|
// [4]
|
|
window.Add(4)
|
|
|
|
require.Equal(1, window.Length())
|
|
oldest, ok = window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(4, oldest)
|
|
// Now we're one second before the ttl of 10 seconds of when [4] was added,
|
|
// no element should be evicted
|
|
// [4, 5]
|
|
clock.Set(start.Add(2 * testTTL))
|
|
window.Add(5)
|
|
require.Equal(2, window.Length())
|
|
oldest, ok = window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(4, oldest)
|
|
|
|
// Now the window is still containing 4:
|
|
// [4, 5]
|
|
// we only evict on Add method because the window is calculated in the last element added
|
|
require.Equal(2, window.Length())
|
|
|
|
oldest, ok = window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(4, oldest)
|
|
}
|
|
|
|
// TestTTLLength tests that elements are evicted on Length
|
|
func TestTTLLength(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
clock := &mockable.Clock{}
|
|
start := time.Now()
|
|
clock.Set(start)
|
|
|
|
window := New[int](
|
|
Config{
|
|
Clock: clock,
|
|
MaxSize: testMaxSize,
|
|
TTL: testTTL,
|
|
},
|
|
)
|
|
|
|
// Now the window looks like this:
|
|
// [1, 2, 3]
|
|
window.Add(1)
|
|
window.Add(2)
|
|
window.Add(3)
|
|
|
|
require.Equal(3, window.Length())
|
|
|
|
// Now we're one second past the ttl of 10 seconds as defined in testTTL,
|
|
// so all existing elements need to be evicted.
|
|
clock.Set(start.Add(testTTL + time.Second))
|
|
|
|
// No more elements should be present in the window.
|
|
require.Equal(0, window.Length())
|
|
}
|
|
|
|
// TestTTLOldest tests that stale elements are evicted on calling Oldest
|
|
func TestTTLOldest(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
clock := &mockable.Clock{}
|
|
start := time.Now()
|
|
clock.Set(start)
|
|
|
|
windowIntf := New[int](
|
|
Config{
|
|
Clock: clock,
|
|
MaxSize: testMaxSize,
|
|
TTL: testTTL,
|
|
},
|
|
)
|
|
require.IsType(&window[int]{}, windowIntf)
|
|
window := windowIntf.(*window[int])
|
|
|
|
// Now the window looks like this:
|
|
// [1, 2, 3]
|
|
window.Add(1)
|
|
window.Add(2)
|
|
window.Add(3)
|
|
|
|
oldest, ok := window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(1, oldest)
|
|
require.Equal(3, window.elements.Len())
|
|
|
|
// Now we're one second before the ttl of 10 seconds as defined in testTTL,
|
|
// so all existing elements shoud still exist.
|
|
// Add 4 to the window to make it:
|
|
// [1, 2, 3, 4]
|
|
clock.Set(start.Add(testTTL - time.Second))
|
|
window.Add(4)
|
|
|
|
oldest, ok = window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(1, oldest)
|
|
require.Equal(4, window.elements.Len())
|
|
|
|
// Now we're one second past the ttl of the initial 3 elements
|
|
// call to oldest should now evict 1,2,3 and return 4.
|
|
clock.Set(start.Add(testTTL + time.Second))
|
|
oldest, ok = window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(4, oldest)
|
|
require.Equal(1, window.elements.Len())
|
|
}
|
|
|
|
// Tests that we bound the amount of elements in the window
|
|
func TestMaxCapacity(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
clock := &mockable.Clock{}
|
|
clock.Set(time.Now())
|
|
|
|
window := New[int](
|
|
Config{
|
|
Clock: clock,
|
|
MaxSize: 3,
|
|
TTL: testTTL,
|
|
},
|
|
)
|
|
|
|
// Now the window looks like this:
|
|
// [1, 2, 3]
|
|
window.Add(1)
|
|
window.Add(2)
|
|
window.Add(3)
|
|
|
|
// We should evict 1 and replace it with 4.
|
|
// Now the window should look like this:
|
|
// [2, 3, 4]
|
|
window.Add(4)
|
|
// We should evict 2 and replace it with 5.
|
|
// Now the window should look like this:
|
|
// [3, 4, 5]
|
|
window.Add(5)
|
|
// We should evict 3 and replace it with 6.
|
|
// Now the window should look like this:
|
|
// [4, 5, 6]
|
|
window.Add(6)
|
|
|
|
require.Equal(3, window.Length())
|
|
oldest, ok := window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(4, oldest)
|
|
}
|
|
|
|
// Tests that we do not evict past the minimum window size
|
|
func TestMinCapacity(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
clock := &mockable.Clock{}
|
|
start := time.Now()
|
|
clock.Set(start)
|
|
|
|
window := New[int](
|
|
Config{
|
|
Clock: clock,
|
|
MaxSize: 3,
|
|
MinSize: 2,
|
|
TTL: testTTL,
|
|
},
|
|
)
|
|
|
|
// Now the window looks like this:
|
|
// [1, 2, 3]
|
|
window.Add(1)
|
|
window.Add(2)
|
|
window.Add(3)
|
|
|
|
clock.Set(start.Add(testTTL + time.Second))
|
|
|
|
// All of [1, 2, 3] are past the ttl now, but we don't evict 3 because of
|
|
// the minimum length.
|
|
// Now the window should look like this:
|
|
// [3, 4]
|
|
window.Add(4)
|
|
|
|
require.Equal(2, window.Length())
|
|
oldest, ok := window.Oldest()
|
|
require.True(ok)
|
|
require.Equal(3, oldest)
|
|
}
|