github.com/decred/dcrlnd@v0.7.6/neutrinocache/lru/lru_test.go (about)

     1  package lru
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  
     8  	cache "github.com/decred/dcrlnd/neutrinocache"
     9  )
    10  
    11  func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
    12  	if a == b {
    13  		return
    14  	}
    15  	if len(message) == 0 {
    16  		message = fmt.Sprintf("%v != %v", a, b)
    17  	}
    18  	t.Fatal(message)
    19  }
    20  
    21  // sizeable is a simple struct that represents an element of arbitrary size
    22  // which holds a simple integer.
    23  type sizeable struct {
    24  	value int
    25  	size  uint64
    26  }
    27  
    28  // Size implements the CacheEntry interface on sizeable struct.
    29  func (s *sizeable) Size() (uint64, error) {
    30  	return s.size, nil
    31  }
    32  
    33  // getSizeableValue is a helper method used for converting the cache.Value
    34  // interface to sizeable struct and extracting the value from it.
    35  func getSizeableValue(generic cache.Value, _ error) int {
    36  	return generic.(*sizeable).value
    37  }
    38  
    39  // TestEmptyCacheSizeZero will check that an empty cache has a size of 0.
    40  func TestEmptyCacheSizeZero(t *testing.T) {
    41  	t.Parallel()
    42  	c := NewCache(10)
    43  	assertEqual(t, c.Len(), 0, "")
    44  }
    45  
    46  // TestCacheNeverExceedsSize inserts many filters into the cache and verifies
    47  // at each step that the cache never exceeds it's initial size.
    48  func TestCacheNeverExceedsSize(t *testing.T) {
    49  	t.Parallel()
    50  	c := NewCache(2)
    51  	c.Put(1, &sizeable{value: 1, size: 1})
    52  	c.Put(2, &sizeable{value: 2, size: 1})
    53  	assertEqual(t, c.Len(), 2, "")
    54  	for i := 0; i < 10; i++ {
    55  		c.Put(i, &sizeable{value: i, size: 1})
    56  		assertEqual(t, c.Len(), 2, "")
    57  	}
    58  }
    59  
    60  // TestCacheAlwaysHasLastAccessedItems will check that the last items that
    61  // were put in the cache are always available, it will also check the eviction
    62  // behavior when items put in the cache exceeds cache capacity.
    63  func TestCacheAlwaysHasLastAccessedItems(t *testing.T) {
    64  	t.Parallel()
    65  	c := NewCache(2)
    66  	c.Put(1, &sizeable{value: 1, size: 1})
    67  	c.Put(2, &sizeable{value: 2, size: 1})
    68  	two := getSizeableValue(c.Get(2))
    69  	one := getSizeableValue(c.Get(1))
    70  	assertEqual(t, two, 2, "")
    71  	assertEqual(t, one, 1, "")
    72  
    73  	c = NewCache(2)
    74  	c.Put(1, &sizeable{value: 1, size: 1})
    75  	c.Put(2, &sizeable{value: 2, size: 1})
    76  	c.Put(3, &sizeable{value: 3, size: 1})
    77  	oneEntry, _ := c.Get(1)
    78  	two = getSizeableValue(c.Get(2))
    79  	three := getSizeableValue(c.Get(3))
    80  	assertEqual(t, oneEntry, nil, "")
    81  	assertEqual(t, two, 2, "")
    82  	assertEqual(t, three, 3, "")
    83  
    84  	c = NewCache(2)
    85  	c.Put(1, &sizeable{value: 1, size: 1})
    86  	c.Put(2, &sizeable{value: 2, size: 1})
    87  	c.Get(1)
    88  	c.Put(3, &sizeable{value: 3, size: 1})
    89  	one = getSizeableValue(c.Get(1))
    90  	twoEntry, _ := c.Get(2)
    91  	three = getSizeableValue(c.Get(3))
    92  	assertEqual(t, one, 1, "")
    93  	assertEqual(t, twoEntry, nil, "")
    94  	assertEqual(t, three, 3, "")
    95  }
    96  
    97  // TestElementSizeCapacityEvictsEverything tests that Cache evicts everything
    98  // from cache when an element with size=capacity is inserted.
    99  func TestElementSizeCapacityEvictsEverything(t *testing.T) {
   100  	t.Parallel()
   101  	c := NewCache(3)
   102  
   103  	c.Put(1, &sizeable{value: 1, size: 1})
   104  	c.Put(2, &sizeable{value: 2, size: 1})
   105  	c.Put(3, &sizeable{value: 3, size: 1})
   106  
   107  	// Insert element with size=capacity of cache, should evict everything.
   108  	c.Put(4, &sizeable{value: 4, size: 3})
   109  	assertEqual(t, c.Len(), 1, "")
   110  	assertEqual(t, len(c.cache), 1, "")
   111  	four := getSizeableValue(c.Get(4))
   112  	assertEqual(t, four, 4, "")
   113  
   114  	c = NewCache(6)
   115  	c.Put(1, &sizeable{value: 1, size: 1})
   116  	c.Put(2, &sizeable{value: 2, size: 2})
   117  	c.Put(3, &sizeable{value: 3, size: 3})
   118  	assertEqual(t, c.size, uint64(6), "")
   119  
   120  	// Insert element with size=capacity of cache.
   121  	c.Put(4, &sizeable{value: 4, size: 6})
   122  	assertEqual(t, c.Len(), 1, "")
   123  	assertEqual(t, len(c.cache), 1, "")
   124  	four = getSizeableValue(c.Get(4))
   125  	assertEqual(t, four, 4, "")
   126  }
   127  
   128  // TestCacheFailsInsertionSizeBiggerCapacity tests that the cache fails the
   129  // put operation when the element's size is bigger than it's capacity.
   130  func TestCacheFailsInsertionSizeBiggerCapacity(t *testing.T) {
   131  	t.Parallel()
   132  	c := NewCache(2)
   133  
   134  	_, err := c.Put(1, &sizeable{value: 1, size: 3})
   135  	if err == nil {
   136  		t.Fatal("shouldn't be able to put elements larger than cache")
   137  	}
   138  	assertEqual(t, c.Len(), 0, "")
   139  }
   140  
   141  // TestManySmallElementCanInsertAfterBigEviction tests that when a big element
   142  // is evicted from the Cache, multiple smaller ones can be inserted without an
   143  // eviction taking place.
   144  func TestManySmallElementCanInsertAfterBigEviction(t *testing.T) {
   145  	t.Parallel()
   146  	c := NewCache(3)
   147  
   148  	_, err := c.Put(1, &sizeable{value: 1, size: 3})
   149  	if err != nil {
   150  		t.Fatal("couldn't insert element")
   151  	}
   152  
   153  	assertEqual(t, c.Len(), 1, "")
   154  
   155  	c.Put(2, &sizeable{value: 2, size: 1})
   156  	two := getSizeableValue(c.Get(2))
   157  	oneEntry, _ := c.Get(1)
   158  	assertEqual(t, c.Len(), 1, "")
   159  	assertEqual(t, two, 2, "")
   160  	assertEqual(t, oneEntry, nil, "")
   161  
   162  	c.Put(3, &sizeable{value: 3, size: 1})
   163  	assertEqual(t, c.Len(), 2, "")
   164  
   165  	c.Put(4, &sizeable{value: 4, size: 1})
   166  	assertEqual(t, c.Len(), 3, "")
   167  
   168  	two = getSizeableValue(c.Get(2))
   169  	three := getSizeableValue(c.Get(3))
   170  	four := getSizeableValue(c.Get(4))
   171  	assertEqual(t, two, 2, "")
   172  	assertEqual(t, three, 3, "")
   173  	assertEqual(t, four, 4, "")
   174  }
   175  
   176  // TestReplacingElementValueSmallerSize tests that if an existing element is
   177  // replaced with a value of size smaller, that the size shrinks and we can
   178  // insert without an eviction taking place.
   179  func TestReplacingElementValueSmallerSize(t *testing.T) {
   180  	t.Parallel()
   181  	c := NewCache(2)
   182  
   183  	c.Put(1, &sizeable{value: 1, size: 2})
   184  
   185  	c.Put(1, &sizeable{value: 1, size: 1})
   186  	c.Put(2, &sizeable{value: 2, size: 1})
   187  	one := getSizeableValue(c.Get(1))
   188  	two := getSizeableValue(c.Get(2))
   189  	assertEqual(t, one, 1, "")
   190  	assertEqual(t, two, 2, "")
   191  	assertEqual(t, c.Len(), 2, "")
   192  }
   193  
   194  // TestReplacingElementValueBiggerSize tests that if an existing element is
   195  // replaced with a value of size bigger, that it evicts accordingly.
   196  func TestReplacingElementValueBiggerSize(t *testing.T) {
   197  	t.Parallel()
   198  	c := NewCache(2)
   199  
   200  	c.Put(1, &sizeable{value: 1, size: 1})
   201  	c.Put(2, &sizeable{value: 2, size: 1})
   202  
   203  	c.Put(1, &sizeable{value: 3, size: 2})
   204  	assertEqual(t, c.Len(), 1, "")
   205  	one := getSizeableValue(c.Get(1))
   206  	assertEqual(t, one, 3, "")
   207  }
   208  
   209  // TestConcurrencySimple is a very simple test that checks concurrent access to
   210  // the lru cache. When running the test, "-race" option should be passed to
   211  // "go test" command.
   212  func TestConcurrencySimple(t *testing.T) {
   213  	t.Parallel()
   214  	c := NewCache(5)
   215  	var wg sync.WaitGroup
   216  
   217  	for i := 0; i < 5; i++ {
   218  		wg.Add(1)
   219  		go func(i int) {
   220  			defer wg.Done()
   221  			_, err := c.Put(i, &sizeable{value: i, size: 1})
   222  			if err != nil {
   223  				panic(err)
   224  			}
   225  		}(i)
   226  	}
   227  
   228  	for i := 0; i < 5; i++ {
   229  		wg.Add(1)
   230  		go func(i int) {
   231  			defer wg.Done()
   232  			_, err := c.Get(i)
   233  			if err != nil && err != cache.ErrElementNotFound {
   234  				panic(err)
   235  			}
   236  		}(i)
   237  	}
   238  
   239  	wg.Wait()
   240  }
   241  
   242  // TestConcurrencySmallCache is a test that checks concurrent access to the
   243  // lru cache when the cache is smaller than the number of elements we want to
   244  // put and retrieve. When running the test, "-race" option should be passed to
   245  // "go test" command.
   246  func TestConcurrencySmallCache(t *testing.T) {
   247  	t.Parallel()
   248  	c := NewCache(5)
   249  	var wg sync.WaitGroup
   250  
   251  	for i := 0; i < 20; i++ {
   252  		wg.Add(1)
   253  		go func(i int) {
   254  			defer wg.Done()
   255  			_, err := c.Put(i, &sizeable{value: i, size: 1})
   256  			if err != nil {
   257  				panic(err)
   258  			}
   259  		}(i)
   260  	}
   261  
   262  	for i := 0; i < 20; i++ {
   263  		wg.Add(1)
   264  		go func(i int) {
   265  			defer wg.Done()
   266  			_, err := c.Get(i)
   267  			if err != nil && err != cache.ErrElementNotFound {
   268  				panic(err)
   269  			}
   270  		}(i)
   271  	}
   272  
   273  	wg.Wait()
   274  }
   275  
   276  // TestConcurrencyBigCache is a test that checks concurrent access to the
   277  // lru cache when the cache is bigger than the number of elements we want to
   278  // put and retrieve. When running the test, "-race" option should be passed to
   279  // "go test" command.
   280  func TestConcurrencyBigCache(t *testing.T) {
   281  	t.Parallel()
   282  	c := NewCache(100)
   283  	var wg sync.WaitGroup
   284  
   285  	for i := 0; i < 50; i++ {
   286  		wg.Add(1)
   287  		go func(i int) {
   288  			defer wg.Done()
   289  			_, err := c.Put(i, &sizeable{value: i, size: 1})
   290  			if err != nil {
   291  				panic(err)
   292  			}
   293  		}(i)
   294  	}
   295  
   296  	for i := 0; i < 50; i++ {
   297  		wg.Add(1)
   298  		go func(i int) {
   299  			defer wg.Done()
   300  			_, err := c.Get(i)
   301  			if err != nil && err != cache.ErrElementNotFound {
   302  				panic(err)
   303  			}
   304  		}(i)
   305  	}
   306  
   307  	wg.Wait()
   308  }