Home | History | Annotate | Download | only in concurrent
      1 /*
      2  * Copyright (C) 2011 The Guava Authors
      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.google.common.util.concurrent;
     18 
     19 import static org.junit.contrib.truth.Truth.ASSERT;
     20 
     21 import junit.framework.AssertionFailedError;
     22 import junit.framework.TestCase;
     23 
     24 import java.util.concurrent.ExecutionException;
     25 import java.util.concurrent.ExecutorService;
     26 import java.util.concurrent.Executors;
     27 import java.util.concurrent.atomic.AtomicReference;
     28 
     29 /**
     30  * Tests for {@link AbstractFuture}.
     31  *
     32  * @author Brian Stoler
     33  */
     34 
     35 public class AbstractFutureTest extends TestCase {
     36   public void testSuccess() throws ExecutionException, InterruptedException {
     37     final Object value = new Object();
     38     assertSame(value, new AbstractFuture<Object>() {
     39       {
     40         set(value);
     41       }
     42     }.get());
     43   }
     44 
     45   public void testException() throws InterruptedException {
     46     final Throwable failure = new Throwable();
     47     AbstractFuture<String> future = new AbstractFuture<String>() {
     48       {
     49         setException(failure);
     50       }
     51     };
     52 
     53     ExecutionException ee1 = getExpectingExecutionException(future);
     54     ExecutionException ee2 = getExpectingExecutionException(future);
     55 
     56     // Ensure we get a unique execution exception on each get
     57     assertNotSame(ee1, ee2);
     58 
     59     assertSame(failure, ee1.getCause());
     60     assertSame(failure, ee2.getCause());
     61 
     62     checkStackTrace(ee1);
     63     checkStackTrace(ee2);
     64   }
     65 
     66   public void testCancel_notDoneNoInterrupt() {
     67     InterruptibleFuture future = new InterruptibleFuture();
     68     assertTrue(future.cancel(false));
     69     assertTrue(future.isCancelled());
     70     assertTrue(future.isDone());
     71     assertFalse(future.wasInterrupted);
     72   }
     73 
     74   public void testCancel_notDoneInterrupt() {
     75     InterruptibleFuture future = new InterruptibleFuture();
     76     assertTrue(future.cancel(true));
     77     assertTrue(future.isCancelled());
     78     assertTrue(future.isDone());
     79     assertTrue(future.wasInterrupted);
     80   }
     81 
     82   public void testCancel_done() {
     83     AbstractFuture<String> future = new AbstractFuture<String>() {
     84       {
     85         set("foo");
     86       }
     87     };
     88     assertFalse(future.cancel(true));
     89     assertFalse(future.isCancelled());
     90     assertTrue(future.isDone());
     91   }
     92 
     93   public void testCompletionFinishesWithDone() {
     94     ExecutorService executor = Executors.newFixedThreadPool(10);
     95     for (int i = 0; i < 50000; i++) {
     96       final AbstractFuture<String> future = new AbstractFuture<String>() {};
     97       final AtomicReference<String> errorMessage = new AtomicReference<String>();
     98       executor.execute(new Runnable() {
     99         @Override
    100         public void run() {
    101           future.set("success");
    102           if (!future.isDone()) {
    103             errorMessage.set("Set call exited before future was complete.");
    104           }
    105         }
    106       });
    107       executor.execute(new Runnable() {
    108         @Override
    109         public void run() {
    110           future.setException(new IllegalArgumentException("failure"));
    111           if (!future.isDone()) {
    112             errorMessage.set("SetException call exited before future was complete.");
    113           }
    114         }
    115       });
    116       executor.execute(new Runnable() {
    117         @Override
    118         public void run() {
    119           future.cancel(true);
    120           if (!future.isDone()) {
    121             errorMessage.set("Cancel call exited before future was complete.");
    122           }
    123         }
    124       });
    125       try {
    126         future.get();
    127       } catch (Throwable t) {
    128         // Ignore, we just wanted to block.
    129       }
    130       String error = errorMessage.get();
    131       assertNull(error, error);
    132     }
    133     executor.shutdown();
    134   }
    135 
    136   private void checkStackTrace(ExecutionException e) {
    137     // Our call site for get() should be in the trace.
    138     int index = findStackFrame(
    139         e, getClass().getName(), "getExpectingExecutionException");
    140 
    141     ASSERT.that(index).isNotEqualTo(0);
    142 
    143     // Above our method should be the call to get(). Don't assert on the class
    144     // because it could be some superclass.
    145     ASSERT.that(e.getStackTrace()[index - 1].getMethodName()).isEqualTo("get");
    146   }
    147 
    148   private static int findStackFrame(
    149       ExecutionException e, String clazz, String method) {
    150     StackTraceElement[] elements = e.getStackTrace();
    151     for (int i = 0; i < elements.length; i++) {
    152       StackTraceElement element = elements[i];
    153       if (element.getClassName().equals(clazz)
    154           && element.getMethodName().equals(method)) {
    155         return i;
    156       }
    157     }
    158     AssertionFailedError failure =
    159         new AssertionFailedError("Expected element " + clazz + "." + method
    160             + " not found in stack trace");
    161     failure.initCause(e);
    162     throw failure;
    163   }
    164 
    165   private ExecutionException getExpectingExecutionException(
    166       AbstractFuture<String> future) throws InterruptedException {
    167     try {
    168       String got = future.get();
    169       fail("Expected exception but got " + got);
    170     } catch (ExecutionException e) {
    171       return e;
    172     }
    173 
    174     // unreachable, but compiler doesn't know that fail() always throws
    175     return null;
    176   }
    177 
    178   private static final class InterruptibleFuture
    179       extends AbstractFuture<String> {
    180     boolean wasInterrupted;
    181 
    182     @Override protected void interruptTask() {
    183       wasInterrupted = true;
    184     }
    185   }
    186 }
    187