package com.github.davidmoten.rx; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import java.sql.SQLException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import com.github.davidmoten.rx.Actions; import com.github.davidmoten.rx.Functions; import com.github.davidmoten.rx.RetryWhen; import com.github.davidmoten.rx.RetryWhen.ErrorAndDuration; import rx.Observable; import rx.functions.Action1; import rx.functions.Func1; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; import rx.schedulers.TestScheduler; public class RetryWhenTest { @Test public void testExponentialBackoff() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); final AtomicInteger logCalls = new AtomicInteger(); Action1<ErrorAndDuration> log = new Action1<ErrorAndDuration>() { @Override public void call(ErrorAndDuration e) { System.out.println("WARN: " + e.throwable().getMessage()); System.out.println("waiting for " + e.durationMs() + "ms"); logCalls.incrementAndGet(); } }; Observable.just(1, 2, 3) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen(RetryWhen.maxRetries(5).action(log) .exponentialBackoff(10, TimeUnit.MILLISECONDS).build()) // go .subscribe(ts); // check results ts.awaitTerminalEvent(); ts.assertError(ex); ts.assertValues(1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3); assertEquals(5, logCalls.get()); } @Test public void testWithScheduler() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); TestScheduler scheduler = new TestScheduler(); Observable.just(1, 2) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen(RetryWhen.maxRetries(2).action(log) .exponentialBackoff(1, TimeUnit.MINUTES).scheduler(scheduler).build()) // go .subscribe(ts); ts.assertValues(1, 2); ts.assertNotCompleted(); scheduler.advanceTimeBy(1, TimeUnit.MINUTES); ts.assertValues(1, 2, 1, 2); ts.assertNotCompleted(); // next wait is 2 seconds so advancing by 1 should do nothing scheduler.advanceTimeBy(1, TimeUnit.MINUTES); ts.assertValues(1, 2, 1, 2); ts.assertNotCompleted(); scheduler.advanceTimeBy(1, TimeUnit.MINUTES); ts.assertValues(1, 2, 1, 2, 1, 2); ts.assertError(ex); } @SuppressWarnings("unchecked") @Test public void testRetryWhenSpecificExceptionFails() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); TestScheduler scheduler = new TestScheduler(); Observable.just(1, 2) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen(RetryWhen.maxRetries(2).action(log) .exponentialBackoff(1, TimeUnit.MINUTES).scheduler(scheduler) .failWhenInstanceOf(IllegalArgumentException.class).build()) // go .subscribe(ts); ts.assertValues(1, 2); ts.assertError(ex); } @SuppressWarnings("unchecked") @Test public void testRetryWhenSpecificExceptionFailsBecauseIsNotInstanceOf() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); TestScheduler scheduler = new TestScheduler(); Observable.just(1, 2) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen(RetryWhen.maxRetries(2).action(log) .exponentialBackoff(1, TimeUnit.MINUTES).scheduler(scheduler) .retryWhenInstanceOf(SQLException.class).build()) // go .subscribe(ts); ts.assertValues(1, 2); ts.assertError(ex); } @SuppressWarnings("unchecked") @Test public void testRetryWhenSpecificExceptionAllowed() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); TestScheduler scheduler = new TestScheduler(); Observable.just(1, 2) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen(RetryWhen.maxRetries(2).action(log) .exponentialBackoff(1, TimeUnit.MINUTES).scheduler(scheduler) .retryWhenInstanceOf(IllegalArgumentException.class).build()) // go .subscribe(ts); ts.assertValues(1, 2); ts.assertNotCompleted(); } private static final Action1<ErrorAndDuration> log = new Action1<ErrorAndDuration>() { @Override public void call(ErrorAndDuration e) { System.out.println("WARN: " + e.throwable().getMessage()); System.out.println("waiting for " + e.durationMs() + "ms"); } }; @Test public void testRetryWhenSpecificExceptionAllowedUsePredicateReturnsTrue() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); TestScheduler scheduler = new TestScheduler(); Func1<Throwable, Boolean> predicate = new Func1<Throwable, Boolean>() { @Override public Boolean call(Throwable t) { return t instanceof IllegalArgumentException; } }; Observable.just(1, 2) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen( RetryWhen.maxRetries(2).action(log).exponentialBackoff(1, TimeUnit.MINUTES) .scheduler(scheduler).retryIf(predicate).build()) // go .subscribe(ts); ts.assertValues(1, 2); ts.assertNotCompleted(); } @Test public void testRetryWhenSpecificExceptionAllowedUsePredicateReturnsFalse() { Exception ex = new IllegalArgumentException("boo"); TestSubscriber<Integer> ts = TestSubscriber.create(); TestScheduler scheduler = new TestScheduler(); Func1<Throwable, Boolean> predicate = Functions.alwaysFalse(); Observable.just(1, 2) // force error after 3 emissions .concatWith(Observable.<Integer> error(ex)) // retry with backoff .retryWhen( RetryWhen.maxRetries(2).action(log).exponentialBackoff(1, TimeUnit.MINUTES) .scheduler(scheduler).retryIf(predicate).build()) // go .subscribe(ts); ts.assertValues(1, 2); ts.assertError(ex); } @Test public void testRetryWhenMultipleRetriesWorkOnSingleDelay() { AtomicInteger count = new AtomicInteger(); TestSubscriber<Object> ts = TestSubscriber.create(); Exception exception = new Exception("boo"); Observable.error(exception) // .doOnSubscribe(Actions.increment0(count)) // .retryWhen(RetryWhen // .delay(1, TimeUnit.MILLISECONDS) // .scheduler(Schedulers.trampoline()) // .maxRetries(10).build()) // .subscribe(ts); ts.assertTerminalEvent(); assertFalse(ts.getOnErrorEvents().isEmpty()); assertEquals(exception, ts.getOnErrorEvents().get(0)); assertEquals(11, count.get()); } }