AbstractListenableFutureTest.java revision 1d580d0f6ee4f21eb309ba7b509d2c6d671c4044
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
17package com.google.common.util.concurrent.testing;
18
19import com.google.common.annotations.Beta;
20import com.google.common.util.concurrent.ListenableFuture;
21
22import junit.framework.TestCase;
23
24import java.util.concurrent.CancellationException;
25import java.util.concurrent.CountDownLatch;
26import java.util.concurrent.ExecutionException;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29import java.util.concurrent.Future;
30import java.util.concurrent.TimeUnit;
31import 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
41public abstract class AbstractListenableFutureTest extends TestCase {
42
43  protected CountDownLatch latch;
44  protected ListenableFuture<Boolean> future;
45
46  @Override
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
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   */
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   */
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   */
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   */
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
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   */
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