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)); } }