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  }