001/* 002 * Copyright (C) 2007 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy 006 * of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 017package com.google.common.util.concurrent.testing; 018 019import com.google.common.annotations.Beta; 020import com.google.common.annotations.GwtIncompatible; 021import com.google.common.util.concurrent.ListenableFuture; 022import java.util.concurrent.CancellationException; 023import java.util.concurrent.CountDownLatch; 024import java.util.concurrent.ExecutionException; 025import java.util.concurrent.ExecutorService; 026import java.util.concurrent.Executors; 027import java.util.concurrent.Future; 028import java.util.concurrent.TimeUnit; 029import java.util.concurrent.TimeoutException; 030import junit.framework.TestCase; 031 032/** 033 * Abstract test case parent for anything implementing {@link ListenableFuture}. Tests the two get 034 * methods and the addListener method. 035 * 036 * @author Sven Mawson 037 * @since 10.0 038 */ 039@Beta 040@GwtIncompatible 041public abstract class AbstractListenableFutureTest extends TestCase { 042 043 protected CountDownLatch latch; 044 protected ListenableFuture<Boolean> future; 045 046 @Override 047 protected void setUp() throws Exception { 048 049 // Create a latch and a future that waits on the latch. 050 latch = new CountDownLatch(1); 051 future = createListenableFuture(Boolean.TRUE, null, latch); 052 } 053 054 @Override 055 protected void tearDown() throws Exception { 056 057 // Make sure we have no waiting threads. 058 latch.countDown(); 059 } 060 061 /** Constructs a listenable future with a value available after the latch has counted down. */ 062 protected abstract <V> ListenableFuture<V> createListenableFuture( 063 V value, Exception except, CountDownLatch waitOn); 064 065 /** Tests that the {@link Future#get()} method blocks until a value is available. */ 066 public void testGetBlocksUntilValueAvailable() throws Throwable { 067 068 assertFalse(future.isDone()); 069 assertFalse(future.isCancelled()); 070 071 final CountDownLatch successLatch = new CountDownLatch(1); 072 final Throwable[] badness = new Throwable[1]; 073 074 // Wait on the future in a separate thread. 075 new Thread( 076 new Runnable() { 077 @Override 078 public void run() { 079 try { 080 assertSame(Boolean.TRUE, future.get()); 081 successLatch.countDown(); 082 } catch (Throwable t) { 083 t.printStackTrace(); 084 badness[0] = t; 085 } 086 } 087 }) 088 .start(); 089 090 // Release the future value. 091 latch.countDown(); 092 093 assertTrue(successLatch.await(10, TimeUnit.SECONDS)); 094 095 if (badness[0] != null) { 096 throw badness[0]; 097 } 098 099 assertTrue(future.isDone()); 100 assertFalse(future.isCancelled()); 101 } 102 103 /** Tests that the {@link Future#get(long, TimeUnit)} method times out correctly. */ 104 public void testTimeoutOnGetWorksCorrectly() throws InterruptedException, ExecutionException { 105 106 // The task thread waits for the latch, so we expect a timeout here. 107 try { 108 future.get(20, TimeUnit.MILLISECONDS); 109 fail("Should have timed out trying to get the value."); 110 } catch (TimeoutException expected) { 111 } finally { 112 latch.countDown(); 113 } 114 } 115 116 /** 117 * Tests that a canceled future throws a cancellation exception. 118 * 119 * <p>This method checks the cancel, isCancelled, and isDone methods. 120 */ 121 public void testCanceledFutureThrowsCancellation() throws Exception { 122 123 assertFalse(future.isDone()); 124 assertFalse(future.isCancelled()); 125 126 final CountDownLatch successLatch = new CountDownLatch(1); 127 128 // Run cancellation in a separate thread as an extra thread-safety test. 129 new Thread( 130 new Runnable() { 131 @Override 132 public void run() { 133 try { 134 future.get(); 135 } catch (CancellationException expected) { 136 successLatch.countDown(); 137 } catch (Exception ignored) { 138 // All other errors are ignored, we expect a cancellation. 139 } 140 } 141 }) 142 .start(); 143 144 assertFalse(future.isDone()); 145 assertFalse(future.isCancelled()); 146 147 future.cancel(true); 148 149 assertTrue(future.isDone()); 150 assertTrue(future.isCancelled()); 151 152 assertTrue(successLatch.await(200, TimeUnit.MILLISECONDS)); 153 154 latch.countDown(); 155 } 156 157 public void testListenersNotifiedOnError() throws Exception { 158 final CountDownLatch successLatch = new CountDownLatch(1); 159 final CountDownLatch listenerLatch = new CountDownLatch(1); 160 161 ExecutorService exec = Executors.newCachedThreadPool(); 162 163 future.addListener( 164 new Runnable() { 165 @Override 166 public void run() { 167 listenerLatch.countDown(); 168 } 169 }, 170 exec); 171 172 new Thread( 173 new Runnable() { 174 @Override 175 public void run() { 176 try { 177 future.get(); 178 } catch (CancellationException expected) { 179 successLatch.countDown(); 180 } catch (Exception ignored) { 181 // No success latch count down. 182 } 183 } 184 }) 185 .start(); 186 187 future.cancel(true); 188 189 assertTrue(future.isCancelled()); 190 assertTrue(future.isDone()); 191 192 assertTrue(successLatch.await(200, TimeUnit.MILLISECONDS)); 193 assertTrue(listenerLatch.await(200, TimeUnit.MILLISECONDS)); 194 195 latch.countDown(); 196 197 exec.shutdown(); 198 exec.awaitTermination(100, TimeUnit.MILLISECONDS); 199 } 200 201 /** 202 * Tests that all listeners complete, even if they were added before or after the future was 203 * finishing. Also acts as a concurrency test to make sure the locking is done correctly when a 204 * future is finishing so that no listeners can be lost. 205 */ 206 public void testAllListenersCompleteSuccessfully() 207 throws InterruptedException, ExecutionException { 208 209 ExecutorService exec = Executors.newCachedThreadPool(); 210 211 int listenerCount = 20; 212 final CountDownLatch listenerLatch = new CountDownLatch(listenerCount); 213 214 // Test that listeners added both before and after the value is available 215 // get called correctly. 216 for (int i = 0; i < 20; i++) { 217 218 // Right in the middle start up a thread to close the latch. 219 if (i == 10) { 220 new Thread( 221 new Runnable() { 222 @Override 223 public void run() { 224 latch.countDown(); 225 } 226 }) 227 .start(); 228 } 229 230 future.addListener( 231 new Runnable() { 232 @Override 233 public void run() { 234 listenerLatch.countDown(); 235 } 236 }, 237 exec); 238 } 239 240 assertSame(Boolean.TRUE, future.get()); 241 // Wait for the listener latch to complete. 242 listenerLatch.await(500, TimeUnit.MILLISECONDS); 243 244 exec.shutdown(); 245 exec.awaitTermination(500, TimeUnit.MILLISECONDS); 246 } 247}