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