<commit_before>package overcurrent

import (
	"context"
	"fmt"
	"time"

	"github.com/aphistic/sweet"
	"github.com/efritz/backoff"
	"github.com/efritz/glock"
	. "github.com/onsi/gomega"
)

type BreakerSuite struct{}

func (s *BreakerSuite) TestSuccess(t sweet.T) {
	breaker := NewCircuitBreaker(testConfig())
	Expect(breaker.Call(nilFunc)).To(BeNil())
}

func (s *BreakerSuite) TestNaturalError(t sweet.T) {
	breaker := NewCircuitBreaker(testConfig())
	Expect(breaker.Call(errFunc)).To(Equal(errTest))
}

func (s *BreakerSuite) TestNaturalErrorTrip(t sweet.T) {
	breaker := NewCircuitBreaker(testConfig())

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	Expect(breaker.Call(errFunc)).To(Equal(ErrCircuitOpen))
}

func (s *BreakerSuite) TestTimeout(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(testConfig(), withClock(clock))
	)

	go func() {
		// Ensure after was called before advancing
		for len(clock.GetAfterArgs()) == 0 {
			<-time.After(time.Millisecond)
		}

		clock.Advance(time.Minute)
	}()

	Expect(breaker.Call(blockingFunc)).To(Equal(ErrInvocationTimeout))
}

func (s *BreakerSuite) TestTimeoutTrip(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(testConfig(), withClock(clock))
	)

	go func() {
		for i := 0; i < 5; i++ {
			// Ensure after was called before advancing
			for len(clock.GetAfterArgs()) == 0 {
				<-time.After(time.Millisecond)
			}

			clock.Advance(time.Minute)
		}
	}()

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(blockingFunc)).To(Equal(ErrInvocationTimeout))
	}

	Expect(breaker.Call(blockingFunc)).To(Equal(ErrCircuitOpen))
}

func (s *BreakerSuite) TestTimeoutDisabled(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		sync    = make(chan time.Time)
		breaker = NewCircuitBreaker(
			testConfig(),
			withClock(clock),
			WithInvocationTimeout(0),
		)
	)

	fn := func(ctx context.Context) error {
		<-sync
		return nil
	}

	close(sync)
	Expect(breaker.Call(fn)).To(BeNil())
	Expect(clock.GetAfterArgs()).To(HaveLen(0))
}

func (s *BreakerSuite) TestHalfOpenFailure(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(
			testConfig(),
			withClock(clock),
			WithHalfClosedRetryProbability(1),
		)
	)

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	Expect(breaker.Call(errFunc)).To(Equal(ErrCircuitOpen))

	// Wait for retry backoff
	clock.Advance(15 * time.Second)
	Expect(breaker.Call(errFunc)).To(Equal(errTest))
	Expect(breaker.Call(nilFunc)).To(Equal(ErrCircuitOpen))
}

func (s *BreakerSuite) TestHalfOpenReset(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(
			testConfig(),
			withClock(clock),
			WithHalfClosedRetryProbability(1),
		)
	)

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	Expect(breaker.Call(errFunc)).To(Equal(ErrCircuitOpen))

	// Wait for retry backoff
	clock.Advance(15 * time.Second)
	Expect(breaker.Call(nilFunc)).To(BeNil())
}

func (s *BreakerSuite) TestCallAsync(t sweet.T) {
	var (
		breaker = NewCircuitBreaker(testConfig())
		errors  = breaker.CallAsync(nilFunc)
	)

	Eventually(errors).Should(Receive(BeNil()))
	Eventually(errors).Should(BeClosed())
}

func (s *BreakerSuite) TestCallAsyncNaturalError(t sweet.T) {
	var (
		breaker = NewCircuitBreaker(testConfig())
		errors  = breaker.CallAsync(errFunc)
	)

	Eventually(errors).Should(Receive(Equal(errTest)))
}

func (s *BreakerSuite) TestCallAsyncNaturalErrorTrip(t sweet.T) {
	breaker := NewCircuitBreaker(testConfig())

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	errors := breaker.CallAsync(errFunc)
	Eventually(errors).Should(Receive(Equal(ErrCircuitOpen)))
}

func (s *BreakerSuite) TestCallAsyncTimeout(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(testConfig(), withClock(clock))
	)

	go func() {
		// Ensure after was called before advancing
		for len(clock.GetAfterArgs()) == 0 {
			<-time.After(time.Millisecond)
		}

		clock.Advance(time.Minute)
	}()

	errors := breaker.CallAsync(blockingFunc)
	Eventually(errors).Should(Receive(Equal(ErrInvocationTimeout)))
}

var (
	TRIALS      = 50000
	PROBAIBLITY = 0.25
)

