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  }