1 package net.secodo.jcircuitbreaker.breaker.impl;
2
3 import static org.hamcrest.core.IsEqual.equalTo;
4 import static org.hamcrest.core.IsInstanceOf.instanceOf;
5 import static org.hamcrest.core.IsNull.notNullValue;
6
7 import static org.junit.Assert.assertThat;
8 import static org.junit.Assert.fail;
9
10 import static org.mockito.Matchers.any;
11 import static org.mockito.Matchers.anyInt;
12 import static org.mockito.Matchers.anyString;
13
14 import static org.mockito.Mockito.mock;
15 import static org.mockito.Mockito.never;
16 import static org.mockito.Mockito.times;
17 import static org.mockito.Mockito.verify;
18 import static org.mockito.Mockito.when;
19
20 import java.io.IOException;
21
22 import java.lang.reflect.Field;
23
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.atomic.AtomicBoolean;
29 import java.util.concurrent.atomic.AtomicInteger;
30
31 import net.secodo.jcircuitbreaker.breaker.CircuitBreaker;
32 import net.secodo.jcircuitbreaker.breaker.ContextAwareCircuitBreaker;
33 import net.secodo.jcircuitbreaker.breaker.execution.ExecutedTask;
34 import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext;
35 import net.secodo.jcircuitbreaker.breakhandler.BreakHandler;
36 import net.secodo.jcircuitbreaker.breakhandler.exception.BreakHandlerException;
37 import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
38 import net.secodo.jcircuitbreaker.exception.CircuitBreakerException;
39 import net.secodo.jcircuitbreaker.exception.TaskExecutionException;
40
41 import net.secodo.jcircuitbreaker.task.Task;
42 import org.junit.Test;
43
44
45
46
47
48
49 @SuppressWarnings("unchecked")
50 public class AbstractCircuitBreakerTest {
51 @Test
52 public void shouldStopMethodExecutionIfBreakerDecidesToBreak() throws TaskExecutionException {
53
54 final long returnValue = 99999L;
55 final long fallbackReturnValue = 33333L;
56 final String paramValue1 = "VALUE IS HERE";
57 final Integer paramValue2 = 10;
58
59 final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
60 final List<?> userData = mock(List.class);
61 final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
62 final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
63
64 Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
65
66 when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true);
67 when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
68 when(
69 breakHandler.onBreak(any(ContextAwareCircuitBreaker.class),
70 any(Task.class),
71 any(BreakStrategy.class),
72 any(ExecutionContext.class))).thenReturn(
73 fallbackReturnValue);
74
75
76 final Long methodReturnValue =
77 new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
78 breakStrategy,
79 breakHandler,
80 userData);
81
82 final Long methodReturnValueFromStatic =
83 new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
84 breakStrategy,
85 breakHandler,
86 userData);
87
88
89
90 verify(someTestObject, never()).someMethod(anyString(), anyInt());
91 verify(breakHandler, times(2)).onBreak(any(ContextAwareCircuitBreaker.class),
92 any(Task.class),
93 any(BreakStrategy.class),
94 any(ExecutionContext.class));
95
96 assertThat(methodReturnValue, equalTo(fallbackReturnValue));
97 assertThat(methodReturnValueFromStatic, equalTo(methodReturnValue));
98
99 }
100
101 @Test
102 public void shouldContinueAndExecuteMethodIfBreakerDecidesNotToBreak() throws TaskExecutionException {
103
104 final Long returnValue = 9999L;
105 final long fallbackReturnValue = 3333L;
106 final String paramValue1 = "VALUE IS HERE";
107 final Integer paramValue2 = 10;
108
109 final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
110 final List<?> userData = mock(List.class);
111 final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
112 final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
113
114 Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
115
116 when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false);
117 when(someTestObject.someMethod(paramValue1, paramValue2)).thenReturn(returnValue);
118 when(
119 breakHandler.onBreak(any(ContextAwareCircuitBreaker.class),
120 any(Task.class),
121 any(BreakStrategy.class),
122 any(ExecutionContext.class))).thenReturn(
123 fallbackReturnValue);
124
125
126 final Long methodReturnValue =
127 new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
128 breakStrategy,
129 breakHandler,
130 userData);
131
132 final Long methodReturnValueFromStatic =
133 new SomeAbstractCircuitBreakerForTest<Long>().execute(methodCall,
134 breakStrategy,
135 breakHandler,
136 userData);
137
138
139
140 verify(someTestObject, times(2)).someMethod(anyString(), anyInt());
141 verify(breakHandler, never()).onBreak(any(ContextAwareCircuitBreaker.class),
142 any(Task.class),
143 any(BreakStrategy.class),
144 any(ExecutionContext.class));
145
146 assertThat(methodReturnValue, equalTo(returnValue));
147 assertThat(methodReturnValueFromStatic, equalTo(methodReturnValue));
148
149 }
150
151 @Test
152 public void shouldThrowTaskExecutionExceptionIfTaskThrowsException() {
153 final String paramValue1 = "VALUE IS HERE";
154 final Integer paramValue2 = 10;
155
156 final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
157
158 final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
159 when(someTestObject.someMethod(paramValue1, paramValue2)).thenThrow(IOException.class);
160
161 final Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
162
163 final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
164 when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false);
165
166 final SomeAbstractCircuitBreakerForTest<Long> defaultCircuitBreaker = new SomeAbstractCircuitBreakerForTest<>();
167
168
169
170
171 try {
172 defaultCircuitBreaker.execute(methodCall, breakStrategy, breakHandler);
173 fail("TaskExecution exception should have been thrown");
174 } catch (TaskExecutionException e) {
175
176 assertThat(e.getCause(), notNullValue());
177 assertThat(IOException.class.isInstance(e.getCause()), equalTo(true));
178 assertThat(e.getTaskException(), notNullValue());
179 assertThat(IOException.class.isInstance(e.getTaskException()), equalTo(true));
180 }
181
182 }
183
184 @Test
185 public void shouldThrowCircuitBreakerExceptionWhenUnexpectedExceptionOccurredWhenProcessingTheTask()
186 throws Exception {
187 final String paramValue1 = "VALUE IS HERE";
188 final Integer paramValue2 = 10;
189
190 final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
191
192 final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
193 when(someTestObject.someMethod(paramValue1, paramValue2)).thenThrow(IOException.class);
194
195 final Task<Long> methodCall = () -> someTestObject.someMethod(paramValue1, paramValue2);
196
197 final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
198 when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false);
199
200 final ConcurrentHashMap<String, ExecutedTask<Long>> hashMapMock = mock(ConcurrentHashMap.class);
201 final Field tasksInProgressField = AbstractCircuitBreaker.class.getDeclaredField("tasksInProgress");
202 tasksInProgressField.setAccessible(true);
203
204 final SomeAbstractCircuitBreakerForTest<Long> circuitBreaker = new SomeAbstractCircuitBreakerForTest<>();
205 tasksInProgressField.set(circuitBreaker, hashMapMock);
206
207 when(hashMapMock.put(any(String.class), any(ExecutedTask.class))).thenThrow(Exception.class);
208
209
210 try {
211 circuitBreaker.execute(methodCall, breakStrategy, breakHandler);
212 fail(CircuitBreakerException.class.getSimpleName() + " should have been thrown");
213 } catch (CircuitBreakerException e) {
214
215
216 }
217
218 verify(hashMapMock, times(1)).put(any(String.class), any(ExecutedTask.class));
219
220 }
221
222 @Test
223 public void shouldThrowCircuitBreakerExceptionWhenTaskHashcodeResultsInException() throws Exception {
224 final String paramValue1 = "VALUE IS HERE";
225 final Integer paramValue2 = 10;
226
227 final BreakHandler<Long> breakHandler = mock(BreakHandler.class);
228
229 final SomeTestClassWithSomeMethod someTestObject = mock(SomeTestClassWithSomeMethod.class);
230 when(someTestObject.someMethod(paramValue1, paramValue2)).thenThrow(IOException.class);
231
232 AtomicBoolean hashcodeCalled = new AtomicBoolean(false);
233
234 final Task<Long> methodCall = new Task<Long>() {
235 @Override
236 public Long execute() throws Exception {
237 return null;
238 }
239
240 @Override
241 public int hashCode() {
242 hashcodeCalled.set(true);
243 throw new UnsupportedOperationException("abc");
244 }
245 };
246
247 final BreakStrategy<Long> breakStrategy = mock(BreakStrategy.class);
248 when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(false);
249
250
251 final SomeAbstractCircuitBreakerForTest<Long> circuitBreaker = new SomeAbstractCircuitBreakerForTest<>();
252
253
254 try {
255 circuitBreaker.execute(methodCall, breakStrategy, breakHandler);
256
257
258
259 fail(CircuitBreakerException.class.getSimpleName() + " should have been thrown");
260 } catch (CircuitBreakerException e) {
261
262 assertThat(e.getCause(), notNullValue());
263 assertThat(e.getCause(), instanceOf(UnsupportedOperationException.class));
264 }
265
266 assertThat(hashcodeCalled.get(), equalTo(true));
267
268 }
269
270 @Test
271 public void shouldReuseExecutionContextWhenBreakHandlerCallsExecuteMethodOfCircuitBreaker() throws Exception {
272
273 HashMap<Integer, Integer> executionContextCounter = new HashMap<>();
274
275 BreakHandler<AtomicInteger> breakHandler = new BreakHandler<AtomicInteger>() {
276 @Override
277 public AtomicInteger onBreak(ContextAwareCircuitBreaker<AtomicInteger> circuitBreaker,
278 Task<AtomicInteger> task, BreakStrategy<AtomicInteger> breakStrategy,
279 ExecutionContext<AtomicInteger> executionContext) throws TaskExecutionException,
280 CircuitBreakerException,
281 BreakHandlerException {
282 final int hashCode = executionContext.hashCode();
283
284 if (!executionContextCounter.containsKey(hashCode)) {
285 executionContextCounter.put(hashCode, 0);
286 }
287
288 executionContextCounter.put(hashCode, executionContextCounter.get(hashCode) + 1);
289 return circuitBreaker.executeInContext(task, breakStrategy, this, executionContext);
290 }
291 };
292
293
294 BreakStrategy<AtomicInteger> breakStrategy = mock(BreakStrategy.class);
295 when(breakStrategy.shouldBreak(any(Task.class), any(ExecutionContext.class))).thenReturn(true)
296 .thenReturn(true)
297 .thenReturn(true)
298 .thenReturn(false);
299
300 final Task<AtomicInteger> task = mock(Task.class);
301 when(task.execute()).thenReturn(new AtomicInteger(999));
302
303 final CircuitBreaker<AtomicInteger> breaker = new SomeAbstractCircuitBreakerForTest<>();
304
305
306 final AtomicInteger result = breaker.execute(task, breakStrategy, breakHandler);
307
308
309 verify(breakStrategy, times(4)).shouldBreak(any(Task.class), any(ExecutionContext.class));
310 assertThat(executionContextCounter.size(), equalTo(1));
311 assertThat(executionContextCounter.entrySet().iterator().next().getValue(), equalTo(3));
312
313 assertThat(result.get(), equalTo(999));
314
315
316 }
317
318
319 class SomeTestClassWithSomeMethod {
320 SomeTestClassWithSomeMethod() {
321 }
322
323 public Long someMethod(String paramValue1, Integer paramValue2) {
324 return (long) 1;
325 }
326
327
328 }
329
330 class SomeAbstractCircuitBreakerForTest<R> extends AbstractCircuitBreaker<R> {
331 }
332
333
334 }