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
101 lines
1.9 KiB
Go
101 lines
1.9 KiB
Go
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")
|
|
}
|
|
}
|