package service import ( "strconv" "sync" "testing" ) func TestLRUEvictsLeastRecentlyUsed(t *testing.T) { c := newLRU[int](2) c.Put("a", 1) c.Put("b", 2) // Touching "a" makes "b" the eviction candidate. if _, ok := c.Get("a"); !ok { t.Fatal("a missing right after Put") } c.Put("c", 3) if _, ok := c.Get("b"); ok { t.Error("b survived, want it evicted as least recently used") } for _, key := range []string{"a", "c"} { if _, ok := c.Get(key); !ok { t.Errorf("%s evicted, want it kept", key) } } // Overwriting an existing key must not grow the cache past its size. c.Put("a", 9) if v, _ := c.Get("a"); v != 9 { t.Errorf("a = %d, want 9", v) } if got := c.order.Len(); got != 2 { t.Errorf("cache holds %d entries, want 2", got) } c.Delete("a") if _, ok := c.Get("a"); ok { t.Error("a readable after Delete") } } // The per-file thumbnail locks are reference counted rather than cached. The map // must not grow with every file ever touched, and must not drop a lock that is // still held. func TestThumbLocksReleasedWhenUnused(t *testing.T) { s := NewLibraryService(t.TempDir(), "ffmpeg", "ffprobe") var wg sync.WaitGroup shared := 0 for i := 0; i < 50; i++ { wg.Add(1) go func(i int) { defer wg.Done() // Half contend on one path, half take a path of their own. if i%2 == 0 { defer s.lockThumbFile("shared")() shared++ return } defer s.lockThumbFile("file-" + strconv.Itoa(i))() }(i) } wg.Wait() if shared != 25 { t.Errorf("shared counter = %d, want 25: the lock did not serialize callers", shared) } if got := len(s.thumbLocks); got != 0 { t.Errorf("%d locks left behind, want 0", got) } }