From cb05a2614c11ebb7b86e723556f98e50546a50ed Mon Sep 17 00:00:00 2001 From: David Karnok Date: Mon, 5 Mar 2018 17:41:09 +0100 Subject: [PATCH] 2.x: Improve coverage & related cleanup 03/05 (#5891) * 2.x: Improve coverage & related cleanup 03/05 * Fix camelCase local variable naming errors in tests. --- .../flowable/BlockingFlowableIterable.java | 4 +- .../flowable/FlowableBufferBoundary.java | 8 +- .../operators/flowable/FlowableCache.java | 4 +- .../flowable/FlowableCombineLatest.java | 4 +- .../operators/flowable/FlowableGroupJoin.java | 8 +- .../flowable/FlowableMergeWithMaybe.java | 6 +- .../flowable/FlowableMergeWithSingle.java | 6 +- .../operators/flowable/FlowableReplay.java | 40 +- .../flowable/FlowableSamplePublisher.java | 8 +- .../operators/flowable/FlowableSkipUntil.java | 4 +- .../operators/flowable/FlowableTakeUntil.java | 4 +- .../operators/flowable/FlowableTimeout.java | 4 +- .../flowable/FlowableWindowBoundary.java | 4 +- .../flowable/FlowableWithLatestFromMany.java | 4 +- .../maybe/MaybeDelayOtherPublisher.java | 4 +- .../operators/maybe/MaybeFromFuture.java | 15 +- .../maybe/MaybeTakeUntilPublisher.java | 4 +- .../maybe/MaybeTimeoutPublisher.java | 4 +- .../operators/observable/ObservableSkip.java | 9 +- .../operators/parallel/ParallelJoin.java | 4 +- .../parallel/ParallelReduceFull.java | 4 +- .../parallel/ParallelSortedJoin.java | 4 +- .../single/SingleDelayWithSingle.java | 2 +- .../operators/single/SingleTakeUntil.java | 4 +- .../schedulers/SchedulerPoolFactory.java | 99 +-- .../schedulers/TrampolineScheduler.java | 14 +- .../subscribers/ForEachWhileSubscriber.java | 4 +- .../subscribers/FutureSubscriber.java | 4 +- .../subscriptions/SubscriptionHelper.java | 20 + src/test/java/io/reactivex/TestHelper.java | 104 +++ .../flowable/FlowableBackpressureTests.java | 136 ++-- .../flowable/FlowableCovarianceTest.java | 6 +- .../observers/BlockingFirstObserverTest.java | 48 ++ .../observers/BlockingObserverTest.java | 38 + .../completable/CompletableAmbTest.java | 24 + .../CompletableFromActionTest.java | 40 +- .../CompletableFromCallableTest.java | 37 +- .../CompletableFromPublisherTest.java | 11 + .../CompletableFromRunnableTest.java | 38 +- .../completable/CompletableTimeoutTest.java | 25 + .../CompletableToFlowableTest.java | 33 + .../operators/flowable/FlowableAmbTest.java | 30 +- .../flowable/FlowableCombineLatestTest.java | 6 +- .../flowable/FlowableConcatMapTest.java | 42 ++ .../flowable/FlowableDebounceTest.java | 180 +++-- .../flowable/FlowableFilterTest.java | 16 +- .../flowable/FlowableGroupByTest.java | 2 +- .../flowable/FlowableGroupJoinTest.java | 37 + .../operators/flowable/FlowableHideTest.java | 83 +++ .../flowable/FlowableIgnoreElementsTest.java | 19 + .../flowable/FlowableIntervalTest.java | 22 +- .../flowable/FlowableMergeDelayErrorTest.java | 4 +- .../operators/flowable/FlowableMergeTest.java | 4 +- .../flowable/FlowableRepeatTest.java | 6 +- .../flowable/FlowableReplayTest.java | 8 + .../operators/flowable/FlowableRetryTest.java | 54 +- .../flowable/FlowableSampleTest.java | 12 + .../flowable/FlowableSkipLastTest.java | 12 + .../operators/flowable/FlowableSkipTest.java | 13 +- .../operators/flowable/FlowableTakeTest.java | 24 + .../flowable/FlowableTakeUntilTest.java | 11 + .../flowable/FlowableTimeIntervalTest.java | 12 +- .../flowable/FlowableToListTest.java | 19 + .../flowable/FlowableUnsubscribeOnTest.java | 20 +- .../operators/maybe/MaybeAmbTest.java | 21 + .../operators/maybe/MaybeDelayOtherTest.java | 26 + .../operators/maybe/MaybeFromActionTest.java | 28 + .../operators/maybe/MaybeFromFutureTest.java | 61 ++ .../maybe/MaybeFromRunnableTest.java | 28 + .../operators/maybe/MaybeMapTest.java | 33 + .../observable/ObservableDebounceTest.java | 76 +- .../observable/ObservableDoAfterNextTest.java | 26 +- .../observable/ObservableGroupJoinTest.java | 37 + .../observable/ObservableIntervalTest.java | 15 + .../observable/ObservablePublishTest.java | 18 + .../observable/ObservableRepeatTest.java | 6 +- .../observable/ObservableRetryTest.java | 40 +- .../observable/ObservableSampleTest.java | 12 + .../observable/ObservableSkipLastTest.java | 12 + .../observable/ObservableSkipTest.java | 12 + .../observable/ObservableTakeUntilTest.java | 11 + .../ObservableTimeIntervalTest.java | 11 + .../observable/ObservableTimerTest.java | 15 + .../observable/ObservableToListTest.java | 19 + .../ObservableUnsubscribeOnTest.java | 20 +- .../operators/single/SingleDelayTest.java | 31 +- .../schedulers/SchedulerPoolFactoryTest.java | 117 +++ .../TrampolineSchedulerInternalTest.java | 48 ++ .../DeferredScalarSubscriberTest.java | 12 +- .../SubscriberResourceWrapperTest.java | 30 + .../subscriptions/SubscriptionHelperTest.java | 29 +- .../internal/util/QueueDrainHelperTest.java | 677 +++++++++++++++++- .../observable/ObservableCovarianceTest.java | 6 +- 93 files changed, 2523 insertions(+), 433 deletions(-) create mode 100644 src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java create mode 100644 src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java create mode 100644 src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java create mode 100644 src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java create mode 100644 src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java create mode 100644 src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java index 478aa2281e..2eaaa5ffd3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java @@ -125,9 +125,7 @@ public T next() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(batchSize); - } + SubscriptionHelper.setOnce(this, s, batchSize); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java index acf35be0cd..317ad249ee 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java @@ -327,9 +327,7 @@ static final class BufferOpenSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -378,9 +376,7 @@ static final class BufferCloseSubscriber> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java index a4294474be..24cad0d2f7 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java @@ -180,9 +180,7 @@ public void removeChild(ReplaySubscription p) { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(connection, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(connection, s, Long.MAX_VALUE); } /** diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java index cbdda4a4bc..6ed1f116ef 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java @@ -516,9 +516,7 @@ static final class CombineLatestInnerSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(prefetch); - } + SubscriptionHelper.setOnce(this, s, prefetch); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java index 499bc38ff7..c0bfb84e6f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java @@ -417,9 +417,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -470,9 +468,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java index 68c8e2c8f5..7a48d38875 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java @@ -98,10 +98,8 @@ static final class MergeWithObserver extends AtomicInteger } @Override - public void onSubscribe(Subscription d) { - if (SubscriptionHelper.setOnce(mainSubscription, d)) { - d.request(prefetch); - } + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(mainSubscription, s, prefetch); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java index b14c5c9b22..fca1812b78 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java @@ -98,10 +98,8 @@ static final class MergeWithObserver extends AtomicInteger } @Override - public void onSubscribe(Subscription d) { - if (SubscriptionHelper.setOnce(mainSubscription, d)) { - d.request(prefetch); - } + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(mainSubscription, s, prefetch); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java index 8d1f2eaaad..3005eb11c8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java @@ -526,36 +526,16 @@ static final class InnerSubscription extends AtomicLong implements Subscripti public void request(long n) { // ignore negative requests if (SubscriptionHelper.validate(n)) { - // In general, RxJava doesn't prevent concurrent requests (with each other or with - // a cancel) so we need a CAS-loop, but we need to handle - // request overflow and cancelled/not requested state as well. - for (;;) { - // get the current request amount - long r = get(); - // if child called cancel() do nothing - if (r == CANCELLED) { - return; - } - // ignore zero requests except any first that sets in zero - if (r >= 0L && n == 0) { - return; - } - // otherwise, increase the request count - long u = BackpressureHelper.addCap(r, n); - - // try setting the new request value - if (compareAndSet(r, u)) { - // increment the total request counter - BackpressureHelper.add(totalRequested, n); - // if successful, notify the parent dispatcher this child can receive more - // elements - parent.manageRequests(); - - parent.buffer.replay(this); - return; - } - // otherwise, someone else changed the state (perhaps a concurrent - // request or cancellation) so retry + // add to the current requested and cap it at MAX_VALUE + // except when there was a concurrent cancellation + if (BackpressureHelper.addCancel(this, n) != CANCELLED) { + // increment the total request counter + BackpressureHelper.add(totalRequested, n); + // if successful, notify the parent dispatcher this child can receive more + // elements + parent.manageRequests(); + // try replaying any cached content + parent.buffer.replay(this); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java index de9a71b223..a75f4c1e05 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java @@ -93,8 +93,8 @@ public void onComplete() { completeMain(); } - boolean setOther(Subscription o) { - return SubscriptionHelper.setOnce(other, o); + void setOther(Subscription o) { + SubscriptionHelper.setOnce(other, o, Long.MAX_VALUE); } @Override @@ -150,9 +150,7 @@ static final class SamplerSubscriber implements FlowableSubscriber { @Override public void onSubscribe(Subscription s) { - if (parent.setOther(s)) { - s.request(Long.MAX_VALUE); - } + parent.setOther(s); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java index 857623d1ac..0f980ac883 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java @@ -114,9 +114,7 @@ final class OtherSubscriber extends AtomicReference @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java index c5352264ff..0743eb1d00 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java @@ -99,9 +99,7 @@ final class OtherSubscriber extends AtomicReference implements Flo @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java index e01fb5b525..99def36def 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java @@ -343,9 +343,7 @@ static final class TimeoutConsumer extends AtomicReference @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java index 8c8164f76f..46e445bd26 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java @@ -95,9 +95,7 @@ static final class WindowBoundaryMainSubscriber @Override public void onSubscribe(Subscription d) { - if (SubscriptionHelper.setOnce(upstream, d)) { - d.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(upstream, d, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java index 874bf92ccf..2566fe3e5c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java @@ -268,9 +268,7 @@ static final class WithLatestInnerSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java index b14b17a671..61ae58f62a 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java @@ -120,9 +120,7 @@ static final class OtherSubscriber extends @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java index 1dd41d38bc..7a206007aa 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java @@ -17,6 +17,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; /** * Waits until the source Future completes or the wait times out; treats a {@code null} @@ -50,17 +51,11 @@ protected void subscribeActual(MaybeObserver observer) { } else { v = future.get(timeout, unit); } - } catch (InterruptedException ex) { - if (!d.isDisposed()) { - observer.onError(ex); - } - return; - } catch (ExecutionException ex) { - if (!d.isDisposed()) { - observer.onError(ex.getCause()); + } catch (Throwable ex) { + if (ex instanceof ExecutionException) { + ex = ex.getCause(); } - return; - } catch (TimeoutException ex) { + Exceptions.throwIfFatal(ex); if (!d.isDisposed()) { observer.onError(ex); } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java index 3ddd26d570..6db80da42f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java @@ -132,9 +132,7 @@ static final class TakeUntilOtherMaybeObserver @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java index 79c2aea625..801e646e7b 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java @@ -155,9 +155,7 @@ static final class TimeoutOtherMaybeObserver @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java index 5eeefde388..944506365a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java @@ -15,6 +15,7 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; public final class ObservableSkip extends AbstractObservableWithUpstream { final long n; @@ -40,9 +41,11 @@ static final class SkipObserver implements Observer, Disposable { } @Override - public void onSubscribe(Disposable s) { - this.d = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.d, d)) { + this.d = d; + actual.onSubscribe(this); + } } @Override diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java index 04ab4033cc..7a29e1bed4 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java @@ -516,9 +516,7 @@ static final class JoinInnerSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(prefetch); - } + SubscriptionHelper.setOnce(this, s, prefetch); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java index 98d536045c..1505ea0ff3 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java @@ -180,9 +180,7 @@ static final class ParallelReduceFullInnerSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java index 33abbd132f..43ef716d1a 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java @@ -280,9 +280,7 @@ static final class SortedJoinInnerSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java index 32e2462513..b42f678878 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java @@ -54,7 +54,7 @@ static final class OtherObserver @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.set(this, d)) { + if (DisposableHelper.setOnce(this, d)) { actual.onSubscribe(this); } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java b/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java index 8c340e4edf..6d1183e80c 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java @@ -139,9 +139,7 @@ static final class TakeUntilOtherSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java index 7aa5bb6a19..b0b1339d29 100644 --- a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java @@ -20,8 +20,6 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import io.reactivex.plugins.RxJavaPlugins; - /** * Manages the creating of ScheduledExecutorServices and sets up purging. */ @@ -57,22 +55,25 @@ private SchedulerPoolFactory() { * Starts the purge thread if not already started. */ public static void start() { - if (!PURGE_ENABLED) { - return; - } - for (;;) { - ScheduledExecutorService curr = PURGE_THREAD.get(); - if (curr != null && !curr.isShutdown()) { - return; - } - ScheduledExecutorService next = Executors.newScheduledThreadPool(1, new RxThreadFactory("RxSchedulerPurge")); - if (PURGE_THREAD.compareAndSet(curr, next)) { + tryStart(PURGE_ENABLED); + } - next.scheduleAtFixedRate(new ScheduledTask(), PURGE_PERIOD_SECONDS, PURGE_PERIOD_SECONDS, TimeUnit.SECONDS); + static void tryStart(boolean purgeEnabled) { + if (purgeEnabled) { + for (;;) { + ScheduledExecutorService curr = PURGE_THREAD.get(); + if (curr != null) { + return; + } + ScheduledExecutorService next = Executors.newScheduledThreadPool(1, new RxThreadFactory("RxSchedulerPurge")); + if (PURGE_THREAD.compareAndSet(curr, next)) { - return; - } else { - next.shutdownNow(); + next.scheduleAtFixedRate(new ScheduledTask(), PURGE_PERIOD_SECONDS, PURGE_PERIOD_SECONDS, TimeUnit.SECONDS); + + return; + } else { + next.shutdownNow(); + } } } } @@ -81,7 +82,7 @@ public static void start() { * Stops the purge thread. */ public static void shutdown() { - ScheduledExecutorService exec = PURGE_THREAD.get(); + ScheduledExecutorService exec = PURGE_THREAD.getAndSet(null); if (exec != null) { exec.shutdownNow(); } @@ -89,25 +90,42 @@ public static void shutdown() { } static { - boolean purgeEnable = true; - int purgePeriod = 1; - Properties properties = System.getProperties(); - if (properties.containsKey(PURGE_ENABLED_KEY)) { - purgeEnable = Boolean.getBoolean(PURGE_ENABLED_KEY); - } + PurgeProperties pp = new PurgeProperties(); + pp.load(properties); - if (purgeEnable && properties.containsKey(PURGE_PERIOD_SECONDS_KEY)) { - purgePeriod = Integer.getInteger(PURGE_PERIOD_SECONDS_KEY, purgePeriod); - } - - PURGE_ENABLED = purgeEnable; - PURGE_PERIOD_SECONDS = purgePeriod; + PURGE_ENABLED = pp.purgeEnable; + PURGE_PERIOD_SECONDS = pp.purgePeriod; start(); } + static final class PurgeProperties { + + boolean purgeEnable; + + int purgePeriod; + + void load(Properties properties) { + if (properties.containsKey(PURGE_ENABLED_KEY)) { + purgeEnable = Boolean.parseBoolean(properties.getProperty(PURGE_ENABLED_KEY)); + } else { + purgeEnable = true; + } + + if (purgeEnable && properties.containsKey(PURGE_PERIOD_SECONDS_KEY)) { + try { + purgePeriod = Integer.parseInt(properties.getProperty(PURGE_PERIOD_SECONDS_KEY)); + } catch (NumberFormatException ex) { + purgePeriod = 1; + } + } else { + purgePeriod = 1; + } + } + } + /** * Creates a ScheduledExecutorService with the given factory. * @param factory the thread factory @@ -115,27 +133,26 @@ public static void shutdown() { */ public static ScheduledExecutorService create(ThreadFactory factory) { final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory); - if (PURGE_ENABLED && exec instanceof ScheduledThreadPoolExecutor) { + tryPutIntoPool(PURGE_ENABLED, exec); + return exec; + } + + static void tryPutIntoPool(boolean purgeEnabled, ScheduledExecutorService exec) { + if (purgeEnabled && exec instanceof ScheduledThreadPoolExecutor) { ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) exec; POOLS.put(e, exec); } - return exec; } static final class ScheduledTask implements Runnable { @Override public void run() { - try { - for (ScheduledThreadPoolExecutor e : new ArrayList(POOLS.keySet())) { - if (e.isShutdown()) { - POOLS.remove(e); - } else { - e.purge(); - } + for (ScheduledThreadPoolExecutor e : new ArrayList(POOLS.keySet())) { + if (e.isShutdown()) { + POOLS.remove(e); + } else { + e.purge(); } - } catch (Throwable e) { - // Exceptions.throwIfFatal(e); nowhere to go - RxJavaPlugins.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java b/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java index 20421072e5..49a053f9db 100644 --- a/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java @@ -190,14 +190,12 @@ public void run() { long t = worker.now(TimeUnit.MILLISECONDS); if (execTime > t) { long delay = execTime - t; - if (delay > 0) { - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - RxJavaPlugins.onError(e); - return; - } + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + RxJavaPlugins.onError(e); + return; } } diff --git a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java index 812a0d601e..c296ffe088 100644 --- a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java @@ -48,9 +48,7 @@ public ForEachWhileSubscriber(Predicate onNext, @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java index b2a06f53e6..9537556008 100644 --- a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java @@ -110,9 +110,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this.s, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this.s, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java index 071aefb683..cddd53b8d1 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java @@ -239,4 +239,24 @@ public static void deferredRequest(AtomicReference field, AtomicLo } } } + + /** + * Atomically sets the subscription on the field if it is still null and issues a positive request + * to the given {@link Subscription}. + *

+ * If the field is not null and doesn't contain the {@link #CANCELLED} + * instance, the {@link #reportSubscriptionSet()} is called. + * @param field the target field + * @param s the new subscription to set + * @param request the amount to request, positive (not verified) + * @return true if the operation succeeded, false if the target field was not null. + * @since 2.1.11 + */ + public static boolean setOnce(AtomicReference field, Subscription s, long request) { + if (setOnce(field, s)) { + s.request(request); + return true; + } + return false; + } } diff --git a/src/test/java/io/reactivex/TestHelper.java b/src/test/java/io/reactivex/TestHelper.java index b0bbf1d9a6..b6660c19d6 100644 --- a/src/test/java/io/reactivex/TestHelper.java +++ b/src/test/java/io/reactivex/TestHelper.java @@ -2050,6 +2050,110 @@ protected void subscribeActual(CompletableObserver observer) { } } + /** + * Check if the given transformed reactive type reports multiple onSubscribe calls to + * RxJavaPlugins. + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeCompletableToFlowable(Function> transform) { + List errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Completable source = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + try { + Disposable d1 = Disposables.empty(); + + observer.onSubscribe(d1); + + Disposable d2 = Disposables.empty(); + + observer.onSubscribe(d2); + + b[0] = d1.isDisposed(); + b[1] = d2.isDisposed(); + } finally { + cdl.countDown(); + } + } + }; + + Publisher out = transform.apply(source); + + out.subscribe(NoOpConsumer.INSTANCE); + + try { + assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + + assertEquals("First disposed?", false, b[0]); + assertEquals("Second not disposed?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } finally { + RxJavaPlugins.reset(); + } + } + + /** + * Check if the given transformed reactive type reports multiple onSubscribe calls to + * RxJavaPlugins. + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeCompletableToObservable(Function> transform) { + List errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Completable source = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + try { + Disposable d1 = Disposables.empty(); + + observer.onSubscribe(d1); + + Disposable d2 = Disposables.empty(); + + observer.onSubscribe(d2); + + b[0] = d1.isDisposed(); + b[1] = d2.isDisposed(); + } finally { + cdl.countDown(); + } + } + }; + + ObservableSource out = transform.apply(source); + + out.subscribe(NoOpConsumer.INSTANCE); + + try { + assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + + assertEquals("First disposed?", false, b[0]); + assertEquals("Second not disposed?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } finally { + RxJavaPlugins.reset(); + } + } + /** * Check if the operator applied to a Maybe source propagates dispose properly. * @param the source value type diff --git a/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java b/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java index 8e658cd5d7..9ad78fd1e4 100644 --- a/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java @@ -80,20 +80,20 @@ public void doAfterTest() { @Test public void testObserveOn() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); - incrementingIntegers(c).observeOn(Schedulers.computation()).take(NUM).subscribe(ts); + incrementingIntegers(c).observeOn(Schedulers.computation()).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testObserveOn => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < Flowable.bufferSize() * 4); } @Test public void testObserveOnWithSlowConsumer() { - int NUM = (int) (Flowable.bufferSize() * 0.2); + int num = (int) (Flowable.bufferSize() * 0.2); AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); incrementingIntegers(c).observeOn(Schedulers.computation()).map( @@ -108,28 +108,28 @@ public Integer apply(Integer i) { return i; } } - ).take(NUM).subscribe(ts); + ).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testObserveOnWithSlowConsumer => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < Flowable.bufferSize() * 2); } @Test public void testMergeSync() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); Flowable merged = Flowable.merge(incrementingIntegers(c1), incrementingIntegers(c2)); - merged.take(NUM).subscribe(ts); + merged.take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); - System.out.println("Expected: " + NUM + " got: " + ts.valueCount()); + System.out.println("Expected: " + num + " got: " + ts.valueCount()); System.out.println("testMergeSync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit @@ -139,7 +139,7 @@ public void testMergeSync() { @Test public void testMergeAsync() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -147,11 +147,11 @@ public void testMergeAsync() { incrementingIntegers(c1).subscribeOn(Schedulers.computation()), incrementingIntegers(c2).subscribeOn(Schedulers.computation())); - merged.take(NUM).subscribe(ts); + merged.take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testMergeAsync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit @@ -167,7 +167,7 @@ public void testMergeAsyncThenObserveOnLoop() { System.out.println("testMergeAsyncThenObserveOnLoop >> " + i); } // Verify there is no MissingBackpressureException - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); @@ -178,7 +178,7 @@ public void testMergeAsyncThenObserveOnLoop() { merged .observeOn(Schedulers.io()) - .take(NUM) + .take(num) .subscribe(ts); @@ -186,13 +186,13 @@ public void testMergeAsyncThenObserveOnLoop() { ts.assertComplete(); ts.assertNoErrors(); System.out.println("testMergeAsyncThenObserveOn => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); } } @Test public void testMergeAsyncThenObserveOn() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -200,11 +200,11 @@ public void testMergeAsyncThenObserveOn() { incrementingIntegers(c1).subscribeOn(Schedulers.computation()), incrementingIntegers(c2).subscribeOn(Schedulers.computation())); - merged.observeOn(Schedulers.newThread()).take(NUM).subscribe(ts); + merged.observeOn(Schedulers.newThread()).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testMergeAsyncThenObserveOn => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit @@ -215,7 +215,7 @@ public void testMergeAsyncThenObserveOn() { @Test public void testFlatMapSync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -226,20 +226,20 @@ public Publisher apply(Integer i) { return incrementingIntegers(new AtomicInteger()).take(10); } }) - .take(NUM).subscribe(ts); + .take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testFlatMapSync => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); - // expect less than 1 buffer since the flatMap is emitting 10 each time, so it is NUM/10 that will be taken. + assertEquals(num, ts.valueCount()); + // expect less than 1 buffer since the flatMap is emitting 10 each time, so it is num/10 that will be taken. assertTrue(c.get() < Flowable.bufferSize()); } @Test @Ignore("The test is non-deterministic and can't be made deterministic") public void testFlatMapAsync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -254,12 +254,12 @@ public Publisher apply(Integer i) { } } ) - .take(NUM).subscribe(ts); + .take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testFlatMapAsync => Received: " + ts.valueCount() + " Emitted: " + c.get() + " Size: " + Flowable.bufferSize()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // even though we only need 10, it will request at least Flowable.bufferSize(), and then as it drains keep requesting more // and then it will be non-deterministic when the take() causes the unsubscribe as it is scheduled on 10 different schedulers (threads) // normally this number is ~250 but can get up to ~1200 when Flowable.bufferSize() == 1024 @@ -268,7 +268,7 @@ public Publisher apply(Integer i) { @Test public void testZipSync() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -283,20 +283,20 @@ public Integer apply(Integer t1, Integer t2) { } }); - zipped.take(NUM) + zipped.take(num) .subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testZipSync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c1.get() < Flowable.bufferSize() * 7); assertTrue(c2.get() < Flowable.bufferSize() * 7); } @Test public void testZipAsync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -310,11 +310,11 @@ public Integer apply(Integer t1, Integer t2) { } }); - zipped.take(NUM).subscribe(ts); + zipped.take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testZipAsync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); int max = Flowable.bufferSize() * 5; assertTrue("" + c1.get() + " >= " + max, c1.get() < max); assertTrue("" + c2.get() + " >= " + max, c2.get() < max); @@ -324,16 +324,16 @@ public Integer apply(Integer t1, Integer t2) { public void testSubscribeOnScheduling() { // in a loop for repeating the concurrency in this to increase chance of failure for (int i = 0; i < 100; i++) { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); ConcurrentLinkedQueue threads = new ConcurrentLinkedQueue(); TestSubscriber ts = new TestSubscriber(); // observeOn is there to make it async and need backpressure - incrementingIntegers(c, threads).subscribeOn(Schedulers.computation()).observeOn(Schedulers.computation()).take(NUM).subscribe(ts); + incrementingIntegers(c, threads).subscribeOn(Schedulers.computation()).observeOn(Schedulers.computation()).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testSubscribeOnScheduling => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < Flowable.bufferSize() * 4); Thread first = null; for (Thread t : threads) { @@ -354,7 +354,7 @@ public void testSubscribeOnScheduling() { @Test public void testTakeFilterSkipChainAsync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); incrementingIntegers(c).observeOn(Schedulers.computation()) @@ -364,19 +364,19 @@ public void testTakeFilterSkipChainAsync() { public boolean test(Integer i) { return i > 11000; } - }).take(NUM).subscribe(ts); + }).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); // emit 10000 that are skipped // emit next 1000 that are filtered out - // take NUM - // so emitted is at least 10000+1000+NUM + extra for buffer size/threshold + // take num + // so emitted is at least 10000+1000+num + extra for buffer size/threshold int expected = 10000 + 1000 + Flowable.bufferSize() * 3 + Flowable.bufferSize() / 2; System.out.println("testTakeFilterSkipChain => Received: " + ts.valueCount() + " Emitted: " + c.get() + " Expected: " + expected); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < expected); } @@ -525,23 +525,23 @@ public void testOnBackpressureDrop() { if (System.currentTimeMillis() - t > TimeUnit.SECONDS.toMillis(9)) { break; } - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); firehose(c).onBackpressureDrop() .observeOn(Schedulers.computation()) - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List onNextEvents = ts.values(); - assertEquals(NUM, onNextEvents.size()); + assertEquals(num, onNextEvents.size()); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println("testOnBackpressureDrop => Received: " + onNextEvents.size() + " Emitted: " + c.get() + " Last value: " + lastEvent); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); } } @@ -551,7 +551,7 @@ public void testOnBackpressureDropWithAction() { final AtomicInteger emitCount = new AtomicInteger(); final AtomicInteger dropCount = new AtomicInteger(); final AtomicInteger passCount = new AtomicInteger(); - final int NUM = Flowable.bufferSize() * 3; // > 1 so that take doesn't prevent buffer overflow + final int num = Flowable.bufferSize() * 3; // > 1 so that take doesn't prevent buffer overflow TestSubscriber ts = new TestSubscriber(); firehose(emitCount) @@ -569,19 +569,19 @@ public void accept(Integer v) { }) .observeOn(Schedulers.computation()) .map(SLOW_PASS_THRU) - .take(NUM).subscribe(ts); + .take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List onNextEvents = ts.values(); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println(testName.getMethodName() + " => Received: " + onNextEvents.size() + " Passed: " + passCount.get() + " Dropped: " + dropCount.get() + " Emitted: " + emitCount.get() + " Last value: " + lastEvent); - assertEquals(NUM, onNextEvents.size()); - // in reality, NUM < passCount - assertTrue(NUM <= passCount.get()); + assertEquals(num, onNextEvents.size()); + // in reality, num < passCount + assertTrue(num <= passCount.get()); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); assertTrue(0 < dropCount.get()); assertEquals(emitCount.get(), passCount.get() + dropCount.get()); } @@ -590,22 +590,22 @@ public void accept(Integer v) { @Test(timeout = 10000) public void testOnBackpressureDropSynchronous() { for (int i = 0; i < 100; i++) { - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); firehose(c).onBackpressureDrop() - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List onNextEvents = ts.values(); - assertEquals(NUM, onNextEvents.size()); + assertEquals(num, onNextEvents.size()); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println("testOnBackpressureDrop => Received: " + onNextEvents.size() + " Emitted: " + c.get() + " Last value: " + lastEvent); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); } } @@ -613,7 +613,7 @@ public void testOnBackpressureDropSynchronous() { public void testOnBackpressureDropSynchronousWithAction() { for (int i = 0; i < 100; i++) { final AtomicInteger dropCount = new AtomicInteger(); - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); firehose(c).onBackpressureDrop(new Consumer() { @@ -622,18 +622,18 @@ public void accept(Integer j) { dropCount.incrementAndGet(); } }) - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List onNextEvents = ts.values(); - assertEquals(NUM, onNextEvents.size()); + assertEquals(num, onNextEvents.size()); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println("testOnBackpressureDrop => Received: " + onNextEvents.size() + " Dropped: " + dropCount.get() + " Emitted: " + c.get() + " Last value: " + lastEvent); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); // no drop in synchronous mode assertEquals(0, dropCount.get()); assertEquals(c.get(), onNextEvents.size()); @@ -642,7 +642,7 @@ public void accept(Integer j) { @Test(timeout = 2000) public void testOnBackpressureBuffer() { - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber ts = new TestSubscriber(); @@ -654,14 +654,14 @@ public boolean test(Integer t1) { }) .onBackpressureBuffer() .observeOn(Schedulers.computation()) - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testOnBackpressureBuffer => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // it buffers, so we should get the right value sequentially - assertEquals(NUM - 1, ts.values().get(NUM - 1).intValue()); + assertEquals(num - 1, ts.values().get(num - 1).intValue()); } /** @@ -694,8 +694,8 @@ public void request(long n) { if (threadsSeen != null) { threadsSeen.offer(Thread.currentThread()); } - long _c = BackpressureHelper.add(requested, n); - if (_c == 0) { + long c = BackpressureHelper.add(requested, n); + if (c == 0) { while (!cancelled) { counter.incrementAndGet(); s.onNext(i++); diff --git a/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java b/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java index 2e8e5f1af6..80e1785eff 100644 --- a/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java @@ -48,7 +48,7 @@ public void testCovarianceOfFrom() { @Test public void testSortedList() { - Comparator SORT_FUNCTION = new Comparator() { + Comparator sortFunction = new Comparator() { @Override public int compare(Media t1, Media t2) { return 1; @@ -57,11 +57,11 @@ public int compare(Media t1, Media t2) { // this one would work without the covariance generics Flowable o = Flowable.just(new Movie(), new TVSeason(), new Album()); - o.toSortedList(SORT_FUNCTION); + o.toSortedList(sortFunction); // this one would NOT work without the covariance generics Flowable o2 = Flowable.just(new Movie(), new ActionMovie(), new HorrorMovie()); - o2.toSortedList(SORT_FUNCTION); + o2.toSortedList(sortFunction); } @Test diff --git a/src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java b/src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java new file mode 100644 index 0000000000..6f69e1fd73 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; + +public class BlockingFirstObserverTest { + + @Test + public void firstValueOnly() { + BlockingFirstObserver bf = new BlockingFirstObserver(); + Disposable d = Disposables.empty(); + bf.onSubscribe(d); + + bf.onNext(1); + + assertTrue(d.isDisposed()); + + assertEquals(1, bf.value.intValue()); + assertEquals(0, bf.getCount()); + + bf.onNext(2); + + assertEquals(1, bf.value.intValue()); + assertEquals(0, bf.getCount()); + + bf.onError(new TestException()); + assertEquals(1, bf.value.intValue()); + assertNull(bf.error); + assertEquals(0, bf.getCount()); + } +} diff --git a/src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java b/src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java new file mode 100644 index 0000000000..72f2ab6bd6 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.Test; + +public class BlockingObserverTest { + + @Test + public void dispose() { + Queue q = new ArrayDeque(); + + BlockingObserver bo = new BlockingObserver(q); + + bo.dispose(); + + assertEquals(BlockingObserver.TERMINATED, q.poll()); + + bo.dispose(); + + assertNull(q.poll()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java index dbb9bf79db..992acdc6e6 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java @@ -16,11 +16,14 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.internal.operators.completable.CompletableAmb.Amb; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -162,4 +165,25 @@ public void ambArrayOrder() { Completable.ambArray(Completable.complete(), error).test().assertComplete(); } + @Test + public void ambRace() { + TestObserver to = new TestObserver(); + to.onSubscribe(Disposables.empty()); + + CompositeDisposable cd = new CompositeDisposable(); + AtomicBoolean once = new AtomicBoolean(); + Amb a = new Amb(once, cd, to); + + a.onComplete(); + a.onComplete(); + + List errors = TestHelper.trackPluginErrors(); + try { + a.onError(new TestException()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java index 6a590afbbb..42b7025dcf 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java @@ -13,12 +13,15 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; -import io.reactivex.functions.Action; +import static org.junit.Assert.assertEquals; + import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; -import static org.junit.Assert.assertEquals; +import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; public class CompletableFromActionTest { @Test(expected = NullPointerException.class) @@ -97,4 +100,35 @@ public void run() throws Exception { .test() .assertFailure(UnsupportedOperationException.class); } + + @Test + public void fromActionDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + calls.incrementAndGet(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } + + @Test + public void fromActionErrorsDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + calls.incrementAndGet(); + throw new TestException(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java index cdd987c8aa..b0d551a64e 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java @@ -13,23 +13,22 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import io.reactivex.Observer; -import io.reactivex.TestHelper; -import io.reactivex.disposables.Disposable; -import io.reactivex.observers.TestObserver; -import io.reactivex.schedulers.Schedulers; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.TestException; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; public class CompletableFromCallableTest { @Test(expected = NullPointerException.class) @@ -164,4 +163,20 @@ public String answer(InvocationOnMock invocation) throws Throwable { verify(observer).onSubscribe(any(Disposable.class)); verifyNoMoreInteractions(observer); } + + @Test + public void fromActionErrorsDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + calls.incrementAndGet(); + throw new TestException(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java index b972e3427e..03db0c5fe0 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java @@ -16,6 +16,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.functions.Function; public class CompletableFromPublisherTest { @Test(expected = NullPointerException.class) @@ -48,4 +49,14 @@ public void fromPublisherThrows() { public void dispose() { TestHelper.checkDisposed(Completable.fromPublisher(Flowable.just(1))); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowableToCompletable(new Function, Completable>() { + @Override + public Completable apply(Flowable f) throws Exception { + return Completable.fromPublisher(f); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java index 48bb226df2..849fd9a81b 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java @@ -13,11 +13,14 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; +import static org.junit.Assert.assertEquals; + import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; -import static org.junit.Assert.assertEquals; +import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; public class CompletableFromRunnableTest { @Test(expected = NullPointerException.class) @@ -96,4 +99,35 @@ public void run() { .test() .assertFailure(UnsupportedOperationException.class); } + + @Test + public void fromRunnableDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + calls.incrementAndGet(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } + + @Test + public void fromRunnableErrorsDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + calls.incrementAndGet(); + throw new TestException(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java index fe6a8478dc..3df78394f3 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java @@ -17,12 +17,15 @@ import java.util.List; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Action; +import io.reactivex.internal.operators.completable.CompletableTimeout.TimeOutObserver; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; @@ -146,4 +149,26 @@ public void run() { } } } + + @Test + public void ambRace() { + TestObserver to = new TestObserver(); + to.onSubscribe(Disposables.empty()); + + CompositeDisposable cd = new CompositeDisposable(); + AtomicBoolean once = new AtomicBoolean(); + TimeOutObserver a = new TimeOutObserver(cd, once, to); + + a.onComplete(); + a.onComplete(); + + List errors = TestHelper.trackPluginErrors(); + try { + a.onError(new TestException()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java new file mode 100644 index 0000000000..54661a96db --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import org.junit.Test; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +public class CompletableToFlowableTest { + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletableToFlowable(new Function>() { + @Override + public Publisher apply(Completable c) throws Exception { + return c.toFlowable(); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java index ba6b837a37..7a1fcdf2ce 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java @@ -94,16 +94,16 @@ public void run() { @Test public void testAmb() { - Flowable Flowable1 = createFlowable(new String[] { + Flowable flowable1 = createFlowable(new String[] { "1", "11", "111", "1111" }, 2000, null); - Flowable Flowable2 = createFlowable(new String[] { + Flowable flowable2 = createFlowable(new String[] { "2", "22", "222", "2222" }, 1000, null); - Flowable Flowable3 = createFlowable(new String[] { + Flowable flowable3 = createFlowable(new String[] { "3", "33", "333", "3333" }, 3000, null); @SuppressWarnings("unchecked") - Flowable o = Flowable.ambArray(Flowable1, - Flowable2, Flowable3); + Flowable o = Flowable.ambArray(flowable1, + flowable2, flowable3); @SuppressWarnings("unchecked") DefaultSubscriber observer = mock(DefaultSubscriber.class); @@ -124,16 +124,16 @@ public void testAmb() { public void testAmb2() { IOException expectedException = new IOException( "fake exception"); - Flowable Flowable1 = createFlowable(new String[] {}, + Flowable flowable1 = createFlowable(new String[] {}, 2000, new IOException("fake exception")); - Flowable Flowable2 = createFlowable(new String[] { + Flowable flowable2 = createFlowable(new String[] { "2", "22", "222", "2222" }, 1000, expectedException); - Flowable Flowable3 = createFlowable(new String[] {}, + Flowable flowable3 = createFlowable(new String[] {}, 3000, new IOException("fake exception")); @SuppressWarnings("unchecked") - Flowable o = Flowable.ambArray(Flowable1, - Flowable2, Flowable3); + Flowable o = Flowable.ambArray(flowable1, + flowable2, flowable3); @SuppressWarnings("unchecked") DefaultSubscriber observer = mock(DefaultSubscriber.class); @@ -152,16 +152,16 @@ public void testAmb2() { @Test public void testAmb3() { - Flowable Flowable1 = createFlowable(new String[] { + Flowable flowable1 = createFlowable(new String[] { "1" }, 2000, null); - Flowable Flowable2 = createFlowable(new String[] {}, + Flowable flowable2 = createFlowable(new String[] {}, 1000, null); - Flowable Flowable3 = createFlowable(new String[] { + Flowable flowable3 = createFlowable(new String[] { "3" }, 3000, null); @SuppressWarnings("unchecked") - Flowable o = Flowable.ambArray(Flowable1, - Flowable2, Flowable3); + Flowable o = Flowable.ambArray(flowable1, + flowable2, flowable3); @SuppressWarnings("unchecked") DefaultSubscriber observer = mock(DefaultSubscriber.class); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java index f43a58dedc..1abc20903f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java @@ -751,11 +751,11 @@ public void testBackpressureLoop() { public void testBackpressure() { BiFunction combineLatestFunction = getConcatStringIntegerCombineLatestFunction(); - int NUM = Flowable.bufferSize() * 4; + int num = Flowable.bufferSize() * 4; TestSubscriber ts = new TestSubscriber(); Flowable.combineLatest( Flowable.just("one", "two"), - Flowable.range(2, NUM), + Flowable.range(2, num), combineLatestFunction ) .observeOn(Schedulers.computation()) @@ -767,7 +767,7 @@ public void testBackpressure() { assertEquals("two2", events.get(0)); assertEquals("two3", events.get(1)); assertEquals("two4", events.get(2)); - assertEquals(NUM, events.size()); + assertEquals(num, events.size()); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java new file mode 100644 index 0000000000..c1ad560478 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import org.junit.Test; + +import io.reactivex.internal.operators.flowable.FlowableConcatMap.WeakScalarSubscription; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatMapTest { + + @Test + public void weakSubscriptionRequest() { + TestSubscriber ts = new TestSubscriber(0); + WeakScalarSubscription ws = new WeakScalarSubscription(1, ts); + ts.onSubscribe(ws); + + ws.request(0); + + ts.assertEmpty(); + + ws.request(1); + + ts.assertResult(1); + + ws.request(1); + + ts.assertResult(1); + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java index db108689ce..b87cd59fbf 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java @@ -14,36 +14,38 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.disposables.*; +import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.processors.PublishProcessor; +import io.reactivex.processors.*; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subscribers.TestSubscriber; public class FlowableDebounceTest { private TestScheduler scheduler; - private Subscriber observer; + private Subscriber Subscriber; private Scheduler.Worker innerScheduler; @Before public void before() { scheduler = new TestScheduler(); - observer = TestHelper.mockSubscriber(); + Subscriber = TestHelper.mockSubscriber(); innerScheduler = scheduler.createWorker(); } @@ -51,25 +53,25 @@ public void before() { public void testDebounceWithCompleted() { Flowable source = Flowable.unsafeCreate(new Publisher() { @Override - public void subscribe(Subscriber observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. - publishNext(observer, 400, "two"); // Should be published since "three" will arrive after the timeout expires. - publishNext(observer, 900, "three"); // Should be skipped since onComplete will arrive before the timeout expires. - publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. + public void subscribe(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. + publishNext(subscriber, 400, "two"); // Should be published since "three" will arrive after the timeout expires. + publishNext(subscriber, 900, "three"); // Should be skipped since onComplete will arrive before the timeout expires. + publishCompleted(subscriber, 1000); // Should be published as soon as the timeout expires. } }); Flowable sampled = source.debounce(400, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(Subscriber); scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(Subscriber); // must go to 800 since it must be 400 after when two is sent, which is at 400 scheduler.advanceTimeTo(800, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(Subscriber, times(1)).onNext("two"); scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(Subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -77,29 +79,29 @@ public void subscribe(Subscriber observer) { public void testDebounceNeverEmits() { Flowable source = Flowable.unsafeCreate(new Publisher() { @Override - public void subscribe(Subscriber observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); // all should be skipped since they are happening faster than the 200ms timeout - publishNext(observer, 100, "a"); // Should be skipped - publishNext(observer, 200, "b"); // Should be skipped - publishNext(observer, 300, "c"); // Should be skipped - publishNext(observer, 400, "d"); // Should be skipped - publishNext(observer, 500, "e"); // Should be skipped - publishNext(observer, 600, "f"); // Should be skipped - publishNext(observer, 700, "g"); // Should be skipped - publishNext(observer, 800, "h"); // Should be skipped - publishCompleted(observer, 900); // Should be published as soon as the timeout expires. + publishNext(subscriber, 100, "a"); // Should be skipped + publishNext(subscriber, 200, "b"); // Should be skipped + publishNext(subscriber, 300, "c"); // Should be skipped + publishNext(subscriber, 400, "d"); // Should be skipped + publishNext(subscriber, 500, "e"); // Should be skipped + publishNext(subscriber, 600, "f"); // Should be skipped + publishNext(subscriber, 700, "g"); // Should be skipped + publishNext(subscriber, 800, "h"); // Should be skipped + publishCompleted(subscriber, 900); // Should be published as soon as the timeout expires. } }); Flowable sampled = source.debounce(200, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(Subscriber); scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(0)).onNext(anyString()); + InOrder inOrder = inOrder(Subscriber); + inOrder.verify(Subscriber, times(0)).onNext(anyString()); scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(Subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -107,51 +109,51 @@ public void subscribe(Subscriber observer) { public void testDebounceWithError() { Flowable source = Flowable.unsafeCreate(new Publisher() { @Override - public void subscribe(Subscriber observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); Exception error = new TestException(); - publishNext(observer, 100, "one"); // Should be published since "two" will arrive after the timeout expires. - publishNext(observer, 600, "two"); // Should be skipped since onError will arrive before the timeout expires. - publishError(observer, 700, error); // Should be published as soon as the timeout expires. + publishNext(subscriber, 100, "one"); // Should be published since "two" will arrive after the timeout expires. + publishNext(subscriber, 600, "two"); // Should be skipped since onError will arrive before the timeout expires. + publishError(subscriber, 700, error); // Should be published as soon as the timeout expires. } }); Flowable sampled = source.debounce(400, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(Subscriber); scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(Subscriber); // 100 + 400 means it triggers at 500 scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onNext("one"); + inOrder.verify(Subscriber).onNext("one"); scheduler.advanceTimeTo(701, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onError(any(TestException.class)); + inOrder.verify(Subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); } - private void publishCompleted(final Subscriber observer, long delay) { + private void publishCompleted(final Subscriber subscriber, long delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } - private void publishError(final Subscriber observer, long delay, final Exception error) { + private void publishError(final Subscriber subscriber, long delay, final Exception error) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onError(error); + subscriber.onError(error); } }, delay, TimeUnit.MILLISECONDS); } - private void publishNext(final Subscriber observer, final long delay, final T value) { + private void publishNext(final Subscriber subscriber, final long delay, final T value) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } @@ -338,12 +340,12 @@ public void badSource() { try { new Flowable() { @Override - protected void subscribeActual(Subscriber observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onComplete(); - observer.onNext(1); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onNext(1); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .debounce(1, TimeUnit.SECONDS, new TestScheduler()) @@ -407,4 +409,82 @@ public void backpressureNoRequestTimed() { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(MissingBackpressureException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.debounce(Functions.justFunction(Flowable.never())); + } + }); + } + + @Test + public void disposeInOnNext() { + final TestSubscriber to = new TestSubscriber(); + + BehaviorProcessor.createDefault(1) + .debounce(new Function>() { + @Override + public Flowable apply(Integer o) throws Exception { + to.cancel(); + return Flowable.never(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void disposedInOnComplete() { + final TestSubscriber to = new TestSubscriber(); + + new Flowable() { + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + to.cancel(); + subscriber.onComplete(); + } + } + .debounce(Functions.justFunction(Flowable.never())) + .subscribeWith(to) + .assertEmpty(); + } + + @Test + public void emitLate() { + final AtomicReference> ref = new AtomicReference>(); + + TestSubscriber to = Flowable.range(1, 2) + .debounce(new Function>() { + @Override + public Flowable apply(Integer o) throws Exception { + if (o != 1) { + return Flowable.never(); + } + return new Flowable() { + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }; + } + }) + .test(); + + ref.get().onNext(1); + + to + .assertResult(2); + } + + @Test + public void badRequestReported() { + TestHelper.assertBadRequestReported(Flowable.never().debounce(Functions.justFunction(Flowable.never()))); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java index eaa2d87515..bda46e6ac1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java @@ -39,7 +39,7 @@ public class FlowableFilterTest { @Test public void testFilter() { Flowable w = Flowable.just("one", "two", "three"); - Flowable Flowable = w.filter(new Predicate() { + Flowable flowable = w.filter(new Predicate() { @Override public boolean test(String t1) { @@ -47,15 +47,15 @@ public boolean test(String t1) { } }); - Subscriber Subscriber = TestHelper.mockSubscriber(); + Subscriber subscriber = TestHelper.mockSubscriber(); - Flowable.subscribe(Subscriber); + flowable.subscribe(subscriber); - verify(Subscriber, Mockito.never()).onNext("one"); - verify(Subscriber, times(1)).onNext("two"); - verify(Subscriber, Mockito.never()).onNext("three"); - verify(Subscriber, Mockito.never()).onError(any(Throwable.class)); - verify(Subscriber, times(1)).onComplete(); + verify(subscriber, Mockito.never()).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } /** diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java index cccfb38a0f..e5a18c80af 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java @@ -2021,7 +2021,7 @@ public V remove(Object key) { @Override public void putAll(Map m) { for (Entry entry: m.entrySet()) { - put(entry.getKey(), entry.getValue()); + put(entry.getKey(), entry.getValue()); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java index fb5d55d265..9bc958c730 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java @@ -15,6 +15,7 @@ */ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -28,6 +29,7 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableGroupJoin.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.TestSubscriber; @@ -687,4 +689,39 @@ public Flowable apply(Object r, Flowable l) throws Exception { to.assertResult(2); } + + @Test + public void leftRightState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightSubscriber o = new LeftRightSubscriber(js, false); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + o.dispose(); + + assertTrue(o.isDisposed()); + + verify(js).innerValue(false, 1); + verify(js).innerValue(false, 2); + } + + @Test + public void leftRightEndState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightEndSubscriber o = new LeftRightEndSubscriber(js, false, 0); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + assertTrue(o.isDisposed()); + + verify(js).innerClose(false, o); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java new file mode 100644 index 0000000000..df67bccf02 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.processors.PublishProcessor; + +public class FlowableHideTest { + @Test + public void testHiding() { + PublishProcessor src = PublishProcessor.create(); + + Flowable dst = src.hide(); + + assertFalse(dst instanceof PublishProcessor); + + Subscriber o = TestHelper.mockSubscriber(); + + dst.subscribe(o); + + src.onNext(1); + src.onComplete(); + + verify(o).onNext(1); + verify(o).onComplete(); + verify(o, never()).onError(any(Throwable.class)); + } + + @Test + public void testHidingError() { + PublishProcessor src = PublishProcessor.create(); + + Flowable dst = src.hide(); + + assertFalse(dst instanceof PublishProcessor); + + Subscriber o = TestHelper.mockSubscriber(); + + dst.subscribe(o); + + src.onError(new TestException()); + + verify(o, never()).onNext(any()); + verify(o, never()).onComplete(); + verify(o).onError(any(TestException.class)); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) + throws Exception { + return o.hide(); + } + }); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(PublishProcessor.create().hide()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java index de62bf09ad..4c07e86ffb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java @@ -325,4 +325,23 @@ public void dispose() { TestHelper.checkDisposed(Flowable.just(1).ignoreElements().toFlowable()); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) + throws Exception { + return o.ignoreElements().toFlowable(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowableToCompletable(new Function, Completable>() { + @Override + public Completable apply(Flowable o) + throws Exception { + return o.ignoreElements(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java index 93824d331b..541de65872 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java @@ -17,8 +17,10 @@ import org.junit.Test; -import io.reactivex.Flowable; +import io.reactivex.*; +import io.reactivex.internal.operators.flowable.FlowableInterval.IntervalSubscriber; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; public class FlowableIntervalTest { @@ -29,4 +31,22 @@ public void cancel() { .test() .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.interval(1, TimeUnit.MILLISECONDS, Schedulers.trampoline())); + } + + @Test + public void cancelledOnRun() { + TestSubscriber ts = new TestSubscriber(); + IntervalSubscriber is = new IntervalSubscriber(ts); + ts.onSubscribe(is); + + is.cancel(); + + is.run(); + + ts.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java index d0f3f81a84..fd4a8a9a21 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java @@ -221,7 +221,7 @@ public void testMergeFlowableOfFlowables() { final Flowable o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); final Flowable o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - Flowable> FlowableOfFlowables = Flowable.unsafeCreate(new Publisher>() { + Flowable> flowableOfFlowables = Flowable.unsafeCreate(new Publisher>() { @Override public void subscribe(Subscriber> observer) { @@ -233,7 +233,7 @@ public void subscribe(Subscriber> observer) { } }); - Flowable m = Flowable.mergeDelayError(FlowableOfFlowables); + Flowable m = Flowable.mergeDelayError(flowableOfFlowables); m.subscribe(stringObserver); verify(stringObserver, never()).onError(any(Throwable.class)); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java index b095878415..e4e68f3922 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java @@ -78,7 +78,7 @@ public void testMergeFlowableOfFlowables() { final Flowable o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); final Flowable o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - Flowable> FlowableOfFlowables = Flowable.unsafeCreate(new Publisher>() { + Flowable> flowableOfFlowables = Flowable.unsafeCreate(new Publisher>() { @Override public void subscribe(Subscriber> observer) { @@ -90,7 +90,7 @@ public void subscribe(Subscriber> observer) { } }); - Flowable m = Flowable.merge(FlowableOfFlowables); + Flowable m = Flowable.merge(flowableOfFlowables); m.subscribe(stringObserver); verify(stringObserver, never()).onError(any(Throwable.class)); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java index 2be61592cf..b74221fdea 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java @@ -37,7 +37,7 @@ public class FlowableRepeatTest { @Test(timeout = 2000) public void testRepetition() { - int NUM = 10; + int num = 10; final AtomicInteger count = new AtomicInteger(); int value = Flowable.unsafeCreate(new Publisher() { @@ -47,9 +47,9 @@ public void subscribe(final Subscriber o) { o.onComplete(); } }).repeat().subscribeOn(Schedulers.computation()) - .take(NUM).blockingLast(); + .take(num).blockingLast(); - assertEquals(NUM, value); + assertEquals(num, value); } @Test(timeout = 2000) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java index 8ad3caa14e..3c0c1ffc22 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java @@ -1767,4 +1767,12 @@ public ConnectableFlowable call() throws Exception { .test() .assertFailure(TestException.class); } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported( + Flowable.never() + .replay() + ); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java index 25cb51af5a..062470229d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java @@ -112,13 +112,13 @@ public static class Tuple { @Test public void testRetryIndefinitely() { Subscriber observer = TestHelper.mockSubscriber(); - int NUM_RETRIES = 20; - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 20; + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retry().subscribe(new TestSubscriber(observer)); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -131,8 +131,8 @@ public void testRetryIndefinitely() { @Test public void testSchedulingNotificationHandler() { Subscriber observer = TestHelper.mockSubscriber(); - int NUM_RETRIES = 2; - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 2; + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); TestSubscriber subscriber = new TestSubscriber(observer); origin.retryWhen(new Function, Flowable>() { @Override @@ -156,7 +156,7 @@ public void accept(Throwable e) { subscriber.awaitTerminalEvent(); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numRetries)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -169,8 +169,8 @@ public void accept(Throwable e) { @Test public void testOnNextFromNotificationHandler() { Subscriber observer = TestHelper.mockSubscriber(); - int NUM_RETRIES = 2; - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 2; + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retryWhen(new Function, Flowable>() { @Override public Flowable apply(Flowable t1) { @@ -186,7 +186,7 @@ public Integer apply(Throwable t1) { InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -283,15 +283,15 @@ public void testOriginFails() { @Test public void testRetryFail() { - int NUM_RETRIES = 1; - int NUM_FAILURES = 2; + int numRetries = 1; + int numFailures = 2; Subscriber observer = TestHelper.mockSubscriber(); - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); - origin.retry(NUM_RETRIES).subscribe(observer); + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numFailures)); + origin.retry(numRetries).subscribe(observer); InOrder inOrder = inOrder(observer); // should show 2 attempts (first time fail, second time (1st retry) fail) - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numRetries)).onNext("beginningEveryTime"); // should only retry once, fail again and emit onError inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); // no success @@ -302,14 +302,14 @@ public void testRetryFail() { @Test public void testRetrySuccess() { - int NUM_FAILURES = 1; + int numFailures = 1; Subscriber observer = TestHelper.mockSubscriber(); - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numFailures)); origin.retry(3).subscribe(observer); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -321,14 +321,14 @@ public void testRetrySuccess() { @Test public void testInfiniteRetry() { - int NUM_FAILURES = 20; + int numFailures = 20; Subscriber observer = TestHelper.mockSubscriber(); - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numFailures)); origin.retry().subscribe(observer); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -710,10 +710,10 @@ public void testTimeoutWithRetry() { public void testRetryWithBackpressure() throws InterruptedException { final int NUM_LOOPS = 1; for (int j = 0;j < NUM_LOOPS; j++) { - final int NUM_RETRIES = Flowable.bufferSize() * 2; + final int numRetries = Flowable.bufferSize() * 2; for (int i = 0; i < 400; i++) { Subscriber observer = TestHelper.mockSubscriber(); - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); TestSubscriber ts = new TestSubscriber(observer); origin.retry().observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(5, TimeUnit.SECONDS); @@ -721,8 +721,8 @@ public void testRetryWithBackpressure() throws InterruptedException { InOrder inOrder = inOrder(observer); // should have no errors verify(observer, never()).onError(any(Throwable.class)); - // should show NUM_RETRIES attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + // should show numRetries attempts + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have a single success inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete @@ -735,7 +735,7 @@ public void testRetryWithBackpressure() throws InterruptedException { @Test//(timeout = 15000) public void testRetryWithBackpressureParallel() throws InterruptedException { final int NUM_LOOPS = 1; - final int NUM_RETRIES = Flowable.bufferSize() * 2; + final int numRetries = Flowable.bufferSize() * 2; int ncpu = Runtime.getRuntime().availableProcessors(); ExecutorService exec = Executors.newFixedThreadPool(Math.max(ncpu / 2, 2)); try { @@ -756,13 +756,13 @@ public void testRetryWithBackpressureParallel() throws InterruptedException { public void run() { final AtomicInteger nexts = new AtomicInteger(); try { - Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + Flowable origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); TestSubscriber ts = new TestSubscriber(); origin.retry() .observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); List onNextEvents = new ArrayList(ts.values()); - if (onNextEvents.size() != NUM_RETRIES + 2) { + if (onNextEvents.size() != numRetries + 2) { for (Throwable t : ts.errors()) { onNextEvents.add(t.toString()); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java index 8c15a44130..f168dc1891 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java @@ -24,6 +24,7 @@ import io.reactivex.*; import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.*; import io.reactivex.schedulers.*; @@ -444,4 +445,15 @@ public void run() { ts.assertResult(1); } } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) + throws Exception { + return o.sample(1, TimeUnit.SECONDS); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java index fb37d544b5..9918af965d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java @@ -24,6 +24,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -121,4 +122,15 @@ public void error() { .assertFailure(TestException.class); } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) + throws Exception { + return o.skipLast(1); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java index 3cbf19df51..7597e23f69 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java @@ -24,7 +24,7 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; -import io.reactivex.functions.LongConsumer; +import io.reactivex.functions.*; import io.reactivex.subscribers.TestSubscriber; public class FlowableSkipTest { @@ -175,4 +175,15 @@ public void dispose() { TestHelper.checkDisposed(Flowable.just(1).skip(2)); } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) + throws Exception { + return o.skip(1); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java index 653b7f034f..c19b2108b1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java @@ -471,4 +471,28 @@ public Flowable apply(Flowable o) throws Exception { }); } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.never().take(1)); + } + + @Test + public void requestRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final TestSubscriber ts = Flowable.range(1, 2).take(2).test(0L); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r1); + + ts.assertResult(1, 2); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java index cb00a08030..ce7bd20179 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java @@ -20,6 +20,7 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.functions.Function; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.TestSubscriber; @@ -282,4 +283,14 @@ public void testBackpressure() { public void dispose() { TestHelper.checkDisposed(PublishProcessor.create().takeUntil(Flowable.never())); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable c) throws Exception { + return c.takeUntil(Flowable.never()); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java index 10e69d885b..bf96ef9e45 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java @@ -19,7 +19,7 @@ import org.junit.*; import org.mockito.InOrder; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.exceptions.TestException; @@ -137,4 +137,14 @@ public void error() { .assertFailure(TestException.class); } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Publisher>>() { + @Override + public Publisher> apply(Flowable f) + throws Exception { + return f.timeInterval(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java index b5b86cf9e2..6babda3c89 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java @@ -25,6 +25,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; @@ -466,4 +467,22 @@ public void run() { } } } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>>() { + @Override + public Flowable> apply(Flowable f) + throws Exception { + return f.toList().toFlowable(); + } + }); + TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function, Single>>() { + @Override + public Single> apply(Flowable f) + throws Exception { + return f.toList(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java index cc9218a1d8..d678bf11bf 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java @@ -35,7 +35,7 @@ public class FlowableUnsubscribeOnTest { @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); @@ -52,8 +52,8 @@ public void subscribe(Subscriber t1) { }); TestSubscriber ts = new TestSubscriber(); - w.subscribeOn(UI_EVENT_LOOP).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + w.subscribeOn(uiEventLoop).observeOn(Schedulers.computation()) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(ts); @@ -70,18 +70,18 @@ public void subscribe(Subscriber t1) { System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertTrue(unsubscribeThread == UI_EVENT_LOOP.getThread()); + assertTrue(unsubscribeThread == uiEventLoop.getThread()); ts.assertValues(1, 2); ts.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); @@ -99,7 +99,7 @@ public void subscribe(Subscriber t1) { TestSubscriber observer = new TestSubscriber(); w.subscribeOn(Schedulers.newThread()).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(observer); @@ -114,15 +114,15 @@ public void subscribe(Subscriber t1) { assertNotSame(Thread.currentThread(), subscribeThread.get()); // True for Schedulers.newThread() - System.out.println("UI Thread: " + UI_EVENT_LOOP.getThread()); + System.out.println("UI Thread: " + uiEventLoop.getThread()); System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertSame(unsubscribeThread, UI_EVENT_LOOP.getThread()); + assertSame(unsubscribeThread, uiEventLoop.getThread()); observer.assertValues(1, 2); observer.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java index 9f3f888ba9..a50c685233 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; @@ -108,4 +109,24 @@ public void run() { } } } + + @Test + public void disposeNoFurtherSignals() { + @SuppressWarnings("unchecked") + TestObserver to = Maybe.ambArray(new Maybe() { + @Override + protected void subscribeActual( + MaybeObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSuccess(1); + observer.onSuccess(2); + observer.onComplete(); + } + }, Maybe.never()) + .test(); + + to.cancel(); + + to.assertResult(1); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java index 086db5388f..ce23150c9a 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java @@ -18,10 +18,12 @@ import java.util.List; import org.junit.Test; +import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; @@ -216,4 +218,28 @@ public MaybeSource apply(Completable c) throws Exception { public void withOtherPublisherDispose() { TestHelper.checkDisposed(Maybe.just(1).delay(Flowable.just(1))); } + + @Test + public void withOtherPublisherDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybe(new Function, MaybeSource>() { + @Override + public MaybeSource apply(Maybe c) throws Exception { + return c.delay(Flowable.never()); + } + }); + } + + @Test + public void otherPublisherNextSlipsThrough() { + Maybe.just(1).delay(new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + } + }) + .test() + .assertResult(1); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java index bdd305a23c..6e7c1b242f 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.*; @@ -154,4 +155,31 @@ public void run() throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void disposedUpfront() throws Exception { + Action run = mock(Action.class); + + Maybe.fromAction(run) + .test(true) + .assertEmpty(); + + verify(run, never()).run(); + } + + @Test + public void cancelWhileRunning() { + final TestObserver to = new TestObserver(); + + Maybe.fromAction(new Action() { + @Override + public void run() throws Exception { + to.dispose(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java index 4f3ab7f571..d08b93a50a 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java @@ -13,12 +13,17 @@ package io.reactivex.internal.operators.maybe; +import static org.junit.Assert.assertTrue; + import java.util.concurrent.*; import org.junit.Test; import io.reactivex.Maybe; +import io.reactivex.exceptions.TestException; import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; public class MaybeFromFutureTest { @@ -58,4 +63,60 @@ public void interrupt() { Maybe.fromFuture(ft, 1, TimeUnit.MILLISECONDS).test() .assertFailure(InterruptedException.class); } + + @Test + public void cancelWhileRunning() { + final TestObserver to = new TestObserver(); + + FutureTask ft = new FutureTask(new Runnable() { + @Override + public void run() { + to.cancel(); + } + }, null); + + Schedulers.single().scheduleDirect(ft, 100, TimeUnit.MILLISECONDS); + + Maybe.fromFuture(ft) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void cancelAndCrashWhileRunning() { + final TestObserver to = new TestObserver(); + + FutureTask ft = new FutureTask(new Runnable() { + @Override + public void run() { + to.cancel(); + throw new TestException(); + } + }, null); + + Schedulers.single().scheduleDirect(ft, 100, TimeUnit.MILLISECONDS); + + Maybe.fromFuture(ft) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void futureNull() { + FutureTask ft = new FutureTask(new Runnable() { + @Override + public void run() { + } + }, null); + + Schedulers.single().scheduleDirect(ft, 100, TimeUnit.MILLISECONDS); + + Maybe.fromFuture(ft) + .test() + .assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java index b7c2f9b3ee..08d0bbc4b3 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.*; @@ -158,4 +159,31 @@ public void run() { RxJavaPlugins.reset(); } } + + @Test + public void disposedUpfront() { + Runnable run = mock(Runnable.class); + + Maybe.fromRunnable(run) + .test(true) + .assertEmpty(); + + verify(run, never()).run(); + } + + @Test + public void cancelWhileRunning() { + final TestObserver to = new TestObserver(); + + Maybe.fromRunnable(new Runnable() { + @Override + public void run() { + to.dispose(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java new file mode 100644 index 0000000000..393c170e1b --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; + +public class MaybeMapTest { + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybe(new Function, MaybeSource>() { + @Override + public MaybeSource apply(Maybe m) throws Exception { + return m.map(Functions.identity()); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java index 39588be465..95cd9a1163 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.junit.*; import org.mockito.InOrder; @@ -32,7 +33,7 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.TestScheduler; -import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.*; public class ObservableDebounceTest { @@ -376,4 +377,77 @@ public void debounceWithEmpty() { .test() .assertResult(1); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>() { + @Override + public Observable apply(Observable o) throws Exception { + return o.debounce(Functions.justFunction(Observable.never())); + } + }); + } + + @Test + public void disposeInOnNext() { + final TestObserver to = new TestObserver(); + + BehaviorSubject.createDefault(1) + .debounce(new Function>() { + @Override + public ObservableSource apply(Integer o) throws Exception { + to.cancel(); + return Observable.never(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void disposedInOnComplete() { + final TestObserver to = new TestObserver(); + + new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + to.cancel(); + observer.onComplete(); + } + } + .debounce(Functions.justFunction(Observable.never())) + .subscribeWith(to) + .assertEmpty(); + } + + @Test + public void emitLate() { + final AtomicReference> ref = new AtomicReference>(); + + TestObserver to = Observable.range(1, 2) + .debounce(new Function>() { + @Override + public ObservableSource apply(Integer o) throws Exception { + if (o != 1) { + return Observable.never(); + } + return new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }; + } + }) + .test(); + + ref.get().onNext(1); + + to + .assertResult(2); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java index ca6280562c..a6002a9117 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java @@ -26,8 +26,7 @@ import io.reactivex.internal.functions.Functions; import io.reactivex.internal.fuseable.QueueSubscription; import io.reactivex.observers.*; -import io.reactivex.processors.UnicastProcessor; -import io.reactivex.subscribers.*; +import io.reactivex.subjects.UnicastSubject; public class ObservableDoAfterNextTest { @@ -58,6 +57,17 @@ public void just() { assertEquals(Arrays.asList(1, -1), values); } + @Test + public void justHidden() { + Observable.just(1) + .hide() + .doAfterNext(afterNext) + .subscribeWith(ts) + .assertResult(1); + + assertEquals(Arrays.asList(1, -1), values); + } + @Test public void range() { Observable.range(1, 5) @@ -118,9 +128,9 @@ public void asyncFusedRejected() { @Test public void asyncFused() { - TestSubscriber ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestObserver ts0 = ObserverFusion.newTest(QueueSubscription.ASYNC); - UnicastProcessor up = UnicastProcessor.create(); + UnicastSubject up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -128,7 +138,7 @@ public void asyncFused() { .doAfterNext(afterNext) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.ASYNC) + ObserverFusion.assertFusion(ts0, QueueSubscription.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -215,9 +225,9 @@ public void asyncFusedRejectedConditional() { @Test public void asyncFusedConditional() { - TestSubscriber ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestObserver ts0 = ObserverFusion.newTest(QueueSubscription.ASYNC); - UnicastProcessor up = UnicastProcessor.create(); + UnicastSubject up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -226,7 +236,7 @@ public void asyncFusedConditional() { .filter(Functions.alwaysTrue()) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.ASYNC) + ObserverFusion.assertFusion(ts0, QueueSubscription.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java index ac89708573..53f3bff298 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java @@ -15,6 +15,7 @@ */ package io.reactivex.internal.operators.observable; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -30,6 +31,7 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableGroupJoin.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.PublishSubject; @@ -688,4 +690,39 @@ public Observable apply(Object r, Observable l) throws Exception to.assertResult(2); } + + @Test + public void leftRightState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightObserver o = new LeftRightObserver(js, false); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + o.dispose(); + + assertTrue(o.isDisposed()); + + verify(js).innerValue(false, 1); + verify(js).innerValue(false, 2); + } + + @Test + public void leftRightEndState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightEndObserver o = new LeftRightEndObserver(js, false, 0); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + assertTrue(o.isDisposed()); + + verify(js).innerClose(false, o); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java index ff8a2e149a..e834a2accb 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java @@ -18,6 +18,8 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.internal.operators.observable.ObservableInterval.IntervalObserver; +import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.*; public class ObservableIntervalTest { @@ -34,4 +36,17 @@ public void cancel() { .test() .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); } + + @Test + public void cancelledOnRun() { + TestObserver ts = new TestObserver(); + IntervalObserver is = new IntervalObserver(ts); + ts.onSubscribe(is); + + is.dispose(); + + is.run(); + + ts.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java index e4f04bba95..9005371fe2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java @@ -720,4 +720,22 @@ protected void subscribeActual(Observer s) { assertTrue(bs.isDisposed()); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, ObservableSource>() { + @Override + public ObservableSource apply(final Observable o) + throws Exception { + return Observable.never().publish(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) + throws Exception { + return o; + } + }); + } + } + ); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java index 075f726283..04fc4cddf8 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java @@ -37,7 +37,7 @@ public class ObservableRepeatTest { @Test(timeout = 2000) public void testRepetition() { - int NUM = 10; + int num = 10; final AtomicInteger count = new AtomicInteger(); int value = Observable.unsafeCreate(new ObservableSource() { @@ -47,9 +47,9 @@ public void subscribe(final Observer o) { o.onComplete(); } }).repeat().subscribeOn(Schedulers.computation()) - .take(NUM).blockingLast(); + .take(num).blockingLast(); - assertEquals(NUM, value); + assertEquals(num, value); } @Test(timeout = 2000) diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java index 4eaf547898..2d5e3d2aa8 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java @@ -111,13 +111,13 @@ public static class Tuple { @Test public void testRetryIndefinitely() { Observer observer = TestHelper.mockObserver(); - int NUM_RETRIES = 20; - Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 20; + Observable origin = Observable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retry().subscribe(new TestObserver(observer)); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -130,8 +130,8 @@ public void testRetryIndefinitely() { @Test public void testSchedulingNotificationHandler() { Observer observer = TestHelper.mockObserver(); - int NUM_RETRIES = 2; - Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 2; + Observable origin = Observable.unsafeCreate(new FuncWithErrors(numRetries)); TestObserver to = new TestObserver(observer); origin.retryWhen(new Function, Observable>() { @Override @@ -157,7 +157,7 @@ public void accept(Throwable e) { to.awaitTerminalEvent(); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numRetries)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -170,8 +170,8 @@ public void accept(Throwable e) { @Test public void testOnNextFromNotificationHandler() { Observer observer = TestHelper.mockObserver(); - int NUM_RETRIES = 2; - Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 2; + Observable origin = Observable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retryWhen(new Function, Observable>() { @Override public Observable apply(Observable t1) { @@ -187,7 +187,7 @@ public Integer apply(Throwable t1) { InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -284,15 +284,15 @@ public void testOriginFails() { @Test public void testRetryFail() { - int NUM_RETRIES = 1; - int NUM_FAILURES = 2; + int numRetries = 1; + int numFailures = 2; Observer observer = TestHelper.mockObserver(); - Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); - origin.retry(NUM_RETRIES).subscribe(observer); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(numFailures)); + origin.retry(numRetries).subscribe(observer); InOrder inOrder = inOrder(observer); // should show 2 attempts (first time fail, second time (1st retry) fail) - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numRetries)).onNext("beginningEveryTime"); // should only retry once, fail again and emit onError inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); // no success @@ -303,14 +303,14 @@ public void testRetryFail() { @Test public void testRetrySuccess() { - int NUM_FAILURES = 1; + int numFailures = 1; Observer observer = TestHelper.mockObserver(); - Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(numFailures)); origin.retry(3).subscribe(observer); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -322,14 +322,14 @@ public void testRetrySuccess() { @Test public void testInfiniteRetry() { - int NUM_FAILURES = 20; + int numFailures = 20; Observer observer = TestHelper.mockObserver(); - Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(numFailures)); origin.retry().subscribe(observer); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java index a45881ef83..92569c5d92 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java @@ -24,6 +24,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.*; import io.reactivex.subjects.PublishSubject; @@ -426,4 +427,15 @@ public void run() { } } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>() { + @Override + public Observable apply(Observable o) + throws Exception { + return o.sample(1, TimeUnit.SECONDS); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java index 73484edcf6..b88bd7855b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java @@ -24,6 +24,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.Schedulers; @@ -120,4 +121,15 @@ public void error() { .test() .assertFailure(TestException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>() { + @Override + public Observable apply(Observable o) + throws Exception { + return o.skipLast(1); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java index 6b54ad0b3e..681475c22c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; public class ObservableSkipTest { @@ -148,4 +149,15 @@ public void testRequestOverflowDoesNotOccur() { public void dispose() { TestHelper.checkDisposed(Observable.just(1).skip(2)); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>() { + @Override + public Observable apply(Observable o) + throws Exception { + return o.skip(1); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java index 4a23b3dd55..9c6fcba337 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java @@ -20,6 +20,7 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.subjects.PublishSubject; @@ -260,4 +261,14 @@ public void testDownstreamUnsubscribes() { public void dispose() { TestHelper.checkDisposed(PublishSubject.create().takeUntil(Observable.never())); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>() { + @Override + public Observable apply(Observable c) throws Exception { + return c.takeUntil(Observable.never()); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java index 3c1bfd1e2a..4542892ba6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java @@ -135,4 +135,15 @@ public void error() { .test() .assertFailure(TestException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>>() { + @Override + public Observable> apply(Observable f) + throws Exception { + return f.timeInterval(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java index e94d5c1ec4..6101e11977 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; @@ -24,8 +25,10 @@ import org.mockito.*; import io.reactivex.*; +import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.operators.observable.ObservableTimer.TimerObserver; import io.reactivex.observables.ConnectableObservable; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; @@ -339,4 +342,16 @@ public Long apply(Long v) throws Exception { } } + @Test + public void cancelledAndRun() { + TestObserver to = new TestObserver(); + to.onSubscribe(Disposables.empty()); + TimerObserver tm = new TimerObserver(to); + + tm.dispose(); + + tm.run(); + + to.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java index 251d98f6b3..fa838ad5f1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java @@ -26,6 +26,7 @@ import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; public class ObservableToListTest { @@ -270,4 +271,22 @@ public Collection call() throws Exception { .assertFailure(NullPointerException.class) .assertErrorMessage("The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function, Observable>>() { + @Override + public Observable> apply(Observable f) + throws Exception { + return f.toList().toObservable(); + } + }); + TestHelper.checkDoubleOnSubscribeObservableToSingle(new Function, Single>>() { + @Override + public Single> apply(Observable f) + throws Exception { + return f.toList(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java index 744c585646..cdb21dafa5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java @@ -34,7 +34,7 @@ public class ObservableUnsubscribeOnTest { @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); @@ -51,8 +51,8 @@ public void subscribe(Observer t1) { }); TestObserver observer = new TestObserver(); - w.subscribeOn(UI_EVENT_LOOP).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + w.subscribeOn(uiEventLoop).observeOn(Schedulers.computation()) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(observer); @@ -69,18 +69,18 @@ public void subscribe(Observer t1) { System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertTrue(unsubscribeThread.toString(), unsubscribeThread == UI_EVENT_LOOP.getThread()); + assertTrue(unsubscribeThread.toString(), unsubscribeThread == uiEventLoop.getThread()); observer.assertValues(1, 2); observer.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); @@ -98,7 +98,7 @@ public void subscribe(Observer t1) { TestObserver observer = new TestObserver(); w.subscribeOn(Schedulers.newThread()).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(observer); @@ -113,15 +113,15 @@ public void subscribe(Observer t1) { assertNotSame(Thread.currentThread(), subscribeThread.get()); // True for Schedulers.newThread() - System.out.println("UI Thread: " + UI_EVENT_LOOP.getThread()); + System.out.println("UI Thread: " + uiEventLoop.getThread()); System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertSame(unsubscribeThread, UI_EVENT_LOOP.getThread()); + assertSame(unsubscribeThread, uiEventLoop.getThread()); observer.assertValues(1, 2); observer.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java index ed8a692d19..d0c2ea165b 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java @@ -13,11 +13,11 @@ package io.reactivex.internal.operators.single; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotEquals; import java.util.List; import java.util.concurrent.*; -import java.util.concurrent.atomic.*; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.reactivestreams.Subscriber; @@ -29,8 +29,7 @@ import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.schedulers.TestScheduler; +import io.reactivex.schedulers.*; import io.reactivex.subjects.PublishSubject; public class SingleDelayTest { @@ -246,4 +245,28 @@ public void withSingleDispose() { public void withCompletableDispose() { TestHelper.checkDisposed(Completable.complete().andThen(Single.just(1))); } + + @Test + public void withCompletableDoubleOnSubscribe() { + + TestHelper.checkDoubleOnSubscribeCompletableToSingle(new Function>() { + @Override + public Single apply(Completable c) throws Exception { + return c.andThen(Single.just((Object)1)); + } + }); + + } + + @Test + public void withSingleDoubleOnSubscribe() { + + TestHelper.checkDoubleOnSubscribeSingle(new Function, Single>() { + @Override + public Single apply(Single s) throws Exception { + return Single.just((Object)1).delaySubscription(s); + } + }); + + } } diff --git a/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java b/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java index a8209f681d..603929ddb4 100644 --- a/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java @@ -16,9 +16,15 @@ package io.reactivex.internal.schedulers; +import static org.junit.Assert.*; + +import java.util.Properties; + import org.junit.Test; import io.reactivex.TestHelper; +import io.reactivex.internal.schedulers.SchedulerPoolFactory.PurgeProperties; +import io.reactivex.schedulers.Schedulers; public class SchedulerPoolFactoryTest { @@ -26,4 +32,115 @@ public class SchedulerPoolFactoryTest { public void utilityClass() { TestHelper.checkUtilityClass(SchedulerPoolFactory.class); } + + @Test + public void multiStartStop() { + SchedulerPoolFactory.shutdown(); + + SchedulerPoolFactory.shutdown(); + + SchedulerPoolFactory.tryStart(false); + + assertNull(SchedulerPoolFactory.PURGE_THREAD.get()); + + SchedulerPoolFactory.start(); + + // restart schedulers + Schedulers.shutdown(); + + Schedulers.start(); + } + + @Test + public void startRace() throws InterruptedException { + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + SchedulerPoolFactory.shutdown(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + SchedulerPoolFactory.start(); + } + }; + + TestHelper.race(r1, r1); + } + + } finally { + // restart schedulers + Schedulers.shutdown(); + + Thread.sleep(200); + + Schedulers.start(); + } + } + + @Test + public void loadPurgeProperties() { + Properties props1 = new Properties(); + + PurgeProperties pp = new PurgeProperties(); + pp.load(props1); + + assertTrue(pp.purgeEnable); + assertEquals(pp.purgePeriod, 1); + } + + @Test + public void loadPurgePropertiesDisabled() { + Properties props1 = new Properties(); + props1.setProperty(SchedulerPoolFactory.PURGE_ENABLED_KEY, "false"); + + PurgeProperties pp = new PurgeProperties(); + pp.load(props1); + + assertFalse(pp.purgeEnable); + assertEquals(pp.purgePeriod, 1); + } + + @Test + public void loadPurgePropertiesEnabledCustomPeriod() { + Properties props1 = new Properties(); + props1.setProperty(SchedulerPoolFactory.PURGE_ENABLED_KEY, "true"); + props1.setProperty(SchedulerPoolFactory.PURGE_PERIOD_SECONDS_KEY, "2"); + + PurgeProperties pp = new PurgeProperties(); + pp.load(props1); + + assertTrue(pp.purgeEnable); + assertEquals(pp.purgePeriod, 2); + } + + @Test + public void loadPurgePropertiesEnabledCustomPeriodNaN() { + Properties props1 = new Properties(); + props1.setProperty(SchedulerPoolFactory.PURGE_ENABLED_KEY, "true"); + props1.setProperty(SchedulerPoolFactory.PURGE_PERIOD_SECONDS_KEY, "abc"); + + PurgeProperties pp = new PurgeProperties(); + pp.load(props1); + + assertTrue(pp.purgeEnable); + assertEquals(pp.purgePeriod, 1); + } + + @Test + public void putIntoPoolNoPurge() { + int s = SchedulerPoolFactory.POOLS.size(); + + SchedulerPoolFactory.tryPutIntoPool(false, null); + + assertEquals(s, SchedulerPoolFactory.POOLS.size()); + } + + @Test + public void putIntoPoolNonThreadPool() { + int s = SchedulerPoolFactory.POOLS.size(); + + SchedulerPoolFactory.tryPutIntoPool(true, null); + + assertEquals(s, SchedulerPoolFactory.POOLS.size()); + } } diff --git a/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java b/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java index 35d5aa5568..2b76b3067a 100644 --- a/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java @@ -25,7 +25,9 @@ import io.reactivex.Scheduler.Worker; import io.reactivex.internal.disposables.EmptyDisposable; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.schedulers.TrampolineScheduler.*; import io.reactivex.schedulers.Schedulers; +import static org.mockito.Mockito.*; public class TrampolineSchedulerInternalTest { @@ -160,4 +162,50 @@ public void run() { w.dispose(); } } + + @Test + public void sleepingRunnableDisposedOnRun() { + TrampolineWorker w = new TrampolineWorker(); + + Runnable r = mock(Runnable.class); + + SleepingRunnable run = new SleepingRunnable(r, w, 0); + w.dispose(); + run.run(); + + verify(r, never()).run(); + } + + @Test + public void sleepingRunnableNoDelayRun() { + TrampolineWorker w = new TrampolineWorker(); + + Runnable r = mock(Runnable.class); + + SleepingRunnable run = new SleepingRunnable(r, w, 0); + + run.run(); + + verify(r).run(); + } + + @Test + public void sleepingRunnableDisposedOnDelayedRun() { + final TrampolineWorker w = new TrampolineWorker(); + + Runnable r = mock(Runnable.class); + + SleepingRunnable run = new SleepingRunnable(r, w, System.currentTimeMillis() + 200); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + w.dispose(); + } + }, 100, TimeUnit.MILLISECONDS); + + run.run(); + + verify(r, never()).run(); + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java index f73fba6ea4..59a7f22fc4 100644 --- a/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java @@ -28,7 +28,6 @@ import io.reactivex.*; import io.reactivex.Scheduler.Worker; import io.reactivex.exceptions.TestException; -import io.reactivex.internal.subscribers.DeferredScalarSubscriber; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -424,4 +423,15 @@ public void downstreamRequest(long n) { request(n); } } + + @Test + public void doubleOnSubscribe() { + TestHelper.doubleOnSubscribe(new DeferredScalarSubscriber(new TestSubscriber()) { + private static final long serialVersionUID = -4445381578878059054L; + + @Override + public void onNext(Integer t) { + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java b/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java index d5a5fc6ac1..c519202ff8 100644 --- a/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java @@ -16,9 +16,12 @@ import static org.junit.Assert.*; import org.junit.Test; +import org.reactivestreams.Subscriber; +import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.subscribers.TestSubscriber; @@ -80,4 +83,31 @@ public void complete() { ts.assertResult(); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.lift(new FlowableOperator() { + @Override + public Subscriber apply( + Subscriber s) throws Exception { + return new SubscriberResourceWrapper(s); + } + }); + } + }); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.never().lift(new FlowableOperator() { + @Override + public Subscriber apply( + Subscriber s) throws Exception { + return new SubscriberResourceWrapper(s); + } + })); + } } diff --git a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java index 3b98ee30c7..f7314b1d68 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java @@ -14,7 +14,7 @@ package io.reactivex.internal.subscriptions; import static org.junit.Assert.*; - +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.atomic.*; @@ -22,6 +22,7 @@ import org.reactivestreams.Subscription; import io.reactivex.TestHelper; +import io.reactivex.exceptions.ProtocolViolationException; import io.reactivex.plugins.RxJavaPlugins; public class SubscriptionHelperTest { @@ -230,4 +231,30 @@ public void run() { assertEquals(0, r.get()); } } + + @Test + public void setOnceAndRequest() { + AtomicReference ref = new AtomicReference(); + + Subscription sub = mock(Subscription.class); + + assertTrue(SubscriptionHelper.setOnce(ref, sub, 1)); + + verify(sub).request(1); + verify(sub, never()).cancel(); + + List errors = TestHelper.trackPluginErrors(); + try { + sub = mock(Subscription.class); + + assertFalse(SubscriptionHelper.setOnce(ref, sub, 1)); + + verify(sub, never()).request(anyLong()); + verify(sub).cancel(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java b/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java index 1c24351a65..dded845762 100644 --- a/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java @@ -16,15 +16,20 @@ import static org.junit.Assert.*; import java.io.IOException; -import java.util.ArrayDeque; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; -import org.reactivestreams.Subscription; +import org.reactivestreams.*; +import io.reactivex.Observer; import io.reactivex.TestHelper; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; import io.reactivex.functions.BooleanSupplier; +import io.reactivex.internal.queue.SpscArrayQueue; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; import io.reactivex.subscribers.TestSubscriber; public class QueueDrainHelperTest { @@ -205,4 +210,672 @@ public boolean getAsBoolean() throws Exception { ts.assertValue(1).assertNoErrors().assertNotComplete(); } + + @Test + public void drainMaxLoopMissingBackpressure() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + q.offer(1); + + QueueDrainHelper.drainMaxLoop(q, ts, false, null, qd); + + ts.assertFailure(MissingBackpressureException.class); + } + + @Test + public void drainMaxLoopMissingBackpressureWithResource() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + q.offer(1); + + Disposable d = Disposables.empty(); + + QueueDrainHelper.drainMaxLoop(q, ts, false, d, qd); + + ts.assertFailure(MissingBackpressureException.class); + + assertTrue(d.isDisposed()); + } + + @Test + public void drainMaxLoopDontAccept() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 1; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + q.offer(1); + + QueueDrainHelper.drainMaxLoop(q, ts, false, null, qd); + + ts.assertEmpty(); + } + + @Test + public void checkTerminatedDelayErrorEmpty() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, qd); + + ts.assertResult(); + } + + @Test + public void checkTerminatedDelayErrorNonEmpty() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, false, ts, true, q, qd); + + ts.assertEmpty(); + } + + @Test + public void checkTerminatedDelayErrorEmptyError() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, qd); + + ts.assertFailure(TestException.class); + } + + @Test + public void checkTerminatedNonDelayErrorError() { + TestSubscriber ts = new TestSubscriber(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain qd = new QueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber a, Integer v) { + return false; + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, false, ts, false, q, qd); + + ts.assertFailure(TestException.class); + } + + @Test + public void observerCheckTerminatedDelayErrorEmpty() { + TestObserver ts = new TestObserver(); + ts.onSubscribe(Disposables.empty()); + + ObservableQueueDrain qd = new ObservableQueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer a, Integer v) { + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, null, qd); + + ts.assertResult(); + } + + @Test + public void observerCheckTerminatedDelayErrorEmptyResource() { + TestObserver ts = new TestObserver(); + ts.onSubscribe(Disposables.empty()); + + ObservableQueueDrain qd = new ObservableQueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer a, Integer v) { + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + Disposable d = Disposables.empty(); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, d, qd); + + ts.assertResult(); + + assertTrue(d.isDisposed()); + } + + @Test + public void observerCheckTerminatedDelayErrorNonEmpty() { + TestObserver ts = new TestObserver(); + ts.onSubscribe(Disposables.empty()); + + ObservableQueueDrain qd = new ObservableQueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer a, Integer v) { + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, false, ts, true, q, null, qd); + + ts.assertEmpty(); + } + + @Test + public void observerCheckTerminatedDelayErrorEmptyError() { + TestObserver ts = new TestObserver(); + ts.onSubscribe(Disposables.empty()); + + ObservableQueueDrain qd = new ObservableQueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer a, Integer v) { + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, null, qd); + + ts.assertFailure(TestException.class); + } + + @Test + public void observerCheckTerminatedNonDelayErrorError() { + TestObserver ts = new TestObserver(); + ts.onSubscribe(Disposables.empty()); + + ObservableQueueDrain qd = new ObservableQueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer a, Integer v) { + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + QueueDrainHelper.checkTerminated(true, false, ts, false, q, null, qd); + + ts.assertFailure(TestException.class); + } + @Test + public void observerCheckTerminatedNonDelayErrorErrorResource() { + TestObserver ts = new TestObserver(); + ts.onSubscribe(Disposables.empty()); + + ObservableQueueDrain qd = new ObservableQueueDrain() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer a, Integer v) { + } + }; + + SpscArrayQueue q = new SpscArrayQueue(32); + + Disposable d = Disposables.empty(); + + QueueDrainHelper.checkTerminated(true, false, ts, false, q, d, qd); + + ts.assertFailure(TestException.class); + + assertTrue(d.isDisposed()); + } + + @Test + public void postCompleteAlreadyComplete() { + + TestSubscriber ts = new TestSubscriber(); + + Queue q = new ArrayDeque(); + q.offer(1); + + AtomicLong state = new AtomicLong(QueueDrainHelper.COMPLETED_MASK); + + QueueDrainHelper.postComplete(ts, q, state, new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return false; + } + }); + } } diff --git a/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java b/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java index 4d4d3753c4..5861cbfd57 100644 --- a/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java +++ b/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java @@ -47,7 +47,7 @@ public void testCovarianceOfFrom() { @Test public void testSortedList() { - Comparator SORT_FUNCTION = new Comparator() { + Comparator sortFunction = new Comparator() { @Override public int compare(Media t1, Media t2) { return 1; @@ -56,11 +56,11 @@ public int compare(Media t1, Media t2) { // this one would work without the covariance generics Observable o = Observable.just(new Movie(), new TVSeason(), new Album()); - o.toSortedList(SORT_FUNCTION); + o.toSortedList(sortFunction); // this one would NOT work without the covariance generics Observable o2 = Observable.just(new Movie(), new ActionMovie(), new HorrorMovie()); - o2.toSortedList(SORT_FUNCTION); + o2.toSortedList(sortFunction); } @Test