/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.concurrent;
import io.vavr.*;
import io.vavr.collection.Seq;
import io.vavr.collection.Iterator;
import io.vavr.collection.List;
import io.vavr.collection.Stream;
import io.vavr.control.Option;
import io.vavr.control.Try;
import org.assertj.core.api.IterableAssert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.concurrent.*;
import java.util.function.Function;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static io.vavr.concurrent.Concurrent.waitUntil;
import static io.vavr.concurrent.Concurrent.zZz;
import static io.vavr.concurrent.ExecutorServices.rejectingExecutorService;
import static io.vavr.concurrent.ExecutorServices.trivialExecutorService;
import static org.assertj.core.api.Assertions.fail;
public class FutureTest extends AbstractValueTest {
@Override
protected <T> IterableAssert<T> assertThat(Iterable<T> actual) {
return new IterableAssert<T>(actual) {
@SuppressWarnings("unchecked")
@Override
public IterableAssert<T> isEqualTo(Object expected) {
if (actual instanceof Future && expected instanceof Future) {
assertThat(((Future<T>) actual).getValue()).isEqualTo(((Future<T>) expected).getValue());
return this;
} else {
return super.isEqualTo(expected);
}
}
};
}
@Override
protected <T> Future<T> empty() {
return Future.failed(trivialExecutorService(), new NoSuchElementException());
}
@Override
protected <T> Future<T> of(T element) {
return Future.of(trivialExecutorService(), () -> element);
}
@SafeVarargs
@Override
protected final <T> Future<T> of(T... elements) {
return of(elements[0]);
}
@Override
protected boolean useIsEqualToInsteadOfIsSameAs() {
return true;
}
@Override
protected int getPeekNonNilPerformingAnAction() {
return 1;
}
// -- static failed()
@Test
public void shouldCreateFailureThatFailsWithRuntimeException() {
final Future<Void> failed = Future.failed(new RuntimeException("ooops"));
waitUntil(failed::isCompleted);
assertThat(failed.isFailure()).isTrue();
final Throwable t = failed.getValue().get().getCause();
assertThat(t.getClass()).isEqualTo(RuntimeException.class);
assertThat(t.getMessage()).isEqualTo("ooops");
}
@Test
public void shouldCreateFailureThatFailsWithError() {
final Future<Void> failed = Future.failed(new Error("ooops"));
waitUntil(failed::isCompleted);
assertThat(failed.isFailure()).isTrue();
final Throwable t = failed.getValue().get().getCause();
assertThat(t.getClass()).isEqualTo(Error.class);
assertThat(t.getMessage()).isEqualTo("ooops");
}
@Test
public void shouldCreateAndFailAFutureUsingForkJoinPool() {
final Future<Integer> future = Future.of(() -> {
throw new Error();
});
waitUntil(future::isCompleted);
assertFailed(future, Error.class);
}
@Test
public void shouldCreateAndFailAFutureUsingTrivialExecutorService() {
final Future<Integer> future = Future.of(trivialExecutorService(), () -> {
throw new Error();
});
assertFailed(future, Error.class);
}
// -- static fromJavaFuture()
@Test
public void shouldCreateFutureFromJavaFuture() {
// Create slow-resolving Java future to show that the wrapping doesn't block
final java.util.concurrent.Future<Integer> jFuture = generateJavaFuture(1, 3000);
final Future<Integer> future = Future.fromJavaFuture(jFuture);
waitUntil(future::isCompleted);
assertCompleted(future, 1);
}
@Test
public void shouldCreateFutureFromJavaFutureUsingTrivialExecutorService() {
// Create slow-resolving Java future to show that the wrapping doesn't block
final java.util.concurrent.Future<String> jFuture = generateJavaFuture("Result", 3000);
final Future<String> future = Future.fromJavaFuture(trivialExecutorService(), jFuture);
waitUntil(future::isCompleted);
assertCompleted(future, "Result");
}
// -- static fromCompletableFuture()
@Test
public void shouldCreateFutureFromCompletedJavaCompletableFuture() {
final CompletableFuture<Integer> jFuture = CompletableFuture.completedFuture(1);
final Future<Integer> future = Future.fromCompletableFuture(jFuture);
assertCompleted(future, 1);
}
@Test
public void shouldCreateFutureFromFailedJavaCompletableFuture() {
final CompletableFuture<Integer> jFuture = new CompletableFuture<>();
jFuture.completeExceptionally(new RuntimeException("some"));
final Future<Integer> future = Future.fromCompletableFuture(jFuture);
assertFailed(future, RuntimeException.class);
}
@Test
public void shouldCreateFutureFromJavaCompletableFuture() {
// Create slow-resolving Java future to show that the wrapping doesn't block
final CompletableFuture<Integer> jFuture = generateJavaCompletableFuture(1, 1000);
final Future<Integer> future = Future.fromCompletableFuture(jFuture);
waitUntil(future::isCompleted);
assertCompleted(future, 1);
}
@Test
public void shouldCreateFutureFromLateFailingJavaCompletableFuture() {
final CompletableFuture<Integer> jFuture = Future.<Integer> of(zZz(new RuntimeException())).toCompletableFuture();
final Future<Integer> future = Future.fromCompletableFuture(jFuture);
waitUntil(future::isCompleted);
assertFailed(future, RuntimeException.class);
}
@Test
public void shouldCreateFutureFromJavaCompletableFutureUsingTrivialExecutorService() {
// Create slow-resolving Java future to show that the wrapping doesn't block
final java.util.concurrent.Future<String> jFuture = generateJavaCompletableFuture("Result", 1000);
final Future<String> future = Future.fromJavaFuture(trivialExecutorService(), jFuture);
waitUntil(future::isCompleted);
assertCompleted(future, "Result");
}
// -- static find()
@Test
public void shouldFindNoneWhenEmptySeq() {
final Future<Option<Object>> testee = Future.find(List.empty(), t -> true);
assertCompleted(testee, Option.none());
}
@Test
public void shouldFindFirstValueThatSatisfiesAPredicateUsingForkJoinPool() {
final Seq<Future<Integer>> futures = Stream.from(1).map(i -> Future.of(() -> i)).take(20);
final Future<Option<Integer>> testee = Future.find(futures, i -> i == 13);
waitUntil(testee::isCompleted);
assertCompleted(testee, Option.some(13));
}
@Test
public void shouldFailFindingFirstValueBecauseNoResultSatisfiesTheGivenPredicateUsingForkJoinPool() {
final Seq<Future<Integer>> futures = Stream.from(1).map(i -> Future.of(() -> i)).take(20);
final Future<Option<Integer>> testee = Future.find(futures, i -> false);
waitUntil(testee::isCompleted);
assertCompleted(testee, Option.none());
}
@Test
public void shouldFindOneSucceedingFutureWhenAllOthersFailUsingDefaultExecutorService() {
final Seq<Future<Integer>> futures = Stream.from(1)
.map(i -> Future.<Integer> of(() -> {
throw new Error();
}))
.take(12)
.append(Future.of(() -> 13));
final Future<Option<Integer>> testee = Future.find(futures, i -> i == 13);
waitUntil(testee::isCompleted);
assertCompleted(testee, Option.some(13));
}
@Test
public void shouldFindNoneWhenAllFuturesFailUsingForkJoinPool() {
final Seq<Future<Integer>> futures = Stream.from(1)
.map(i -> Future.<Integer> of(() -> {
throw new Error(String.valueOf(i));
}))
.take(20);
final Future<Option<Integer>> testee = Future.find(futures, i -> i == 13);
waitUntil(testee::isCompleted);
assertCompleted(testee, Option.none());
}
// -- static firstCompletedOf()
@Test
public void shouldGetFirstCompletedOfFailuresUsingForkJoinPool() {
final Seq<Future<Object>> futures = Stream.from(1).map(i -> Future.of(zZz(new Error()))).take(3);
final Future<?> testee = Future.firstCompletedOf(futures);
waitUntil(testee::isCompleted);
assertThat(testee.getValue().get().isFailure()).isTrue();
}
@Test
public void shouldGetFirstCompletedOfSucceedingFuturesUsingForkJoinPool() {
final Seq<Future<Integer>> futures = Stream.from(1).map(i -> Future.of(zZz(i))).take(3);
final Future<?> testee = Future.firstCompletedOf(futures).await();
assertThat(testee.getValue().get().isSuccess()).isTrue();
}
// -- static fromTry()
@Test
public void shouldCreateFailFutureFromTry() {
final Future<Integer> future = Future.fromTry(Try.of(() -> { throw new Error(); }));
waitUntil(future::isCompleted);
assertThat(future.isFailure()).isTrue();
}
@Test
public void shouldCreateSuccessFutureFromTry() {
final Future<Integer> future = Future.fromTry(Try.of(() -> 42));
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo(42);
}
// -- static of()
@Test
public void shouldCreateAndCompleteAFutureUsingTrivialExecutorService() {
final Future<Integer> future = Future.of(trivialExecutorService(), () -> 1);
assertCompleted(future, 1);
}
@Test
public void shouldNotCancelCompletedFutureUsingTrivialExecutorService() {
final Future<Integer> future = Future.of(trivialExecutorService(), () -> 1);
assertThat(future.cancel()).isFalse();
assertCompleted(future, 1);
}
@Test
public void shouldCompleteWithFailureWhenExecutorServiceThrowsRejectedExecutionException() {
final Future<Integer> future = Future.of(rejectingExecutorService(), () -> 1);
assertFailed(future, RejectedExecutionException.class);
}
// TODO: Re-enable this test when solving #1530
@Ignore
@Test
public void shouldCompleteOneFuturesUsingAThreadPoolExecutorLimitedToOneThread() {
final ExecutorService service = new ThreadPoolExecutor(1, 1, 0L, MILLISECONDS, new SynchronousQueue<>());
final Future<Integer> future = Future.of(service, () -> expensiveOperation(1)).await();
assertCompleted(future, 1);
service.shutdown();
}
// TODO: Re-enable this test when solving #1530
@Ignore
@Test
public void shouldCompleteThreeFuturesUsingAThreadPoolExecutorLimitedToOneThread() {
final ExecutorService service = new ThreadPoolExecutor(1, 1, 0L, MILLISECONDS, new SynchronousQueue<>());
final Stream<Future<Integer>> futures = Stream
.rangeClosed(1, 3)
.map(value -> Future.of(service, () -> expensiveOperation(value)));
futures.forEach(Future::await);
assertThat(futures.flatMap(Function.identity()).toList().sorted()).isEqualTo(List.of(1, 2, 3));
service.shutdown();
}
private static <T> T expensiveOperation(T value) throws InterruptedException {
Thread.sleep(500);
return value;
}
// -- static ofSupplier()
@Test
public void shouldCreateAndCompleteAFutureUsingTrivialExecutorServiceAndSupplier() {
final Future<Integer> future = Future.ofSupplier(trivialExecutorService(), () -> 1);
assertThat(future.executorService()).isSameAs(trivialExecutorService());
assertCompleted(future, 1);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEWhenExecutorIsNullUsingSupplier() {
Future.ofSupplier(null, () -> 1);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEWhenSupplierIsNull() {
Future.ofSupplier(trivialExecutorService(), null);
}
@Test
public void shouldCreateAndCompleteAFutureUsingDefaultExecutorServiceAndSupplier() {
final Future<Integer> future = Future.ofSupplier(() -> 1);
assertThat(future.executorService()).isSameAs(Future.DEFAULT_EXECUTOR_SERVICE);
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo(1);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEUsingDefaultExecutorWhenSupplierIsNull() {
Future.ofSupplier(null);
}
// -- static ofCallable()
@Test
public void shouldCreateAndCompleteAFutureUsingTrivialExecutorServiceAndCallable() {
final Future<Integer> future = Future.ofCallable(trivialExecutorService(), () -> 1);
assertThat(future.executorService()).isSameAs(trivialExecutorService());
assertCompleted(future, 1);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEWhenExecutorIsNullUsingCallable() {
Future.ofCallable(null, () -> 1);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEWhenCallableIsNull() {
Future.ofCallable(trivialExecutorService(), null);
}
@Test
public void shouldCreateAndCompleteAFutureUsingDefaultExecutorServiceAndCallable() {
final Future<Integer> future = Future.ofCallable(() -> 1);
assertThat(future.executorService()).isSameAs(Future.DEFAULT_EXECUTOR_SERVICE);
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo(1);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEUsingDefaultExecutorWhenCallableIsNull() {
Future.ofCallable(null);
}
// -- static runRunnable()
@Test
public void shouldCreateAndCompleteAFutureUsingTrivialExecutorServiceAndRunnable() {
final int[] sideEffect = new int[] { 0 };
final Future<Void> future = Future.runRunnable(trivialExecutorService(), () -> sideEffect[0] = 42);
assertThat(future.executorService()).isSameAs(trivialExecutorService());
waitUntil(future::isCompleted);
assertThat(sideEffect[0]).isEqualTo(42);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEWhenExecutorIsNullUsingRunnable() {
Future.runRunnable(null, () -> {});
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEWhenRunnableIsNull() {
Future.runRunnable(trivialExecutorService(), null);
}
@Test
public void shouldCreateAndCompleteAFutureUsingDefaultExecutorServiceAndRunnable() {
final int[] sideEffect = new int[] { 0 };
final Future<Void> future = Future.runRunnable(() -> sideEffect[0] = 42);
assertThat(future.executorService()).isSameAs(Future.DEFAULT_EXECUTOR_SERVICE);
waitUntil(future::isCompleted);
assertThat(sideEffect[0]).isEqualTo(42);
}
@Test(expected = NullPointerException.class)
public void shouldThrowNPEUsingDefaultExecutorWhenRunnableIsNull() {
Future.runRunnable(null);
}
// -- static reduce()
@Test(expected = NoSuchElementException.class)
public void shouldFailReduceEmptySequence() {
Future.<Integer> reduce(List.empty(), (i1, i2) -> i1 + i2);
}
@Test
public void shouldReduceSequenceOfFutures() {
final Future<String> future = Future.reduce(
List.of(Future.of(zZz("Va")), Future.of(zZz("vr"))),
(i1, i2) -> i1 + i2
);
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo("Vavr");
}
@Test
public void shouldReduceWithErrorIfSequenceOfFuturesContainsOneError() {
final Future<Integer> future = Future.reduce(
List.of(Future.of(zZz(13)), Future.of(zZz(new Error()))),
(i1, i2) -> i1 + i2
);
waitUntil(future::isCompleted);
assertFailed(future, Error.class);
}
// -- static run()
@Test
public void shouldCompleteRunnable() {
final int[] sideEffect = new int[] { 0 };
final Future<Void> future = Future.run(() -> sideEffect[0] = 42);
waitUntil(future::isCompleted);
assertThat(sideEffect[0]).isEqualTo(42);
}
// -- static sequence()
@Test
public void shouldCompleteWithSeqOfValueIfSequenceOfFuturesContainsNoError() {
final Future<Seq<Integer>> sequence = Future.sequence(
List.of(Future.of(zZz(1)), Future.of(zZz(2)))
);
waitUntil(sequence::isCompleted);
assertThat(sequence.getValue().get()).isEqualTo(Try.success(Stream.of(1, 2)));
}
@Test
public void shouldCompleteWithErrorIfSequenceOfFuturesContainsOneError() {
final Future<Seq<Integer>> sequence = Future.sequence(
List.of(Future.of(zZz(13)), Future.of(zZz(new Error())))
);
waitUntil(sequence::isCompleted);
assertFailed(sequence, Error.class);
}
// -- static successful()
@Test
public void shouldCreateSuccessful() {
final Future<Integer> succ = Future.successful(42);
assertThat(succ.isCompleted()).isTrue();
assertThat(succ.isSuccess()).isTrue();
assertThat(succ.get()).isEqualTo(42);
}
// -- static traverse()
@Test
public void shouldCompleteTraverse() {
final Future<Seq<Integer>> future = Future.traverse(List.of(1, 2, 3), i -> Future.of(zZz(i)));
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo(Stream.of(1, 2, 3));
}
// -- andThen
@Test
public void shouldCompleteWithErrorIfFailAndThenFail() {
final Future<Integer> future = Future.<Integer> of(zZz(new Error("fail!")))
.andThen(t -> zZz(new Error("and then fail!")));
waitUntil(future::isCompleted);
assertFailed(future, Error.class);
}
@Test
public void shouldCompleteWithSuccessIfSuccessAndThenFail() {
final Future<Integer> future = Future.of(zZz(42))
.andThen(t -> zZz(new Error("and then fail!")));
waitUntil(future::isCompleted);
assertThat(future.getValue().get()).isEqualTo(Try.success(42));
}
@Test
public void shouldCompleteWithSpecificOrderIfSuccessAndThenSuccess() {
final boolean[] lock = new boolean[] { true };
final int[] sideEffect = new int[] { 0 };
final Future<Void> future = Future.<Void> of(() -> {
waitUntil(() -> !lock[0]);
return null;
}).andThen(t -> sideEffect[0] = 42);
assertThat(future.isCompleted()).isFalse();
assertThat(sideEffect[0]).isEqualTo(0);
lock[0] = false;
waitUntil(future::isCompleted);
assertThat(sideEffect[0]).isEqualTo(42);
}
// -- orElse
@Test
public void shouldReturnSelfResultOnOrElseIfSuccess() {
final Future<String> f1 = Future.of(() -> "f1");
final Future<String> f2 = f1.orElse(Future.of(() -> "f2"));
waitUntil(f2::isCompleted);
assertThat(f2.get()).isEqualTo("f1");
}
@Test
public void shouldReturnSelfResultOnOrElseSupplierIfSuccess() {
final Future<String> f1 = Future.of(() -> "f1");
final Future<String> f2 = f1.orElse(() -> Future.of(() -> "f2"));
waitUntil(f2::isCompleted);
assertThat(f2.get()).isEqualTo("f1");
}
@Test
public void shouldReturnOtherResultOnOrElseIfFailure() {
final Future<String> f1 = Future.failed(new RuntimeException());
final Future<String> f2 = f1.orElse(Future.of(() -> "f2"));
waitUntil(f2::isCompleted);
assertThat(f2.get()).isEqualTo("f2");
}
@Test
public void shouldReturnOtherResultOnOrElseSupplierIfFailure() {
final Future<String> f1 = Future.failed(new RuntimeException());
final Future<String> f2 = f1.orElse(() -> Future.of(() -> "f2"));
waitUntil(f2::isCompleted);
assertThat(f2.get()).isEqualTo("f2");
}
// -- await
@Test
public void shouldAwaitOnGet() {
final Future<Integer> future = Future.of(() -> {
Try.run(() -> Thread.sleep(250L));
return 1;
});
assertThat(future.get()).isEqualTo(1);
}
// -- failed
@Test
public void shouldConvertToFailedFromFail() {
final Future<Throwable> future = Future.of(zZz(new Error())).failed();
waitUntil(future::isCompleted);
assertThat(future.isSuccess()).isTrue();
assertThat(future.get().getClass()).isEqualTo(Error.class);
}
@Test
public void shouldConvertToFailedFromSuccess() {
final Future<Throwable> future = Future.of(zZz(42)).failed();
waitUntil(future::isCompleted);
assertThat(future.isFailure()).isTrue();
assertThat(future.getValue().get().getCause().getClass()).isEqualTo(NoSuchElementException.class);
}
// -- fallbackTo
@Test
public void shouldFallbackToThisResult() {
final Future<Integer> future = Future.of(() -> 1);
final Future<Integer> that = Future.of(() -> {
throw new Error();
});
final Future<Integer> testee = future.fallbackTo(that);
waitUntil(testee::isCompleted);
assertThat(testee.getValue().get()).isEqualTo(Try.success(1));
}
@Test
public void shouldFallbackToThatResult() {
final Future<Integer> future = Future.of(() -> {
throw new Error();
});
final Future<Integer> that = Future.of(() -> 1);
final Future<Integer> testee = future.fallbackTo(that);
waitUntil(testee::isCompleted);
assertThat(testee.getValue().get()).isEqualTo(Try.success(1));
}
@Test
public void shouldFallbackToThisFailure() {
final Future<Integer> future = Future.of(() -> {
throw new Error("ok");
});
final Future<Integer> that = Future.of(() -> {
throw new Error();
});
final Future<Integer> testee = future.fallbackTo(that);
waitUntil(testee::isCompleted);
final Try<Integer> result = testee.getValue().get();
assertThat(result.isFailure()).isTrue();
assertThat(result.getCause().getMessage()).isEqualTo("ok");
}
// -- fold()
@Test
public void shouldFoldEmptyIterable() {
final Seq<Future<Integer>> futures = Stream.empty();
final Future<Integer> testee = Future.fold(futures, 0, (a, b) -> a + b);
waitUntil(testee::isCompleted);
assertThat(testee.getValue().get()).isEqualTo(Try.success(0));
}
@Test
public void shouldFoldNonEmptyIterableOfSucceedingFutures() {
final Seq<Future<Integer>> futures = Stream.from(1).map(i -> Future.of(zZz(i))).take(5);
final Future<Integer> testee = Future.fold(futures, 0, (a, b) -> a + b);
waitUntil(testee::isCompleted);
assertThat(testee.getValue().get()).isEqualTo(Try.success(15));
}
@Test
public void shouldFoldNonEmptyIterableOfFailingFutures() {
final Seq<Future<Integer>> futures = Stream.from(1).map(i -> Future.<Integer> of(zZz(new Error()))).take(5);
final Future<Integer> testee = Future.fold(futures, 0, (a, b) -> a + b);
waitUntil(testee::isCompleted);
assertFailed(testee, Error.class);
}
// -- cancel()
@SuppressWarnings("InfiniteLoopStatement")
@Test
public void shouldInterruptLockedFuture() {
final Future<?> future = Future.of(() -> {
while (true) {
Try.run(() -> Thread.sleep(100));
}
});
future.onComplete(r -> fail("future should lock forever"));
future.cancel();
assertCancelled(future);
}
@Test(expected = CancellationException.class)
public void shouldThrowOnGetAfterCancellation() {
final Future<?> future = Future.of(Concurrent::waitForever);
final boolean isCancelled = future.cancel();
assertThat(isCancelled).isTrue();
future.get();
fail("Future was expected to throw on get() after cancellation!");
}
// -- collect()
@Test
public void shouldCollectDefinedValueUsingPartialFunction() {
final PartialFunction<Integer, String> pf = Function1.<Integer, String> of(String::valueOf).partial(i -> i % 2 == 1);
final Future<String> future = Future.of(zZz(3)).collect(pf);
waitUntil(future::isCompleted);
assertThat(future.getValue().get()).isEqualTo(Try.success("3"));
}
@Test
public void shouldFilterNotDefinedValueUsingPartialFunction() {
final PartialFunction<Integer, String> pf = Function1.<Integer, String> of(String::valueOf).partial(i -> i % 2 == 1);
final Future<String> future = Future.of(zZz(2)).collect(pf);
waitUntil(future::isCompleted);
assertThat(future.getValue().get().isFailure()).isTrue();
}
@Test
public void shouldCollectEmptyFutureUsingPartialFunction() {
final PartialFunction<Integer, String> pf = Function1.<Integer, String> of(String::valueOf).partial(i -> i % 2 == 1);
final Future<String> future = Future.<Integer> of(zZz(new Error())).collect(pf);
waitUntil(future::isCompleted);
assertThat(future.getValue().get().isFailure()).isTrue();
}
@Test(expected = NullPointerException.class)
public void shouldThrowExceptionOnNullCollectPartialFunction() {
final PartialFunction<Integer, String> pf = null;
Future.of(zZz(3)).collect(pf);
}
// -- executorService()
@Test
public void shouldReturnExecutorService() {
final Future<Integer> f1 = Future.of(() -> 42);
assertThat(f1.executorService()).isSameAs(Future.DEFAULT_EXECUTOR_SERVICE);
final ExecutorService customExecutorService = Executors.newCachedThreadPool();
final Future<Integer> f2 = Future.of(customExecutorService, () -> 42);
assertThat(f2.executorService()).isSameAs(customExecutorService);
}
// -- getCause()
@Test
public void shouldGetCauseOfUncompletedFuture() {
final Future<?> future = Future.of(Concurrent::waitForever);
assertThat(future.getCause()).isEqualTo(Option.none());
}
@Test
public void shouldGetCauseOfFailedFuture() {
final Error error = new Error();
assertThat(Future.failed(error).getCause()).isEqualTo(Option.some(error));
}
@Test(expected = UnsupportedOperationException.class)
public void shouldThrowWhenGettingCauseOfSucceededFuture() {
Future.successful("ok").getCause();
}
// -- getValue()
@Test
public void shouldGetValueOfUncompletedFuture() {
final Future<?> future = Future.of(Concurrent::waitForever);
assertThat(future.getValue()).isEqualTo(Option.none());
}
@Test
public void shouldGetValueOfSucceededFuture() {
assertThat(Future.successful("ok").getValue()).isEqualTo(Option.some(Try.success("ok")));
}
@Test
public void shouldThrowWhenGettingValueOfFailedFuture() {
final Error error = new Error();
assertThat(Future.failed(error).getValue()).isEqualTo(Option.some(Try.failure(error)));
}
// -- isAsync
@Override
@Test
public void shouldVerifyAsyncProperty() {
assertThat(empty().isAsync()).isTrue();
assertThat(of(1).isAsync()).isTrue();
}
// -- isCompleted()
@Test
public void shouldBeCompletedWhenSuccessful() {
assertThat(Future.successful(null).isCompleted()).isTrue();
}
@Test
public void shouldBeCompletedWhenFailed() {
assertThat(Future.failed(new Exception()).isCompleted()).isTrue();
}
@Test
public void shouldBeCompletedWhenResultIsPresent() {
final Future<Integer> future = Future.of(() -> null);
waitUntil(future::isCompleted);
assertThat(future.isCompleted()).isTrue();
}
// -- isSuccess()
@Test
public void shouldBeSuccessful() {
assertThat(Future.successful(null).isSuccess()).isTrue();
}
// -- isFailure()
@Test
public void shouldBeFailed() {
assertThat(Future.failed(new Exception()).isFailure()).isTrue();
}
// -- onComplete()
@Test
public void shouldExecuteOnCompleteOnCompletedFuture() {
final int[] actual = new int[] { 0 };
Future.successful(1).onComplete(t -> {
actual[0] = t.get();
});
waitUntil(() -> actual[0] == 1);
}
@Test
public void shouldRegisterCallbackBeforeFutureCompletes() {
// instead of delaying we wait/notify
final Object lock = new Object();
final int[] actual = new int[] { -1 };
final boolean[] futureWaiting = new boolean[] { false };
final int expected = 1;
// create a future and put it to sleep
final Future<Integer> future = Future.of(() -> {
synchronized (lock) {
futureWaiting[0] = true;
lock.wait();
}
return expected;
});
// give the future thread some time to sleep
waitUntil(() -> futureWaiting[0]);
// the future now is on hold and we have time to register a callback
future.onComplete(result -> actual[0] = result.get());
assertThat(future.isCompleted()).isFalse();
assertThat(actual[0]).isEqualTo(-1);
// now awake the future
synchronized (lock) {
lock.notify();
}
// give the future thread some time to complete
waitUntil(future::isCompleted);
// the callback is also executed on its own thread - we have to wait for it to complete.
waitUntil(() -> actual[0] == expected);
}
@Test
public void shouldPerformActionAfterFutureCompleted() {
final int[] actual = new int[] { -1 };
final Future<Integer> future = Future.of(trivialExecutorService(), () -> 1);
assertCompleted(future, 1);
assertThat(actual[0]).isEqualTo(-1);
future.onComplete(result -> actual[0] = result.get());
assertThat(actual[0]).isEqualTo(1);
}
// -- onFailure()
@Test
public void shouldDoActionOnFailure() {
final Future<?> future = Future.of(zZz(new Error()));
final Throwable[] holder = new Throwable[] { null };
future.onFailure(t -> holder[0] = t);
waitUntil(() -> holder[0] != null);
assertThat(holder[0].getClass()).isEqualTo(Error.class);
}
// -- onSuccess()
@Test
public void shouldDoActionOnSuccess() {
final Future<Integer> future = Future.of(zZz(42));
final int[] holder = new int[] { 0 };
future.onSuccess(i -> holder[0] = i);
waitUntil(() -> holder[0] > 0);
assertThat(holder[0]).isEqualTo(42);
}
// -- recover()
@Test
public void shouldRecoverFailedFuture() {
final Future<Integer> recovered = Future.<Integer> of(zZz(new Error())).recover(t -> 42);
waitUntil(recovered::isCompleted);
assertThat(recovered.isSuccess()).isTrue();
assertThat(recovered.get()).isEqualTo(42);
}
@Test
public void shouldNotCrashWhenRecoverFails() {
final Future<Integer> recovered = Future.<Integer> of(zZz(new Error())).recover(t -> {
throw new ArithmeticException();
});
waitUntil(recovered::isCompleted);
waitUntil(recovered::isFailure);
assertThat(recovered.getCause().get().getClass()).isEqualTo(ArithmeticException.class);
}
// -- recoverWith()
@Test
public void shouldRecoverFailedFutureWithFuture() {
final Future<String> recovered = Future.<String> of(() -> { throw new Error("oh!"); })
.recoverWith(x -> Future.of(x::getMessage));
waitUntil(recovered::isCompleted);
assertThat(recovered.isSuccess()).isTrue();
assertThat(recovered.get()).isEqualTo("oh!");
}
@Test
public void shouldRecoverSuccessFutureWithFuture() {
final Future<String> recovered = Future.of(() -> "oh!")
.recoverWith(ignored -> Future.of(() -> "ignored"));
waitUntil(recovered::isCompleted);
assertThat(recovered.isSuccess()).isTrue();
assertThat(recovered.get()).isEqualTo("oh!");
}
// -- toCompletableFuture
@Test
public void shouldConvertCompletedFutureToCompletableFuture() {
final CompletableFuture<Integer> completableFuture = Future.of(() -> 42).toCompletableFuture();
assertThat(completableFuture.isDone());
assertThat(Try.of(completableFuture::get).get()).isEqualTo(42);
}
@Test
public void shouldConvertFailedFutureToCompletableFuture() {
final CompletableFuture<Integer> completableFuture = Future.<Integer> failed(new RuntimeException()).toCompletableFuture();
assertThat(completableFuture.isCompletedExceptionally());
assertThat(Try.of(completableFuture::get).getCause()).isExactlyInstanceOf(ExecutionException.class);
}
@Test
public void shouldConvertLateFutureToCompletableFuture() {
final CompletableFuture<Integer> completableFuture = Future.of(zZz(42)).toCompletableFuture();
assertThat(Try.of(completableFuture::get).get()).isEqualTo(42);
}
// -- toTry()
@Test
public void shouldConvertSuccessfulFutureToTry() {
assertThat(Future.successful(1).toTry()).isEqualTo(Try.success(1));
}
@Test
public void shouldConvertFailedFutureToTry() {
assertThat(Future.failed(new Error("!")).toTry()).isEqualTo(Try.failure(new Error("!")));
}
// -- transform()
@Test
public void shouldTransform() {
final String transformed = Future.of(() -> 42).transform(f -> String.valueOf(f.get()));
assertThat(transformed).isEqualTo("42");
}
// -- transformValue()
@Test
public void shouldTransformResultFromSuccessToSuccess() {
final Future<String> future = Future.of(zZz(42)).transformValue(t -> Try.of(() -> "forty two"));
waitUntil(future::isCompleted);
waitUntil(future::isSuccess);
assertThat(future.get()).isEqualTo("forty two");
}
@Test
public void shouldTransformResultFromSuccessToFailure() {
final Future<String> future = Future.of(zZz(42)).transformValue(t -> Try.failure(new Error()));
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(Error.class);
}
@Test
public void shouldTransformResultFromSuccessToFailureThroughError() {
final Future<String> future = Future.of(zZz(42)).transformValue(t -> Try.of(() -> {
throw new ArithmeticException();
}));
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(ArithmeticException.class);
}
@Test
public void shouldTransformResultFromFailureToSuccess() {
final Future<String> future = Future.of(zZz(new Error())).transformValue(t -> Try.of(() -> "forty two"));
waitUntil(future::isCompleted);
waitUntil(future::isSuccess);
assertThat(future.get()).isEqualTo("forty two");
}
@Test
public void shouldTransformResultFromFailureToFailure() {
final Future<String> future = Future.of(() -> {
throw new ArithmeticException();
}).transformValue(t -> Try.failure(new Error()));
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(Error.class);
}
@Test
public void shouldTransformResultFromFailureToFailureThroughError() {
final Future<String> future = Future.of(zZz(new Error())).transformValue(t -> Try.of(() -> {
throw new ArithmeticException();
}));
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(ArithmeticException.class);
}
// -- zip()
@Test
public void shouldZipSuccess() {
final Future<Tuple2<Integer, Integer>> future = Future.of(zZz(1)).zip(Future.of(zZz(2)));
waitUntil(future::isCompleted);
waitUntil(future::isSuccess);
assertThat(future.get()).isEqualTo(Tuple.of(1, 2));
}
@Test
public void shouldZipFailure() {
final Future<Tuple2<Integer, Integer>> future = Future.<Integer> of(zZz(new Error())).zip(Future.of(zZz(2)));
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(Error.class);
}
// -- zipWith()
@Test
public void shouldZipWithSuccess() {
final Future<List<Integer>> future = Future.of(zZz(1)).zipWith(Future.of(zZz(2)), List::of);
waitUntil(future::isCompleted);
waitUntil(future::isSuccess);
assertThat(future.get()).isEqualTo(List.of(1, 2));
}
@Test
public void shouldZipWithFailure() {
final Future<List<Integer>> future = Future.<Integer> of(zZz(new Error())).zipWith(Future.of(zZz(2)), List::of);
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(Error.class);
}
@Test
public void shouldZipWithCombinatorFailure() {
final Future<List<Integer>> future = Future.of(zZz(3)).zipWith(Future.of(zZz(2)), (t, u) -> {
throw new RuntimeException();
});
waitUntil(future::isCompleted);
waitUntil(future::isFailure);
assertThat(future.getCause().get().getClass()).isEqualTo(RuntimeException.class);
}
// -- Value implementation
@Test
public void shouldFilterFuture() {
final Future<Integer> future = Future.successful(42);
assertThat(future.filter(i -> i == 42).get()).isEqualTo(42);
assertThat(future.filter(i -> i == 43).isEmpty()).isTrue();
}
@Test
public void shouldFlatMapFuture() {
final Future<Integer> future = Future.of(zZz(42)).flatMap(i -> Future.of(zZz(i * 2)));
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo(84);
}
@Test
public void shouldMapFuture() {
final Future<Integer> future = Future.of(zZz(42)).map(i -> i * 2);
waitUntil(future::isCompleted);
assertThat(future.get()).isEqualTo(84);
}
@Test
public void shouldPeekFuture() {
final int[] consumer = new int[] { -1 };
Future.of(zZz(42)).peek(i -> consumer[0] = i);
waitUntil(() -> consumer[0] > 0);
assertThat(consumer[0]).isEqualTo(42);
}
@Test
public void shouldReturnIterator() {
final Iterator<Integer> it = Future.successful(42).iterator();
assertThat(it.hasNext()).isTrue();
assertThat(it.next()).isEqualTo(42);
assertThat(it.hasNext()).isFalse();
}
// TODO: also test what happens an exception occurs within one of these
// TODO: { filter, flatten, flatMap, get, isEmpty, iterator, map, peek }
// TODO: method calls and compare it with Scala
// -- map()
@Test
public void shouldMapTheHappyPath() {
final Future<String> testee = Future.of(zZz(1)).map(Object::toString);
waitUntil(testee::isCompleted);
assertCompleted(testee, "1");
}
@Test
public void shouldMapWhenCrashingDuringFutureComputation() {
final Future<String> testee = Future.<Integer> of(zZz(new Error())).map(Object::toString);
waitUntil(testee::isCompleted);
assertFailed(testee, Error.class);
}
@Test
public void shouldMapWhenCrashingDuringMapping() {
final Future<String> testee = Future.of(zZz(1)).map(i -> {
throw new IllegalStateException();
});
waitUntil(testee::isCompleted);
assertFailed(testee, IllegalStateException.class);
}
// -- mapTry()
@Test
public void shouldMapTryTheHappyPath() {
final Future<String> testee = Future.of(zZz(1)).mapTry(Object::toString);
waitUntil(testee::isCompleted);
assertCompleted(testee, "1");
}
@Test
public void shouldMapTryWhenCrashingDuringFutureComputation() {
final Future<String> testee = Future.<Integer> of(zZz(new Error())).mapTry(Object::toString);
waitUntil(testee::isCompleted);
assertFailed(testee, Error.class);
}
@Test
public void shouldMapTryWhenCrashingDuringMapping() {
final Future<String> testee = Future.of(zZz(1)).mapTry(i -> {
throw new IOException();
});
waitUntil(testee::isCompleted);
assertFailed(testee, IOException.class);
}
// -- equals
@Override
@Test
public void shouldRecognizeEqualObjects() {
// Future equality undefined
}
@Override
@Test
public void shouldRecognizeUnequalObjects() {
// Future equality undefined
}
// -- (helpers)
// checks the invariant for cancelled state
private void assertCancelled(Future<?> future) {
assertFailed(future, CancellationException.class);
}
private void assertFailed(Future<?> future, Class<? extends Throwable> exception) {
assertThat(future.isCompleted()).isTrue();
assertThat(future.getValue().get().failed().get()).isExactlyInstanceOf(exception);
}
// checks the invariant for cancelled state
private <T> void assertCompleted(Future<?> future, T value) {
assertThat(future.isCompleted()).isTrue();
assertThat(future.getValue()).isEqualTo(Option.some(Try.success(value)));
}
private <T> java.util.concurrent.Future<T> generateJavaFuture(T value, int waitPeriod) {
return generateJavaCompletableFuture(value, waitPeriod);
}
private <T> java.util.concurrent.CompletableFuture<T> generateJavaCompletableFuture(T value, int waitPeriod) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(waitPeriod);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
});
}
// -- spliterator
@Test
public void shouldHaveSizedSpliterator() {
assertThat(of(1).spliterator().hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED)).isTrue();
}
@Test
public void shouldHaveOrderedSpliterator() {
assertThat(of(1).spliterator().hasCharacteristics(Spliterator.ORDERED)).isTrue();
}
@Test
public void shouldReturnSizeWhenSpliterator() {
assertThat(of(1).spliterator().getExactSizeIfKnown()).isEqualTo(1);
}
}