func (s *BreakerSuite) TestHalfOpenProbability(t sweet.T) {
	success := 0
	failure := 0<commit_msg>Use glock.BlockingAdvance where possible.<commit_after>package overcurrent

import (
	"context"
	"fmt"
	"time"

	"github.com/aphistic/sweet"
	"github.com/efritz/backoff"
	"github.com/efritz/glock"
	. "github.com/onsi/gomega"
)

type BreakerSuite struct{}

func (s *BreakerSuite) TestSuccess(t sweet.T) {
	Expect(NewCircuitBreaker(testConfig()).Call(nilFunc)).To(BeNil())
}

func (s *BreakerSuite) TestNaturalError(t sweet.T) {
	Expect(NewCircuitBreaker(testConfig()).Call(errFunc)).To(Equal(errTest))
}

func (s *BreakerSuite) TestNaturalErrorTrip(t sweet.T) {
	breaker := NewCircuitBreaker(testConfig())

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	Expect(breaker.Call(errFunc)).To(Equal(ErrCircuitOpen))
}

func (s *BreakerSuite) TestTimeout(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(testConfig(), withClock(clock))
	)

	go func() {
		clock.BlockingAdvance(time.Minute)
	}()

	Expect(breaker.Call(blockingFunc)).To(Equal(ErrInvocationTimeout))
}

func (s *BreakerSuite) TestTimeoutTrip(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(testConfig(), withClock(clock))
	)

	go func() {
		for i := 0; i < 5; i++ {
			clock.BlockingAdvance(time.Minute)
		}
	}()

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(blockingFunc)).To(Equal(ErrInvocationTimeout))
	}

	Expect(breaker.Call(blockingFunc)).To(Equal(ErrCircuitOpen))
}

func (s *BreakerSuite) TestTimeoutDisabled(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		sync    = make(chan time.Time)
		breaker = NewCircuitBreaker(
			testConfig(),
			withClock(clock),
			WithInvocationTimeout(0),
		)
	)

	fn := func(ctx context.Context) error {
		<-sync
		return nil
	}

	close(sync)
	Expect(breaker.Call(fn)).To(BeNil())
	Expect(clock.GetAfterArgs()).To(HaveLen(0))
}

func (s *BreakerSuite) TestHalfOpenFailure(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(
			testConfig(),
			withClock(clock),
			WithHalfClosedRetryProbability(1),
		)
	)

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	Expect(breaker.Call(errFunc)).To(Equal(ErrCircuitOpen))

	// Wait for retry backoff
	clock.Advance(15 * time.Second)
	Expect(breaker.Call(errFunc)).To(Equal(errTest))
	Expect(breaker.Call(nilFunc)).To(Equal(ErrCircuitOpen))
}

func (s *BreakerSuite) TestHalfOpenReset(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(
			testConfig(),
			withClock(clock),
			WithHalfClosedRetryProbability(1),
		)
	)

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	Expect(breaker.Call(errFunc)).To(Equal(ErrCircuitOpen))

	// Wait for retry backoff
	clock.Advance(15 * time.Second)
	Expect(breaker.Call(nilFunc)).To(BeNil())
}

func (s *BreakerSuite) TestCallAsync(t sweet.T) {
	var (
		breaker = NewCircuitBreaker(testConfig())
		errors  = breaker.CallAsync(nilFunc)
	)

	Eventually(errors).Should(Receive(BeNil()))
	Eventually(errors).Should(BeClosed())
}

func (s *BreakerSuite) TestCallAsyncNaturalError(t sweet.T) {
	var (
		breaker = NewCircuitBreaker(testConfig())
		errors  = breaker.CallAsync(errFunc)
	)

	Eventually(errors).Should(Receive(Equal(errTest)))
}

func (s *BreakerSuite) TestCallAsyncNaturalErrorTrip(t sweet.T) {
	breaker := NewCircuitBreaker(testConfig())

	for i := 0; i < 5; i++ {
		Expect(breaker.Call(errFunc)).To(Equal(errTest))
	}

	errors := breaker.CallAsync(errFunc)
	Eventually(errors).Should(Receive(Equal(ErrCircuitOpen)))
}

func (s *BreakerSuite) TestCallAsyncTimeout(t sweet.T) {
	var (
		clock   = glock.NewMockClock()
		breaker = NewCircuitBreaker(testConfig(), withClock(clock))
	)

	go func() {
		clock.BlockingAdvance(time.Minute)
	}()

	errors := breaker.CallAsync(blockingFunc)
	Eventually(errors).Should(Receive(Equal(ErrInvocationTimeout)))
}

var (
	TRIALS      = 50000
	PROBAIBLITY = 0.25
)

func (s *BreakerSuite) TestHalfOpenProbability(t sweet.T) {
	success := 0
	failure := 0
{'fraction_non_alphanumeric': 0.15455195424213536, 'fraction_numerical': 0.007507149666348903, 'mean_word_length': 4.852859135285914, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '603458', 'n_tokens_mistral': 3457, 'n_tokens_neox': 3086, 'n_words': 510}