forked from lei.chen/myxce
- COR-001: drain runHeartbeat goroutine via done channel before test returns, eliminating race against concurrent SetHeartbeatInterval tests - COR-002: replace time.After with time.NewTimer + defer t.Stop() + t.Reset() to prevent timer goroutine leaks on context cancellation - COR-005: guard HeartbeatInterval store write with > 0 check in OnClusterUpdated, keeping store consistent with SetHeartbeatInterval's zero-ignore semantics
81 lines
2 KiB
Go
81 lines
2 KiB
Go
package backend
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSetHeartbeatInterval_ValidValue(t *testing.T) {
|
|
atomic.StoreInt32(&heartbeatIntervalSecs, 30)
|
|
SetHeartbeatInterval(60)
|
|
if got := atomic.LoadInt32(&heartbeatIntervalSecs); got != 60 {
|
|
t.Errorf("heartbeatIntervalSecs = %d, want 60", got)
|
|
}
|
|
}
|
|
|
|
func TestSetHeartbeatInterval_ZeroIgnored(t *testing.T) {
|
|
atomic.StoreInt32(&heartbeatIntervalSecs, 30)
|
|
SetHeartbeatInterval(0)
|
|
if got := atomic.LoadInt32(&heartbeatIntervalSecs); got != 30 {
|
|
t.Errorf("heartbeatIntervalSecs = %d, want 30 (zero should be ignored)", got)
|
|
}
|
|
}
|
|
|
|
func TestSetHeartbeatInterval_NegativeIgnored(t *testing.T) {
|
|
atomic.StoreInt32(&heartbeatIntervalSecs, 30)
|
|
SetHeartbeatInterval(-1)
|
|
if got := atomic.LoadInt32(&heartbeatIntervalSecs); got != 30 {
|
|
t.Errorf("heartbeatIntervalSecs = %d, want 30 (negative should be ignored)", got)
|
|
}
|
|
}
|
|
|
|
func TestRunHeartbeat_CallsFunction(t *testing.T) {
|
|
atomic.StoreInt32(&heartbeatIntervalSecs, 1)
|
|
|
|
var callCount int64
|
|
fn := func(_ int) { atomic.AddInt64(&callCount, 1) }
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan struct{})
|
|
go func() {
|
|
runHeartbeat(ctx, fn)
|
|
close(done)
|
|
}()
|
|
|
|
time.Sleep(1500 * time.Millisecond)
|
|
cancel()
|
|
<-done // wait for goroutine to exit before the test returns
|
|
|
|
if got := atomic.LoadInt64(&callCount); got < 1 {
|
|
t.Errorf("fn was called %d times, want at least 1", got)
|
|
}
|
|
}
|
|
|
|
func TestRunHeartbeat_CancelsOnContext(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // already cancelled
|
|
|
|
var callCount int64
|
|
fn := func(_ int) {
|
|
atomic.AddInt64(&callCount, 1)
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
runHeartbeat(ctx, fn)
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// returned quickly as expected
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("runHeartbeat did not return promptly after context cancellation")
|
|
}
|
|
|
|
if got := atomic.LoadInt64(&callCount); got != 0 {
|
|
t.Errorf("fn was called %d times, want 0 for already-cancelled context", got)
|
|
}
|
|
}
|