| /* |
| * Copyright (C) 2008 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 com.google.common.base.Throwables.propagateIfInstanceOf; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.util.concurrent.Futures.allAsList; |
| import static com.google.common.util.concurrent.Futures.get; |
| import static com.google.common.util.concurrent.Futures.getUnchecked; |
| import static com.google.common.util.concurrent.Futures.immediateFailedFuture; |
| import static com.google.common.util.concurrent.Futures.immediateFuture; |
| import static com.google.common.util.concurrent.Futures.successfulAsList; |
| import static com.google.common.util.concurrent.MoreExecutors.directExecutor; |
| import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; |
| import static java.util.concurrent.Executors.newSingleThreadExecutor; |
| import static java.util.concurrent.TimeUnit.MILLISECONDS; |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| import static org.easymock.EasyMock.expect; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Functions; |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.testing.ClassSanityTester; |
| import com.google.common.testing.TestLogHandler; |
| import com.google.common.util.concurrent.ForwardingFuture.SimpleForwardingFuture; |
| |
| import junit.framework.AssertionFailedError; |
| import junit.framework.TestCase; |
| |
| import org.easymock.EasyMock; |
| import org.easymock.IMocksControl; |
| |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.logging.Handler; |
| import java.util.logging.LogRecord; |
| import java.util.logging.Logger; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * Unit tests for {@link Futures}. |
| * |
| * TODO: Add tests for other Futures methods |
| * |
| * @author Nishant Thakkar |
| */ |
| public class FuturesTest extends TestCase { |
| private static final Logger combinedFutureLogger = Logger.getLogger( |
| "com.google.common.util.concurrent.Futures$CombinedFuture"); |
| private final TestLogHandler combinedFutureLogHandler = new TestLogHandler(); |
| |
| private static final String DATA1 = "data"; |
| private static final String DATA2 = "more data"; |
| private static final String DATA3 = "most data"; |
| |
| private IMocksControl mocksControl; |
| |
| @Override protected void setUp() throws Exception { |
| super.setUp(); |
| combinedFutureLogger.addHandler(combinedFutureLogHandler); |
| mocksControl = EasyMock.createControl(); |
| } |
| |
| @Override protected void tearDown() throws Exception { |
| /* |
| * Clear interrupt for future tests. |
| * |
| * (Ideally we would perform interrupts only in threads that we create, but |
| * it's hard to imagine that anything will break in practice.) |
| */ |
| Thread.interrupted(); |
| combinedFutureLogger.removeHandler(combinedFutureLogHandler); |
| super.tearDown(); |
| } |
| |
| public void testImmediateFuture() throws Exception { |
| ListenableFuture<String> future = Futures.immediateFuture(DATA1); |
| |
| // Verify that the proper object is returned without waiting |
| assertSame(DATA1, future.get(0L, TimeUnit.MILLISECONDS)); |
| } |
| |
| public void testMultipleImmediateFutures() throws Exception { |
| ListenableFuture<String> future1 = Futures.immediateFuture(DATA1); |
| ListenableFuture<String> future2 = Futures.immediateFuture(DATA2); |
| |
| // Verify that the proper objects are returned without waiting |
| assertSame(DATA1, future1.get(0L, TimeUnit.MILLISECONDS)); |
| assertSame(DATA2, future2.get(0L, TimeUnit.MILLISECONDS)); |
| } |
| |
| public void testImmediateFailedFuture() throws Exception { |
| Exception exception = new Exception(); |
| ListenableFuture<String> future = |
| Futures.immediateFailedFuture(exception); |
| |
| try { |
| future.get(0L, TimeUnit.MILLISECONDS); |
| fail("This call was supposed to throw an ExecutionException"); |
| } catch (ExecutionException expected) { |
| // This is good and expected |
| assertSame(exception, expected.getCause()); |
| } |
| } |
| |
| public void testImmediateFailedFuture_cancellationException() throws Exception { |
| CancellationException exception = new CancellationException(); |
| ListenableFuture<String> future = |
| Futures.immediateFailedFuture(exception); |
| |
| try { |
| future.get(0L, TimeUnit.MILLISECONDS); |
| fail("This call was supposed to throw an ExecutionException"); |
| } catch (ExecutionException expected) { |
| // This is good and expected |
| assertSame(exception, expected.getCause()); |
| assertFalse(future.isCancelled()); |
| } |
| } |
| |
| public void testImmediateCancelledFuture() throws Exception { |
| ListenableFuture<String> future = |
| Futures.immediateCancelledFuture(); |
| assertTrue(future.isCancelled()); |
| } |
| |
| private static class MyException extends Exception {} |
| |
| public void testImmediateCheckedFuture() throws Exception { |
| CheckedFuture<String, MyException> future = Futures.immediateCheckedFuture( |
| DATA1); |
| |
| // Verify that the proper object is returned without waiting |
| assertSame(DATA1, future.get(0L, TimeUnit.MILLISECONDS)); |
| assertSame(DATA1, future.checkedGet(0L, TimeUnit.MILLISECONDS)); |
| } |
| |
| public void testMultipleImmediateCheckedFutures() throws Exception { |
| CheckedFuture<String, MyException> future1 = Futures.immediateCheckedFuture( |
| DATA1); |
| CheckedFuture<String, MyException> future2 = Futures.immediateCheckedFuture( |
| DATA2); |
| |
| // Verify that the proper objects are returned without waiting |
| assertSame(DATA1, future1.get(0L, TimeUnit.MILLISECONDS)); |
| assertSame(DATA1, future1.checkedGet(0L, TimeUnit.MILLISECONDS)); |
| assertSame(DATA2, future2.get(0L, TimeUnit.MILLISECONDS)); |
| assertSame(DATA2, future2.checkedGet(0L, TimeUnit.MILLISECONDS)); |
| } |
| |
| public void testImmediateFailedCheckedFuture() throws Exception { |
| MyException exception = new MyException(); |
| CheckedFuture<String, MyException> future = |
| Futures.immediateFailedCheckedFuture(exception); |
| |
| try { |
| future.get(0L, TimeUnit.MILLISECONDS); |
| fail("This call was supposed to throw an ExecutionException"); |
| } catch (ExecutionException expected) { |
| // This is good and expected |
| assertSame(exception, expected.getCause()); |
| } |
| |
| try { |
| future.checkedGet(0L, TimeUnit.MILLISECONDS); |
| fail("This call was supposed to throw an MyException"); |
| } catch (MyException expected) { |
| // This is good and expected |
| assertSame(exception, expected); |
| } |
| } |
| |
| // Class hierarchy for generics sanity checks |
| private static class Foo {} |
| private static class FooChild extends Foo {} |
| private static class Bar {} |
| private static class BarChild extends Bar {} |
| |
| public void testTransform_genericsNull() throws Exception { |
| ListenableFuture<?> nullFuture = Futures.immediateFuture(null); |
| ListenableFuture<?> transformedFuture = |
| Futures.transform(nullFuture, Functions.constant(null)); |
| assertNull(transformedFuture.get()); |
| } |
| |
| public void testTransform_genericsHierarchy() throws Exception { |
| ListenableFuture<FooChild> future = Futures.immediateFuture(null); |
| final BarChild barChild = new BarChild(); |
| Function<Foo, BarChild> function = new Function<Foo, BarChild>() { |
| @Override public BarChild apply(Foo unused) { |
| return barChild; |
| } |
| }; |
| Bar bar = Futures.transform(future, function).get(); |
| assertSame(barChild, bar); |
| } |
| |
| public void testTransform_cancelPropagatesToInput() throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| AsyncFunction<Foo, Bar> function = new AsyncFunction<Foo, Bar>() { |
| @Override public ListenableFuture<Bar> apply(Foo unused) { |
| throw new AssertionFailedError("Unexpeted call to apply."); |
| } |
| }; |
| assertTrue(Futures.transform(input, function).cancel(false)); |
| assertTrue(input.isCancelled()); |
| assertFalse(input.wasInterrupted()); |
| } |
| |
| public void testTransform_interruptPropagatesToInput() throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| AsyncFunction<Foo, Bar> function = new AsyncFunction<Foo, Bar>() { |
| @Override public ListenableFuture<Bar> apply(Foo unused) { |
| throw new AssertionFailedError("Unexpeted call to apply."); |
| } |
| }; |
| assertTrue(Futures.transform(input, function).cancel(true)); |
| assertTrue(input.isCancelled()); |
| assertTrue(input.wasInterrupted()); |
| } |
| |
| public void testTransform_cancelPropagatesToAsyncOutput() throws Exception { |
| ListenableFuture<Foo> immediate = Futures.immediateFuture(new Foo()); |
| final SettableFuture<Bar> secondary = SettableFuture.create(); |
| AsyncFunction<Foo, Bar> function = new AsyncFunction<Foo, Bar>() { |
| @Override public ListenableFuture<Bar> apply(Foo unused) { |
| return secondary; |
| } |
| }; |
| assertTrue(Futures.transform(immediate, function).cancel(false)); |
| assertTrue(secondary.isCancelled()); |
| assertFalse(secondary.wasInterrupted()); |
| } |
| |
| public void testTransform_interruptPropagatesToAsyncOutput() |
| throws Exception { |
| ListenableFuture<Foo> immediate = Futures.immediateFuture(new Foo()); |
| final SettableFuture<Bar> secondary = SettableFuture.create(); |
| AsyncFunction<Foo, Bar> function = new AsyncFunction<Foo, Bar>() { |
| @Override public ListenableFuture<Bar> apply(Foo unused) { |
| return secondary; |
| } |
| }; |
| assertTrue(Futures.transform(immediate, function).cancel(true)); |
| assertTrue(secondary.isCancelled()); |
| assertTrue(secondary.wasInterrupted()); |
| } |
| |
| public void testTransform_rejectionPropagatesToOutput() |
| throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| ExecutorService executor = newDirectExecutorService(); |
| ListenableFuture<String> transformed = |
| Futures.transform(input, Functions.toStringFunction(), executor); |
| executor.shutdown(); |
| input.set(new Foo()); |
| try { |
| transformed.get(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (ExecutionException expected) { |
| assertTrue(expected.getCause() instanceof RejectedExecutionException); |
| } |
| } |
| |
| /** |
| * Tests that the function is invoked only once, even if it throws an |
| * exception. |
| */ |
| public void testTransformValueRemainsMemoized() throws Exception { |
| class Holder { |
| int value = 2; |
| } |
| final Holder holder = new Holder(); |
| |
| // This function adds the holder's value to the input value. |
| Function<Integer, Integer> adder = new Function<Integer, Integer>() { |
| @Override public Integer apply(Integer from) { |
| return from + holder.value; |
| } |
| }; |
| |
| // Since holder.value is 2, applying 4 should yield 6. |
| assertEquals(6, adder.apply(4).intValue()); |
| |
| ListenableFuture<Integer> immediateFuture = Futures.immediateFuture(4); |
| Future<Integer> transformedFuture = Futures.transform(immediateFuture, adder); |
| |
| // The composed future also yields 6. |
| assertEquals(6, transformedFuture.get().intValue()); |
| |
| // Repeated calls yield the same value even though the function's behavior |
| // changes |
| holder.value = 3; |
| assertEquals(6, transformedFuture.get().intValue()); |
| assertEquals(7, adder.apply(4).intValue()); |
| |
| // Once more, with feeling. |
| holder.value = 4; |
| assertEquals(6, transformedFuture.get().intValue()); |
| assertEquals(8, adder.apply(4).intValue()); |
| |
| // Memoized get also retains the value. |
| assertEquals(6, transformedFuture.get(1000, TimeUnit.SECONDS).intValue()); |
| |
| // Unsurprisingly, recomposing the future will return an updated value. |
| assertEquals(8, Futures.transform(immediateFuture, adder).get().intValue()); |
| |
| // Repeating, with the timeout version |
| assertEquals(8, Futures.transform(immediateFuture, adder).get( |
| 1000, TimeUnit.SECONDS).intValue()); |
| } |
| |
| static class MyError extends Error {} |
| static class MyRuntimeException extends RuntimeException {} |
| |
| /** |
| * Test that the function is invoked only once, even if it throws an |
| * exception. Also, test that that function's result is wrapped in an |
| * ExecutionException. |
| */ |
| public void testTransformExceptionRemainsMemoized() throws Throwable { |
| // We need to test with two input futures since ExecutionList.execute |
| // doesn't catch Errors and we cannot depend on the order that our |
| // transformations run. (So it is possible that the Error being thrown |
| // could prevent our second transformations from running). |
| SettableFuture<Integer> exceptionInput = SettableFuture.create(); |
| ListenableFuture<Integer> exceptionComposedFuture = |
| Futures.transform(exceptionInput, newOneTimeExceptionThrower()); |
| exceptionInput.set(0); |
| runGetIdempotencyTest(exceptionComposedFuture, MyRuntimeException.class); |
| |
| SettableFuture<Integer> errorInput = SettableFuture.create(); |
| ListenableFuture<Integer> errorComposedFuture = |
| Futures.transform(errorInput, newOneTimeErrorThrower()); |
| errorInput.set(0); |
| |
| runGetIdempotencyTest(errorComposedFuture, MyError.class); |
| |
| /* |
| * Try again when the input's value is already filled in, since the flow is |
| * slightly different in that case. |
| */ |
| exceptionComposedFuture = |
| Futures.transform(exceptionInput, newOneTimeExceptionThrower()); |
| runGetIdempotencyTest(exceptionComposedFuture, MyRuntimeException.class); |
| |
| runGetIdempotencyTest(Futures.transform(errorInput, newOneTimeErrorThrower()), MyError.class); |
| runGetIdempotencyTest(errorComposedFuture, MyError.class); |
| } |
| |
| private static void runGetIdempotencyTest(Future<Integer> transformedFuture, |
| Class<? extends Throwable> expectedExceptionClass) throws Throwable { |
| for (int i = 0; i < 5; i++) { |
| try { |
| transformedFuture.get(); |
| fail(); |
| } catch (ExecutionException expected) { |
| if (!expectedExceptionClass.isInstance(expected.getCause())) { |
| throw expected.getCause(); |
| } |
| } |
| } |
| } |
| |
| private static Function<Integer, Integer> newOneTimeExceptionThrower() { |
| return new Function<Integer, Integer>() { |
| int calls = 0; |
| |
| @Override public Integer apply(Integer from) { |
| if (++calls > 1) { |
| fail(); |
| } |
| throw new MyRuntimeException(); |
| } |
| }; |
| } |
| |
| private static Function<Integer, Integer> newOneTimeErrorThrower() { |
| return new Function<Integer, Integer>() { |
| int calls = 0; |
| |
| @Override public Integer apply(Integer from) { |
| if (++calls > 1) { |
| fail(); |
| } |
| throw new MyError(); |
| } |
| }; |
| } |
| |
| // TODO(cpovirk): top-level class? |
| static class ExecutorSpy implements Executor { |
| Executor delegate; |
| boolean wasExecuted; |
| |
| public ExecutorSpy(Executor delegate) { |
| this.delegate = delegate; |
| } |
| |
| @Override public void execute(Runnable command) { |
| delegate.execute(command); |
| wasExecuted = true; |
| } |
| } |
| |
| public void testTransform_Executor() throws Exception { |
| Object value = new Object(); |
| ExecutorSpy spy = new ExecutorSpy(directExecutor()); |
| |
| assertFalse(spy.wasExecuted); |
| |
| ListenableFuture<Object> future = Futures.transform( |
| Futures.immediateFuture(value), |
| Functions.identity(), spy); |
| |
| assertSame(value, future.get()); |
| assertTrue(spy.wasExecuted); |
| } |
| |
| public void testLazyTransform() throws Exception { |
| FunctionSpy<Object, String> spy = |
| new FunctionSpy<Object, String>(Functions.constant("bar")); |
| Future<String> input = Futures.immediateFuture("foo"); |
| Future<String> transformed = Futures.lazyTransform(input, spy); |
| assertEquals(0, spy.getApplyCount()); |
| assertEquals("bar", transformed.get()); |
| assertEquals(1, spy.getApplyCount()); |
| assertEquals("bar", transformed.get()); |
| assertEquals(2, spy.getApplyCount()); |
| } |
| |
| public void testLazyTransform_exception() throws Exception { |
| final RuntimeException exception = new RuntimeException("deliberate"); |
| Function<Integer, String> function = new Function<Integer, String>() { |
| @Override public String apply(Integer input) { |
| throw exception; |
| } |
| }; |
| Future<String> transformed = Futures.lazyTransform(Futures.immediateFuture(1), function); |
| try { |
| transformed.get(); |
| fail(); |
| } catch (ExecutionException expected) { |
| assertSame(exception, expected.getCause()); |
| } |
| try { |
| transformed.get(1, TimeUnit.SECONDS); |
| fail(); |
| } catch (ExecutionException expected) { |
| assertSame(exception, expected.getCause()); |
| } |
| } |
| |
| private static class FunctionSpy<I, O> implements Function<I, O> { |
| private int applyCount; |
| private final Function<I, O> delegate; |
| |
| public FunctionSpy(Function<I, O> delegate) { |
| this.delegate = delegate; |
| } |
| |
| @Override |
| public O apply(I input) { |
| applyCount++; |
| return delegate.apply(input); |
| } |
| |
| public int getApplyCount() { |
| return applyCount; |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void testWithFallback_inputDoesNotRaiseException() throws Exception { |
| FutureFallback<Integer> fallback = mocksControl.createMock(FutureFallback.class); |
| ListenableFuture<Integer> originalFuture = Futures.immediateFuture(7); |
| |
| mocksControl.replay(); |
| ListenableFuture<Integer> faultToleranteFuture = Futures.withFallback(originalFuture, fallback); |
| assertEquals(7, faultToleranteFuture.get().intValue()); |
| mocksControl.verify(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void testWithFallback_inputRaisesException() throws Exception { |
| FutureFallback<Integer> fallback = mocksControl.createMock(FutureFallback.class); |
| RuntimeException raisedException = new RuntimeException(); |
| expect(fallback.create(raisedException)).andReturn(Futures.immediateFuture(20)); |
| ListenableFuture<Integer> failingFuture = Futures.immediateFailedFuture(raisedException); |
| |
| mocksControl.replay(); |
| ListenableFuture<Integer> faultToleranteFuture = Futures.withFallback(failingFuture, fallback); |
| assertEquals(20, faultToleranteFuture.get().intValue()); |
| mocksControl.verify(); |
| } |
| |
| public void testWithFallback_fallbackGeneratesRuntimeException() throws Exception { |
| RuntimeException expectedException = new RuntimeException(); |
| runExpectedExceptionFallbackTest(expectedException, false); |
| } |
| |
| public void testWithFallback_fallbackGeneratesCheckedException() throws Exception { |
| Exception expectedException = new Exception() {}; |
| runExpectedExceptionFallbackTest(expectedException, false); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void testWithFallback_fallbackGeneratesError() throws Exception { |
| final Error error = new Error("deliberate"); |
| FutureFallback<Integer> fallback = new FutureFallback<Integer>() { |
| @Override public ListenableFuture<Integer> create(Throwable t) throws Exception { |
| throw error; |
| } |
| }; |
| ListenableFuture<Integer> failingFuture = Futures.immediateFailedFuture(new RuntimeException()); |
| try { |
| Futures.withFallback(failingFuture, fallback).get(); |
| fail("An Exception should have been thrown!"); |
| } catch (ExecutionException expected) { |
| assertSame(error, expected.getCause()); |
| } |
| } |
| |
| public void testWithFallback_fallbackReturnsRuntimeException() throws Exception { |
| RuntimeException expectedException = new RuntimeException(); |
| runExpectedExceptionFallbackTest(expectedException, true); |
| } |
| |
| public void testWithFallback_fallbackReturnsCheckedException() throws Exception { |
| Exception expectedException = new Exception() {}; |
| runExpectedExceptionFallbackTest(expectedException, true); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void runExpectedExceptionFallbackTest( |
| Throwable expectedException, boolean wrapInFuture) throws Exception { |
| FutureFallback<Integer> fallback = mocksControl.createMock(FutureFallback.class); |
| RuntimeException raisedException = new RuntimeException(); |
| if (!wrapInFuture) { |
| // Exception is thrown in the body of the "fallback" method. |
| expect(fallback.create(raisedException)).andThrow(expectedException); |
| } else { |
| // Exception is wrapped in a future and returned. |
| expect(fallback.create(raisedException)).andReturn( |
| Futures.<Integer>immediateFailedFuture(expectedException)); |
| } |
| |
| ListenableFuture<Integer> failingFuture = Futures.immediateFailedFuture(raisedException); |
| |
| mocksControl.replay(); |
| ListenableFuture<Integer> faultToleranteFuture = Futures.withFallback(failingFuture, fallback); |
| try { |
| faultToleranteFuture.get(); |
| fail("An Exception should have been thrown!"); |
| } catch (ExecutionException ee) { |
| assertSame(expectedException, ee.getCause()); |
| } |
| mocksControl.verify(); |
| } |
| |
| public void testWithFallback_fallbackNotReady() throws Exception { |
| ListenableFuture<Integer> primary = immediateFailedFuture(new Exception()); |
| final SettableFuture<Integer> secondary = SettableFuture.create(); |
| FutureFallback<Integer> fallback = new FutureFallback<Integer>() { |
| @Override |
| public ListenableFuture<Integer> create(Throwable t) { |
| return secondary; |
| } |
| }; |
| ListenableFuture<Integer> derived = Futures.withFallback(primary, fallback); |
| secondary.set(1); |
| assertEquals(1, (int) derived.get()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void testWithFallback_resultInterruptedBeforeFallback() throws Exception { |
| SettableFuture<Integer> primary = SettableFuture.create(); |
| FutureFallback<Integer> fallback = mocksControl.createMock(FutureFallback.class); |
| |
| mocksControl.replay(); |
| ListenableFuture<Integer> derived = Futures.withFallback(primary, fallback); |
| derived.cancel(true); |
| assertTrue(primary.isCancelled()); |
| assertTrue(primary.wasInterrupted()); |
| mocksControl.verify(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void testWithFallback_resultCancelledBeforeFallback() throws Exception { |
| SettableFuture<Integer> primary = SettableFuture.create(); |
| FutureFallback<Integer> fallback = mocksControl.createMock(FutureFallback.class); |
| |
| mocksControl.replay(); |
| ListenableFuture<Integer> derived = Futures.withFallback(primary, fallback); |
| derived.cancel(false); |
| assertTrue(primary.isCancelled()); |
| assertFalse(primary.wasInterrupted()); |
| mocksControl.verify(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void testWithFallback_resultCancelledAfterFallback() throws Exception { |
| SettableFuture<Integer> secondary = SettableFuture.create(); |
| FutureFallback<Integer> fallback = mocksControl.createMock(FutureFallback.class); |
| RuntimeException raisedException = new RuntimeException(); |
| expect(fallback.create(raisedException)).andReturn(secondary); |
| ListenableFuture<Integer> failingFuture = Futures.immediateFailedFuture(raisedException); |
| |
| mocksControl.replay(); |
| ListenableFuture<Integer> derived = Futures.withFallback(failingFuture, fallback); |
| derived.cancel(false); |
| assertTrue(secondary.isCancelled()); |
| assertFalse(secondary.wasInterrupted()); |
| mocksControl.verify(); |
| } |
| |
| public void testTransform_genericsWildcard_AsyncFunction() throws Exception { |
| ListenableFuture<?> nullFuture = Futures.immediateFuture(null); |
| ListenableFuture<?> chainedFuture = |
| Futures.transform(nullFuture, constantAsyncFunction(nullFuture)); |
| assertNull(chainedFuture.get()); |
| } |
| |
| private static <I, O> AsyncFunction<I, O> constantAsyncFunction( |
| final ListenableFuture<O> output) { |
| return new AsyncFunction<I, O>() { |
| @Override |
| public ListenableFuture<O> apply(I input) { |
| return output; |
| } |
| }; |
| } |
| |
| public void testTransform_genericsHierarchy_AsyncFunction() throws Exception { |
| ListenableFuture<FooChild> future = Futures.immediateFuture(null); |
| final BarChild barChild = new BarChild(); |
| AsyncFunction<Foo, BarChild> function = |
| new AsyncFunction<Foo, BarChild>() { |
| @Override public AbstractFuture<BarChild> apply(Foo unused) { |
| AbstractFuture<BarChild> future = new AbstractFuture<BarChild>() {}; |
| future.set(barChild); |
| return future; |
| } |
| }; |
| Bar bar = Futures.transform(future, function).get(); |
| assertSame(barChild, bar); |
| } |
| |
| public void testTransform_asyncFunction_timeout() |
| throws InterruptedException, ExecutionException { |
| AsyncFunction<String, Integer> function = constantAsyncFunction(Futures.immediateFuture(1)); |
| ListenableFuture<Integer> future = Futures.transform( |
| SettableFuture.<String>create(), function); |
| try { |
| future.get(1, TimeUnit.MILLISECONDS); |
| fail(); |
| } catch (TimeoutException expected) {} |
| } |
| |
| public void testTransform_asyncFunction_error() throws InterruptedException { |
| final Error error = new Error("deliberate"); |
| AsyncFunction<String, Integer> function = new AsyncFunction<String, Integer>() { |
| @Override public ListenableFuture<Integer> apply(String input) { |
| throw error; |
| } |
| }; |
| SettableFuture<String> inputFuture = SettableFuture.create(); |
| ListenableFuture<Integer> outputFuture = Futures.transform(inputFuture, function); |
| inputFuture.set("value"); |
| try { |
| outputFuture.get(); |
| fail("should have thrown error"); |
| } catch (ExecutionException e) { |
| assertSame(error, e.getCause()); |
| } |
| } |
| |
| public void testTransform_asyncFunction_cancelledWhileApplyingFunction() |
| throws InterruptedException, ExecutionException { |
| final CountDownLatch inFunction = new CountDownLatch(1); |
| final CountDownLatch functionDone = new CountDownLatch(1); |
| final SettableFuture<Integer> resultFuture = SettableFuture.create(); |
| AsyncFunction<String, Integer> function = new AsyncFunction<String, Integer>() { |
| @Override public ListenableFuture<Integer> apply(String input) throws Exception { |
| inFunction.countDown(); |
| functionDone.await(); |
| return resultFuture; |
| } |
| }; |
| SettableFuture<String> inputFuture = SettableFuture.create(); |
| ListenableFuture<Integer> future = Futures.transform( |
| inputFuture, function, Executors.newSingleThreadExecutor()); |
| inputFuture.set("value"); |
| inFunction.await(); |
| future.cancel(false); |
| functionDone.countDown(); |
| try { |
| future.get(); |
| fail(); |
| } catch (CancellationException expected) {} |
| try { |
| resultFuture.get(); |
| fail(); |
| } catch (CancellationException expected) {} |
| } |
| |
| public void testDereference_genericsWildcard() throws Exception { |
| ListenableFuture<?> inner = Futures.immediateFuture(null); |
| ListenableFuture<ListenableFuture<?>> outer = |
| Futures.<ListenableFuture<?>>immediateFuture(inner); |
| ListenableFuture<?> dereferenced = Futures.dereference(outer); |
| assertNull(dereferenced.get()); |
| } |
| |
| public void testDereference_genericsHierarchy() throws Exception { |
| FooChild fooChild = new FooChild(); |
| ListenableFuture<FooChild> inner = Futures.immediateFuture(fooChild); |
| ListenableFuture<ListenableFuture<FooChild>> outer = Futures.immediateFuture(inner); |
| ListenableFuture<Foo> dereferenced = Futures.<Foo>dereference(outer); |
| assertSame(fooChild, dereferenced.get()); |
| } |
| |
| public void testDereference_resultCancelsOuter() throws Exception { |
| ListenableFuture<ListenableFuture<Foo>> outer = SettableFuture.create(); |
| ListenableFuture<Foo> dereferenced = Futures.dereference(outer); |
| dereferenced.cancel(true); |
| assertTrue(outer.isCancelled()); |
| } |
| |
| public void testDereference_resultCancelsInner() throws Exception { |
| ListenableFuture<Foo> inner = SettableFuture.create(); |
| ListenableFuture<ListenableFuture<Foo>> outer = Futures.immediateFuture(inner); |
| ListenableFuture<Foo> dereferenced = Futures.dereference(outer); |
| dereferenced.cancel(true); |
| assertTrue(inner.isCancelled()); |
| } |
| |
| public void testDereference_outerCancelsResult() throws Exception { |
| ListenableFuture<ListenableFuture<Foo>> outer = SettableFuture.create(); |
| ListenableFuture<Foo> dereferenced = Futures.dereference(outer); |
| outer.cancel(true); |
| assertTrue(dereferenced.isCancelled()); |
| } |
| |
| public void testDereference_innerCancelsResult() throws Exception { |
| ListenableFuture<Foo> inner = SettableFuture.create(); |
| ListenableFuture<ListenableFuture<Foo>> outer = Futures.immediateFuture(inner); |
| ListenableFuture<Foo> dereferenced = Futures.dereference(outer); |
| inner.cancel(true); |
| assertTrue(dereferenced.isCancelled()); |
| } |
| |
| /** |
| * Runnable which can be called a single time, and only after |
| * {@link #expectCall} is called. |
| */ |
| // TODO(cpovirk): top-level class? |
| static class SingleCallListener implements Runnable { |
| private boolean expectCall = false; |
| private final CountDownLatch calledCountDown = |
| new CountDownLatch(1); |
| |
| @Override public void run() { |
| assertTrue("Listener called before it was expected", expectCall); |
| assertFalse("Listener called more than once", wasCalled()); |
| calledCountDown.countDown(); |
| } |
| |
| public void expectCall() { |
| assertFalse("expectCall is already true", expectCall); |
| expectCall = true; |
| } |
| |
| public boolean wasCalled() { |
| return calledCountDown.getCount() == 0; |
| } |
| |
| public void waitForCall() throws InterruptedException { |
| assertTrue("expectCall is false", expectCall); |
| calledCountDown.await(); |
| } |
| } |
| |
| public void testAllAsList() throws Exception { |
| // Create input and output |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| SettableFuture<String> future3 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.allAsList(future1, future2, future3); |
| |
| // Attach a listener |
| SingleCallListener listener = new SingleCallListener(); |
| compound.addListener(listener, directExecutor()); |
| |
| // Satisfy each input and check the output |
| assertFalse(compound.isDone()); |
| future1.set(DATA1); |
| assertFalse(compound.isDone()); |
| future2.set(DATA2); |
| assertFalse(compound.isDone()); |
| listener.expectCall(); |
| future3.set(DATA3); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(DATA1, DATA2, DATA3).inOrder(); |
| } |
| |
| public void testAllAsList_emptyList() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| listener.expectCall(); |
| List<ListenableFuture<String>> futures = ImmutableList.of(); |
| ListenableFuture<List<String>> compound = Futures.allAsList(futures); |
| compound.addListener(listener, directExecutor()); |
| assertTrue(compound.isDone()); |
| assertTrue(compound.get().isEmpty()); |
| assertTrue(listener.wasCalled()); |
| } |
| |
| public void testAllAsList_emptyArray() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| listener.expectCall(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = Futures.allAsList(); |
| compound.addListener(listener, directExecutor()); |
| assertTrue(compound.isDone()); |
| assertTrue(compound.get().isEmpty()); |
| assertTrue(listener.wasCalled()); |
| } |
| |
| public void testAllAsList_failure() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.allAsList(future1, future2); |
| compound.addListener(listener, directExecutor()); |
| |
| listener.expectCall(); |
| Throwable exception = new Throwable("failed1"); |
| future1.setException(exception); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| future2.set("result2"); |
| |
| try { |
| compound.get(); |
| fail("Expected exception not thrown"); |
| } catch (ExecutionException e) { |
| assertSame(exception, e.getCause()); |
| } |
| } |
| |
| public void testAllAsList_singleFailure() throws Exception { |
| Throwable exception = new Throwable("failed"); |
| ListenableFuture<String> future = Futures.immediateFailedFuture(exception); |
| ListenableFuture<List<String>> compound = Futures.allAsList(ImmutableList.of(future)); |
| |
| try { |
| compound.get(); |
| fail("Expected exception not thrown"); |
| } catch (ExecutionException e) { |
| assertSame(exception, e.getCause()); |
| } |
| } |
| |
| public void testAllAsList_immediateFailure() throws Exception { |
| Throwable exception = new Throwable("failed"); |
| ListenableFuture<String> future1 = Futures.immediateFailedFuture(exception); |
| ListenableFuture<String> future2 = Futures.immediateFuture("results"); |
| ListenableFuture<List<String>> compound = Futures.allAsList(ImmutableList.of(future1, future2)); |
| |
| try { |
| compound.get(); |
| fail("Expected exception not thrown"); |
| } catch (ExecutionException e) { |
| assertSame(exception, e.getCause()); |
| } |
| } |
| |
| public void testAllAsList_error() throws Exception { |
| Error error = new Error("deliberate"); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| ListenableFuture<String> future2 = Futures.immediateFuture("results"); |
| ListenableFuture<List<String>> compound = Futures.allAsList(ImmutableList.of(future1, future2)); |
| |
| future1.setException(error); |
| try { |
| compound.get(); |
| fail("Expected error not set in compound future."); |
| } catch (ExecutionException ee) { |
| assertSame(error, ee.getCause()); |
| } |
| } |
| |
| public void testAllAsList_cancelled() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.allAsList(future1, future2); |
| compound.addListener(listener, directExecutor()); |
| |
| listener.expectCall(); |
| future1.cancel(true); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| future2.setException(new Throwable("failed2")); |
| |
| try { |
| compound.get(); |
| fail("Expected exception not thrown"); |
| } catch (CancellationException e) { |
| // Expected |
| } |
| } |
| |
| public void testAllAsList_resultCancelled() throws Exception { |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.allAsList(future1, future2); |
| |
| future2.set(DATA2); |
| assertFalse(compound.isDone()); |
| assertTrue(compound.cancel(false)); |
| assertTrue(compound.isCancelled()); |
| assertTrue(future1.isCancelled()); |
| assertFalse(future1.wasInterrupted()); |
| } |
| |
| public void testAllAsList_resultInterrupted() throws Exception { |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.allAsList(future1, future2); |
| |
| future2.set(DATA2); |
| assertFalse(compound.isDone()); |
| assertTrue(compound.cancel(true)); |
| assertTrue(compound.isCancelled()); |
| assertTrue(future1.isCancelled()); |
| assertTrue(future1.wasInterrupted()); |
| } |
| |
| /** |
| * Test the case where the futures are fulfilled prior to |
| * constructing the ListFuture. There was a bug where the |
| * loop that connects a Listener to each of the futures would die |
| * on the last loop-check as done() on ListFuture nulled out the |
| * variable being looped over (the list of futures). |
| */ |
| public void testAllAsList_doneFutures() throws Exception { |
| // Create input and output |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| SettableFuture<String> future3 = SettableFuture.create(); |
| |
| // Satisfy each input prior to creating compound and check the output |
| future1.set(DATA1); |
| future2.set(DATA2); |
| future3.set(DATA3); |
| |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.allAsList(future1, future2, future3); |
| |
| // Attach a listener |
| SingleCallListener listener = new SingleCallListener(); |
| listener.expectCall(); |
| compound.addListener(listener, directExecutor()); |
| |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(DATA1, DATA2, DATA3).inOrder(); |
| } |
| |
| /** |
| * A single non-error failure is not logged because it is reported via the output future. |
| */ |
| @SuppressWarnings("unchecked") |
| public void testAllAsList_logging_exception() throws Exception { |
| try { |
| Futures.allAsList(immediateFailedFuture(new MyException())).get(); |
| fail(); |
| } catch (ExecutionException e) { |
| assertTrue(e.getCause() instanceof MyException); |
| assertEquals("Nothing should be logged", 0, |
| combinedFutureLogHandler.getStoredLogRecords().size()); |
| } |
| } |
| |
| /** |
| * Ensure that errors are always logged. |
| */ |
| @SuppressWarnings("unchecked") |
| public void testAllAsList_logging_error() throws Exception { |
| try { |
| Futures.allAsList(immediateFailedFuture(new MyError())).get(); |
| fail(); |
| } catch (ExecutionException e) { |
| assertTrue(e.getCause() instanceof MyError); |
| List<LogRecord> logged = combinedFutureLogHandler.getStoredLogRecords(); |
| assertEquals(1, logged.size()); // errors are always logged |
| assertTrue(logged.get(0).getThrown() instanceof MyError); |
| } |
| } |
| |
| /** |
| * All as list will log extra exceptions that occur after failure. |
| */ |
| @SuppressWarnings("unchecked") |
| public void testAllAsList_logging_multipleExceptions() throws Exception { |
| try { |
| Futures.allAsList(immediateFailedFuture(new MyException()), |
| immediateFailedFuture(new MyException())).get(); |
| fail(); |
| } catch (ExecutionException e) { |
| assertTrue(e.getCause() instanceof MyException); |
| List<LogRecord> logged = combinedFutureLogHandler.getStoredLogRecords(); |
| assertEquals(1, logged.size()); // the second failure is logged |
| assertTrue(logged.get(0).getThrown() instanceof MyException); |
| } |
| } |
| |
| private static String createCombinedResult(Integer i, Boolean b) { |
| return "-" + i + "-" + b; |
| } |
| |
| /* |
| * TODO(cpovirk): maybe pass around TestFuture instances instead of |
| * ListenableFuture instances |
| */ |
| /** |
| * A future in {@link TestFutureBatch} that also has a name for debugging |
| * purposes and a {@code finisher}, a task that will complete the future in |
| * some fashion when it is called, allowing for testing both before and after |
| * the completion of the future. |
| */ |
| private static final class TestFuture { |
| final ListenableFuture<String> future; |
| final String name; |
| final Runnable finisher; |
| |
| TestFuture( |
| ListenableFuture<String> future, String name, Runnable finisher) { |
| this.future = future; |
| this.name = name; |
| this.finisher = finisher; |
| } |
| } |
| |
| /** |
| * A collection of several futures, covering cancellation, success, and |
| * failure (both {@link ExecutionException} and {@link RuntimeException}), |
| * both immediate and delayed. We use each possible pair of these futures in |
| * {@link FuturesTest#runExtensiveMergerTest}. |
| * |
| * <p>Each test requires a new {@link TestFutureBatch} because we need new |
| * delayed futures each time, as the old delayed futures were completed as |
| * part of the old test. |
| */ |
| private static final class TestFutureBatch { |
| final ListenableFuture<String> doneSuccess = immediateFuture("a"); |
| final ListenableFuture<String> doneFailed = |
| immediateFailedFuture(new Exception()); |
| final SettableFuture<String> doneCancelled = SettableFuture.create(); |
| { |
| doneCancelled.cancel(true); |
| } |
| |
| final ListenableFuture<String> doneRuntimeException = |
| new ForwardingListenableFuture<String>() { |
| final ListenableFuture<String> delegate = |
| immediateFuture("Should never be seen"); |
| |
| @Override |
| protected ListenableFuture<String> delegate() { |
| return delegate; |
| } |
| |
| @Override |
| public String get() { |
| throw new RuntimeException(); |
| } |
| |
| @Override |
| public String get(long timeout, TimeUnit unit) { |
| throw new RuntimeException(); |
| } |
| }; |
| |
| final SettableFuture<String> delayedSuccess = SettableFuture.create(); |
| final SettableFuture<String> delayedFailed = SettableFuture.create(); |
| final SettableFuture<String> delayedCancelled = SettableFuture.create(); |
| |
| final SettableFuture<String> delegateForDelayedRuntimeException = |
| SettableFuture.create(); |
| final ListenableFuture<String> delayedRuntimeException = |
| new ForwardingListenableFuture<String>() { |
| @Override |
| protected ListenableFuture<String> delegate() { |
| return delegateForDelayedRuntimeException; |
| } |
| |
| @Override |
| public String get() throws ExecutionException, InterruptedException { |
| delegateForDelayedRuntimeException.get(); |
| throw new RuntimeException(); |
| } |
| |
| @Override |
| public String get(long timeout, TimeUnit unit) throws |
| ExecutionException, InterruptedException, TimeoutException { |
| delegateForDelayedRuntimeException.get(timeout, unit); |
| throw new RuntimeException(); |
| } |
| }; |
| |
| final Runnable doNothing = new Runnable() { |
| @Override |
| public void run() { |
| } |
| }; |
| final Runnable finishSuccess = new Runnable() { |
| @Override |
| public void run() { |
| delayedSuccess.set("b"); |
| } |
| }; |
| final Runnable finishFailure = new Runnable() { |
| @Override |
| public void run() { |
| delayedFailed.setException(new Exception()); |
| } |
| }; |
| final Runnable finishCancelled = new Runnable() { |
| @Override |
| public void run() { |
| delayedCancelled.cancel(true); |
| } |
| }; |
| final Runnable finishRuntimeException = new Runnable() { |
| @Override |
| public void run() { |
| delegateForDelayedRuntimeException.set("Should never be seen"); |
| } |
| }; |
| |
| /** |
| * All the futures, together with human-readable names for use by |
| * {@link #smartToString}. |
| */ |
| final ImmutableList<TestFuture> allFutures = |
| ImmutableList.of(new TestFuture(doneSuccess, "doneSuccess", doNothing), |
| new TestFuture(doneFailed, "doneFailed", doNothing), |
| new TestFuture(doneCancelled, "doneCancelled", doNothing), |
| new TestFuture( |
| doneRuntimeException, "doneRuntimeException", doNothing), |
| new TestFuture(delayedSuccess, "delayedSuccess", finishSuccess), |
| new TestFuture(delayedFailed, "delayedFailed", finishFailure), |
| new TestFuture( |
| delayedCancelled, "delayedCancelled", finishCancelled), |
| new TestFuture(delayedRuntimeException, "delayedRuntimeException", |
| finishRuntimeException)); |
| |
| final Function<ListenableFuture<String>, String> nameGetter = |
| new Function<ListenableFuture<String>, String>() { |
| @Override |
| public String apply(ListenableFuture<String> input) { |
| for (TestFuture future : allFutures) { |
| if (future.future == input) { |
| return future.name; |
| } |
| } |
| throw new IllegalArgumentException(input.toString()); |
| } |
| }; |
| |
| static boolean intersect(Set<?> a, Set<?> b) { |
| return !Sets.intersection(a, b).isEmpty(); |
| } |
| |
| /** |
| * Like {@code inputs.toString()}, but with the nonsense {@code toString} |
| * representations replaced with the name of each future from |
| * {@link #allFutures}. |
| */ |
| String smartToString(ImmutableSet<ListenableFuture<String>> inputs) { |
| Iterable<String> inputNames = Iterables.transform(inputs, nameGetter); |
| return Joiner.on(", ").join(inputNames); |
| } |
| |
| void smartAssertTrue(ImmutableSet<ListenableFuture<String>> inputs, |
| Exception cause, boolean expression) { |
| if (!expression) { |
| failWithCause(cause, smartToString(inputs)); |
| } |
| } |
| |
| boolean hasDelayed(ListenableFuture<String> a, ListenableFuture<String> b) { |
| ImmutableSet<ListenableFuture<String>> inputs = ImmutableSet.of(a, b); |
| return intersect(inputs, ImmutableSet.of( |
| delayedSuccess, delayedFailed, delayedCancelled, |
| delayedRuntimeException)); |
| } |
| |
| void assertHasDelayed( |
| ListenableFuture<String> a, ListenableFuture<String> b, Exception e) { |
| ImmutableSet<ListenableFuture<String>> inputs = ImmutableSet.of(a, b); |
| smartAssertTrue(inputs, e, hasDelayed(a, b)); |
| } |
| |
| void assertHasFailure( |
| ListenableFuture<String> a, ListenableFuture<String> b, Exception e) { |
| ImmutableSet<ListenableFuture<String>> inputs = ImmutableSet.of(a, b); |
| smartAssertTrue(inputs, e, intersect(inputs, ImmutableSet.of(doneFailed, |
| doneRuntimeException, delayedFailed, delayedRuntimeException))); |
| } |
| |
| void assertHasCancel( |
| ListenableFuture<String> a, ListenableFuture<String> b, Exception e) { |
| ImmutableSet<ListenableFuture<String>> inputs = ImmutableSet.of(a, b); |
| smartAssertTrue(inputs, e, |
| intersect(inputs, ImmutableSet.of(doneCancelled, delayedCancelled))); |
| } |
| |
| void assertHasImmediateFailure( |
| ListenableFuture<String> a, ListenableFuture<String> b, Exception e) { |
| ImmutableSet<ListenableFuture<String>> inputs = ImmutableSet.of(a, b); |
| smartAssertTrue(inputs, e, intersect( |
| inputs, ImmutableSet.of(doneFailed, doneRuntimeException))); |
| } |
| |
| void assertHasImmediateCancel( |
| ListenableFuture<String> a, ListenableFuture<String> b, Exception e) { |
| ImmutableSet<ListenableFuture<String>> inputs = ImmutableSet.of(a, b); |
| smartAssertTrue(inputs, e, |
| intersect(inputs, ImmutableSet.of(doneCancelled))); |
| } |
| } |
| |
| /** |
| * {@link Futures#allAsList(Iterable)} or |
| * {@link Futures#successfulAsList(Iterable)}, hidden behind a common |
| * interface for testing. |
| */ |
| private interface Merger { |
| ListenableFuture<List<String>> merged( |
| ListenableFuture<String> a, ListenableFuture<String> b); |
| |
| Merger allMerger = new Merger() { |
| @Override |
| public ListenableFuture<List<String>> merged( |
| ListenableFuture<String> a, ListenableFuture<String> b) { |
| return allAsList(ImmutableSet.of(a, b)); |
| } |
| }; |
| Merger successMerger = new Merger() { |
| @Override |
| public ListenableFuture<List<String>> merged( |
| ListenableFuture<String> a, ListenableFuture<String> b) { |
| return successfulAsList(ImmutableSet.of(a, b)); |
| } |
| }; |
| } |
| |
| /** |
| * Very rough equivalent of a timed get, produced by calling the no-arg get |
| * method in another thread and waiting a short time for it. |
| * |
| * <p>We need this to test the behavior of no-arg get methods without hanging |
| * the main test thread forever in the case of failure. |
| */ |
| private static <V> V pseudoTimedGet( |
| final Future<V> input, long timeout, TimeUnit unit) |
| throws InterruptedException, ExecutionException, TimeoutException { |
| ExecutorService executor = newSingleThreadExecutor(); |
| Future<V> waiter = executor.submit(new Callable<V>() { |
| @Override |
| public V call() throws Exception { |
| return input.get(); |
| } |
| }); |
| |
| try { |
| return waiter.get(timeout, unit); |
| } catch (ExecutionException e) { |
| propagateIfInstanceOf(e.getCause(), ExecutionException.class); |
| propagateIfInstanceOf(e.getCause(), CancellationException.class); |
| AssertionFailedError error = |
| new AssertionFailedError("Unexpected exception"); |
| error.initCause(e); |
| throw error; |
| } finally { |
| executor.shutdownNow(); |
| assertTrue(executor.awaitTermination(10, SECONDS)); |
| } |
| } |
| |
| /** |
| * For each possible pair of futures from {@link TestFutureBatch}, for each |
| * possible completion order of those futures, test that various get calls |
| * (timed before future completion, untimed before future completion, and |
| * untimed after future completion) return or throw the proper values. |
| */ |
| private static void runExtensiveMergerTest(Merger merger) |
| throws InterruptedException { |
| int inputCount = new TestFutureBatch().allFutures.size(); |
| |
| for (int i = 0; i < inputCount; i++) { |
| for (int j = 0; j < inputCount; j++) { |
| for (boolean iBeforeJ : new boolean[] { true, false }) { |
| TestFutureBatch inputs = new TestFutureBatch(); |
| ListenableFuture<String> iFuture = inputs.allFutures.get(i).future; |
| ListenableFuture<String> jFuture = inputs.allFutures.get(j).future; |
| ListenableFuture<List<String>> future = |
| merger.merged(iFuture, jFuture); |
| |
| // Test timed get before we've completed any delayed futures. |
| try { |
| List<String> result = future.get(0, MILLISECONDS); |
| assertTrue("Got " + result, |
| Arrays.asList("a", null).containsAll(result)); |
| } catch (CancellationException e) { |
| assertTrue(merger == Merger.allMerger); |
| inputs.assertHasImmediateCancel(iFuture, jFuture, e); |
| } catch (ExecutionException e) { |
| assertTrue(merger == Merger.allMerger); |
| inputs.assertHasImmediateFailure(iFuture, jFuture, e); |
| } catch (TimeoutException e) { |
| inputs.assertHasDelayed(iFuture, jFuture, e); |
| } |
| |
| // Same tests with pseudoTimedGet. |
| try { |
| List<String> result = conditionalPseudoTimedGet( |
| inputs, iFuture, jFuture, future, 20, MILLISECONDS); |
| assertTrue("Got " + result, |
| Arrays.asList("a", null).containsAll(result)); |
| } catch (CancellationException e) { |
| assertTrue(merger == Merger.allMerger); |
| inputs.assertHasImmediateCancel(iFuture, jFuture, e); |
| } catch (ExecutionException e) { |
| assertTrue(merger == Merger.allMerger); |
| inputs.assertHasImmediateFailure(iFuture, jFuture, e); |
| } catch (TimeoutException e) { |
| inputs.assertHasDelayed(iFuture, jFuture, e); |
| } |
| |
| // Finish the two futures in the currently specified order: |
| inputs.allFutures.get(iBeforeJ ? i : j).finisher.run(); |
| inputs.allFutures.get(iBeforeJ ? j : i).finisher.run(); |
| |
| // Test untimed get now that we've completed any delayed futures. |
| try { |
| List<String> result = future.get(); |
| assertTrue("Got " + result, |
| Arrays.asList("a", "b", null).containsAll(result)); |
| } catch (CancellationException e) { |
| assertTrue(merger == Merger.allMerger); |
| inputs.assertHasCancel(iFuture, jFuture, e); |
| } catch (ExecutionException e) { |
| assertTrue(merger == Merger.allMerger); |
| inputs.assertHasFailure(iFuture, jFuture, e); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Call the non-timed {@link Future#get()} in a way that allows us to abort if |
| * it's expected to hang forever. More precisely, if it's expected to return, |
| * we simply call it[*], but if it's expected to hang (because one of the |
| * input futures that we know makes it up isn't done yet), then we call it in |
| * a separate thread (using pseudoTimedGet). The result is that we wait as |
| * long as necessary when the method is expected to return (at the cost of |
| * hanging forever if there is a bug in the class under test) but that we time |
| * out fairly promptly when the method is expected to hang (possibly too |
| * quickly, but too-quick failures should be very unlikely, given that we used |
| * to bail after 20ms during the expected-successful tests, and there we saw a |
| * failure rate of ~1/5000, meaning that the other thread's get() call nearly |
| * always completes within 20ms if it's going to complete at all). |
| * |
| * [*] To avoid hangs, I've disabled the in-thread calls. This makes the test |
| * take (very roughly) 2.5s longer. (2.5s is also the maximum length of time |
| * we will wait for a timed get that is expected to succeed; the fact that the |
| * numbers match is only a coincidence.) See the comment below for how to |
| * restore the fast but hang-y version. |
| */ |
| private static List<String> conditionalPseudoTimedGet( |
| TestFutureBatch inputs, |
| ListenableFuture<String> iFuture, |
| ListenableFuture<String> jFuture, |
| ListenableFuture<List<String>> future, |
| int timeout, |
| TimeUnit unit) |
| throws InterruptedException, ExecutionException, TimeoutException { |
| /* |
| * For faster tests (that may hang indefinitely if the class under test has |
| * a bug!), switch the second branch to call untimed future.get() instead of |
| * pseudoTimedGet. |
| */ |
| return (inputs.hasDelayed(iFuture, jFuture)) |
| ? pseudoTimedGet(future, timeout, unit) |
| : pseudoTimedGet(future, 2500, MILLISECONDS); |
| } |
| |
| public void testAllAsList_extensive() throws InterruptedException { |
| runExtensiveMergerTest(Merger.allMerger); |
| } |
| |
| public void testSuccessfulAsList_extensive() throws InterruptedException { |
| runExtensiveMergerTest(Merger.successMerger); |
| } |
| |
| public void testSuccessfulAsList() throws Exception { |
| // Create input and output |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| SettableFuture<String> future3 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2, future3); |
| |
| // Attach a listener |
| SingleCallListener listener = new SingleCallListener(); |
| compound.addListener(listener, directExecutor()); |
| |
| // Satisfy each input and check the output |
| assertFalse(compound.isDone()); |
| future1.set(DATA1); |
| assertFalse(compound.isDone()); |
| future2.set(DATA2); |
| assertFalse(compound.isDone()); |
| listener.expectCall(); |
| future3.set(DATA3); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(DATA1, DATA2, DATA3).inOrder(); |
| } |
| |
| public void testSuccessfulAsList_emptyList() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| listener.expectCall(); |
| List<ListenableFuture<String>> futures = ImmutableList.of(); |
| ListenableFuture<List<String>> compound = Futures.successfulAsList(futures); |
| compound.addListener(listener, directExecutor()); |
| assertTrue(compound.isDone()); |
| assertTrue(compound.get().isEmpty()); |
| assertTrue(listener.wasCalled()); |
| } |
| |
| public void testSuccessfulAsList_emptyArray() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| listener.expectCall(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = Futures.successfulAsList(); |
| compound.addListener(listener, directExecutor()); |
| assertTrue(compound.isDone()); |
| assertTrue(compound.get().isEmpty()); |
| assertTrue(listener.wasCalled()); |
| } |
| |
| public void testSuccessfulAsList_partialFailure() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2); |
| compound.addListener(listener, directExecutor()); |
| |
| assertFalse(compound.isDone()); |
| future1.setException(new Throwable("failed1")); |
| assertFalse(compound.isDone()); |
| listener.expectCall(); |
| future2.set(DATA2); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(null, DATA2).inOrder(); |
| } |
| |
| public void testSuccessfulAsList_totalFailure() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2); |
| compound.addListener(listener, directExecutor()); |
| |
| assertFalse(compound.isDone()); |
| future1.setException(new Throwable("failed1")); |
| assertFalse(compound.isDone()); |
| listener.expectCall(); |
| future2.setException(new Throwable("failed2")); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(null, null).inOrder(); |
| } |
| |
| public void testSuccessfulAsList_cancelled() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2); |
| compound.addListener(listener, directExecutor()); |
| |
| assertFalse(compound.isDone()); |
| future1.cancel(true); |
| assertFalse(compound.isDone()); |
| listener.expectCall(); |
| future2.set(DATA2); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(null, DATA2).inOrder(); |
| } |
| |
| public void testSuccessfulAsList_resultCancelled() throws Exception { |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2); |
| |
| future2.set(DATA2); |
| assertFalse(compound.isDone()); |
| assertTrue(compound.cancel(false)); |
| assertTrue(compound.isCancelled()); |
| assertTrue(future1.isCancelled()); |
| assertFalse(future1.wasInterrupted()); |
| } |
| |
| public void testSuccessfulAsList_resultCancelledRacingInputDone() |
| throws Exception { |
| /* |
| * The IllegalStateException that we're testing for is caught by |
| * ExecutionList and logged rather than allowed to propagate. We need to |
| * turn that back into a failure. |
| */ |
| Handler throwingHandler = new Handler() { |
| @Override public void publish(@Nullable LogRecord record) { |
| AssertionFailedError error = new AssertionFailedError(); |
| error.initCause(record.getThrown()); |
| throw error; |
| } |
| |
| @Override public void flush() {} |
| |
| @Override public void close() {} |
| }; |
| |
| ExecutionList.log.addHandler(throwingHandler); |
| try { |
| doTestSuccessfulAsList_resultCancelledRacingInputDone(); |
| } finally { |
| ExecutionList.log.removeHandler(throwingHandler); |
| } |
| } |
| |
| private static void doTestSuccessfulAsList_resultCancelledRacingInputDone() |
| throws Exception { |
| // Simple (combined.cancel -> input.cancel -> setOneValue): |
| Futures.successfulAsList(ImmutableList.of(SettableFuture.create())) |
| .cancel(true); |
| |
| /* |
| * Complex (combined.cancel -> input.cancel -> other.set -> setOneValue), |
| * to show that this isn't just about problems with the input future we just |
| * cancelled: |
| */ |
| final SettableFuture<String> future1 = SettableFuture.create(); |
| final SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2); |
| |
| future1.addListener(new Runnable() { |
| @Override public void run() { |
| assertTrue(future1.isCancelled()); |
| /* |
| * This test relies on behavior that's unspecified but currently |
| * guaranteed by the implementation: Cancellation of inputs is |
| * performed in the order they were provided to the constructor. Verify |
| * that as a sanity check: |
| */ |
| assertFalse(future2.isCancelled()); |
| // Now attempt to trigger the exception: |
| future2.set(DATA2); |
| } |
| }, directExecutor()); |
| assertTrue(compound.cancel(false)); |
| assertTrue(compound.isCancelled()); |
| assertTrue(future1.isCancelled()); |
| assertFalse(future2.isCancelled()); |
| |
| try { |
| compound.get(); |
| fail("Expected exception not thrown"); |
| } catch (CancellationException e) { |
| // Expected |
| } |
| } |
| |
| public void testSuccessfulAsList_resultInterrupted() throws Exception { |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2); |
| |
| future2.set(DATA2); |
| assertFalse(compound.isDone()); |
| assertTrue(compound.cancel(true)); |
| assertTrue(compound.isCancelled()); |
| assertTrue(future1.isCancelled()); |
| assertTrue(future1.wasInterrupted()); |
| } |
| |
| public void testSuccessfulAsList_mixed() throws Exception { |
| SingleCallListener listener = new SingleCallListener(); |
| SettableFuture<String> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| SettableFuture<String> future3 = SettableFuture.create(); |
| @SuppressWarnings("unchecked") // array is never modified |
| ListenableFuture<List<String>> compound = |
| Futures.successfulAsList(future1, future2, future3); |
| compound.addListener(listener, directExecutor()); |
| |
| // First is cancelled, second fails, third succeeds |
| assertFalse(compound.isDone()); |
| future1.cancel(true); |
| assertFalse(compound.isDone()); |
| future2.setException(new Throwable("failed2")); |
| assertFalse(compound.isDone()); |
| listener.expectCall(); |
| future3.set(DATA3); |
| assertTrue(compound.isDone()); |
| assertTrue(listener.wasCalled()); |
| |
| List<String> results = compound.get(); |
| assertThat(results).has().exactly(null, null, DATA3).inOrder(); |
| } |
| |
| /** Non-Error exceptions are never logged. */ |
| @SuppressWarnings("unchecked") |
| public void testSuccessfulAsList_logging_exception() throws Exception { |
| assertEquals(Lists.newArrayList((Object) null), |
| Futures.successfulAsList( |
| immediateFailedFuture(new MyException())).get()); |
| assertEquals("Nothing should be logged", 0, |
| combinedFutureLogHandler.getStoredLogRecords().size()); |
| |
| // Not even if there are a bunch of failures. |
| assertEquals(Lists.newArrayList(null, null, null), |
| Futures.successfulAsList( |
| immediateFailedFuture(new MyException()), |
| immediateFailedFuture(new MyException()), |
| immediateFailedFuture(new MyException())).get()); |
| assertEquals("Nothing should be logged", 0, |
| combinedFutureLogHandler.getStoredLogRecords().size()); |
| } |
| |
| /** |
| * Ensure that errors are always logged. |
| */ |
| @SuppressWarnings("unchecked") |
| public void testSuccessfulAsList_logging_error() throws Exception { |
| assertEquals(Lists.newArrayList((Object) null), |
| Futures.successfulAsList( |
| immediateFailedFuture(new MyError())).get()); |
| List<LogRecord> logged = combinedFutureLogHandler.getStoredLogRecords(); |
| assertEquals(1, logged.size()); // errors are always logged |
| assertTrue(logged.get(0).getThrown() instanceof MyError); |
| } |
| |
| public void testNonCancellationPropagating_successful() throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| ListenableFuture<Foo> wrapper = Futures.nonCancellationPropagating(input); |
| Foo foo = new Foo(); |
| |
| assertFalse(wrapper.isDone()); |
| input.set(foo); |
| assertTrue(wrapper.isDone()); |
| assertSame(foo, wrapper.get()); |
| } |
| |
| public void testNonCancellationPropagating_failure() throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| ListenableFuture<Foo> wrapper = Futures.nonCancellationPropagating(input); |
| Throwable failure = new Throwable("thrown"); |
| |
| assertFalse(wrapper.isDone()); |
| input.setException(failure); |
| assertTrue(wrapper.isDone()); |
| try { |
| wrapper.get(); |
| fail("Expected ExecutionException"); |
| } catch (ExecutionException e) { |
| assertSame(failure, e.getCause()); |
| } |
| } |
| |
| public void testNonCancellationPropagating_delegateCancelled() throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| ListenableFuture<Foo> wrapper = Futures.nonCancellationPropagating(input); |
| |
| assertFalse(wrapper.isDone()); |
| assertTrue(input.cancel(false)); |
| assertTrue(wrapper.isCancelled()); |
| } |
| |
| public void testNonCancellationPropagating_doesNotPropagate() throws Exception { |
| SettableFuture<Foo> input = SettableFuture.create(); |
| ListenableFuture<Foo> wrapper = Futures.nonCancellationPropagating(input); |
| |
| assertTrue(wrapper.cancel(true)); |
| assertTrue(wrapper.isCancelled()); |
| assertTrue(wrapper.isDone()); |
| assertFalse(input.isCancelled()); |
| assertFalse(input.isDone()); |
| } |
| |
| private static class TestException extends Exception { |
| TestException(@Nullable Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| private static final Function<Exception, TestException> mapper = |
| new Function<Exception, TestException>() { |
| @Override public TestException apply(Exception from) { |
| if (from instanceof ExecutionException) { |
| return new TestException(from.getCause()); |
| } else { |
| assertTrue("got " + from.getClass(), |
| from instanceof InterruptedException |
| || from instanceof CancellationException); |
| return new TestException(from); |
| } |
| } |
| }; |
| |
| public void testMakeChecked_mapsExecutionExceptions() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, mapper); |
| |
| future.setException(new IOException("checked")); |
| |
| assertTrue(checked.isDone()); |
| assertFalse(checked.isCancelled()); |
| |
| try { |
| checked.get(); |
| fail(); |
| } catch (ExecutionException e) { |
| assertTrue(e.getCause() instanceof IOException); |
| } |
| |
| try { |
| checked.get(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (ExecutionException e) { |
| assertTrue(e.getCause() instanceof IOException); |
| } |
| |
| try { |
| checked.checkedGet(); |
| fail(); |
| } catch (TestException e) { |
| assertTrue(e.getCause() instanceof IOException); |
| } |
| |
| try { |
| checked.checkedGet(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (TestException e) { |
| assertTrue(e.getCause() instanceof IOException); |
| } |
| } |
| |
| public void testMakeChecked_mapsInterruption() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, mapper); |
| |
| Thread.currentThread().interrupt(); |
| |
| try { |
| checked.get(); |
| fail(); |
| } catch (InterruptedException e) { |
| // Expected. |
| } |
| |
| Thread.currentThread().interrupt(); |
| |
| try { |
| checked.get(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (InterruptedException e) { |
| // Expected. |
| } |
| |
| Thread.currentThread().interrupt(); |
| |
| try { |
| checked.checkedGet(); |
| fail(); |
| } catch (TestException e) { |
| assertTrue(e.getCause() instanceof InterruptedException); |
| } |
| |
| Thread.currentThread().interrupt(); |
| |
| try { |
| checked.checkedGet(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (TestException e) { |
| assertTrue(e.getCause() instanceof InterruptedException); |
| } |
| } |
| |
| public void testMakeChecked_mapsCancellation() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, mapper); |
| |
| assertTrue(future.cancel(true)); // argument is ignored |
| |
| try { |
| checked.get(); |
| fail(); |
| } catch (CancellationException expected) {} |
| |
| try { |
| checked.get(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (CancellationException expected) {} |
| |
| try { |
| checked.checkedGet(); |
| fail(); |
| } catch (TestException expected) { |
| assertTrue(expected.getCause() instanceof CancellationException); |
| } |
| |
| try { |
| checked.checkedGet(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (TestException expected) { |
| assertTrue(expected.getCause() instanceof CancellationException); |
| } |
| } |
| |
| public void testMakeChecked_propagatesFailedMappers() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, new Function<Exception, TestException>() { |
| @Override public TestException apply(Exception from) { |
| throw new NullPointerException(); |
| } |
| }); |
| |
| future.setException(new Exception("failed")); |
| |
| try { |
| checked.checkedGet(); |
| fail(); |
| } catch (NullPointerException expected) {} |
| |
| try { |
| checked.checkedGet(5, TimeUnit.SECONDS); |
| fail(); |
| } catch (NullPointerException expected) {} |
| } |
| |
| public void testMakeChecked_listenersRunOnceCompleted() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, new Function<Exception, TestException>() { |
| @Override public TestException apply(Exception from) { |
| throw new NullPointerException(); |
| } |
| }); |
| |
| ListenableFutureTester tester = new ListenableFutureTester(checked); |
| tester.setUp(); |
| future.set(DATA1); |
| tester.testCompletedFuture(DATA1); |
| tester.tearDown(); |
| } |
| |
| public void testMakeChecked_listenersRunOnCancel() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, new Function<Exception, TestException>() { |
| @Override public TestException apply(Exception from) { |
| throw new NullPointerException(); |
| } |
| }); |
| |
| ListenableFutureTester tester = new ListenableFutureTester(checked); |
| tester.setUp(); |
| future.cancel(true); // argument is ignored |
| tester.testCancelledFuture(); |
| tester.tearDown(); |
| } |
| |
| public void testMakeChecked_listenersRunOnFailure() throws Exception { |
| SettableFuture<String> future = SettableFuture.create(); |
| |
| CheckedFuture<String, TestException> checked = Futures.makeChecked( |
| future, new Function<Exception, TestException>() { |
| @Override public TestException apply(Exception from) { |
| throw new NullPointerException(); |
| } |
| }); |
| |
| ListenableFutureTester tester = new ListenableFutureTester(checked); |
| tester.setUp(); |
| future.setException(new Exception("failed")); |
| tester.testFailedFuture("failed"); |
| tester.tearDown(); |
| } |
| |
| private interface MapperFunction extends Function<Throwable, Exception> {} |
| |
| private static final class OtherThrowable extends Throwable {} |
| |
| private static final Exception CHECKED_EXCEPTION = new Exception("mymessage"); |
| private static final Future<String> FAILED_FUTURE_CHECKED_EXCEPTION = |
| immediateFailedFuture(CHECKED_EXCEPTION); |
| private static final RuntimeException UNCHECKED_EXCEPTION = |
| new RuntimeException("mymessage"); |
| private static final Future<String> FAILED_FUTURE_UNCHECKED_EXCEPTION = |
| immediateFailedFuture(UNCHECKED_EXCEPTION); |
| private static final RuntimeException RUNTIME_EXCEPTION = |
| new RuntimeException(); |
| private static final OtherThrowable OTHER_THROWABLE = new OtherThrowable(); |
| private static final Future<String> FAILED_FUTURE_OTHER_THROWABLE = |
| immediateFailedFuture(OTHER_THROWABLE); |
| private static final Error ERROR = new Error("mymessage"); |
| private static final Future<String> FAILED_FUTURE_ERROR = |
| immediateFailedFuture(ERROR); |
| private static final Future<String> RUNTIME_EXCEPTION_FUTURE = |
| new SimpleForwardingFuture<String>(FAILED_FUTURE_CHECKED_EXCEPTION) { |
| @Override public String get() { |
| throw RUNTIME_EXCEPTION; |
| } |
| |
| @Override public String get(long timeout, TimeUnit unit) { |
| throw RUNTIME_EXCEPTION; |
| } |
| }; |
| |
| // Boring untimed-get tests: |
| |
| public void testGetUntimed_success() |
| throws TwoArgConstructorException { |
| assertEquals("foo", |
| get(immediateFuture("foo"), TwoArgConstructorException.class)); |
| } |
| |
| public void testGetUntimed_interrupted() { |
| SettableFuture<String> future = SettableFuture.create(); |
| Thread.currentThread().interrupt(); |
| try { |
| get(future, TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertTrue(expected.getCause() instanceof InterruptedException); |
| assertTrue(Thread.currentThread().isInterrupted()); |
| } finally { |
| Thread.interrupted(); |
| } |
| } |
| |
| public void testGetUntimed_cancelled() |
| throws TwoArgConstructorException { |
| SettableFuture<String> future = SettableFuture.create(); |
| future.cancel(true); |
| try { |
| get(future, TwoArgConstructorException.class); |
| fail(); |
| } catch (CancellationException expected) { |
| } |
| } |
| |
| public void testGetUntimed_ExecutionExceptionChecked() { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertEquals(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetUntimed_ExecutionExceptionUnchecked() |
| throws TwoArgConstructorException { |
| try { |
| get(FAILED_FUTURE_UNCHECKED_EXCEPTION, TwoArgConstructorException.class); |
| fail(); |
| } catch (UncheckedExecutionException expected) { |
| assertEquals(UNCHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetUntimed_ExecutionExceptionError() |
| throws TwoArgConstructorException { |
| try { |
| get(FAILED_FUTURE_ERROR, TwoArgConstructorException.class); |
| fail(); |
| } catch (ExecutionError expected) { |
| assertEquals(ERROR, expected.getCause()); |
| } |
| } |
| |
| public void testGetUntimed_ExecutionExceptionOtherThrowable() { |
| try { |
| get(FAILED_FUTURE_OTHER_THROWABLE, TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertEquals(OTHER_THROWABLE, expected.getCause()); |
| } |
| } |
| |
| public void testGetUntimed_RuntimeException() |
| throws TwoArgConstructorException { |
| try { |
| get(RUNTIME_EXCEPTION_FUTURE, TwoArgConstructorException.class); |
| fail(); |
| } catch (RuntimeException expected) { |
| assertEquals(RUNTIME_EXCEPTION, expected); |
| } |
| } |
| |
| public void testGetUntimed_badExceptionConstructor_wrapsOriginalChecked() throws Exception { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithBadConstructor.class); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| assertSame(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetUntimed_withGoodAndBadExceptionConstructor() throws Exception { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithGoodAndBadConstructor.class); |
| fail(); |
| } catch (ExceptionWithGoodAndBadConstructor expected) { |
| assertSame(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| // Boring timed-get tests: |
| |
| public void testGetTimed_success() |
| throws TwoArgConstructorException { |
| assertEquals("foo", get( |
| immediateFuture("foo"), 0, SECONDS, TwoArgConstructorException.class)); |
| } |
| |
| public void testGetTimed_interrupted() { |
| SettableFuture<String> future = SettableFuture.create(); |
| Thread.currentThread().interrupt(); |
| try { |
| get(future, 0, SECONDS, TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertTrue(expected.getCause() instanceof InterruptedException); |
| assertTrue(Thread.currentThread().isInterrupted()); |
| } finally { |
| Thread.interrupted(); |
| } |
| } |
| |
| public void testGetTimed_cancelled() |
| throws TwoArgConstructorException { |
| SettableFuture<String> future = SettableFuture.create(); |
| future.cancel(true); |
| try { |
| get(future, 0, SECONDS, TwoArgConstructorException.class); |
| fail(); |
| } catch (CancellationException expected) { |
| } |
| } |
| |
| public void testGetTimed_ExecutionExceptionChecked() { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, 0, SECONDS, |
| TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertEquals(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetTimed_ExecutionExceptionUnchecked() |
| throws TwoArgConstructorException { |
| try { |
| get(FAILED_FUTURE_UNCHECKED_EXCEPTION, 0, SECONDS, |
| TwoArgConstructorException.class); |
| fail(); |
| } catch (UncheckedExecutionException expected) { |
| assertEquals(UNCHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetTimed_ExecutionExceptionError() |
| throws TwoArgConstructorException { |
| try { |
| get(FAILED_FUTURE_ERROR, 0, SECONDS, TwoArgConstructorException.class); |
| fail(); |
| } catch (ExecutionError expected) { |
| assertEquals(ERROR, expected.getCause()); |
| } |
| } |
| |
| public void testGetTimed_ExecutionExceptionOtherThrowable() { |
| try { |
| get(FAILED_FUTURE_OTHER_THROWABLE, 0, SECONDS, |
| TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertEquals(OTHER_THROWABLE, expected.getCause()); |
| } |
| } |
| |
| public void testGetTimed_RuntimeException() |
| throws TwoArgConstructorException { |
| try { |
| get(RUNTIME_EXCEPTION_FUTURE, 0, SECONDS, |
| TwoArgConstructorException.class); |
| fail(); |
| } catch (RuntimeException expected) { |
| assertEquals(RUNTIME_EXCEPTION, expected); |
| } |
| } |
| |
| public void testGetTimed_TimeoutException() { |
| SettableFuture<String> future = SettableFuture.create(); |
| try { |
| get(future, 0, SECONDS, TwoArgConstructorException.class); |
| fail(); |
| } catch (TwoArgConstructorException expected) { |
| assertTrue(expected.getCause() instanceof TimeoutException); |
| } |
| } |
| |
| public void testGetTimed_badExceptionConstructor_wrapsOriginalChecked() throws Exception { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, 1, TimeUnit.SECONDS, ExceptionWithBadConstructor.class); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| assertSame(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetTimed_withGoodAndBadExceptionConstructor() throws Exception { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, 1, TimeUnit.SECONDS, |
| ExceptionWithGoodAndBadConstructor.class); |
| fail(); |
| } catch (ExceptionWithGoodAndBadConstructor expected) { |
| assertSame(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| // Boring getUnchecked tests: |
| |
| public void testGetUnchecked_success() { |
| assertEquals("foo", getUnchecked(immediateFuture("foo"))); |
| } |
| |
| public void testGetUnchecked_interrupted() { |
| Thread.currentThread().interrupt(); |
| try { |
| assertEquals("foo", getUnchecked(immediateFuture("foo"))); |
| assertTrue(Thread.currentThread().isInterrupted()); |
| } finally { |
| Thread.interrupted(); |
| } |
| } |
| |
| public void testGetUnchecked_cancelled() { |
| SettableFuture<String> future = SettableFuture.create(); |
| future.cancel(true); |
| try { |
| getUnchecked(future); |
| fail(); |
| } catch (CancellationException expected) { |
| } |
| } |
| |
| public void testGetUnchecked_ExecutionExceptionChecked() { |
| try { |
| getUnchecked(FAILED_FUTURE_CHECKED_EXCEPTION); |
| fail(); |
| } catch (UncheckedExecutionException expected) { |
| assertEquals(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetUnchecked_ExecutionExceptionUnchecked() { |
| try { |
| getUnchecked(FAILED_FUTURE_UNCHECKED_EXCEPTION); |
| fail(); |
| } catch (UncheckedExecutionException expected) { |
| assertEquals(UNCHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testGetUnchecked_ExecutionExceptionError() { |
| try { |
| getUnchecked(FAILED_FUTURE_ERROR); |
| fail(); |
| } catch (ExecutionError expected) { |
| assertEquals(ERROR, expected.getCause()); |
| } |
| } |
| |
| public void testGetUnchecked_ExecutionExceptionOtherThrowable() { |
| try { |
| getUnchecked(FAILED_FUTURE_OTHER_THROWABLE); |
| fail(); |
| } catch (UncheckedExecutionException expected) { |
| assertEquals(OTHER_THROWABLE, expected.getCause()); |
| } |
| } |
| |
| public void testGetUnchecked_RuntimeException() { |
| try { |
| getUnchecked(RUNTIME_EXCEPTION_FUTURE); |
| fail(); |
| } catch (RuntimeException expected) { |
| assertEquals(RUNTIME_EXCEPTION, expected); |
| } |
| } |
| |
| // Edge case tests of the exception-construction code through untimed get(): |
| |
| public void testGetUntimed_exceptionClassIsRuntimeException() { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, |
| TwoArgConstructorRuntimeException.class); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| public void testGetUntimed_exceptionClassSomePublicConstructors() { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, |
| ExceptionWithSomePrivateConstructors.class); |
| fail(); |
| } catch (ExceptionWithSomePrivateConstructors expected) { |
| } |
| } |
| |
| public void testGetUntimed_exceptionClassNoPublicConstructor() |
| throws ExceptionWithPrivateConstructor { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, |
| ExceptionWithPrivateConstructor.class); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| public void testGetUntimed_exceptionClassPublicConstructorWrongType() |
| throws ExceptionWithWrongTypesConstructor { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, |
| ExceptionWithWrongTypesConstructor.class); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| public void testGetUntimed_exceptionClassPrefersStringConstructor() { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, |
| ExceptionWithManyConstructors.class); |
| fail(); |
| } catch (ExceptionWithManyConstructors expected) { |
| assertTrue(expected.usedExpectedConstructor); |
| } |
| } |
| |
| public void testGetUntimed_exceptionClassUsedInitCause() { |
| try { |
| get(FAILED_FUTURE_CHECKED_EXCEPTION, |
| ExceptionWithoutThrowableConstructor.class); |
| fail(); |
| } catch (ExceptionWithoutThrowableConstructor expected) { |
| assertThat(expected.getMessage()).contains("mymessage"); |
| assertEquals(CHECKED_EXCEPTION, expected.getCause()); |
| } |
| } |
| |
| public void testCompletionOrder() throws Exception { |
| SettableFuture<Long> future1 = SettableFuture.create(); |
| SettableFuture<Long> future2 = SettableFuture.create(); |
| SettableFuture<Long> future3 = SettableFuture.create(); |
| SettableFuture<Long> future4 = SettableFuture.create(); |
| SettableFuture<Long> future5 = SettableFuture.create(); |
| |
| ImmutableList<ListenableFuture<Long>> futures = Futures.inCompletionOrder( |
| ImmutableList.<ListenableFuture<Long>>of(future1, future2, future3, future4, future5)); |
| future2.set(1L); |
| future5.set(2L); |
| future1.set(3L); |
| future3.set(4L); |
| future4.set(5L); |
| |
| long expected = 1L; |
| for (ListenableFuture<Long> future : futures) { |
| assertEquals((Long) expected, future.get()); |
| expected++; |
| } |
| } |
| |
| public void testCompletionOrderExceptionThrown() throws Exception { |
| SettableFuture<Long> future1 = SettableFuture.create(); |
| SettableFuture<Long> future2 = SettableFuture.create(); |
| SettableFuture<Long> future3 = SettableFuture.create(); |
| SettableFuture<Long> future4 = SettableFuture.create(); |
| SettableFuture<Long> future5 = SettableFuture.create(); |
| |
| ImmutableList<ListenableFuture<Long>> futures = Futures.inCompletionOrder( |
| ImmutableList.<ListenableFuture<Long>>of(future1, future2, future3, future4, future5)); |
| future2.set(1L); |
| future5.setException(new IllegalStateException("2L")); |
| future1.set(3L); |
| future3.set(4L); |
| future4.set(5L); |
| |
| long expected = 1L; |
| for (ListenableFuture<Long> future : futures) { |
| if (expected != 2) { |
| assertEquals((Long) expected, future.get()); |
| } else { |
| try { |
| future.get(); |
| fail(); |
| } catch (ExecutionException e) { |
| // Expected |
| assertEquals("2L", e.getCause().getMessage()); |
| } |
| } |
| expected++; |
| } |
| } |
| |
| public void testCompletionOrderFutureCancelled() throws Exception { |
| SettableFuture<Long> future1 = SettableFuture.create(); |
| SettableFuture<Long> future2 = SettableFuture.create(); |
| SettableFuture<Long> future3 = SettableFuture.create(); |
| SettableFuture<Long> future4 = SettableFuture.create(); |
| SettableFuture<Long> future5 = SettableFuture.create(); |
| |
| ImmutableList<ListenableFuture<Long>> futures = Futures.inCompletionOrder( |
| ImmutableList.<ListenableFuture<Long>>of(future1, future2, future3, future4, future5)); |
| future2.set(1L); |
| future5.set(2L); |
| future1.set(3L); |
| future3.cancel(true); |
| future4.set(5L); |
| |
| long expected = 1L; |
| for (ListenableFuture<Long> future : futures) { |
| if (expected != 4) { |
| assertEquals((Long) expected, future.get()); |
| } else { |
| try { |
| future.get(); |
| fail(); |
| } catch (CancellationException e) { |
| // Expected |
| } |
| } |
| expected++; |
| } |
| } |
| |
| public void testCancellingADelegateDoesNotPropagate() throws Exception { |
| SettableFuture<Long> future1 = SettableFuture.create(); |
| SettableFuture<Long> future2 = SettableFuture.create(); |
| |
| ImmutableList<ListenableFuture<Long>> delegates = Futures.inCompletionOrder( |
| ImmutableList.<ListenableFuture<Long>>of(future1, future2)); |
| |
| future1.set(1L); |
| // Cannot cancel a complete delegate |
| assertFalse(delegates.get(0).cancel(true)); |
| // Cancel the delegate before the input future is done |
| assertTrue(delegates.get(1).cancel(true)); |
| // Setting the future still works since cancellation didn't propagate |
| assertTrue(future2.set(2L)); |
| // Second check to ensure the input future was not cancelled |
| assertEquals((Long) 2L, future2.get()); |
| } |
| |
| // Mostly an example of how it would look like to use a list of mixed types |
| public void testCompletionOrderMixedBagOTypes() throws Exception { |
| SettableFuture<Long> future1 = SettableFuture.create(); |
| SettableFuture<String> future2 = SettableFuture.create(); |
| SettableFuture<Integer> future3 = SettableFuture.create(); |
| |
| ImmutableList<? extends ListenableFuture<?>> inputs = |
| ImmutableList.<ListenableFuture<?>>of(future1, future2, future3); |
| ImmutableList<ListenableFuture<Object>> futures = Futures.inCompletionOrder(inputs); |
| future2.set("1L"); |
| future1.set(2L); |
| future3.set(3); |
| |
| ImmutableList<?> expected = ImmutableList.of("1L", 2L, 3); |
| for (int i = 0; i < expected.size(); i++) { |
| assertEquals(expected.get(i), futures.get(i).get()); |
| } |
| } |
| |
| public static final class TwoArgConstructorException extends Exception { |
| public TwoArgConstructorException(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| |
| public static final class TwoArgConstructorRuntimeException |
| extends RuntimeException { |
| public TwoArgConstructorRuntimeException(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| |
| public static final class ExceptionWithPrivateConstructor extends Exception { |
| private ExceptionWithPrivateConstructor(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| |
| @SuppressWarnings("unused") // we're testing that they're not used |
| public static final class ExceptionWithSomePrivateConstructors |
| extends Exception { |
| private ExceptionWithSomePrivateConstructors(String a) { |
| } |
| |
| private ExceptionWithSomePrivateConstructors(String a, String b) { |
| } |
| |
| public ExceptionWithSomePrivateConstructors( |
| String a, String b, String c) { |
| } |
| |
| private ExceptionWithSomePrivateConstructors( |
| String a, String b, String c, String d) { |
| } |
| |
| private ExceptionWithSomePrivateConstructors( |
| String a, String b, String c, String d, String e) { |
| } |
| } |
| |
| public static final class ExceptionWithManyConstructors extends Exception { |
| boolean usedExpectedConstructor; |
| |
| public ExceptionWithManyConstructors() { |
| } |
| |
| public ExceptionWithManyConstructors(Integer i) { |
| } |
| |
| public ExceptionWithManyConstructors(Throwable a) { |
| } |
| |
| public ExceptionWithManyConstructors(Throwable a, Throwable b) { |
| } |
| |
| public ExceptionWithManyConstructors(String s, Throwable b) { |
| usedExpectedConstructor = true; |
| } |
| |
| public ExceptionWithManyConstructors( |
| Throwable a, Throwable b, Throwable c) { |
| } |
| |
| public ExceptionWithManyConstructors( |
| Throwable a, Throwable b, Throwable c, Throwable d) { |
| } |
| |
| public ExceptionWithManyConstructors( |
| Throwable a, Throwable b, Throwable c, Throwable d, Throwable e) { |
| } |
| |
| public ExceptionWithManyConstructors(Throwable a, Throwable b, Throwable c, |
| Throwable d, Throwable e, String s, Integer i) { |
| } |
| } |
| |
| public static final class ExceptionWithoutThrowableConstructor |
| extends Exception { |
| public ExceptionWithoutThrowableConstructor(String s) { |
| super(s); |
| } |
| } |
| |
| public static final class ExceptionWithWrongTypesConstructor |
| extends Exception { |
| public ExceptionWithWrongTypesConstructor(Integer i, String s) { |
| super(s); |
| } |
| } |
| |
| private static final class ExceptionWithGoodAndBadConstructor extends Exception { |
| public ExceptionWithGoodAndBadConstructor(String message, Throwable cause) { |
| throw new RuntimeException("bad constructor"); |
| } |
| public ExceptionWithGoodAndBadConstructor(Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| private static final class ExceptionWithBadConstructor extends Exception { |
| public ExceptionWithBadConstructor(String message, Throwable cause) { |
| throw new RuntimeException("bad constructor"); |
| } |
| } |
| |
| public void testFutures_nullChecks() throws Exception { |
| new ClassSanityTester() |
| .forAllPublicStaticMethods(Futures.class) |
| .thatReturn(Future.class) |
| .testNulls(); |
| } |
| |
| private static void failWithCause(Throwable cause, String message) { |
| AssertionFailedError failure = new AssertionFailedError(message); |
| failure.initCause(cause); |
| throw failure; |
| } |
| } |