package main
import (
"sync"
"sync/atomic"
"testing"
"time"
)
// Startup registration runs a bounded number of repositories at once
// and finishes only when every one has: the window becomes the slowest
// store plus a little, not the sum. Serially, 26 stores took 46–97s
// and the forge refused every sync for that long (a3b9623e).
func TestRegistrationRunsBoundedAndWaitsForAll(t *testing.T) {
const items, bound = 12, 4
var inFlight, peak, done int64
var mu sync.Mutex
list := make([]int, items)
for i := range list {
list[i] = i
}
start := time.Now()
eachConcurrently(list, bound, func(int) {
cur := atomic.AddInt64(&inFlight, 1)
mu.Lock()
if cur > peak {
peak = cur
}
mu.Unlock()
time.Sleep(20 * time.Millisecond)
atomic.AddInt64(&inFlight, -1)
atomic.AddInt64(&done, 1)
})
took := time.Since(start)
if done != items {
t.Fatalf("FAIL: returned with %d of %d finished", done, items)
}
if peak > bound || peak < 2 {
t.Fatalf("FAIL: peak in flight %d, want between 2 and %d", peak, bound)
}
// 12 × 20ms serially is 240ms; at 4 wide it is ~60ms. Generous
// bound so a loaded machine does not flake, tight enough that
// serial execution fails it.
if took > 180*time.Millisecond {
t.Fatalf("FAIL: took %s; that is the serial cost", took)
}
t.Logf("ok: %d items, peak %d in flight, %s", items, peak, took.Round(time.Millisecond))
}
// A bound below one still runs everything, one at a time.
func TestRegistrationBoundOfZeroStillRunsEverything(t *testing.T) {
var n int64
eachConcurrently([]int{1, 2, 3}, 0, func(int) { atomic.AddInt64(&n, 1) })
if n != 3 {
t.Fatalf("FAIL: ran %d of 3", n)
}
t.Log("ok")
}