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
17package com.google.common.util.concurrent;
18
19import static org.junit.contrib.truth.Truth.ASSERT;
20
21import junit.framework.AssertionFailedError;
22import junit.framework.TestCase;
23
24import java.util.concurrent.ExecutionException;
25import java.util.concurrent.ExecutorService;
26import java.util.concurrent.Executors;
27import java.util.concurrent.atomic.AtomicReference;
28
29/**
30 * Tests for {@link AbstractFuture}.
31 *
32 * @author Brian Stoler
33 */
34
35public 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