/* * Copyright 2010 Nabil Ben Said. * * 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 net.greencoding.thysdrus.circuitbreaker.aspect; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import net.greencoding.thysdrus.circuitbreaker.annotation.MonitoredByCircuitBreaker; import net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerAspect; import net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerMethodRegistry; import net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerRegistryEntry; import net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerStatus; import net.greencoding.thysdrus.circuitbreaker.exception.OpenCircuitException; import org.junit.Before; import org.junit.Test; /** * * @author Nabil Ben Said (nabil.bensaid@gmail.com) * */ public class CircuitBreakerTest { final String methodName = "private void net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerTest.callExternalResourceNonSilient(boolean)"; CircuitBreakerMethodRegistry registry; @Before public void setUp() { registry = new CircuitBreakerMethodRegistry(); CircuitBreakerAspect.setRegistry(registry); } @Test(expected = OpenCircuitException.class) public void testOpenCircuitException() { try { callExternalResourceNonSilient(true); } catch (MyException ignore) {} try { callExternalResourceNonSilient(true); } catch (MyException ignore) {} // next call shall be fail with OpenCircuitException callExternalResourceNonSilient(true); } @Test public void testCircuitBreakerReadAnnotationParameter() { callExternalResourceNonSilient(false); CircuitBreakerRegistryEntry entry = registry.getEntry(methodName); assertEquals(methodName, entry.getName()); assertTrue(entry.getFailureIndications().contains(MyException.class)); assertEquals(2, entry.getFailureThreshold()); assertEquals(2000, entry.getFailureThresholdTimeFrameMs()); assertEquals(3000, entry.getRetryAfterMs()); assertEquals(CircuitBreakerStatus.CLOSED, entry.getStatus()); assertEquals(0, entry.getFailures().size()); assertEquals(0, entry.getLastOpenedTime()); assertEquals(0, entry.getClosedCycleCounter()); } @Test public void testCircuitBreakerSingleThread() throws InterruptedException { externalResourceNonSilient(true); Thread.sleep(1000); externalResourceNonSilient(true); // next call must be blocked externalResourceNonSilient(false); assertEquals(CircuitBreakerStatus.OPEN, registry.getEntry(methodName).getStatus()); assertEquals(0, registry.getEntry(methodName).getFailures().size()); long openedTime = registry.getEntry(methodName).getLastOpenedTime(); Thread.sleep(3000); // half open call externalResourceNonSilient(true); // status back to OPEN assertEquals(CircuitBreakerStatus.OPEN, registry.getEntry(methodName).getStatus()); // lastOpenedTime updated? assertFalse(openedTime == registry.getEntry(methodName).getLastOpenedTime()); Thread.sleep(3000); externalResourceNonSilient(false); assertEquals(CircuitBreakerStatus.CLOSED, registry.getEntry(methodName).getStatus()); assertEquals(1, registry.getEntry(methodName).getClosedCycleCounter()); } @Test public void testCircuitBreakerMultiThreadedNonSilient() throws InterruptedException { String methodSignatureNonSilient = "private void net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerTest.MyRunnable.externalCallNonSilient(boolean, long)"; boolean isSilientMode = false; Thread thread1 = new Thread(new MyRunnable(true, 1000l, isSilientMode)); Thread thread2 = new Thread(new MyRunnable(true, 2000l, isSilientMode)); Thread thread3 = new Thread(new MyRunnable(true, 4000l, isSilientMode)); // MyThread thread4 = new MyThread(false, 1000l); thread1.start(); thread2.start(); thread3.start(); Thread.sleep(4500); assertEquals(CircuitBreakerStatus.OPEN, registry.getEntry(methodSignatureNonSilient).getStatus()); // thread4.start(); } @Test public void testCircuitBreakerMultiThreadedSilient() throws InterruptedException { String methodSignatureNonSilient = "private void net.greencoding.thysdrus.circuitbreaker.aspect.CircuitBreakerTest.MyRunnable.externalCallSilient(boolean, long)"; boolean isSilientMode = true; Thread thread1 = new Thread(new MyRunnable(true, 1000l, isSilientMode)); Thread thread2 = new Thread(new MyRunnable(true, 2000l, isSilientMode)); Thread thread3 = new Thread(new MyRunnable(true, 4000l, isSilientMode)); // MyThread thread4 = new MyThread(false, 1000l); thread1.start(); thread2.start(); thread3.start(); Thread.sleep(4500); assertEquals(CircuitBreakerStatus.OPEN, registry.getEntry(methodSignatureNonSilient).getStatus()); // thread4.start(); } private void externalResourceNonSilient(boolean fail) { try { callExternalResourceNonSilient(fail); } catch (MyException ignore) { // ignore } catch (OpenCircuitException ignore) { // ignore } } @MonitoredByCircuitBreaker(failureThreshold = 2, failureThresholdTimeFrameMs = 2000, retryAfterMs = 3000, failureIndications = { MyException.class }, isSilientMode = false) private void callExternalResourceNonSilient(boolean fail) { if (fail) { throw new MyException(); } } private class MyException extends RuntimeException { private static final long serialVersionUID = 1L; } private class MyRunnable implements Runnable { private boolean fail = false; private long executionDuration = 0l; private boolean silient = true; public MyRunnable(boolean fail, long executionDuration, boolean silient) { this.fail = fail; this.executionDuration = executionDuration; this.silient = silient; } public void run(){ if (silient) { externalCallSilient(fail, executionDuration); } else { try { externalCallNonSilient(fail, executionDuration); } catch (MyException ignore) { // ignore } catch (OpenCircuitException ignore) { // ignore } } } @MonitoredByCircuitBreaker(failureThreshold = 2, failureThresholdTimeFrameMs = 2000, retryAfterMs = 3000, failureIndications = { MyException.class }, isSilientMode = false) private void externalCallNonSilient(boolean fail, long executionDuration) { try { Thread.sleep(executionDuration); } catch (InterruptedException e) { throw new RuntimeException(e); } if (fail) { throw new MyException(); } } @MonitoredByCircuitBreaker(failureThreshold = 2, failureThresholdTimeFrameMs = 2000, retryAfterMs = 3000, failureIndications = { MyException.class }, isSilientMode = true) private void externalCallSilient(boolean fail, long executionDuration) { try { Thread.sleep(executionDuration); } catch (InterruptedException e) { throw new RuntimeException(e); } if (fail) { throw new MyException(); } } } @Test public void testObjectReturnValueOfCalledMethod() { try { externalCallWithReturnObject(true); } catch (MyException e) {} assertEquals(null, externalCallWithReturnObject(true)); } @MonitoredByCircuitBreaker(failureThreshold = 1, isSilientMode = true) private Object externalCallWithReturnObject(boolean fail) { if (fail) { throw new MyException(); } return null; } }