80 lines
2.1 KiB
Go
80 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// signalled reports whether ch carries a signal within d.
|
|
func signalled(ch <-chan struct{}, d time.Duration) bool {
|
|
select {
|
|
case <-ch:
|
|
return true
|
|
case <-time.After(d):
|
|
return false
|
|
}
|
|
}
|
|
|
|
// One signal reaches every registered loop, a burst is one pending signal per
|
|
// loop, and a loop that stopped is not signalled.
|
|
func TestKickBeatsReachesEveryLoopOnce(t *testing.T) {
|
|
a, stopA := subscribeBeatNow()
|
|
defer stopA()
|
|
b, stopB := subscribeBeatNow()
|
|
gone, stopGone := subscribeBeatNow()
|
|
stopGone()
|
|
kickBeats()
|
|
kickBeats()
|
|
kickBeats()
|
|
stopB()
|
|
for name, ch := range map[string]<-chan struct{}{"a": a, "b": b} {
|
|
if !signalled(ch, time.Second) {
|
|
t.Fatalf("loop %s was not asked to beat", name)
|
|
}
|
|
if signalled(ch, 50*time.Millisecond) {
|
|
t.Fatalf("loop %s holds more than one pending beat after a burst", name)
|
|
}
|
|
}
|
|
if signalled(gone, 50*time.Millisecond) {
|
|
t.Fatal("a loop that unregistered was still signalled")
|
|
}
|
|
}
|
|
|
|
// The beat loop beats on a kick without waiting for its tick, and the
|
|
// tick-only step (path failover) does not run for it.
|
|
func TestRunBeatLoopBeatsOnKick(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
var beats, ticks atomic.Int32
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
runBeatLoop(ctx, time.Hour, func() { ticks.Add(1) }, func() { beats.Add(1) })
|
|
}()
|
|
// The loop registers inside its goroutine; keep asking until it hears.
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for beats.Load() == 0 && time.Now().Before(deadline) {
|
|
kickBeats()
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if beats.Load() == 0 {
|
|
t.Fatal("a kicked beat loop did not beat before its 1-hour tick")
|
|
}
|
|
if ticks.Load() != 0 {
|
|
t.Fatal("a kicked beat ran the tick-only step")
|
|
}
|
|
cancel()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("the beat loop did not return when its context ended")
|
|
}
|
|
beatNow.Lock()
|
|
left := len(beatNow.subs)
|
|
beatNow.Unlock()
|
|
if left != 0 {
|
|
t.Fatalf("a returned beat loop stayed registered (%d left)", left)
|
|
}
|
|
}
|