• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License.  You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.google.common.util.concurrent.testing;
18 
19 import com.google.common.annotations.Beta;
20 import com.google.common.util.concurrent.ListenableFuture;
21 
22 import junit.framework.TestCase;
23 
24 import java.util.concurrent.CancellationException;
25 import java.util.concurrent.CountDownLatch;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.Future;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.TimeoutException;
32 
33 /**
34  * Abstract test case parent for anything implementing {@link ListenableFuture}.
35  * Tests the two get methods and the addListener method.
36  *
37  * @author Sven Mawson
38  * @since 10.0
39  */
40 @Beta
41 public abstract class AbstractListenableFutureTest extends TestCase {
42 
43   protected CountDownLatch latch;
44   protected ListenableFuture<Boolean> future;
45 
46   @Override
setUp()47   protected void setUp() throws Exception {
48 
49     // Create a latch and a future that waits on the latch.
50     latch = new CountDownLatch(1);
51     future = createListenableFuture(Boolean.TRUE, null, latch);
52   }
53 
54   @Override
tearDown()55   protected void tearDown() throws Exception {
56 
57     // Make sure we have no waiting threads.
58     latch.countDown();
59   }
60 
61   /**
62    * Constructs a listenable future with a value available after the latch
63    * has counted down.
64    */
createListenableFuture( V value, Exception except, CountDownLatch waitOn)65   protected abstract <V> ListenableFuture<V> createListenableFuture(
66       V value, Exception except, CountDownLatch waitOn);
67 
68   /**
69    * Tests that the {@link Future#get()} method blocks until a value is
70    * available.
71    */
testGetBlocksUntilValueAvailable()72   public void testGetBlocksUntilValueAvailable() throws Throwable {
73 
74     assertFalse(future.isDone());
75     assertFalse(future.isCancelled());
76 
77     final CountDownLatch successLatch = new CountDownLatch(1);
78     final Throwable[] badness = new Throwable[1];
79 
80     // Wait on the future in a separate thread.
81     new Thread(new Runnable() {
82       @Override
83       public void run() {
84         try {
85           assertSame(Boolean.TRUE, future.get());
86           successLatch.countDown();
87         } catch (Throwable t) {
88           t.printStackTrace();
89           badness[0] = t;
90         }
91       }}).start();
92 
93     // Release the future value.
94     latch.countDown();
95 
96     assertTrue(successLatch.await(10, TimeUnit.SECONDS));
97 
98     if (badness[0] != null) {
99       throw badness[0];
100     }
101 
102     assertTrue(future.isDone());
103     assertFalse(future.isCancelled());
104   }
105 
106   /**
107    * Tests that the {@link Future#get(long, TimeUnit)} method times out
108    * correctly.
109    */
testTimeoutOnGetWorksCorrectly()110   public void testTimeoutOnGetWorksCorrectly() throws InterruptedException,
111       ExecutionException {
112 
113     // The task thread waits for the latch, so we expect a timeout here.
114     try {
115       future.get(20, TimeUnit.MILLISECONDS);
116       fail("Should have timed out trying to get the value.");
117     } catch (TimeoutException expected) {
118       // Expected.
119     } finally {
120       latch.countDown();
121     }
122   }
123 
124   /**
125    * Tests that a canceled future throws a cancellation exception.
126    *
127    * This method checks the cancel, isCancelled, and isDone methods.
128    */
testCanceledFutureThrowsCancellation()129   public void testCanceledFutureThrowsCancellation() throws Exception {
130 
131     assertFalse(future.isDone());
132     assertFalse(future.isCancelled());
133 
134     final CountDownLatch successLatch = new CountDownLatch(1);
135 
136     // Run cancellation in a separate thread as an extra thread-safety test.
137     new Thread(new Runnable() {
138       @Override
139       public void run() {
140         try {
141           future.get();
142         } catch (CancellationException expected) {
143           successLatch.countDown();
144         } catch (Exception ignored) {
145           // All other errors are ignored, we expect a cancellation.
146         }
147       }
148     }).start();
149 
150     assertFalse(future.isDone());
151     assertFalse(future.isCancelled());
152 
153     future.cancel(true);
154 
155     assertTrue(future.isDone());
156     assertTrue(future.isCancelled());
157 
158     assertTrue(successLatch.await(200, TimeUnit.MILLISECONDS));
159 
160     latch.countDown();
161   }
162 
testListenersNotifiedOnError()163   public void testListenersNotifiedOnError() throws Exception {
164     final CountDownLatch successLatch = new CountDownLatch(1);
165     final CountDownLatch listenerLatch = new CountDownLatch(1);
166 
167     ExecutorService exec = Executors.newCachedThreadPool();
168 
169     future.addListener(new Runnable() {
170       @Override
171       public void run() {
172         listenerLatch.countDown();
173       }
174     }, exec);
175 
176     new Thread(new Runnable() {
177       @Override
178       public void run() {
179         try {
180           future.get();
181         } catch (CancellationException expected) {
182           successLatch.countDown();
183         } catch (Exception ignored) {
184           // No success latch count down.
185         }
186       }
187     }).start();
188 
189     future.cancel(true);
190 
191     assertTrue(future.isCancelled());
192     assertTrue(future.isDone());
193 
194     assertTrue(successLatch.await(200, TimeUnit.MILLISECONDS));
195     assertTrue(listenerLatch.await(200, TimeUnit.MILLISECONDS));
196 
197     latch.countDown();
198 
199     exec.shutdown();
200     exec.awaitTermination(100, TimeUnit.MILLISECONDS);
201   }
202 
203   /**
204    * Tests that all listeners complete, even if they were added before or after
205    * the future was finishing.  Also acts as a concurrency test to make sure the
206    * locking is done correctly when a future is finishing so that no listeners
207    * can be lost.
208    */
testAllListenersCompleteSuccessfully()209   public void testAllListenersCompleteSuccessfully()
210       throws InterruptedException, ExecutionException {
211 
212     ExecutorService exec = Executors.newCachedThreadPool();
213 
214     int listenerCount = 20;
215     final CountDownLatch listenerLatch = new CountDownLatch(listenerCount);
216 
217     // Test that listeners added both before and after the value is available
218     // get called correctly.
219     for (int i = 0; i < 20; i++) {
220 
221       // Right in the middle start up a thread to close the latch.
222       if (i == 10) {
223         new Thread(new Runnable() {
224           @Override
225           public void run() {
226             latch.countDown();
227           }
228         }).start();
229       }
230 
231       future.addListener(new Runnable() {
232         @Override
233         public void run() {
234           listenerLatch.countDown();
235         }
236       }, exec);
237     }
238 
239     assertSame(Boolean.TRUE, future.get());
240     // Wait for the listener latch to complete.
241     listenerLatch.await(500, TimeUnit.MILLISECONDS);
242 
243     exec.shutdown();
244     exec.awaitTermination(500, TimeUnit.MILLISECONDS);
245   }
246 }
247