89 lines
2.2 KiB
Go
89 lines
2.2 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
func TestThumbnailCacheDeduplicatesConcurrentBuilds(t *testing.T) {
|
|
c := newThumbnailCache()
|
|
key := thumbnailCacheKey{Path: "photo.jpg", Size: 10, ModTime: 20}
|
|
var builds atomic.Int32
|
|
const workers = 24
|
|
var wg sync.WaitGroup
|
|
results := make([][]byte, workers)
|
|
errs := make([]error, workers)
|
|
wg.Add(workers)
|
|
for i := 0; i < workers; i++ {
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
results[i], errs[i] = c.Do(key, func() ([]byte, error) {
|
|
builds.Add(1)
|
|
return []byte("thumbnail"), nil
|
|
})
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
if builds.Load() != 1 {
|
|
t.Fatalf("thumbnail builder ran %d times, want 1", builds.Load())
|
|
}
|
|
for i := range results {
|
|
if errs[i] != nil || string(results[i]) != "thumbnail" {
|
|
t.Fatalf("worker %d got result %q, err=%v", i, results[i], errs[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestThumbnailCacheDoesNotCacheErrors(t *testing.T) {
|
|
c := newThumbnailCache()
|
|
key := thumbnailCacheKey{Path: "broken.jpg"}
|
|
wantErr := errors.New("decode failed")
|
|
var builds atomic.Int32
|
|
for i := 0; i < 2; i++ {
|
|
_, err := c.Do(key, func() ([]byte, error) {
|
|
builds.Add(1)
|
|
return nil, wantErr
|
|
})
|
|
if !errors.Is(err, wantErr) {
|
|
t.Fatalf("got err %v, want %v", err, wantErr)
|
|
}
|
|
}
|
|
if builds.Load() != 2 {
|
|
t.Fatalf("error result was cached; builder ran %d times", builds.Load())
|
|
}
|
|
}
|
|
|
|
func TestThumbnailCacheEvictsByBound(t *testing.T) {
|
|
c := newThumbnailCache()
|
|
c.maxItems = 2
|
|
for i := 0; i < 3; i++ {
|
|
key := thumbnailCacheKey{Path: string(rune('a' + i))}
|
|
if _, err := c.Do(key, func() ([]byte, error) { return []byte("x"), nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if len(c.items) != 2 || c.order.Len() != 2 {
|
|
t.Fatalf("cache size=%d/list=%d, want 2/2", len(c.items), c.order.Len())
|
|
}
|
|
}
|
|
|
|
func TestThumbnailCacheEvictsByBytes(t *testing.T) {
|
|
c := newThumbnailCache()
|
|
c.maxBytes = 3
|
|
for i := 0; i < 2; i++ {
|
|
key := thumbnailCacheKey{Path: string(rune('x' + i))}
|
|
if _, err := c.Do(key, func() ([]byte, error) { return []byte("xx"), nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.bytes > c.maxBytes || len(c.items) != 1 {
|
|
t.Fatalf("cache bytes=%d items=%d, want <=%d/1", c.bytes, len(c.items), c.maxBytes)
|
|
}
|
|
}
|