{
+ /**
+ * Handles the successful completion of a call.
+ */
+ void onSuccess(T result);
+}
diff --git a/src/test/java/net/jodah/recurrent/Asserts.java b/src/test/java/net/jodah/recurrent/Asserts.java
index 84d39360..66d56627 100644
--- a/src/test/java/net/jodah/recurrent/Asserts.java
+++ b/src/test/java/net/jodah/recurrent/Asserts.java
@@ -1,25 +1,57 @@
package net.jodah.recurrent;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
+
+import java.util.function.Consumer;
+
+import org.testng.Assert;
public class Asserts {
- public static void shouldFail(Runnable runnable, Class extends Exception> expected) {
+ @FunctionalInterface
+ public interface ThrowableRunnable {
+ void run() throws Throwable;
+ }
+
+ /**
+ * Asserts thrown exceptions, including checked exceptions, via a lambda.
+ *
+ * Sweet idea courtesy of Lukas Eder: http://blog.jooq.org/2014/05/23/java-8-friday-better-exceptions/
+ */
+ public static void assertThrows(Throwable throwable, ThrowableRunnable runnable) {
+ boolean fail = false;
try {
runnable.run();
- fail("A failure was expected");
- } catch (Exception e) {
- assertTrue(e.getClass().isAssignableFrom(expected), "The expected exception was not of the expected type " + e);
+ } catch (Throwable t) {
+ assertEquals(throwable, t, "The expected exception was not thrown");
}
+
+ if (fail)
+ Assert.fail("No exception was thrown");
+ }
+
+ /**
+ * Asserts thrown exceptions, including checked exceptions, via a lambda.
+ *
+ * Sweet idea courtesy of Lukas Eder: http://blog.jooq.org/2014/05/23/java-8-friday-better-exceptions/
+ */
+ public static void assertThrows(Class extends Throwable> throwable, ThrowableRunnable runnable) {
+ assertThrows(throwable, runnable, t -> {
+ });
}
- public static void shouldFail(Runnable runnable, Exception expected) {
+ public static void assertThrows(Class extends Throwable> throwable, ThrowableRunnable runnable,
+ Consumer exceptionConsumer) {
+ boolean fail = false;
try {
runnable.run();
- fail("A failure was expected");
- } catch (Exception e) {
- assertEquals(expected, e, "The expected exception was not of the expected type " + e);
+ fail = true;
+ } catch (Throwable t) {
+ if (!throwable.isInstance(t))
+ Assert.fail("Bad exception type");
+ exceptionConsumer.accept(t);
}
+
+ if (fail)
+ Assert.fail("No exception was thrown");
}
}
diff --git a/src/test/java/net/jodah/recurrent/RecurrentTest.java b/src/test/java/net/jodah/recurrent/RecurrentTest.java
index b4640e82..0714cc11 100644
--- a/src/test/java/net/jodah/recurrent/RecurrentTest.java
+++ b/src/test/java/net/jodah/recurrent/RecurrentTest.java
@@ -1,10 +1,11 @@
package net.jodah.recurrent;
-import static net.jodah.recurrent.Asserts.shouldFail;
+import static net.jodah.recurrent.Asserts.assertThrows;
import static net.jodah.recurrent.Testing.failAlways;
import static net.jodah.recurrent.Testing.failNTimes;
import static org.testng.Assert.assertEquals;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -12,7 +13,7 @@
import org.testng.annotations.Test;
import net.jodah.concurrentunit.Waiter;
-import net.jodah.recurrent.Testing.RecordingRunnable;
+import net.jodah.recurrent.Testing.RecordingCallable;
@Test
public class RecurrentTest {
@@ -25,26 +26,28 @@ protected void beforeMethod() {
waiter = new Waiter();
}
- public void testDoWithRetries() throws Throwable {
+ public void testWithRetries() throws Throwable {
// Fail twice then succeed
RuntimeException expectedFailure = new IllegalArgumentException();
- RecordingRunnable runnable = failNTimes(2, expectedFailure);
- Recurrent.withRetries(runnable, new RetryPolicy());
+ RecordingCallable runnable = failNTimes(2, expectedFailure, "foo");
+ assertEquals(Recurrent.withRetries(runnable, new RetryPolicy()), "foo");
assertEquals(runnable.failures, 2);
// Fail three times
- final RecordingRunnable runnable2 = failAlways(expectedFailure);
- shouldFail(() -> Recurrent.withRetries(runnable2, retryTwice), expectedFailure);
+ final RecordingCallable> runnable2 = failAlways(expectedFailure);
+ assertThrows(expectedFailure, () -> Recurrent.withRetries(runnable2, retryTwice));
assertEquals(runnable2.failures, 3);
}
- public void testDoWithRetriesWithScheduler() throws Throwable {
+ public void testWithRetriesWithScheduler() throws Throwable {
RuntimeException expectedFailure = new IllegalArgumentException();
// Fail twice then succeed
waiter.expectResume();
- RecordingRunnable runnable = failNTimes(2, expectedFailure);
+ RecordingCallable runnable = failNTimes(2, expectedFailure, "foo");
Recurrent.withRetries(runnable, new RetryPolicy(), executor).whenComplete((result, failure) -> {
+ waiter.assertEquals("foo", result);
+ waiter.assertNull(failure);
waiter.resume();
});
@@ -53,13 +56,30 @@ public void testDoWithRetriesWithScheduler() throws Throwable {
// Fail three times
waiter.expectResume();
- runnable = failAlways(expectedFailure);
- Recurrent.withRetries(runnable, retryTwice, executor).whenComplete((result, failure) -> {
- waiter.assertEquals(expectedFailure, failure);
+ RecordingCallable runnable2 = failAlways(expectedFailure);
+ assertThrows(ExecutionException.class,
+ () -> Recurrent.withRetries(runnable2, retryTwice, executor).whenComplete((result, failure) -> {
+ waiter.assertNull(result);
+ waiter.assertEquals(expectedFailure, failure);
+ waiter.resume();
+ }).get());
+
+ waiter.await();
+ assertEquals(runnable2.failures, 3);
+ }
+
+ public void testWithRetriesWithInitialSynchronousCall() throws Throwable {
+ RuntimeException expectedFailure = new IllegalArgumentException();
+ RecordingCallable runnable = failNTimes(2, expectedFailure, "foo");
+ waiter.expectResume();
+
+ // Fail twice then succeed
+ Recurrent.withRetries(runnable, new RetryPolicy(), executor, true).whenComplete((result, failure) -> {
+ waiter.assertEquals("foo", result);
+ waiter.assertNull(failure);
waiter.resume();
});
waiter.await();
- assertEquals(runnable.failures, 3);
}
}
diff --git a/src/test/java/net/jodah/recurrent/Testing.java b/src/test/java/net/jodah/recurrent/Testing.java
index 1df1cd36..59d897d5 100644
--- a/src/test/java/net/jodah/recurrent/Testing.java
+++ b/src/test/java/net/jodah/recurrent/Testing.java
@@ -1,28 +1,48 @@
package net.jodah.recurrent;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+
public class Testing {
- public static class RecordingRunnable implements Runnable {
+ public static class RecordingCallable implements Callable {
public int failures;
int n;
RuntimeException exception;
+ T eventualResult;
- RecordingRunnable(int n, RuntimeException exception) {
+ RecordingCallable(int n, RuntimeException exception, T eventualResult) {
this.n = n;
this.exception = exception;
+ this.eventualResult = eventualResult;
}
- public void run() {
- failures++;
- if (n == -1 || failures < n)
+ public T call() {
+ if (n == -1 || failures < n) {
+ failures++;
throw exception;
+ }
+ return eventualResult;
}
};
- public static RecordingRunnable failNTimes(int n, RuntimeException exception) {
- return new RecordingRunnable(n, exception);
+ public static void withExceptions(Runnable runnable) {
+ withExceptions(runnable, t -> {
+ });
+ }
+
+ public static void withExceptions(Runnable runnable, Consumer exceptionConsumer) {
+ try {
+ runnable.run();
+ } catch (Throwable t) {
+ exceptionConsumer.accept(t);
+ }
+ }
+
+ public static RecordingCallable failNTimes(int n, RuntimeException exception, T eventualResult) {
+ return new RecordingCallable(n, exception, eventualResult);
}
- public static RecordingRunnable failAlways(RuntimeException exception) {
- return new RecordingRunnable(-1, exception);
+ public static RecordingCallable failAlways(RuntimeException exception) {
+ return new RecordingCallable(-1, exception, null);
}
}