mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			224 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
 | 
						|
// All rights reserved.
 | 
						|
// Use of this source code is governed by a MIT-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
package lrucache
 | 
						|
 | 
						|
import (
 | 
						|
	"sync"
 | 
						|
	"sync/atomic"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
func TestBasics(t *testing.T) {
 | 
						|
	cache := New(123)
 | 
						|
 | 
						|
	value1 := cache.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		return "bar", 1 * time.Second, 0
 | 
						|
	})
 | 
						|
 | 
						|
	if value1.(string) != "bar" {
 | 
						|
		t.Error("cache returned wrong value")
 | 
						|
	}
 | 
						|
 | 
						|
	value2 := cache.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		t.Error("value should be cached")
 | 
						|
		return "", 0, 0
 | 
						|
	})
 | 
						|
 | 
						|
	if value2.(string) != "bar" {
 | 
						|
		t.Error("cache returned wrong value")
 | 
						|
	}
 | 
						|
 | 
						|
	existed := cache.Del("foo")
 | 
						|
	if !existed {
 | 
						|
		t.Error("delete did not work as expected")
 | 
						|
	}
 | 
						|
 | 
						|
	value3 := cache.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		return "baz", 1 * time.Second, 0
 | 
						|
	})
 | 
						|
 | 
						|
	if value3.(string) != "baz" {
 | 
						|
		t.Error("cache returned wrong value")
 | 
						|
	}
 | 
						|
 | 
						|
	cache.Keys(func(key string, value interface{}) {
 | 
						|
		if key != "foo" || value.(string) != "baz" {
 | 
						|
			t.Error("cache corrupted")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestExpiration(t *testing.T) {
 | 
						|
	cache := New(123)
 | 
						|
 | 
						|
	failIfCalled := func() (interface{}, time.Duration, int) {
 | 
						|
		t.Error("Value should be cached!")
 | 
						|
		return "", 0, 0
 | 
						|
	}
 | 
						|
 | 
						|
	val1 := cache.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		return "bar", 5 * time.Millisecond, 0
 | 
						|
	})
 | 
						|
	val2 := cache.Get("bar", func() (interface{}, time.Duration, int) {
 | 
						|
		return "foo", 20 * time.Millisecond, 0
 | 
						|
	})
 | 
						|
 | 
						|
	val3 := cache.Get("foo", failIfCalled).(string)
 | 
						|
	val4 := cache.Get("bar", failIfCalled).(string)
 | 
						|
 | 
						|
	if val1 != val3 || val3 != "bar" || val2 != val4 || val4 != "foo" {
 | 
						|
		t.Error("Wrong values returned")
 | 
						|
	}
 | 
						|
 | 
						|
	time.Sleep(10 * time.Millisecond)
 | 
						|
 | 
						|
	val5 := cache.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		return "baz", 0, 0
 | 
						|
	})
 | 
						|
	val6 := cache.Get("bar", failIfCalled)
 | 
						|
 | 
						|
	if val5.(string) != "baz" || val6.(string) != "foo" {
 | 
						|
		t.Error("unexpected values")
 | 
						|
	}
 | 
						|
 | 
						|
	cache.Keys(func(key string, val interface{}) {
 | 
						|
		if key != "bar" || val.(string) != "foo" {
 | 
						|
			t.Error("wrong value expired")
 | 
						|
		}
 | 
						|
	})
 | 
						|
 | 
						|
	time.Sleep(15 * time.Millisecond)
 | 
						|
	cache.Keys(func(key string, val interface{}) {
 | 
						|
		t.Error("cache should be empty now")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestEviction(t *testing.T) {
 | 
						|
	c := New(100)
 | 
						|
	failIfCalled := func() (interface{}, time.Duration, int) {
 | 
						|
		t.Error("Value should be cached!")
 | 
						|
		return "", 0, 0
 | 
						|
	}
 | 
						|
 | 
						|
	v1 := c.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		return "bar", 1 * time.Second, 1000
 | 
						|
	})
 | 
						|
 | 
						|
	v2 := c.Get("foo", func() (interface{}, time.Duration, int) {
 | 
						|
		return "baz", 1 * time.Second, 1000
 | 
						|
	})
 | 
						|
 | 
						|
	if v1.(string) != "bar" || v2.(string) != "baz" {
 | 
						|
		t.Error("wrong values returned")
 | 
						|
	}
 | 
						|
 | 
						|
	c.Keys(func(key string, val interface{}) {
 | 
						|
		t.Error("cache should be empty now")
 | 
						|
	})
 | 
						|
 | 
						|
	_ = c.Get("A", func() (interface{}, time.Duration, int) {
 | 
						|
		return "a", 1 * time.Second, 50
 | 
						|
	})
 | 
						|
 | 
						|
	_ = c.Get("B", func() (interface{}, time.Duration, int) {
 | 
						|
		return "b", 1 * time.Second, 50
 | 
						|
	})
 | 
						|
 | 
						|
	_ = c.Get("A", failIfCalled)
 | 
						|
	_ = c.Get("B", failIfCalled)
 | 
						|
	_ = c.Get("C", func() (interface{}, time.Duration, int) {
 | 
						|
		return "c", 1 * time.Second, 50
 | 
						|
	})
 | 
						|
 | 
						|
	_ = c.Get("B", failIfCalled)
 | 
						|
	_ = c.Get("C", failIfCalled)
 | 
						|
 | 
						|
	v4 := c.Get("A", func() (interface{}, time.Duration, int) {
 | 
						|
		return "evicted", 1 * time.Second, 25
 | 
						|
	})
 | 
						|
 | 
						|
	if v4.(string) != "evicted" {
 | 
						|
		t.Error("value should have been evicted")
 | 
						|
	}
 | 
						|
 | 
						|
	c.Keys(func(key string, val interface{}) {
 | 
						|
		if key != "A" && key != "C" {
 | 
						|
			t.Errorf("'%s' was not expected", key)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// I know that this is a shity test,
 | 
						|
// time is relative and unreliable.
 | 
						|
func TestConcurrency(t *testing.T) {
 | 
						|
	c := New(100)
 | 
						|
	var wg sync.WaitGroup
 | 
						|
 | 
						|
	numActions := 20000
 | 
						|
	numThreads := 4
 | 
						|
	wg.Add(numThreads)
 | 
						|
 | 
						|
	var concurrentModifications int32 = 0
 | 
						|
 | 
						|
	for i := 0; i < numThreads; i++ {
 | 
						|
		go func() {
 | 
						|
			for j := 0; j < numActions; j++ {
 | 
						|
				_ = c.Get("key", func() (interface{}, time.Duration, int) {
 | 
						|
					m := atomic.AddInt32(&concurrentModifications, 1)
 | 
						|
					if m != 1 {
 | 
						|
						t.Error("only one goroutine at a time should calculate a value for the same key")
 | 
						|
					}
 | 
						|
 | 
						|
					time.Sleep(1 * time.Millisecond)
 | 
						|
					atomic.AddInt32(&concurrentModifications, -1)
 | 
						|
					return "value", 3 * time.Millisecond, 1
 | 
						|
				})
 | 
						|
			}
 | 
						|
 | 
						|
			wg.Done()
 | 
						|
		}()
 | 
						|
	}
 | 
						|
 | 
						|
	wg.Wait()
 | 
						|
 | 
						|
	c.Keys(func(key string, val interface{}) {})
 | 
						|
}
 | 
						|
 | 
						|
func TestPanic(t *testing.T) {
 | 
						|
	c := New(100)
 | 
						|
 | 
						|
	c.Put("bar", "baz", 3, 1*time.Minute)
 | 
						|
 | 
						|
	testpanic := func() {
 | 
						|
		defer func() {
 | 
						|
			if r := recover(); r != nil {
 | 
						|
				if r.(string) != "oops" {
 | 
						|
					t.Fatal("unexpected panic value")
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}()
 | 
						|
 | 
						|
		_ = c.Get("foo", func() (value interface{}, ttl time.Duration, size int) {
 | 
						|
			panic("oops")
 | 
						|
		})
 | 
						|
 | 
						|
		t.Fatal("should have paniced!")
 | 
						|
	}
 | 
						|
 | 
						|
	testpanic()
 | 
						|
 | 
						|
	v := c.Get("bar", func() (value interface{}, ttl time.Duration, size int) {
 | 
						|
		t.Fatal("should not be called!")
 | 
						|
		return nil, 0, 0
 | 
						|
	})
 | 
						|
 | 
						|
	if v.(string) != "baz" {
 | 
						|
		t.Fatal("unexpected value")
 | 
						|
	}
 | 
						|
 | 
						|
	testpanic()
 | 
						|
}
 |