1   /**
2    * Copyright 2008 Atlassian Pty Ltd 
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); 
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0 
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package com.atlassian.util.concurrent;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.fail;
21  
22  import java.util.concurrent.Callable;
23  import java.util.concurrent.CountDownLatch;
24  import java.util.concurrent.ExecutionException;
25  import java.util.concurrent.atomic.AtomicInteger;
26  
27  import org.junit.Test;
28  
29  /**
30   * Copyright 2007 Atlassian Software. All rights reserved.
31   */
32  public class ConcurrentOperationMapImplTest {
33  
34      @Test
35      public void runOperationsConcurrently() throws InterruptedException {
36          final AtomicInteger counter = new AtomicInteger(0);
37          final CountDownLatch runSignal = new CountDownLatch(2);
38          final CountDownLatch startSignal = new CountDownLatch(1);
39          final CountDownLatch doneSignal = new CountDownLatch(2);
40  
41          final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>(
42              new Function<Callable<Integer>, ConcurrentOperationMapImpl.CallerRunsFuture<Integer>>() {
43                  public ConcurrentOperationMapImpl.CallerRunsFuture<Integer> get(final Callable<Integer> input) {
44                      return new ConcurrentOperationMapImpl.CallerRunsFuture<Integer>(input) {
45                          @Override
46                          public Integer get() throws ExecutionException {
47                              runSignal.countDown();
48                              try {
49                                  runSignal.await();
50                              } catch (final InterruptedException e) {
51                                  throw new RuntimeException(e);
52                              }
53                              return super.get();
54                          }
55                      };
56                  }
57              });
58  
59          // Create two threads whose job will be to call runOperation with the
60          // same name object
61          new Thread(new SignallingWorker(startSignal, doneSignal) {
62              @Override
63              void doWork() {
64                  try {
65                      assertEquals(Integer.valueOf(1), concurrentOperationMap.runOperation("same-key", new Callable<Integer>() {
66                          public Integer call() {
67                              return counter.incrementAndGet();
68                          }
69                      }));
70                  } catch (final ExecutionException e) {
71                      fail(e.toString());
72                  }
73              }
74          }).start();
75          new Thread(new SignallingWorker(startSignal, doneSignal) {
76              @Override
77              void doWork() {
78                  try {
79                      assertEquals(Integer.valueOf(1), concurrentOperationMap.runOperation("same-key", new Callable<Integer>() {
80                          public Integer call() {
81                              return counter.incrementAndGet();
82                          }
83                      }));
84                  } catch (final ExecutionException e) {
85                      fail(e.toString());
86                  }
87              }
88          }).start();
89  
90          // Hang them off a latch so that we can ensure they are both going to
91          // run through the runOperation method
92          // together
93  
94          startSignal.countDown();
95  
96          // Ensure after running that only one of the callables from one of the
97          // thread was invoked and that the same
98          // result is found by both threads.
99  
100         doneSignal.await();
101         assertEquals(1, counter.get());
102     }
103 
104     @Test
105     public void exceptionsGetRemoved() throws Exception {
106         final AtomicInteger counter = new AtomicInteger();
107         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
108 
109         // Create two threads whose job will be to call runOpertion with the
110         // same name object
111 
112         class MyException extends RuntimeException {
113             private static final long serialVersionUID = 1472906008827102127L;
114         }
115 
116         final Callable<Integer> operation = new Callable<Integer>() {
117             public Integer call() {
118                 counter.incrementAndGet();
119                 throw new MyException();
120             }
121         };
122         try {
123             concurrentOperationMap.runOperation("same-key", operation);
124             fail("MyException expected");
125         } catch (final MyException expected) {}
126         try {
127             concurrentOperationMap.runOperation("same-key", operation);
128             fail("MyException expected");
129         } catch (final MyException expected) {}
130         try {
131             concurrentOperationMap.runOperation("same-key", operation);
132             fail("MyException expected");
133         } catch (final MyException expected) {}
134 
135         assertEquals(3, counter.get());
136     }
137 
138     @Test
139     public void runtimeExceptionGetsReThrown() throws Exception {
140         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
141 
142         // Create two threads whose job will be to call runOpertion with the
143         // same name object
144 
145         class MyException extends RuntimeException {
146             private static final long serialVersionUID = -9171222011228163934L;
147         }
148 
149         final Callable<Integer> operation = new Callable<Integer>() {
150             public Integer call() {
151                 throw new MyException();
152             }
153         };
154         try {
155             concurrentOperationMap.runOperation("same-key", operation);
156             fail("MyException expected");
157         } catch (final MyException expected) {}
158     }
159 
160     @Test(expected = MyError.class)
161     public void errorGetsReThrown() throws Exception {
162         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
163 
164         // Create two threads whose job will be to call runOpertion with the
165         // same name object
166 
167         final Callable<Integer> operation = new Callable<Integer>() {
168             public Integer call() {
169                 throw new MyError();
170             }
171         };
172         concurrentOperationMap.runOperation("same-key", operation);
173     }
174 
175     @Test
176     public void checkedExceptionGetsWrapped() throws Exception {
177         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
178 
179         // Create two threads whose job will be to call runOpertion with the
180         // same name object
181 
182         class MyException extends Exception {
183             private static final long serialVersionUID = 22367874459914044L;
184         }
185 
186         final Callable<Integer> operation = new Callable<Integer>() {
187             public Integer call() throws MyException {
188                 throw new MyException();
189             }
190         };
191         try {
192             concurrentOperationMap.runOperation("same-key", operation);
193             fail("MyException expected");
194         } catch (final ExecutionException expected) {
195             assertEquals(MyException.class, expected.getCause().getClass());
196         }
197     }
198 
199     static class MyError extends Error {
200         private static final long serialVersionUID = -1416631799712180762L;
201     }
202 
203     abstract class SignallingWorker implements Runnable {
204         private final CountDownLatch startSignal;
205         private final CountDownLatch doneSignal;
206 
207         SignallingWorker(final CountDownLatch startSignal, final CountDownLatch doneSignal) {
208             this.startSignal = startSignal;
209             this.doneSignal = doneSignal;
210         }
211 
212         public void run() {
213             try {
214                 startSignal.await();
215                 doWork();
216             } catch (final InterruptedException ex) {} finally {
217                 doneSignal.countDown();
218             }
219         }
220 
221         abstract void doWork();
222     }
223 }