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 org.junit.Test;
23  
24  import java.util.concurrent.Callable;
25  import java.util.concurrent.CountDownLatch;
26  import java.util.concurrent.ExecutionException;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  /**
30   * Copyright 2007 Atlassian Software. All rights reserved.
31   */
32  public class ConcurrentOperationMapImplTest {
33  
34      @Test public void runOperationsConcurrently() throws InterruptedException {
35          final AtomicInteger counter = new AtomicInteger(0);
36          final CountDownLatch runSignal = new CountDownLatch(2);
37          final CountDownLatch startSignal = new CountDownLatch(1);
38          final CountDownLatch doneSignal = new CountDownLatch(2);
39  
40          final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>(new Function<Callable<Integer>, ConcurrentOperationMapImpl.CallerRunsFuture<Integer>>() {
41              public ConcurrentOperationMapImpl.CallerRunsFuture<Integer> get(final Callable<Integer> input) {
42                  return new ConcurrentOperationMapImpl.CallerRunsFuture<Integer>(input) {
43                      @Override public Integer get() throws ExecutionException {
44                          runSignal.countDown();
45                          try {
46                              runSignal.await();
47                          }
48                          catch (final InterruptedException e) {
49                              throw new RuntimeException(e);
50                          }
51                          return super.get();
52                      }
53                  };
54              }
55          });
56  
57          // Create two threads whose job will be to call runOpertion with the
58          // same name object
59          new Thread(new Worker(startSignal, doneSignal) {
60              @Override void doWork() {
61                  try {
62                      assertEquals(Integer.valueOf(1), concurrentOperationMap.runOperation("same-key", new Callable<Integer>() {
63                          public Integer call() {
64                              return counter.incrementAndGet();
65                          }
66                      }));
67                  }
68                  catch (final ExecutionException e) {
69                      fail(e.toString());
70                  }
71              }
72          }).start();
73          new Thread(new Worker(startSignal, doneSignal) {
74              @Override void doWork() {
75                  try {
76                      assertEquals(Integer.valueOf(1), concurrentOperationMap.runOperation("same-key", new Callable<Integer>() {
77                          public Integer call() {
78                              return counter.incrementAndGet();
79                          }
80                      }));
81                  }
82                  catch (final ExecutionException e) {
83                      fail(e.toString());
84                  }
85              }
86          }).start();
87  
88          // Hang them off a latch so that we can ensure they are both going to
89          // run through the runOperation method
90          // together
91  
92          startSignal.countDown();
93  
94          // Ensure after running that only one of the callables from one of the
95          // thread was invoked and that the same
96          // result is found by both threads.
97  
98          doneSignal.await();
99          assertEquals(1, counter.get());
100     }
101 
102     @Test public void exceptionsGetRemoved() throws Exception {
103         final AtomicInteger counter = new AtomicInteger();
104         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
105 
106         // Create two threads whose job will be to call runOpertion with the
107         // same name object
108 
109         class MyException extends RuntimeException {
110             private static final long serialVersionUID = 1472906008827102127L;
111         }
112 
113         final Callable<Integer> operation = new Callable<Integer>() {
114             public Integer call() {
115                 counter.incrementAndGet();
116                 throw new MyException();
117             }
118         };
119         try {
120             concurrentOperationMap.runOperation("same-key", operation);
121             fail("MyException expected");
122         }
123         catch (final MyException expected) {}
124         try {
125             concurrentOperationMap.runOperation("same-key", operation);
126             fail("MyException expected");
127         }
128         catch (final MyException expected) {}
129         try {
130             concurrentOperationMap.runOperation("same-key", operation);
131             fail("MyException expected");
132         }
133         catch (final MyException expected) {}
134 
135         assertEquals(3, counter.get());
136     }
137 
138     @Test public void runtimeExceptionGetsReThrown() throws Exception {
139         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
140 
141         // Create two threads whose job will be to call runOpertion with the
142         // same name object
143 
144         class MyException extends RuntimeException {
145             private static final long serialVersionUID = -9171222011228163934L;
146         }
147 
148         final Callable<Integer> operation = new Callable<Integer>() {
149             public Integer call() {
150                 throw new MyException();
151             }
152         };
153         try {
154             concurrentOperationMap.runOperation("same-key", operation);
155             fail("MyException expected");
156         }
157         catch (final MyException expected) {}
158     }
159 
160     @Test public void errorGetsReThrown() throws Exception {
161         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
162 
163         // Create two threads whose job will be to call runOpertion with the
164         // same name object
165 
166         class MyError extends Error {
167             private static final long serialVersionUID = -1416631799712180762L;
168         }
169 
170         final Callable<Integer> operation = new Callable<Integer>() {
171             public Integer call() {
172                 throw new MyError();
173             }
174         };
175         try {
176             concurrentOperationMap.runOperation("same-key", operation);
177             fail("MyException expected");
178         }
179         catch (final MyError expected) {}
180     }
181 
182     @Test public void checkedExceptionGetsWrapped() throws Exception {
183         final ConcurrentOperationMap<String, Integer> concurrentOperationMap = new ConcurrentOperationMapImpl<String, Integer>();
184 
185         // Create two threads whose job will be to call runOpertion with the
186         // same name object
187 
188         class MyException extends Exception {
189             private static final long serialVersionUID = 22367874459914044L;
190         }
191 
192         final Callable<Integer> operation = new Callable<Integer>() {
193             public Integer call() throws MyException {
194                 throw new MyException();
195             }
196         };
197         try {
198             concurrentOperationMap.runOperation("same-key", operation);
199             fail("MyException expected");
200         }
201         catch (final ExecutionException expected) {
202             assertEquals(MyException.class, expected.getCause().getClass());
203         }
204     }
205 
206     abstract class Worker implements Runnable {
207         private final CountDownLatch startSignal;
208         private final CountDownLatch doneSignal;
209 
210         Worker(final CountDownLatch startSignal, final CountDownLatch doneSignal) {
211             this.startSignal = startSignal;
212             this.doneSignal = doneSignal;
213         }
214 
215         public void run() {
216             try {
217                 startSignal.await();
218                 doWork();
219                 doneSignal.countDown();
220             }
221             catch (final InterruptedException ex) {} // return;
222         }
223 
224         abstract void doWork();
225     }
226 }