RetryingFunction implements Function<U, V> { @SuppressWarnings("squid:S1181") @Override public V apply(final U input) { int retryAttempts = 0; while (true) { try { return baseFunction.apply(input); } catch (final Throwable t) { if (!exceptionClass.isAssignableFrom(t.getClass()) || retryAttempts == maxRetries) { throwIfUnchecked(t); throw new RuntimeException(t); } Retries.randomDelay(maxDelayBetweenRetries); retryAttempts++; } } } RetryingFunction(
      final Function<U, V> baseFunction,
      final Class<? extends Throwable> exceptionClass,
      final int maxRetries,
      final int maxDelayBetweenRetries); @SuppressWarnings("squid:S1181") // Yes we really do want to catch Throwable @Override V apply(final U input);  }
@Test(expected = RetryableException.class) public void testNoRetries() { new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 0, 10) .apply(null); }
@Test public void testSuccessAfterOneRetry() { new RetryingFunction<>(this::succeedAfterOneFailure, RetryableException.class, 1, 10) .apply(null); }
@Test(expected = RetryableException.class) public void testFailureAfterOneRetry() { new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 1, 10) .apply(null); }
@Test public void testFailureAfterTwoRetries() { new RetryingFunction<>(this::succeedAfterTwoFailures, RetryableException.class, 2, 10) .apply(null); }
@Test(expected = NonRetryableException.class) public void testFailureWithNonRetryableFailure() { new RetryingFunction<>(this::failCompletely, RetryableException.class, 2, 10).apply(null); }