/* * * Copyright 2016 Robert Winkler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ package io.github.resilience4j.circuitbreaker.internal; import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; import io.reactivex.subscribers.TestSubscriber; import org.junit.Before; import org.junit.Test; import java.time.Duration; import static io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent.Type.*; import static java.lang.Thread.sleep; import static org.assertj.core.api.BDDAssertions.assertThat; public class CircuitBreakerStateMachineTest { private CircuitBreaker circuitBreaker; private TestSubscriber<CircuitBreakerEvent.Type> testSubscriber; @Before public void setUp(){ circuitBreaker = new CircuitBreakerStateMachine("testName", CircuitBreakerConfig.custom() .failureRateThreshold(50) .ringBufferSizeInClosedState(5) .ringBufferSizeInHalfOpenState(3) .waitDurationInOpenState(Duration.ofSeconds(1)) .recordFailure(error -> !(error instanceof NumberFormatException)) .build()); testSubscriber = circuitBreaker.getEventStream() .map(CircuitBreakerEvent::getEventType) .test(); } @Test public void shouldReturnTheCorrectName() { assertThat(circuitBreaker.getName()).isEqualTo("testName"); } @Test public void testCircuitBreakerStateMachine() throws InterruptedException { // A ring buffer with size 5 is used in closed state // Initially the CircuitBreaker is closed assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(0); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(0); assertThat(circuitBreaker.getMetrics().getNumberOfNotPermittedCalls()).isEqualTo(0); // Call 1 is a failure circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (1) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 2 is a failure circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (2) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(2); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(2); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 3 is a failure circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (3) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 4 is a success circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (4) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(4); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 5 is a success circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (5) // The ring buffer is filled and the failure rate is above 50% assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); // Should create a CircuitBreakerOnStateTransitionEvent (6) assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(5); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(60.0f); sleep(500); // The CircuitBreaker is still open, because the wait duration of 1 second is not elapsed assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); assertThat(circuitBreaker.isCallPermitted()).isEqualTo(false); // Should create a CircuitBreakerOnCallNotPermittedEvent (7) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(false); // Should create a CircuitBreakerOnCallNotPermittedEvent (8) // Two calls are tried, but not permitted, because the CircuitBreaker is open assertThat(circuitBreaker.getMetrics().getNumberOfNotPermittedCalls()).isEqualTo(2); sleep(800); // The CircuitBreaker switches to half open, because the wait duration of 1 second is elapsed assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.HALF_OPEN); // Should create a CircuitBreakerOnStateTransitionEvent (9) // Metrics are reseted assertThat(circuitBreaker.getMetrics().getMaxNumberOfBufferedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getNumberOfNotPermittedCalls()).isEqualTo(0); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(0); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(0); // A ring buffer with size 2 is used in half open state // Call 1 is a failure circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (10) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.HALF_OPEN); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfNotPermittedCalls()).isEqualTo(0); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 2 is a failure circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (11) // Call 3 is a success circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (12) // The ring buffer is filled and the failure rate is above 50% // The state machine transitions back to OPEN state assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); // Should create a CircuitBreakerOnStateTransitionEvent (13) assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(2); assertThat(circuitBreaker.getMetrics().getFailureRate()).isGreaterThan(50f); sleep(1300); // The CircuitBreaker switches to half open, because the wait duration of 1 second is elapsed assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.HALF_OPEN); // Should create a CircuitBreakerOnStateTransitionEvent (14) // Call 1 is a failure circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (15) assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.HALF_OPEN); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 2 should be ignored, because it's a NumberFormatException circuitBreaker.onError(0, new NumberFormatException()); // Should create a CircuitBreakerOnIgnoredErrorEvent (16) assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.HALF_OPEN); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); // Call 3 is a success circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (17) // Call 43 is a success circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (18) // The ring buffer is filled and the failure rate is below 50% // The state machine transitions back to CLOSED state assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // Should create a CircuitBreakerOnStateTransitionEvent (19) assertThat(circuitBreaker.getMetrics().getMaxNumberOfBufferedCalls()).isEqualTo(5); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); testSubscriber .assertValueCount(19) .assertValues(ERROR, ERROR, ERROR, SUCCESS, SUCCESS, STATE_TRANSITION, NOT_PERMITTED, NOT_PERMITTED, STATE_TRANSITION, ERROR, ERROR, SUCCESS, STATE_TRANSITION, STATE_TRANSITION, ERROR, IGNORED_ERROR, SUCCESS, SUCCESS, STATE_TRANSITION); } }