Files
kacerrandClaude Fable 5 7219eb5a23 Initial commit: Go SRE interview exercises
Includes solved 02-worker-pool and 09-safe-counter (mutex, atomic, sharded).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
2026-08-28 22:18:09 +02:00

130 lines
2.5 KiB
Go

package main
import (
"fmt"
"sync"
"testing"
)
const (
goroutines = 16
perG = 1000
)
func TestMutexCounter(t *testing.T) {
c := NewMutexCounter()
var wg sync.WaitGroup
for g := 0; g < goroutines; g++ {
wg.Add(1)
go func(g int) {
defer wg.Done()
for i := 0; i < perG; i++ {
c.Inc(fmt.Sprintf("k%d", i%4))
}
}(g)
}
wg.Wait()
var total int64
for i := 0; i < 4; i++ {
total += c.Get(fmt.Sprintf("k%d", i))
}
if want := int64(goroutines * perG); total != want {
t.Fatalf("lost updates: want %d got %d", want, total)
}
}
func TestSnapshotIsACopy(t *testing.T) {
c := NewMutexCounter()
c.Inc("a")
snap := c.Snapshot()
if snap == nil || snap["a"] != 1 {
t.Fatalf("bad snapshot: %v", snap)
}
snap["a"] = 999 // mutating the snapshot must not affect the counter
if c.Get("a") != 1 {
t.Fatal("Snapshot returned the internal map, not a copy")
}
// And writing to the snapshot while counting must not race (-race checks).
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); for i := 0; i < 100; i++ { c.Inc("a") } }()
for i := 0; i < 100; i++ {
snap["b"] = int64(i)
}
wg.Wait()
}
func TestAtomicCounter(t *testing.T) {
c := NewAtomicCounter()
var wg sync.WaitGroup
for g := 0; g < goroutines; g++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < perG; i++ {
c.Inc()
}
}()
}
wg.Wait()
if want := int64(goroutines * perG); c.Get() != want {
t.Fatalf("lost updates: want %d got %d", want, c.Get())
}
}
func TestShardedCounter(t *testing.T) {
c := NewShardedCounter()
var wg sync.WaitGroup
for g := 0; g < goroutines; g++ {
wg.Add(1)
go func(g int) {
defer wg.Done()
for i := 0; i < perG; i++ {
c.Inc(fmt.Sprintf("key-%d", i%50))
}
}(g)
}
wg.Wait()
var total int64
for k, v := range c.Snapshot() {
_ = k
total += v
}
if want := int64(goroutines * perG); total != want {
t.Fatalf("lost updates across shards: want %d got %d", want, total)
}
if c.Get("key-0") == 0 {
t.Fatal("Get by key broken")
}
}
func BenchmarkMutexCounter(b *testing.B) {
c := NewMutexCounter()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
c.Inc("hot")
}
})
}
func BenchmarkShardedCounter(b *testing.B) {
c := NewShardedCounter()
keys := [8]string{"a", "b", "c", "d", "e", "f", "g", "h"}
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
c.Inc(keys[i&7])
i++
}
})
}
func BenchmarkAtomicCounter(b *testing.B) {
c := NewAtomicCounter()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
c.Inc()
}
})
}