Files
acp/cache_test.go
T
Samuel Cui 4d5dbd0b8c Release ACP v0.2.3
Simplify indexed copy facts and unify staged output completion.
Fix cleanup, rewrite persistence, native paths, and terminal reporting.
Maintain compatibility, upstream notices, and automated release checks.
2026-10-05 16:29:55 +08:00

116 lines
2.3 KiB
Go

package acp
import (
"sync"
"sync/atomic"
"testing"
"time"
)
func TestCacheConcurrentInitOnce(t *testing.T) {
var initialized int64
cached := Cache(func(in int) *int {
atomic.AddInt64(&initialized, 1)
out := new(int)
*out = in
return out
})
const workers = 64
start := make(chan struct{})
results := make(chan *int, workers)
var wg sync.WaitGroup
for idx := 0; idx < workers; idx++ {
wg.Add(1)
go func() {
defer wg.Done()
<-start
results <- cached(1)
}()
}
close(start)
wg.Wait()
close(results)
if got := atomic.LoadInt64(&initialized); got != 1 {
t.Fatalf("cache initialized %d times", got)
}
var first *int
for result := range results {
if first == nil {
first = result
continue
}
if result != first {
t.Fatalf("cache returned different values")
}
}
}
func TestCacheInitializerCanLoadAnotherKey(t *testing.T) {
var cached func(int) int
cached = Cache(func(in int) int {
if in == 0 {
return 0
}
return cached(in-1) + 1
})
done := make(chan int, 1)
go func() { done <- cached(2) }()
select {
case got := <-done:
if got != 2 {
t.Fatalf("cached value = %d", got)
}
case <-time.After(time.Second):
t.Fatalf("cache initializer deadlocked")
}
}
func TestCacheInitializesDifferentKeysConcurrently(t *testing.T) {
// Keep one initializer in flight until the independent key has returned.
started := make(chan struct{})
release := make(chan struct{})
first := make(chan int, 1)
cached := Cache(func(key int) int {
if key == 1 {
close(started)
<-release
}
return key
})
go func() { first <- cached(1) }()
t.Cleanup(func() {
close(release)
select {
case got := <-first:
if got != 1 {
t.Errorf("first key = %d, want 1", got)
}
case <-time.After(5 * time.Second):
t.Error("the first initializer did not finish after release")
}
})
select {
case <-started:
case <-time.After(5 * time.Second):
t.Fatal("the first initializer never started")
}
// A different key must finish without waiting for the first key's initializer.
second := make(chan int, 1)
go func() { second <- cached(2) }()
select {
case got := <-second:
if got != 2 {
t.Fatalf("second key = %d, want 2", got)
}
case <-time.After(5 * time.Second):
t.Fatal("a different key waited for the blocked initializer")
}
}