blob: 4024a8e6d9912a49bc10e015aa158041e7f0d2c4 [file] [log] [blame]
/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.util.concurrent;
import static org.truth0.Truth.ASSERT;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
/**
* Tests for {@link AbstractFuture}.
*
* @author Brian Stoler
*/
public class AbstractFutureTest extends TestCase {
public void testSuccess() throws ExecutionException, InterruptedException {
final Object value = new Object();
assertSame(value, new AbstractFuture<Object>() {
{
set(value);
}
}.get());
}
public void testException() throws InterruptedException {
final Throwable failure = new Throwable();
AbstractFuture<String> future = new AbstractFuture<String>() {
{
setException(failure);
}
};
ExecutionException ee1 = getExpectingExecutionException(future);
ExecutionException ee2 = getExpectingExecutionException(future);
// Ensure we get a unique execution exception on each get
assertNotSame(ee1, ee2);
assertSame(failure, ee1.getCause());
assertSame(failure, ee2.getCause());
checkStackTrace(ee1);
checkStackTrace(ee2);
}
public void testCancel_notDoneNoInterrupt() throws Exception {
InterruptibleFuture future = new InterruptibleFuture();
assertTrue(future.cancel(false));
assertTrue(future.isCancelled());
assertTrue(future.isDone());
assertFalse(future.wasInterrupted());
assertFalse(future.interruptTaskWasCalled);
try {
future.get();
fail("Expected CancellationException");
} catch (CancellationException e) {
assertNotNull(e.getCause());
}
}
public void testCancel_notDoneInterrupt() throws Exception {
InterruptibleFuture future = new InterruptibleFuture();
assertTrue(future.cancel(true));
assertTrue(future.isCancelled());
assertTrue(future.isDone());
assertTrue(future.wasInterrupted());
assertTrue(future.interruptTaskWasCalled);
try {
future.get();
fail("Expected CancellationException");
} catch (CancellationException e) {
assertNotNull(e.getCause());
}
}
public void testCancel_done() throws Exception {
AbstractFuture<String> future = new AbstractFuture<String>() {
{
set("foo");
}
};
assertFalse(future.cancel(true));
assertFalse(future.isCancelled());
assertTrue(future.isDone());
}
public void testCompletionFinishesWithDone() {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 50000; i++) {
final AbstractFuture<String> future = new AbstractFuture<String>() {};
final AtomicReference<String> errorMessage = new AtomicReference<String>();
executor.execute(new Runnable() {
@Override
public void run() {
future.set("success");
if (!future.isDone()) {
errorMessage.set("Set call exited before future was complete.");
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
future.setException(new IllegalArgumentException("failure"));
if (!future.isDone()) {
errorMessage.set("SetException call exited before future was complete.");
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
future.cancel(true);
if (!future.isDone()) {
errorMessage.set("Cancel call exited before future was complete.");
}
}
});
try {
future.get();
} catch (Throwable t) {
// Ignore, we just wanted to block.
}
String error = errorMessage.get();
assertNull(error, error);
}
executor.shutdown();
}
private void checkStackTrace(ExecutionException e) {
// Our call site for get() should be in the trace.
int index = findStackFrame(
e, getClass().getName(), "getExpectingExecutionException");
ASSERT.that(index).isNotEqualTo(0);
// Above our method should be the call to get(). Don't assert on the class
// because it could be some superclass.
ASSERT.that(e.getStackTrace()[index - 1].getMethodName()).isEqualTo("get");
}
private static int findStackFrame(
ExecutionException e, String clazz, String method) {
StackTraceElement[] elements = e.getStackTrace();
for (int i = 0; i < elements.length; i++) {
StackTraceElement element = elements[i];
if (element.getClassName().equals(clazz)
&& element.getMethodName().equals(method)) {
return i;
}
}
AssertionFailedError failure =
new AssertionFailedError("Expected element " + clazz + "." + method
+ " not found in stack trace");
failure.initCause(e);
throw failure;
}
private ExecutionException getExpectingExecutionException(
AbstractFuture<String> future) throws InterruptedException {
try {
String got = future.get();
fail("Expected exception but got " + got);
} catch (ExecutionException e) {
return e;
}
// unreachable, but compiler doesn't know that fail() always throws
return null;
}
private static final class InterruptibleFuture
extends AbstractFuture<String> {
boolean interruptTaskWasCalled;
@Override protected void interruptTask() {
assertFalse(interruptTaskWasCalled);
interruptTaskWasCalled = true;
}
}
}