// Copyright (c) 2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package lru import ( "testing" ) // TestCache ensures the LRU Cache behaves as expected including limiting, // eviction of least-recently used entries, specific entry removal, and // existence tests. func TestCache(t *testing.T) { // Create a bunch of fake nonces to use in testing the lru nonce code. numNonces := 10 nonces := make([]uint64, 0, numNonces) for i := 0; i < numNonces; i++ { nonces = append(nonces, uint64(i)) } tests := []struct { name string limit int }{ {name: "limit 0", limit: 0}, {name: "limit 1", limit: 1}, {name: "limit 5", limit: 5}, {name: "limit 7", limit: 7}, {name: "limit one less than available", limit: numNonces - 1}, {name: "limit all available", limit: numNonces}, } testLoop: for i, test := range tests { // Create a new lru cache limited by the specified test limit and add // all of the test vectors. This will cause eviction since there are // more test items than the limits. cache := NewCache(uint(test.limit)) for j := 0; j < numNonces; j++ { cache.Add(nonces[j]) } // Ensure the limited number of most recent entries in the list exist. for j := numNonces - test.limit; j < numNonces; j++ { if !cache.Contains(nonces[j]) { t.Errorf("Contains #%d (%s) entry %d does not exist", i, test.name, nonces[j]) continue testLoop } } // Ensure the entries before the limited number of most recent entries // in the list do not exist. for j := 0; j < numNonces-test.limit; j++ { if cache.Contains(nonces[j]) { t.Errorf("Contains #%d (%s) entry %d exists", i, test.name, nonces[j]) continue testLoop } } // Access the entry that should currently be the least-recently used // entry so it becomes the most-recently used entry, then force an // eviction by adding an entry that doesn't exist and ensure the evicted // entry is the new least-recently used entry. // // This check needs at least 2 entries. if test.limit > 1 { origLruIndex := numNonces - test.limit _ = cache.Contains(nonces[origLruIndex]) cache.Add(uint64(numNonces) + 1) // Ensure the original lru entry still exists since it was updated // and should have become the lru entry. if !cache.Contains(nonces[origLruIndex]) { t.Errorf("Contains #%d (%s) entry %d does not exist", i, test.name, nonces[origLruIndex]) continue testLoop } // Ensure the entry that should've become the new lru entry was // evicted. newLruIndex := origLruIndex + 1 if cache.Contains(nonces[newLruIndex]) { t.Errorf("Contains #%d (%s) entry %d exists", i, test.name, nonces[newLruIndex]) continue testLoop } } // Add the entry that should currently be the least-recently used entry // again so it becomes the most-recently used entry, then force an // eviction by adding an entry that doesn't exist and ensure the evicted // entry is the new least-recently used entry. // // This check needs at least 2 entries. if test.limit > 1 { origLruIndex := numNonces - test.limit cache.Add(nonces[origLruIndex]) cache.Add(uint64(numNonces) + 2) // Ensure the original lru entry still exists since it was updated // and should've have become the lru entry. if !cache.Contains(nonces[origLruIndex]) { t.Errorf("Contains #%d (%s) entry %d does not exist", i, test.name, nonces[origLruIndex]) continue testLoop } // Ensure the entry that should've become the new lru entry was // evicted. newLruIndex := origLruIndex + 1 if cache.Contains(nonces[newLruIndex]) { t.Errorf("Contains #%d (%s) entry %d exists", i, test.name, nonces[newLruIndex]) continue testLoop } } // Delete all of the entries in the list, including those that don't // exist in the cache, and ensure they no longer exist. for j := 0; j < numNonces; j++ { cache.Delete(nonces[j]) if cache.Contains(nonces[j]) { t.Errorf("Delete #%d (%s) entry %d exists", i, test.name, nonces[j]) continue testLoop } } } } // BenchmarkCache performs basic benchmarks on the least recently used cache // handling. func BenchmarkCache(b *testing.B) { // Create a bunch of fake nonces to use in benchmarking the lru nonce code. b.StopTimer() numNonces := 100000 nonces := make([]uint64, 0, numNonces) for i := 0; i < numNonces; i++ { nonces = append(nonces, uint64(i)) } b.StartTimer() // Benchmark the add plus eviction code. limit := uint(20000) cache := NewCache(limit) for i := 0; i < b.N; i++ { cache.Add(nonces[i%numNonces]) } }