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
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAllJobsProcessedAndOutputCloses(t *testing.T) {
|
||||
jobs := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
jobs <- i
|
||||
}
|
||||
close(jobs)
|
||||
}()
|
||||
|
||||
out := Pool(jobs, 5, func(n int) int { return n * 2 })
|
||||
|
||||
got := []int{}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for v := range out { // terminates only if out is closed
|
||||
got = append(got, v)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("output channel never closed — deadlock or missing close(out)")
|
||||
}
|
||||
|
||||
if len(got) != 100 {
|
||||
t.Fatalf("want 100 results, got %d", len(got))
|
||||
}
|
||||
sort.Ints(got)
|
||||
for i, v := range got {
|
||||
if v != i*2 {
|
||||
t.Fatalf("missing/wrong result: index %d = %d", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestActuallyConcurrent(t *testing.T) {
|
||||
var inFlight, peak atomic.Int32
|
||||
jobs := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 12; i++ {
|
||||
jobs <- i
|
||||
}
|
||||
close(jobs)
|
||||
}()
|
||||
|
||||
out := Pool(jobs, 4, func(n int) int {
|
||||
cur := inFlight.Add(1)
|
||||
for {
|
||||
p := peak.Load()
|
||||
if cur <= p || peak.CompareAndSwap(p, cur) {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
inFlight.Add(-1)
|
||||
return n
|
||||
})
|
||||
drained := make(chan struct{})
|
||||
go func() {
|
||||
for range out {
|
||||
}
|
||||
close(drained)
|
||||
}()
|
||||
select {
|
||||
case <-drained:
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("output never closed")
|
||||
}
|
||||
|
||||
if p := peak.Load(); p < 2 {
|
||||
t.Errorf("expected concurrent execution, peak parallelism was %d", p)
|
||||
} else if p > 4 {
|
||||
t.Errorf("more than `workers` goroutines running fn: peak %d > 4", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyJobs(t *testing.T) {
|
||||
jobs := make(chan int)
|
||||
close(jobs)
|
||||
out := Pool(jobs, 3, func(n int) int { return n })
|
||||
select {
|
||||
case _, ok := <-out:
|
||||
if ok {
|
||||
t.Fatal("got a value from empty job stream")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("output not closed for empty job stream")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user