diff --git a/src/main/java/io/foldright/cffu/CompletableFutureUtils.java b/src/main/java/io/foldright/cffu/CompletableFutureUtils.java index 2729baa5..d3194386 100644 --- a/src/main/java/io/foldright/cffu/CompletableFutureUtils.java +++ b/src/main/java/io/foldright/cffu/CompletableFutureUtils.java @@ -171,6 +171,9 @@ private static void fill(CompletableFuture[] cfs, /** * Returns a new CompletableFuture that is completed * when any of the given CompletableFutures complete, with the same result. + * Otherwise, if it completed exceptionally, the returned CompletableFuture also does so, + * with a CompletionException holding this exception as its cause. + * If no CompletableFutures are provided, returns an incomplete CompletableFuture. *
* Same as {@link CompletableFuture#anyOf(CompletableFuture[])}, * but return result type is specified type instead of {@code Object}. diff --git a/src/test/java/io/foldright/cffu/CffuFactoryTest.java b/src/test/java/io/foldright/cffu/CffuFactoryTest.java index 4f8d4833..a9fb21c2 100644 --- a/src/test/java/io/foldright/cffu/CffuFactoryTest.java +++ b/src/test/java/io/foldright/cffu/CffuFactoryTest.java @@ -37,7 +37,7 @@ class CffuFactoryTest { static final double d = 42.1; static final RuntimeException rte = new RuntimeException("Bang"); - static final RuntimeException another_rte = new RuntimeException("BangBang"); + static final RuntimeException another_rte = new RuntimeException("BangAnother"); private static CffuFactory cffuFactory; diff --git a/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java b/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java index 0cf492a1..433297e4 100644 --- a/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java +++ b/src/test/java/io/foldright/cffu/CompletableFutureUtilsTest.java @@ -7,59 +7,443 @@ import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static io.foldright.cffu.CffuFactoryTest.*; -import static io.foldright.cffu.CompletableFutureUtils.anyOfSuccessWithType; -import static io.foldright.cffu.CompletableFutureUtils.anyOfWithType; +import static io.foldright.cffu.CompletableFutureUtils.*; import static io.foldright.test_utils.TestUtils.*; import static org.junit.jupiter.api.Assertions.*; class CompletableFutureUtilsTest { + //////////////////////////////////////////////////////////////////////////////// + //# allOf* methods + //////////////////////////////////////////////////////////////////////////////// + @Test - void test_allOfWithResult() throws Exception { - assertEquals(Arrays.asList(n, n + 1, n + 2), CompletableFutureUtils.allOfWithResult( + void test_allOf__success__trivial_case() throws Exception { + assertEquals(Arrays.asList(n, n + 1, n + 2), allOfWithResult( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1), + CompletableFuture.completedFuture(n + 2) + ).get()); + + assertEquals(Arrays.asList(n, n + 1), allOfWithResult( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1) + ).get()); + + assertEquals(Collections.singletonList(n), allOfWithResult( + CompletableFuture.completedFuture(n) + ).get()); + + assertEquals(Collections.emptyList(), allOfWithResult().get()); + + //////////////////////////////////////////////////////////////////////////////// + + assertEquals(Arrays.asList(n, n + 1, n + 2), allOfFastFailWithResult( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1), + CompletableFuture.completedFuture(n + 2) + ).get()); + + assertEquals(Arrays.asList(n, n + 1), allOfFastFailWithResult( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1) + ).get()); + + assertEquals(Collections.singletonList(n), allOfFastFailWithResult( + CompletableFuture.completedFuture(n) + ).get()); + + assertEquals(Collections.emptyList(), allOfFastFailWithResult().get()); + + //////////////////////////////////////////////////////////////////////////////// + + assertNull(allOfFastFail( CompletableFuture.completedFuture(n), CompletableFuture.completedFuture(n + 1), CompletableFuture.completedFuture(n + 2) ).get()); - assertTrue(CompletableFutureUtils.allOfWithResult().isDone()); + assertNull(allOfFastFail( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1) + ).get()); + + assertNull(allOfFastFail( + CompletableFuture.completedFuture(n) + ).get()); + + assertNull(allOfFastFail().get()); } @Test - void test_allOfWithResult_exceptionally() throws Exception { + void test_allOf__exceptionally() throws Exception { + // all failed + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + allOfWithResult( + createFailedFuture(rte), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first given cf argument win + // ❗dependent on the implementation behavior of `CF.allOf`️ + assertSame(rte, expected.getCause()); + } + + // all failed - concurrent + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + allOfWithResult( + CompletableFuture.supplyAsync(() -> { + sleep(100); + throw rte; + }), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first given cf argument win + // ❗dependent on the implementation behavior of `CF.allOf`️ + assertSame(rte, expected.getCause()); + } + + // success and failed try { - CompletableFutureUtils.allOfWithResult( + allOfWithResult( CompletableFuture.completedFuture(n), createFailedFuture(rte), - CompletableFuture.completedFuture(s) + CompletableFuture.completedFuture(s), + createFailedFuture(another_rte) + ).get(); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + // failed/incomplete/failed + try { + allOfWithResult( + CompletableFuture.completedFuture(n), + createFailedFuture(rte), + new CompletableFuture<>() + ).get(30, TimeUnit.MILLISECONDS); + + fail(); + } catch (TimeoutException expected) { + // do nothing + } + + // incomplete fail incomplete + try { + allOfWithResult( + createIncompleteFuture(), + createFailedFuture(rte), + createIncompleteFuture() + ).get(100, TimeUnit.MILLISECONDS); + + fail(); + } catch (TimeoutException expected) { + // do nothing + } + + //////////////////////////////////////////////////////////////////////////////// + + // all failed + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + allOfFastFailWithResult( + createFailedFuture(rte), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) ).get(); + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first complete(in time) cf argument win + // ❗dependent on the implementation behavior of `CF.anyOf`️ + assertSame(rte, expected.getCause()); + } + + // all failed - concurrent + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + allOfFastFailWithResult( + CompletableFuture.supplyAsync(() -> { + sleep(100); + throw rte; + }), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first complete(in time) cf argument win + // ❗dependent on the implementation behavior of `CF.anyOf`️ + assertSame(another_rte, expected.getCause()); + } + + // success and failed + try { + allOfFastFailWithResult( + CompletableFuture.completedFuture(n), + createFailedFuture(rte), + CompletableFuture.completedFuture(s), + createFailedFuture(another_rte) + ).get(); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + // failed/incomplete/failed + try { + allOfFastFailWithResult( + CompletableFuture.completedFuture(n), + createFailedFuture(rte), + new CompletableFuture<>() + ).get(30, TimeUnit.MILLISECONDS); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + // incomplete fail incomplete + try { + allOfFastFailWithResult( + createIncompleteFuture(), + createFailedFuture(rte), + createIncompleteFuture() + ).get(100, TimeUnit.MILLISECONDS); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + //////////////////////////////////////////////////////////////////////////////// + + // all failed + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + allOfFastFailWithResult( + createFailedFuture(rte), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first complete(in time) cf argument win + // ❗dependent on the implementation behavior of `CF.anyOf`️ + assertSame(rte, expected.getCause()); + } + + // all failed - concurrent + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + allOfFastFail( + CompletableFuture.supplyAsync(() -> { + sleep(100); + throw rte; + }), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first complete(in time) cf argument win + // ❗dependent on the implementation behavior of `CF.anyOf`️ + assertSame(another_rte, expected.getCause()); + } + + // success and failed + try { + allOfFastFail( + CompletableFuture.completedFuture(n), + createFailedFuture(rte), + CompletableFuture.completedFuture(s), + createFailedFuture(another_rte) + ).get(); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + // failed/incomplete/failed + try { + allOfFastFail( + CompletableFuture.completedFuture(n), + createFailedFuture(rte), + new CompletableFuture<>() + ).get(30, TimeUnit.MILLISECONDS); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + // incomplete fail incomplete + try { + allOfFastFail( + createIncompleteFuture(), + createFailedFuture(rte), + createIncompleteFuture() + ).get(100, TimeUnit.MILLISECONDS); + fail(); } catch (ExecutionException expected) { assertSame(rte, expected.getCause()); } } + //////////////////////////////////////////////////////////////////////////////// + //# anyOf* methods + //////////////////////////////////////////////////////////////////////////////// + @Test - void test_anyOfWithType() throws Exception { + void test_anyOf__success__trivial_case() throws Exception { + assertEquals(n, anyOfWithType( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1), + CompletableFuture.completedFuture(n + 2) + ).get()); + assertEquals(n, anyOfWithType( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1) + ).get()); + + assertEquals(n, anyOfWithType( + CompletableFuture.completedFuture(n) + ).get()); + assertFalse(anyOfWithType().isDone()); + + // success with incomplete CF assertEquals(n, anyOfWithType( createIncompleteFuture(), createIncompleteFuture(), CompletableFuture.completedFuture(n) ).get()); - assertFalse(anyOfWithType().isDone()); + //////////////////////////////////////// + + assertEquals(n, anyOfSuccessWithType( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1), + CompletableFuture.completedFuture(n + 2) + ).get()); + assertEquals(n, anyOfSuccessWithType( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1) + ).get()); + + assertEquals(n, anyOfSuccessWithType( + CompletableFuture.completedFuture(n) + ).get()); + try { + anyOfSuccessWithType().get(); + + fail(); + } catch (ExecutionException expected) { + assertSame(NoCfsProvidedException.class, expected.getCause().getClass()); + } + + // success with incomplete CF + assertEquals(n, anyOfSuccessWithType( + createIncompleteFuture(), + createIncompleteFuture(), + CompletableFuture.completedFuture(n) + ).get()); + + //////////////////////////////////////// + + assertEquals(n, anyOfSuccess( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1), + CompletableFuture.completedFuture(n + 2) + ).get()); + assertEquals(n, anyOfSuccess( + CompletableFuture.completedFuture(n), + CompletableFuture.completedFuture(n + 1) + ).get()); + + assertEquals(n, anyOfSuccess( + CompletableFuture.completedFuture(n) + ).get()); + try { + anyOfSuccess().get(); + + fail(); + } catch (ExecutionException expected) { + assertSame(NoCfsProvidedException.class, expected.getCause().getClass()); + } + + // success with incomplete CF + assertEquals(n, anyOfSuccess( + createIncompleteFuture(), + createIncompleteFuture(), + CompletableFuture.completedFuture(n) + ).get()); + } @Test - void test_anyOfWithType_exceptionally() throws Exception { - // first exceptionally completed cffuAnyOf cf win, - // even later cfs normally completed! + void test_anyOf__exceptionally() throws Exception { + // NOTE: skip anyOfSuccess test intended + // same implementation with anyOfSuccessWithType + + //////////////////////////////////////// + + // all failed + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + anyOfWithType( + CompletableFuture.supplyAsync(() -> { + sleep(100); + throw rte; + }), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first complete(in time) cf argument win + // ❗dependent on the implementation behavior of `CF.anyOf`️ + assertSame(another_rte, expected.getCause()); + } + // incomplete fail incomplete try { anyOfWithType( createIncompleteFuture(), @@ -72,20 +456,51 @@ void test_anyOfWithType_exceptionally() throws Exception { assertSame(rte, expected.getCause()); } - // first normally completed cffuAnyOf cf win, - // even later cfs exceptionally completed! + //////////////////////////////////////// - assertEquals(n, anyOfWithType( - createIncompleteFuture(), - CompletableFuture.completedFuture(n), - createIncompleteFuture() - ).get()); + // all failed + try { + RuntimeException ex1 = new RuntimeException("ex1"); + RuntimeException ex2 = new RuntimeException("ex2"); + anyOfSuccessWithType( + CompletableFuture.supplyAsync(() -> { + sleep(100); + throw rte; + }), + createFailedFuture(another_rte), + createFailedFuture(ex1), + createFailedFuture(ex2) + ).get(); + + fail(); + } catch (ExecutionException expected) { + // anyOfSuccessWithType: the ex of first given cf argument win + // ❗dependent on the implementation behavior of `CF.allOf`️ + assertSame(rte, expected.getCause()); + } + // incomplete fail incomplete + try { + anyOfSuccessWithType( + createIncompleteFuture(), + createFailedFuture(rte), + createIncompleteFuture() + ).get(30, TimeUnit.MILLISECONDS); + + fail(); + } catch (TimeoutException expected) { + // do nothing + } } @Test - void test_anyOfSuccess__trivial_case() throws Exception { - // success then success - assertEquals(n, anyOfSuccessWithType( + void test_anyOf__concurrent() throws Exception { + // NOTE: skip anyOfSuccess test intended + // same implementation with anyOfSuccessWithType + + //////////////////////////////////////// + + // incomplete/wait-success then success + assertEquals(n, anyOfWithType( createIncompleteFuture(), createIncompleteFuture(), CompletableFuture.supplyAsync(() -> { @@ -95,8 +510,21 @@ void test_anyOfSuccess__trivial_case() throws Exception { CompletableFuture.completedFuture(n) ).get()); + // wait/success then success + assertEquals(n, anyOfWithType( + CompletableFuture.supplyAsync(() -> { + sleep(300); + return another_n; + }), + CompletableFuture.supplyAsync(() -> { + sleep(300); + return another_n; + }), + CompletableFuture.completedFuture(n) + ).get()); + // success then failed - assertEquals(n, anyOfSuccessWithType( + assertEquals(n, anyOfWithType( createIncompleteFuture(), createIncompleteFuture(), CompletableFuture.supplyAsync(() -> { @@ -106,7 +534,36 @@ void test_anyOfSuccess__trivial_case() throws Exception { CompletableFuture.completedFuture(n) ).get()); - // all success + // failed then success + try { + anyOfWithType( + CompletableFuture.supplyAsync(() -> { + sleep(100); + return n; + }), + createFailedFuture(rte), + createFailedFuture(rte) + ).get(); + + fail(); + } catch (ExecutionException expected) { + assertSame(rte, expected.getCause()); + } + + //////////////////////////////////////// + + // incomplete/wait-success then success + assertEquals(n, anyOfSuccessWithType( + createIncompleteFuture(), + createIncompleteFuture(), + CompletableFuture.supplyAsync(() -> { + sleep(300); + return another_n; + }), + CompletableFuture.completedFuture(n) + ).get()); + + // wait/success then success assertEquals(n, anyOfSuccessWithType( CompletableFuture.supplyAsync(() -> { sleep(300); @@ -119,49 +576,31 @@ void test_anyOfSuccess__trivial_case() throws Exception { CompletableFuture.completedFuture(n) ).get()); + // success then failed + assertEquals(n, anyOfSuccessWithType( + createIncompleteFuture(), + createIncompleteFuture(), + CompletableFuture.supplyAsync(() -> { + sleep(300); + throw rte; + }), + CompletableFuture.completedFuture(n) + ).get()); - assertTrue(anyOfSuccessWithType().isDone()); - try { - anyOfSuccessWithType().get(); - - fail(); - } catch (ExecutionException expected) { - assertSame(NoCfsProvidedException.class, expected.getCause().getClass()); - } - } - - @Test - void test_anyOfSuccess__fastFailed_Then_success() throws Exception { + // failed then success assertEquals(n, anyOfSuccessWithType( - createFailedFuture(rte), CompletableFuture.supplyAsync(() -> { sleep(100); return n; }), + createFailedFuture(rte), createFailedFuture(rte) ).get()); } - @Test - void test_anyOfSuccess__allFailed() throws Exception { - RuntimeException ex1 = new RuntimeException(); - RuntimeException ex2 = new RuntimeException(); - try { - anyOfSuccessWithType( - CompletableFuture.supplyAsync(() -> { - sleep(100); - throw rte; - }), - createFailedFuture(ex1), - createFailedFuture(ex2), - createFailedFuture(another_rte) - ).get(); - - fail(); - } catch (ExecutionException expected) { - assertSame(rte, expected.getCause()); - } - } + //////////////////////////////////////////////////////////////////////////////// + //# combine methods + //////////////////////////////////////////////////////////////////////////////// @Test void test_combine() throws Exception {