| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package bcache | |
| import ( | |
| "fmt" | |
| "runtime" | |
| "sync" | |
| "sync/atomic" | |
| "testing" | |
| ) | |
| var registeredCache Cache[int, int32] | |
| func init() { | |
| registeredCache.Register() | |
| } | |
| var seq atomic.Uint32 | |
| func next[T int | int32]() *T { | |
| x := new(T) | |
| *x = T(seq.Add(1)) | |
| return x | |
| } | |
| func str[T int | int32](x *T) string { | |
| if x == nil { | |
| return "nil" | |
| } | |
| return fmt.Sprint(*x) | |
| } | |
| func TestCache(t *testing.T) { | |
| // Use unregistered cache for functionality tests, | |
| // to keep the runtime from clearing behind our backs. | |
| c := new(Cache[int, int32]) | |
| // Create many entries. | |
| m := make(map[*int]*int32) | |
| for i := 0; i < 10000; i++ { | |
| k := next[int]() | |
| v := next[int32]() | |
| m[k] = v | |
| c.Put(k, v) | |
| } | |
| // Overwrite a random 20% of those. | |
| n := 0 | |
| for k := range m { | |
| v := next[int32]() | |
| m[k] = v | |
| c.Put(k, v) | |
| if n++; n >= 2000 { | |
| break | |
| } | |
| } | |
| // Check results. | |
| for k, v := range m { | |
| if cv := c.Get(k); cv != v { | |
| t.Fatalf("c.Get(%v) = %v, want %v", str(k), str(cv), str(v)) | |
| } | |
| } | |
| c.Clear() | |
| for k := range m { | |
| if cv := c.Get(k); cv != nil { | |
| t.Fatalf("after GC, c.Get(%v) = %v, want nil", str(k), str(cv)) | |
| } | |
| } | |
| // Check that registered cache is cleared at GC. | |
| c = ®isteredCache | |
| for k, v := range m { | |
| c.Put(k, v) | |
| } | |
| runtime.GC() | |
| for k := range m { | |
| if cv := c.Get(k); cv != nil { | |
| t.Fatalf("after Clear, c.Get(%v) = %v, want nil", str(k), str(cv)) | |
| } | |
| } | |
| // Check that cache works for concurrent access. | |
| // Lists are discarded if they reach 1000 entries, | |
| // and there are cacheSize list heads, so we should be | |
| // able to do 100 * cacheSize entries with no problem at all. | |
| c = new(Cache[int, int32]) | |
| var barrier, wg sync.WaitGroup | |
| const N = 100 | |
| barrier.Add(N) | |
| wg.Add(N) | |
| var lost int32 | |
| for i := 0; i < N; i++ { | |
| go func() { | |
| defer wg.Done() | |
| m := make(map[*int]*int32) | |
| for j := 0; j < cacheSize; j++ { | |
| k, v := next[int](), next[int32]() | |
| m[k] = v | |
| c.Put(k, v) | |
| } | |
| barrier.Done() | |
| barrier.Wait() | |
| for k, v := range m { | |
| if cv := c.Get(k); cv != v { | |
| t.Errorf("c.Get(%v) = %v, want %v", str(k), str(cv), str(v)) | |
| atomic.AddInt32(&lost, +1) | |
| } | |
| } | |
| }() | |
| } | |
| wg.Wait() | |
| if lost != 0 { | |
| t.Errorf("lost %d entries", lost) | |
| } | |
| } |