package harness import ( "sync" "testing" "time" ) // newTestCB 造一个可控时钟、低阈值的熔断器,便于确定性测试。 func newTestCB(threshold int, cooldown time.Duration, clock *time.Time) *CircuitBreaker { c := NewCircuitBreaker() c.threshold = threshold c.cooldown = cooldown c.now = func() time.Time { return *clock } return c } func TestCircuitBreaker_OpensAfterThreshold(t *testing.T) { now := time.Unix(0, 0) c := newTestCB(3, 10*time.Second, &now) if !c.Allow() || c.State() != Closed { t.Fatal("初始应闭合放行") } c.Report(false) c.Report(false) if c.State() != Closed { t.Fatal("未达阈值不应断开") } c.Report(false) // 第 3 次连续失败 → 断开 if c.State() != Open { t.Fatalf("达阈值应断开, got %v", c.State()) } if c.Allow() { t.Error("断开态应拒绝放行") } } func TestCircuitBreaker_SuccessResetsFails(t *testing.T) { now := time.Unix(0, 0) c := newTestCB(3, 10*time.Second, &now) c.Report(false) c.Report(false) c.Report(true) // 成功清零连续失败 c.Report(false) c.Report(false) if c.State() != Closed { t.Errorf("成功应清零计数,不应断开, got %v", c.State()) } } func TestCircuitBreaker_HalfOpenRecovers(t *testing.T) { now := time.Unix(0, 0) c := newTestCB(2, 10*time.Second, &now) c.Report(false) c.Report(false) // 断开 if c.State() != Open || c.Allow() { t.Fatal("应断开并拒绝") } // 冷却未到 → 仍拒绝。 now = now.Add(5 * time.Second) if c.Allow() { t.Fatal("冷却未到不应放行") } // 冷却到点 → 半开放行一个探测,第二个被拒。 now = now.Add(6 * time.Second) if !c.Allow() || c.State() != HalfOpen { t.Fatalf("冷却到点应转半开并放行探测, state=%v", c.State()) } if c.Allow() { t.Error("半开态超过探测名额应拒绝") } // 探测成功 → 恢复闭合。 c.Report(true) if c.State() != Closed || !c.Allow() { t.Errorf("探测成功应恢复闭合, state=%v", c.State()) } } func TestCircuitBreaker_HalfOpenProbeFailReopens(t *testing.T) { now := time.Unix(0, 0) c := newTestCB(1, 10*time.Second, &now) c.Report(false) // 阈值 1 → 立即断开 now = now.Add(11 * time.Second) if !c.Allow() || c.State() != HalfOpen { t.Fatal("应转半开") } c.Report(false) // 探测失败 → 重新断开,冷却从此刻重算 if c.State() != Open { t.Fatalf("探测失败应重新断开, got %v", c.State()) } if c.Allow() { t.Error("重新断开后冷却内应拒绝") } } func TestCircuitBreaker_ConcurrentSafe(t *testing.T) { now := time.Now() c := newTestCB(1000000, time.Second, &now) var wg sync.WaitGroup for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 200; j++ { c.Allow() c.Report(j%2 == 0) } }() } wg.Wait() // 仅验证无数据竞争 / 死锁(配合 -race) }