mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	This package allows caching arbitrary key/value pairs in-memory, along with an interface implemented by the cache types. Extracted from #7493 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Ic8ca820927c456721cf324a0c8f3882a57752cc9
		
			
				
	
	
		
			200 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package cache
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| var startTime = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
 | |
| 
 | |
| func TestSingleCache(t *testing.T) {
 | |
| 	testTime := startTime
 | |
| 	timeNow := func() time.Time { return testTime }
 | |
| 	c := &Single[string, int]{
 | |
| 		timeNow: timeNow,
 | |
| 	}
 | |
| 
 | |
| 	t.Run("NoServeExpired", func(t *testing.T) {
 | |
| 		testCacheImpl(t, c, &testTime, false)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("ServeExpired", func(t *testing.T) {
 | |
| 		c.Empty()
 | |
| 		c.ServeExpired = true
 | |
| 		testTime = startTime
 | |
| 		testCacheImpl(t, c, &testTime, true)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestLocking(t *testing.T) {
 | |
| 	testTime := startTime
 | |
| 	timeNow := func() time.Time { return testTime }
 | |
| 	c := NewLocking(&Single[string, int]{
 | |
| 		timeNow: timeNow,
 | |
| 	})
 | |
| 
 | |
| 	// Just verify that the inner cache's behaviour hasn't changed.
 | |
| 	testCacheImpl(t, c, &testTime, false)
 | |
| }
 | |
| 
 | |
| func testCacheImpl(t *testing.T, c Cache[string, int], testTime *time.Time, serveExpired bool) {
 | |
| 	var fillTime time.Time
 | |
| 	t.Run("InitialFill", func(t *testing.T) {
 | |
| 		fillTime = testTime.Add(time.Hour)
 | |
| 		val, err := c.Get("key", func() (int, time.Time, error) {
 | |
| 			return 123, fillTime, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 123 {
 | |
| 			t.Fatalf("got val=%d; want 123", val)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Fetching again won't call our fill function
 | |
| 	t.Run("SecondFetch", func(t *testing.T) {
 | |
| 		*testTime = fillTime.Add(-1 * time.Second)
 | |
| 		called := false
 | |
| 		val, err := c.Get("key", func() (int, time.Time, error) {
 | |
| 			called = true
 | |
| 			return -1, fillTime, nil
 | |
| 		})
 | |
| 		if called {
 | |
| 			t.Fatal("wanted no call to fill function")
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 123 {
 | |
| 			t.Fatalf("got val=%d; want 123", val)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Fetching after the expiry time will re-fill
 | |
| 	t.Run("ReFill", func(t *testing.T) {
 | |
| 		*testTime = fillTime.Add(1)
 | |
| 		fillTime = fillTime.Add(time.Hour)
 | |
| 		val, err := c.Get("key", func() (int, time.Time, error) {
 | |
| 			return 999, fillTime, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 999 {
 | |
| 			t.Fatalf("got val=%d; want 999", val)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// An error on fetch will serve the expired value.
 | |
| 	t.Run("FetchError", func(t *testing.T) {
 | |
| 		if !serveExpired {
 | |
| 			t.Skipf("not testing ServeExpired")
 | |
| 		}
 | |
| 
 | |
| 		*testTime = fillTime.Add(time.Hour + 1)
 | |
| 		val, err := c.Get("key", func() (int, time.Time, error) {
 | |
| 			return 0, time.Time{}, errors.New("some error")
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 999 {
 | |
| 			t.Fatalf("got val=%d; want 999", val)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Fetching a different key re-fills
 | |
| 	t.Run("DifferentKey", func(t *testing.T) {
 | |
| 		*testTime = fillTime.Add(time.Hour + 1)
 | |
| 
 | |
| 		var calls int
 | |
| 		val, err := c.Get("key1", func() (int, time.Time, error) {
 | |
| 			calls++
 | |
| 			return 123, fillTime, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 123 {
 | |
| 			t.Fatalf("got val=%d; want 123", val)
 | |
| 		}
 | |
| 		if calls != 1 {
 | |
| 			t.Errorf("got %d, want 1 call", calls)
 | |
| 		}
 | |
| 
 | |
| 		val, err = c.Get("key2", func() (int, time.Time, error) {
 | |
| 			calls++
 | |
| 			return 456, fillTime, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 456 {
 | |
| 			t.Fatalf("got val=%d; want 456", val)
 | |
| 		}
 | |
| 		if calls != 2 {
 | |
| 			t.Errorf("got %d, want 2 call", calls)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Calling Forget with the wrong key does nothing, and with the correct
 | |
| 	// key will drop the cache.
 | |
| 	t.Run("Forget", func(t *testing.T) {
 | |
| 		// Add some time so that previously-cached values don't matter.
 | |
| 		fillTime = testTime.Add(2 * time.Hour)
 | |
| 		*testTime = fillTime.Add(-1 * time.Second)
 | |
| 
 | |
| 		const key = "key"
 | |
| 
 | |
| 		var calls int
 | |
| 		val, err := c.Get(key, func() (int, time.Time, error) {
 | |
| 			calls++
 | |
| 			return 123, fillTime, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 123 {
 | |
| 			t.Fatalf("got val=%d; want 123", val)
 | |
| 		}
 | |
| 		if calls != 1 {
 | |
| 			t.Errorf("got %d, want 1 call", calls)
 | |
| 		}
 | |
| 
 | |
| 		// Forgetting the wrong key does nothing
 | |
| 		c.Forget("other")
 | |
| 		val, err = c.Get(key, func() (int, time.Time, error) {
 | |
| 			t.Fatal("should not be called")
 | |
| 			panic("unreachable")
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 123 {
 | |
| 			t.Fatalf("got val=%d; want 123", val)
 | |
| 		}
 | |
| 
 | |
| 		// Forgetting the correct key re-fills
 | |
| 		c.Forget(key)
 | |
| 
 | |
| 		val, err = c.Get("key2", func() (int, time.Time, error) {
 | |
| 			calls++
 | |
| 			return 456, fillTime, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if val != 456 {
 | |
| 			t.Fatalf("got val=%d; want 456", val)
 | |
| 		}
 | |
| 		if calls != 2 {
 | |
| 			t.Errorf("got %d, want 2 call", calls)
 | |
| 		}
 | |
| 	})
 | |
| }
 |