package io.trane.future;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.junit.After;
import org.junit.Test;
public class FutureTest {
private <T> T get(Future<T> future) throws CheckedFutureException {
return future.get(Duration.ofMillis(1));
}
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Exception ex = new TestException();
@After
public void shutdownScheduler() {
scheduler.shutdown();
}
/*** true ***/
@Test
public void trueConst() throws CheckedFutureException {
assertEquals(true, get(Future.TRUE));
}
/*** false ***/
@Test
public void falseConst() throws CheckedFutureException {
assertEquals(false, get(Future.FALSE));
}
/*** never ***/
@Test
public void never() {
assertTrue(Future.never() instanceof NoFuture);
}
/*** apply ***/
@Test
public void applyValue() throws CheckedFutureException {
Integer value = 1;
Future<Integer> future = Future.apply(() -> value);
assertEquals(value, get(future));
}
@Test(expected = ArithmeticException.class)
public void applyException() throws CheckedFutureException {
Future<Integer> future = Future.apply(() -> 1 / 0);
get(future);
}
/*** flatApply ***/
@Test
public void flatApplyValue() throws CheckedFutureException {
Integer value = 1;
Future<Integer> future = Future.flatApply(() -> Future.value(value));
assertEquals(value, get(future));
}
@Test(expected = ArithmeticException.class)
public void flatApplyException() throws CheckedFutureException {
Future<Integer> future = Future.flatApply(() -> Future.value(1 / 0));
get(future);
}
/*** value ***/
@Test
public void value() throws CheckedFutureException {
Integer value = 1;
Future<Integer> future = Future.value(value);
assertEquals(value, get(future));
}
/*** exception ***/
@Test(expected = TestException.class)
public void exception() throws CheckedFutureException {
Future<Integer> future = Future.exception(ex);
get(future);
}
/*** flatten ***/
@Test
public void flatten() throws CheckedFutureException {
Future<Future<Integer>> future = Future.value(Future.value(1));
assertEquals(get(Future.flatten(future)), get(future.flatMap(f -> f)));
}
/*** tailrec ***/
Future<Integer> tailrecLoop(Future<Integer> f) {
return Tailrec.apply(() -> {
return f.flatMap(i -> {
if (i == 0)
return Future.value(0);
else
return tailrecLoop(Future.value(i - 1));
});
});
}
@Test
public void tailrec() throws CheckedFutureException {
assertEquals(new Integer(0), get(tailrecLoop(Future.value(20000))));
}
Future<Integer> tailrecLoopDelayed(Future<Integer> f) {
return Tailrec.apply(() -> {
return f.flatMap(i -> {
if (i == 0)
return Future.value(0);
else
return tailrecLoopDelayed(Future.value(i - 1).delayed(Duration.ofNanos(1), scheduler));
});
});
}
@Test
public void tailrecDelayed() throws CheckedFutureException {
assertEquals(new Integer(0), tailrecLoopDelayed(Future.value(20000)).get(Duration.ofSeconds(10)));
}
Future<Integer> nonTailrecLoop(Future<Integer> f) {
return f.flatMap(i -> {
if (i == 0)
return Future.value(0);
else
return nonTailrecLoop(Future.value(i - 1));
});
}
@Test(expected = StackOverflowError.class)
public void nonTailrec() throws CheckedFutureException {
assertEquals(new Integer(0), get(nonTailrecLoop(Future.value(20000))));
}
Future<Integer> nonTailrecLoopDelayed(Future<Integer> f) {
return f.flatMap(i -> {
if (i == 0)
return Future.value(0);
else
return nonTailrecLoop(Future.value(i - 1).delayed(Duration.ofNanos(1), scheduler));
});
}
@Test(expected = StackOverflowError.class)
public void nonTailrecDelayed() throws CheckedFutureException {
assertEquals(new Integer(0), get(nonTailrecLoop(Future.value(20000))));
}
/*** emptyList ***/
@Test
public void emptyList() throws CheckedFutureException {
Future<List<String>> future = Future.emptyList();
assertTrue(get(future).isEmpty());
}
@Test(expected = UnsupportedOperationException.class)
public void emptyListIsUnmodifiable() throws CheckedFutureException {
Future<List<String>> future = Future.emptyList();
get(future).add("s");
}
/*** emptyOptional ***/
@Test
public void emptyOptional() throws CheckedFutureException {
Future<Optional<String>> future = Future.emptyOptional();
assertFalse(get(future).isPresent());
}
/*** collect ***/
@Test
public void collectEmpty() {
Future<List<String>> future = Future.collect(new ArrayList<>());
assertEquals(Future.emptyList(), future);
}
@Test
public void collectOne() throws CheckedFutureException {
Future<List<Integer>> future = Future.collect(Arrays.asList(Future.value(1)));
Integer[] expected = { 1 };
assertArrayEquals(expected, get(future).toArray());
}
@Test
public void collectTwo() throws CheckedFutureException {
Future<List<Integer>> future = Future.collect(Arrays.asList(Future.value(1), Future.value(2)));
Integer[] expected = { 1, 2 };
assertArrayEquals(expected, get(future).toArray());
}
@Test
public void collectSatisfiedFutures() throws CheckedFutureException {
Future<List<Integer>> future = Future.collect(Arrays.asList(Future.value(1), Future.value(2), Future.value(3)));
Integer[] expected = { 1, 2, 3 };
assertArrayEquals(expected, get(future).toArray());
}
@Test(expected = TestException.class)
public void collectSatisfiedFuturesException() throws CheckedFutureException {
Future<List<Integer>> future = Future
.collect(Arrays.asList(Future.value(1), Future.exception(ex), Future.value(3)));
get(future);
}
@Test
public void collectPromises() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Promise<Integer> p3 = Promise.apply();
Future<List<Integer>> future = Future.collect(Arrays.asList(p1, p2, p3));
p1.setValue(1);
p2.setValue(2);
p3.setValue(3);
Integer[] expected = { 1, 2, 3 };
Object[] result = get(future).toArray();
assertArrayEquals(expected, result);
}
@Test(expected = TestException.class)
public void collectPromisesException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Promise<Integer> p3 = Promise.apply();
Future<List<Integer>> future = Future.collect(Arrays.asList(p1, p2, p3));
p1.setValue(1);
p2.setException(ex);
p3.setValue(3);
get(future);
}
@Test
public void collectMixed() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<List<Integer>> future = Future.collect(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setValue(2);
Integer[] expected = { 1, 2, 3 };
assertArrayEquals(expected, get(future).toArray());
}
@Test(expected = TestException.class)
public void collectMixedException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<List<Integer>> future = Future.collect(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setException(ex);
Integer[] expected = { 1, 2, 3 };
assertArrayEquals(expected, get(future).toArray());
}
@Test
public void collectConcurrentResults() throws CheckedFutureException {
ExecutorService ex = Executors.newFixedThreadPool(10);
try {
List<Promise<Integer>> promises = Stream.generate(() -> Promise.<Integer>apply()).limit(20000).collect(toList());
AtomicBoolean start = new AtomicBoolean();
Future<List<Integer>> future = Future.collect(promises);
for (Promise<Integer> p : promises) {
ex.submit(() -> {
while (true) {
if (start.get())
break;
}
p.setValue(p.hashCode());
});
}
start.set(true);
List<Integer> expected = promises.stream().map(p -> p.hashCode()).collect(toList());
List<Integer> result = future.get(Duration.ofSeconds(1));
assertArrayEquals(expected.toArray(), result.toArray());
} finally {
ex.shutdown();
}
}
@Test
public void collectInterrupts() {
AtomicReference<Throwable> p1Intr = new AtomicReference<>();
AtomicReference<Throwable> p2Intr = new AtomicReference<>();
AtomicReference<Throwable> p3Intr = new AtomicReference<>();
Promise<Integer> p1 = Promise.apply(p1Intr::set);
Promise<Integer> p2 = Promise.apply(p2Intr::set);
Promise<Integer> p3 = Promise.apply(p3Intr::set);
Future<List<Integer>> future = Future.collect(Arrays.asList(p1, p2, p3));
future.raise(ex);
assertEquals(ex, p1Intr.get());
assertEquals(ex, p2Intr.get());
assertEquals(ex, p3Intr.get());
}
@Test(expected = TimeoutException.class)
public void collectTimeout() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<List<Integer>> future = Future.collect(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
future.get(Duration.ofMillis(10));
}
/*** join ***/
@Test
public void joinEmpty() {
Future<Void> future = Future.join(new ArrayList<>());
assertEquals(Future.VOID, future);
}
@Test
public void joinOne() throws CheckedFutureException {
Future<Integer> f = Future.value(1);
assertEquals(f.voided(), Future.join(Arrays.asList(f)));
}
@Test
public void joinSatisfiedFutures() throws CheckedFutureException {
Future<Void> future = Future.join(Arrays.asList(Future.value(1), Future.value(2)));
get(future);
}
@Test(expected = TestException.class)
public void joinSatisfiedFuturesException() throws CheckedFutureException {
Future<Void> future = Future.join(Arrays.asList(Future.value(1), Future.exception(ex)));
get(future);
}
@Test
public void joinPromises() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Void> future = Future.join(Arrays.asList(p1, p2));
p1.setValue(1);
p2.setValue(2);
get(future);
}
@Test(expected = TestException.class)
public void joinPromisesException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Void> future = Future.join(Arrays.asList(p1, p2));
p1.setValue(1);
p2.setException(ex);
get(future);
}
@Test
public void joinMixed() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Void> future = Future.join(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setValue(2);
get(future);
}
@Test(expected = TestException.class)
public void joinMixedException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Void> future = Future.join(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setException(ex);
get(future);
}
@Test
public void joinConcurrentResults() throws CheckedFutureException {
List<Promise<Integer>> promises = Stream.generate(() -> Promise.<Integer>apply()).limit(20000).collect(toList());
ExecutorService ex = Executors.newFixedThreadPool(10);
try {
Future<Void> future = Future.join(promises);
for (Promise<Integer> p : promises) {
ex.submit(() -> {
p.setValue(p.hashCode());
});
}
future.get(Duration.ofSeconds(1));
} finally {
ex.shutdown();
}
}
@Test
public void joinInterrupts() {
AtomicReference<Throwable> p1Intr = new AtomicReference<>();
AtomicReference<Throwable> p2Intr = new AtomicReference<>();
Promise<Integer> p1 = Promise.apply(p1Intr::set);
Promise<Integer> p2 = Promise.apply(p2Intr::set);
Future<Void> future = Future.join(Arrays.asList(p1, p2));
future.raise(ex);
assertEquals(ex, p1Intr.get());
assertEquals(ex, p2Intr.get());
}
@Test(expected = TimeoutException.class)
public void joinTimeout() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Void> future = Future.join(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
future.get(Duration.ofMillis(10));
}
/*** selectIndex **/
@Test(expected = IllegalArgumentException.class)
public void selectIndexEmpty() throws CheckedFutureException {
get(Future.selectIndex(new ArrayList<>()));
}
@Test
public void selectIndexOne() throws CheckedFutureException {
Future<Integer> f = Future.selectIndex(Arrays.asList(Future.value(1)));
assertEquals(new Integer(0), get(f));
}
@Test
public void selectIndexSatisfiedFutures() throws CheckedFutureException {
Future<Integer> future = Future.selectIndex(Arrays.asList(Future.value(1), Future.value(2)));
assertEquals(new Integer(0), get(future));
}
@Test
public void selectIndexPromises() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.selectIndex(Arrays.asList(p1, p2));
p2.setValue(2);
assertEquals(new Integer(1), get(future));
}
@Test
public void selectIndexPromisesException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.selectIndex(Arrays.asList(p1, p2));
p1.setException(new Throwable());
assertEquals(new Integer(0), get(future));
}
@Test
public void selectIndexMixed() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.selectIndex(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setValue(2);
assertEquals(new Integer(2), get(future));
}
@Test
public void selectIndexMixedException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.selectIndex(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setException(new Throwable());
assertEquals(new Integer(2), get(future));
}
@Test
public void selectIndexInterrupts() {
AtomicReference<Throwable> p1Intr = new AtomicReference<>();
AtomicReference<Throwable> p2Intr = new AtomicReference<>();
Promise<Integer> p1 = Promise.apply(p1Intr::set);
Promise<Integer> p2 = Promise.apply(p2Intr::set);
Future<Integer> future = Future.selectIndex(Arrays.asList(p1, p2));
future.raise(ex);
assertEquals(ex, p1Intr.get());
assertEquals(ex, p2Intr.get());
}
@Test(expected = TimeoutException.class)
public void selectIndexTimeout() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.selectIndex(Arrays.asList(p1, p2));
future.get(Duration.ofMillis(100));
}
/*** firstCompletedOf **/
@Test(expected = IllegalArgumentException.class)
public void firstCompletedOfEmpty() throws CheckedFutureException {
get(Future.firstCompletedOf(new ArrayList<>()));
}
@Test
public void firstCompletedOfOne() throws CheckedFutureException {
Future<Integer> f = Future.firstCompletedOf(Arrays.asList(Future.value(1)));
assertEquals(new Integer(1), get(f));
}
@Test
public void firstCompletedOfSatisfiedFutures() throws CheckedFutureException {
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(Future.value(1), Future.value(2)));
assertEquals(new Integer(1), get(future));
}
@Test
public void firstCompletedOfPromises() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(p1, p2));
p2.setValue(2);
assertEquals(new Integer(2), get(future));
}
@Test(expected = TestException.class)
public void firstCompletedOfPromisesException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(p1, p2));
p1.setException(new TestException());
get(future);
}
@Test
public void firstCompletedOfMixed() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setValue(2);
assertEquals(new Integer(3), get(future));
}
@Test
public void firstCompletedOfMixedException() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(p1, p2, Future.value(3)));
p1.setValue(1);
p2.setException(new Throwable());
assertEquals(new Integer(3), get(future));
}
@Test
public void firstCompletedOfInterrupts() {
AtomicReference<Throwable> p1Intr = new AtomicReference<>();
AtomicReference<Throwable> p2Intr = new AtomicReference<>();
Promise<Integer> p1 = Promise.apply(p1Intr::set);
Promise<Integer> p2 = Promise.apply(p2Intr::set);
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(p1, p2));
future.raise(ex);
assertEquals(ex, p1Intr.get());
assertEquals(ex, p2Intr.get());
}
@Test(expected = TimeoutException.class)
public void firstCompletedOfTimeout() throws CheckedFutureException {
Promise<Integer> p1 = Promise.apply();
Promise<Integer> p2 = Promise.apply();
Future<Integer> future = Future.firstCompletedOf(Arrays.asList(p1, p2));
future.get(Duration.ofMillis(10));
}
/*** whileDo ***/
@Test
public void whileDo() throws CheckedFutureException {
int iterations = 200000;
AtomicInteger count = new AtomicInteger(iterations);
AtomicInteger callCount = new AtomicInteger(0);
Future<Void> future = Future.whileDo(() -> count.decrementAndGet() >= 0,
() -> Future.apply(() -> callCount.incrementAndGet()));
get(future);
assertEquals(-1, count.get());
assertEquals(iterations, callCount.get());
}
/*** delay ***/
@Test
public void delay() throws CheckedFutureException {
long delay = 10;
long start = System.currentTimeMillis();
Future.delay(Duration.ofMillis(delay), scheduler).get(Duration.ofMillis(200));
assertTrue(System.currentTimeMillis() - start >= delay);
}
/*** within ***/
@Test(expected = TimeoutException.class)
public void withinDefaultExceptionFailure() throws CheckedFutureException {
Future<Integer> f = (Promise.<Integer>apply()).within(Duration.ofMillis(1), scheduler);
get(f);
}
@Test
public void withinDefaultExceptionSuccess() throws CheckedFutureException {
Promise<Integer> p = Promise.<Integer>apply();
Future<Integer> f = p.within(Duration.ofMillis(10), scheduler);
p.setValue(1);
assertEquals(new Integer(1), get(f));
}
}