package tree import ( "errors" "testing" "time" "github.com/stretchr/testify/require" ) func TestCircuitBreaker(t *testing.T) { remoteErr := errors.New("service is being synchronized") breakDuration := 1 * time.Second threshold := 10 cb := newCircuitBreaker(breakDuration, threshold) // Hit threshold for i := 0; i < threshold; i++ { err := cb.Do(1, func() error { return remoteErr }) require.ErrorIs(t, err, remoteErr) } // Different client should not be affected by threshold require.NoError(t, cb.Do(2, func() error { return nil })) // Immediate request should return circuit breaker error require.ErrorIs(t, cb.Do(1, func() error { return nil }), ErrCBClosed) // Request after breakDuration should be ok time.Sleep(breakDuration) require.NoError(t, cb.Do(1, func() error { return nil })) // Try hitting threshold one more time after break duration for i := 0; i < threshold; i++ { err := cb.Do(1, func() error { return remoteErr }) require.ErrorIs(t, err, remoteErr) } // Immediate request should return circuit breaker error require.ErrorIs(t, cb.Do(1, func() error { return nil }), ErrCBClosed) } func TestCircuitBreakerNoBlock(t *testing.T) { remoteErr := errors.New("service is being synchronized") funcDuration := 200 * time.Millisecond threshold := 100 cb := newCircuitBreaker(10*funcDuration, threshold) slowFunc := func() error { time.Sleep(funcDuration) return remoteErr } for i := 0; i < threshold; i++ { // run in multiple goroutines Do function go func() { cb.Do(1, slowFunc) }() } time.Sleep(funcDuration) // eventually at most after one more func duration circuit breaker will be // closed and not blocked by slow func execution under mutex require.Eventually(t, func() bool { return errors.Is(cb.Do(1, func() error { return nil }), ErrCBClosed) }, funcDuration, funcDuration/10) }