From 428027f60024c5c27626475c060b6c5f196c4330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Karnok?= Date: Sun, 16 Oct 2016 16:22:39 +0200 Subject: [PATCH] 2.x: test sync from Observable to Flowable 10/16-1 --- .../io/reactivex/CompletableOperator.java | 1 + src/main/java/io/reactivex/Flowable.java | 46 +- .../java/io/reactivex/FlowableOperator.java | 1 + src/main/java/io/reactivex/MaybeOperator.java | 1 + .../java/io/reactivex/ObservableOperator.java | 1 + .../java/io/reactivex/SingleOperator.java | 1 + .../internal/fuseable/QueueDisposable.java | 63 +-- .../internal/fuseable/QueueFuseable.java | 84 ++++ .../internal/fuseable/QueueSubscription.java | 65 +-- .../flowable/BlockingFlowableLatest.java | 40 +- .../flowable/BlockingFlowableMostRecent.java | 49 +- .../flowable/BlockingFlowableNext.java | 36 +- .../flowable/FlowableBlockingSubscribe.java | 9 +- .../flowable/FlowableBufferTimed.java | 4 +- .../operators/flowable/FlowableCreate.java | 8 + .../operators/flowable/FlowableDistinct.java | 220 ++++----- .../flowable/FlowableFromIterable.java | 9 +- .../operators/flowable/FlowablePublish.java | 11 +- .../flowable/FlowablePublishMulticast.java | 7 +- .../flowable/FlowableSequenceEqualSingle.java | 340 ++++++++++++++ .../flowable/FlowableTimeoutTimed.java | 38 +- .../flowable/FlowableWindowTimed.java | 1 + .../observable/BlockingObservableLatest.java | 1 - .../subscriptions/ScalarSubscription.java | 8 + .../io/reactivex/InternalWrongNaming.java | 3 +- src/test/java/io/reactivex/TestHelper.java | 169 +++++++ .../reactivex/flowable/FlowableNullTests.java | 14 +- .../flowable/BlockingFlowableLatestTest.java | 63 +++ .../BlockingFlowableMostRecentTest.java | 23 +- .../flowable/BlockingFlowableNextTest.java | 59 +++ .../BlockingFlowableToIteratorTest.java | 41 +- .../operators/flowable/FlowableAllTest.java | 42 ++ .../operators/flowable/FlowableAmbTest.java | 129 +++++- .../operators/flowable/FlowableAnyTest.java | 54 +++ .../flowable/FlowableBlockingTest.java | 68 +++ .../flowable/FlowableBufferTest.java | 357 ++++++++++++++- .../operators/flowable/FlowableCacheTest.java | 90 +++- .../flowable/FlowableCombineLatestTest.java | 158 ++++++- .../flowable/FlowableConcatMapEagerTest.java | 219 ++++++++- .../operators/flowable/FlowableCountTest.java | 28 +- .../flowable/FlowableCreateTest.java | 427 ++++++++++++++++++ .../flowable/FlowableDetachTest.java | 19 +- .../flowable/FlowableDistinctTest.java | 119 ++++- .../FlowableDistinctUntilChangedTest.java | 81 +++- .../flowable/FlowableElementAtTest.java | 17 +- .../flowable/FlowableFilterTest.java | 101 ++++- .../FlowableFlatMapCompletableTest.java | 14 +- .../flowable/FlowableFlatMapMaybeTest.java | 89 ++++ .../flowable/FlowableFlatMapSingleTest.java | 51 +++ .../flowable/FlowableFlatMapTest.java | 104 +++++ .../flowable/FlowableFlattenIterableTest.java | 13 + .../flowable/FlowableFromIterableTest.java | 116 ++++- .../flowable/FlowableGenerateTest.java | 85 +++- .../flowable/FlowableGroupJoinTest.java | 334 +++++++++++++- .../operators/flowable/FlowableLastTest.java | 67 ++- .../operators/flowable/FlowableMapTest.java | 157 +++++-- .../flowable/FlowablePublishTest.java | 309 ++++++++++++- .../flowable/FlowableRangeLongTest.java | 65 ++- .../flowable/FlowableReduceTest.java | 9 + .../flowable/FlowableRefCountTest.java | 32 +- .../flowable/FlowableRepeatTest.java | 41 +- .../operators/flowable/FlowableRetryTest.java | 8 +- .../FlowableRetryWithPredicateTest.java | 122 ++++- .../flowable/FlowableScalarXMapTest.java | 61 ++- .../flowable/FlowableSequenceEqualTest.java | 239 +++++++++- .../flowable/FlowableSingleTest.java | 49 +- .../flowable/FlowableSkipLastTest.java | 15 + .../flowable/FlowableSwitchTest.java | 267 +++++++++++ .../flowable/FlowableTakeLastTimedTest.java | 44 ++ .../flowable/FlowableThrottleFirstTest.java | 35 ++ .../flowable/FlowableTimeIntervalTest.java | 16 + .../flowable/FlowableTimeoutTests.java | 107 ++++- .../operators/flowable/FlowableTimerTest.java | 6 + .../flowable/FlowableToListTest.java | 61 ++- .../flowable/FlowableUnsubscribeOnTest.java | 71 +++ .../operators/flowable/FlowableUsingTest.java | 148 +++++- .../flowable/FlowableWindowWithTimeTest.java | 50 +- .../ObservableFlatMapSingleTest.java | 1 + .../util/ObservableToFlowabeTestSync.java | 172 ++++--- .../reactivex/tck/SequenceEqualTckTest.java | 5 +- 80 files changed, 5655 insertions(+), 633 deletions(-) create mode 100644 src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java create mode 100644 src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java diff --git a/src/main/java/io/reactivex/CompletableOperator.java b/src/main/java/io/reactivex/CompletableOperator.java index 74baba4586..91484d22f7 100644 --- a/src/main/java/io/reactivex/CompletableOperator.java +++ b/src/main/java/io/reactivex/CompletableOperator.java @@ -21,6 +21,7 @@ public interface CompletableOperator { * Applies a function to the child CompletableObserver and returns a new parent CompletableObserver. * @param observer the child CompletableObservable instance * @return the parent CompletableObserver instance + * @throws Exception on failure */ CompletableObserver apply(CompletableObserver observer) throws Exception; } diff --git a/src/main/java/io/reactivex/Flowable.java b/src/main/java/io/reactivex/Flowable.java index 8787bf4e30..c5f6a269ba 100644 --- a/src/main/java/io/reactivex/Flowable.java +++ b/src/main/java/io/reactivex/Flowable.java @@ -3500,7 +3500,7 @@ public static Flowable rangeLong(long start, long count) { } /** - * Returns a Flowable that emits a Boolean value that indicates whether two Publisher sequences are the + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the * same by comparing the items emitted by each Publisher pairwise. *

* @@ -3523,12 +3523,12 @@ public static Flowable rangeLong(long start, long count) { */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) - public static Flowable sequenceEqual(Publisher source1, Publisher source2) { + public static Single sequenceEqual(Publisher source1, Publisher source2) { return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate(), bufferSize()); } /** - * Returns a Flowable that emits a Boolean value that indicates whether two Publisher sequences are the + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the * same by comparing the items emitted by each Publisher pairwise based on the results of a specified * equality function. *

@@ -3549,19 +3549,19 @@ public static Flowable sequenceEqual(Publisher source1 * a function used to compare items emitted by each Publisher * @param * the type of items emitted by each Publisher - * @return a Flowable that emits a Boolean value that indicates whether the two Publisher sequences + * @return a Single that emits a Boolean value that indicates whether the two Publisher sequences * are the same according to the specified function * @see ReactiveX operators documentation: SequenceEqual */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) - public static Flowable sequenceEqual(Publisher source1, Publisher source2, + public static Single sequenceEqual(Publisher source1, Publisher source2, BiPredicate isEqual) { return sequenceEqual(source1, source2, isEqual, bufferSize()); } /** - * Returns a Flowable that emits a Boolean value that indicates whether two Publisher sequences are the + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the * same by comparing the items emitted by each Publisher pairwise based on the results of a specified * equality function. *

@@ -3584,23 +3584,23 @@ public static Flowable sequenceEqual(Publisher source1 * the number of items to prefetch from the first and second source Publisher * @param * the type of items emitted by each Publisher - * @return a Flowable that emits a Boolean value that indicates whether the two Publisher sequences + * @return a Single that emits a Boolean value that indicates whether the two Publisher sequences * are the same according to the specified function * @see ReactiveX operators documentation: SequenceEqual */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) - public static Flowable sequenceEqual(Publisher source1, Publisher source2, + public static Single sequenceEqual(Publisher source1, Publisher source2, BiPredicate isEqual, int bufferSize) { ObjectHelper.requireNonNull(source1, "source1 is null"); ObjectHelper.requireNonNull(source2, "source2 is null"); ObjectHelper.requireNonNull(isEqual, "isEqual is null"); ObjectHelper.verifyPositive(bufferSize, "bufferSize"); - return RxJavaPlugins.onAssembly(new FlowableSequenceEqual(source1, source2, isEqual, bufferSize)); + return RxJavaPlugins.onAssembly(new FlowableSequenceEqualSingle(source1, source2, isEqual, bufferSize)); } /** - * Returns a Flowable that emits a Boolean value that indicates whether two Publisher sequences are the + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the * same by comparing the items emitted by each Publisher pairwise. *

* @@ -3620,12 +3620,12 @@ public static Flowable sequenceEqual(Publisher source1 * the number of items to prefetch from the first and second source Publisher * @param * the type of items emitted by each Publisher - * @return a Flowable that emits a Boolean value that indicates whether the two sequences are the same + * @return a Single that emits a Boolean value that indicates whether the two sequences are the same * @see ReactiveX operators documentation: SequenceEqual */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) - public static Flowable sequenceEqual(Publisher source1, Publisher source2, int bufferSize) { + public static Single sequenceEqual(Publisher source1, Publisher source2, int bufferSize) { return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate(), bufferSize); } @@ -5196,7 +5196,7 @@ public final T blockingLast(T defaultItem) { @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Iterable blockingLatest() { - return BlockingFlowableLatest.latest(this); + return new BlockingFlowableLatest(this); } /** @@ -5222,7 +5222,7 @@ public final Iterable blockingLatest() { @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Iterable blockingMostRecent(T initialItem) { - return BlockingFlowableMostRecent.mostRecent(this, initialItem); + return new BlockingFlowableMostRecent(this, initialItem); } /** @@ -5245,7 +5245,7 @@ public final Iterable blockingMostRecent(T initialItem) { @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Iterable blockingNext() { - return BlockingFlowableNext.next(this); + return new BlockingFlowableNext(this); } /** @@ -5267,7 +5267,7 @@ public final Iterable blockingNext() { @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final T blockingSingle() { - return singleElement().blockingGet(); + return singleOrError().blockingGet(); } /** @@ -7248,7 +7248,7 @@ public final Flowable distinct(Function keySelector, Callable> collectionSupplier) { ObjectHelper.requireNonNull(keySelector, "keySelector is null"); ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); - return FlowableDistinct.withCollection(this, keySelector, collectionSupplier); + return RxJavaPlugins.onAssembly(new FlowableDistinct(this, keySelector, collectionSupplier)); } /** @@ -7271,7 +7271,7 @@ public final Flowable distinct(Function keySelector, @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable distinctUntilChanged() { - return FlowableDistinct.untilChanged(this); + return new FlowableDistinctUntilChanged(this, Functions.equalsPredicate()); } /** @@ -7299,7 +7299,7 @@ public final Flowable distinctUntilChanged() { @SchedulerSupport(SchedulerSupport.NONE) public final Flowable distinctUntilChanged(Function keySelector) { ObjectHelper.requireNonNull(keySelector, "keySelector is null"); - return FlowableDistinct.untilChanged(this, keySelector); + return new FlowableDistinctUntilChanged(this, Functions.equalsPredicate(keySelector)); } /** @@ -13734,7 +13734,7 @@ public final Single> toList(final int capacityHint) { } /** - * Returns a Flowable that emits a single item, a list composed of all the items emitted by the source + * Returns a Single that emits a single item, a list composed of all the items emitted by the source * Publisher. *

* @@ -13758,15 +13758,15 @@ public final Single> toList(final int capacityHint) { * @param the subclass of a collection of Ts * @param collectionSupplier * the Callable returning the collection (for each individual Subscriber) to be filled in - * @return a Flowable that emits a single item: a List containing all of the items emitted by the source + * @return a Single that emits a single item: a List containing all of the items emitted by the source * Publisher * @see ReactiveX operators documentation: To */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) - public final > Flowable toList(Callable collectionSupplier) { + public final > Single toList(Callable collectionSupplier) { ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); - return RxJavaPlugins.onAssembly(new FlowableToList(this, collectionSupplier)); + return RxJavaPlugins.onAssembly(new FlowableToListSingle(this, collectionSupplier)); } /** diff --git a/src/main/java/io/reactivex/FlowableOperator.java b/src/main/java/io/reactivex/FlowableOperator.java index b814a56219..9ab46d869c 100644 --- a/src/main/java/io/reactivex/FlowableOperator.java +++ b/src/main/java/io/reactivex/FlowableOperator.java @@ -26,6 +26,7 @@ public interface FlowableOperator { * Applies a function to the child Subscriber and returns a new parent Subscriber. * @param observer the child Subscriber instance * @return the parent Subscriber instance + * @throws Exception on failure */ Subscriber apply(Subscriber observer) throws Exception; } diff --git a/src/main/java/io/reactivex/MaybeOperator.java b/src/main/java/io/reactivex/MaybeOperator.java index 72a7424547..fbbf6d0a72 100644 --- a/src/main/java/io/reactivex/MaybeOperator.java +++ b/src/main/java/io/reactivex/MaybeOperator.java @@ -24,6 +24,7 @@ public interface MaybeOperator { * Applies a function to the child MaybeObserver and returns a new parent MaybeObserver. * @param observer the child MaybeObserver instance * @return the parent MaybeObserver instance + * @throws Exception on failure */ MaybeObserver apply(MaybeObserver observer) throws Exception; } diff --git a/src/main/java/io/reactivex/ObservableOperator.java b/src/main/java/io/reactivex/ObservableOperator.java index c1494b013b..c7443e1350 100644 --- a/src/main/java/io/reactivex/ObservableOperator.java +++ b/src/main/java/io/reactivex/ObservableOperator.java @@ -24,6 +24,7 @@ public interface ObservableOperator { * Applies a function to the child Observer and returns a new parent Observer. * @param observer the child Observer instance * @return the parent Observer instance + * @throws Exception on failure */ Observer apply(Observer observer) throws Exception; } diff --git a/src/main/java/io/reactivex/SingleOperator.java b/src/main/java/io/reactivex/SingleOperator.java index 74228ca2cb..faced1fcd1 100644 --- a/src/main/java/io/reactivex/SingleOperator.java +++ b/src/main/java/io/reactivex/SingleOperator.java @@ -24,6 +24,7 @@ public interface SingleOperator { * Applies a function to the child SingleObserver and returns a new parent SingleObserver. * @param observer the child SingleObserver instance * @return the parent SingleObserver instance + * @throws Exception on failure */ SingleObserver apply(SingleObserver observer) throws Exception; } diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java b/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java index 6a6b93a88c..700c701e3e 100644 --- a/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java +++ b/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java @@ -51,66 +51,5 @@ * * @param the value type transmitted through the queue */ -public interface QueueDisposable extends SimpleQueue, Disposable { - /** - * Returned by the {@link #requestFusion(int)} if the upstream doesn't support - * the requested mode. - */ - int NONE = 0; - - /** - * Request a synchronous fusion mode and can be returned by {@link #requestFusion(int)} - * for an accepted mode. - *

- * In synchronous fusion, all upstream values are either already available or is generated - * when {@link #poll()} is called synchronously. When the {@link #poll()} returns null, - * that is the indication if a terminated stream. - * In this mode, the upstream won't call the onXXX methods and callers of - * {@link #poll()} should be prepared to catch exceptions. Note that {@link #poll()} has - * to be called sequentially (from within a serializing drain-loop). - */ - int SYNC = 1; - - /** - * Request an asynchronous fusion mode and can be returned by {@link #requestFusion(int)} - * for an accepted mode. - *

- * In asynchronous fusion, upstream values may become available to {@link #poll()} eventually. - * Upstream signals onError() and onComplete() as usual but onNext may not actually contain - * the upstream value but have {@code null} instead. Downstream should treat such onNext as indication - * that {@link #poll()} can be called. Note that {@link #poll()} has to be called sequentially - * (from within a serializing drain-loop). In addition, callers of {@link #poll()} should be - * prepared to catch exceptions. - */ - int ASYNC = 2; - - /** - * Request any of the {@link #SYNC} or {@link #ASYNC} modes. - */ - int ANY = SYNC | ASYNC; - - /** - * Used in binary or combination with the other constants as an input to {@link #requestFusion(int)} - * indicating that the {@link #poll()} will be called behind an asynchronous boundary and thus - * may change the non-trivial computation locations attached to the {@link #poll()} chain of - * fused operators. - *

- * For example, fusing map() and observeOn() may move the computation of the map's function over to - * the thread run after the observeOn(), which is generally unexpected. - */ - int BOUNDARY = 4; - - /** - * Request a fusion mode from the upstream. - *

- * This should be called before {@code onSubscribe} returns. - *

- * Calling this method multiple times or after {@code onSubscribe} finished is not allowed - * and may result in undefined behavior. - *

- * @param mode the requested fusion mode, allowed values are {@link #SYNC}, {@link #ASYNC}, - * {@link #ANY} combined with {@link #BOUNDARY} (e.g., {@code requestFusion(SYNC | BOUNDARY)}). - * @return the established fusion mode: {@link #NONE}, {@link #SYNC}, {@link #ASYNC}. - */ - int requestFusion(int mode); +public interface QueueDisposable extends QueueFuseable, Disposable { } diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java new file mode 100644 index 0000000000..abe54f2f77 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java @@ -0,0 +1,84 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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.fuseable; + + +/** + * Represents a SimpleQueue plus the means and constants for requesting a fusion mode. + * @param the value type returned by the SimpleQueue.poll() + */ +public interface QueueFuseable extends SimpleQueue { + /** + * Returned by the {@link #requestFusion(int)} if the upstream doesn't support + * the requested mode. + */ + int NONE = 0; + + /** + * Request a synchronous fusion mode and can be returned by {@link #requestFusion(int)} + * for an accepted mode. + *

+ * In synchronous fusion, all upstream values are either already available or is generated + * when {@link #poll()} is called synchronously. When the {@link #poll()} returns null, + * that is the indication if a terminated stream. + * In this mode, the upstream won't call the onXXX methods and callers of + * {@link #poll()} should be prepared to catch exceptions. Note that {@link #poll()} has + * to be called sequentially (from within a serializing drain-loop). + */ + int SYNC = 1; + + /** + * Request an asynchronous fusion mode and can be returned by {@link #requestFusion(int)} + * for an accepted mode. + *

+ * In asynchronous fusion, upstream values may become available to {@link #poll()} eventually. + * Upstream signals onError() and onComplete() as usual but onNext may not actually contain + * the upstream value but have {@code null} instead. Downstream should treat such onNext as indication + * that {@link #poll()} can be called. Note that {@link #poll()} has to be called sequentially + * (from within a serializing drain-loop). In addition, callers of {@link #poll()} should be + * prepared to catch exceptions. + */ + int ASYNC = 2; + + /** + * Request any of the {@link #SYNC} or {@link #ASYNC} modes. + */ + int ANY = SYNC | ASYNC; + + /** + * Used in binary or combination with the other constants as an input to {@link #requestFusion(int)} + * indicating that the {@link #poll()} will be called behind an asynchronous boundary and thus + * may change the non-trivial computation locations attached to the {@link #poll()} chain of + * fused operators. + *

+ * For example, fusing map() and observeOn() may move the computation of the map's function over to + * the thread run after the observeOn(), which is generally unexpected. + */ + int BOUNDARY = 4; + + /** + * Request a fusion mode from the upstream. + *

+ * This should be called before {@code onSubscribe} returns. + *

+ * Calling this method multiple times or after {@code onSubscribe} finished is not allowed + * and may result in undefined behavior. + *

+ * @param mode the requested fusion mode, allowed values are {@link #SYNC}, {@link #ASYNC}, + * {@link #ANY} combined with {@link #BOUNDARY} (e.g., {@code requestFusion(SYNC | BOUNDARY)}). + * @return the established fusion mode: {@link #NONE}, {@link #SYNC}, {@link #ASYNC}. + */ + int requestFusion(int mode); + +} diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java b/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java index 3b91764def..8a4219c0fc 100644 --- a/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java +++ b/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java @@ -53,68 +53,5 @@ * * @param the value type transmitted through the queue */ -public interface QueueSubscription extends SimpleQueue, Subscription { - /** - * Returned by the {@link #requestFusion(int)} if the upstream doesn't support - * the requested mode. - */ - int NONE = 0; - - /** - * Request a synchronous fusion mode and can be returned by {@link #requestFusion(int)} - * for an accepted mode. - *

- * In synchronous fusion, all upstream values are either already available or is generated - * when {@link #poll()} is called synchronously. When the {@link #poll()} returns null, - * that is the indication if a terminated stream. Downstream should not call {@link #request(long)} - * in this mode. In this mode, the upstream won't call the onXXX methods and callers of - * {@link #poll()} should be prepared to catch exceptions. Note that {@link #poll()} has - * to be called sequentially (from within a serializing drain-loop). - */ - int SYNC = 1; - - /** - * Request an asynchronous fusion mode and can be returned by {@link #requestFusion(int)} - * for an accepted mode. - *

- * In asynchronous fusion, upstream values may become available to {@link #poll()} eventually. - * Upstream signals onError() and onComplete() as usual but onNext may not actually contain - * the upstream value but have {@code null} instead. Downstream should treat such onNext as indication - * that {@link #poll()} can be called. Note that {@link #poll()} has to be called sequentially - * (from within a serializing drain-loop). In addition, callers of {@link #poll()} should be - * prepared to catch exceptions. In this mode, the downstream still has to call {@link #request(long)} - * to indicate it is prepared to receive more values. - */ - int ASYNC = 2; - - /** - * Request any of the {@link #SYNC} or {@link #ASYNC} modes. - */ - int ANY = SYNC | ASYNC; - - /** - * Used in binary or combination with the other constants as an input to {@link #requestFusion(int)} - * indicating that the {@link #poll()} will be called behind an asynchronous boundary and thus - * may change the non-trivial computation locations attached to the {@link #poll()} chain of - * fused operators. - *

- * For example, fusing map() and observeOn() may move the computation of the map's function over to - * the thread run after the observeOn(), which is generally unexpected. - */ - int BOUNDARY = 4; - - /** - * Request a fusion mode from the upstream. - *

- * This should be called before {@code onSubscribe} returns or even - * calls {@link #request(long)}. - *

- * Calling this method multiple times or after {@code onSubscribe} finished is not allowed - * and may result in undefined behavior. - *

- * @param mode the requested fusion mode, allowed values are {@link #SYNC}, {@link #ASYNC}, - * {@link #ANY} combined with {@link #BOUNDARY} (e.g., {@code requestFusion(SYNC | BOUNDARY)}). - * @return the established fusion mode: {@link #NONE}, {@link #SYNC}, {@link #ASYNC}. - */ - int requestFusion(int mode); +public interface QueueSubscription extends QueueFuseable, Subscription { } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java index 1514f6a2fc..fc7567621a 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java @@ -21,34 +21,27 @@ import io.reactivex.*; import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.DisposableSubscriber; /** * Wait for and iterate over the latest values of the source observable. If the source works faster than the * iterator, values may be skipped, but not the {@code onError} or {@code onComplete} events. + * @param the value type emitted */ -public enum BlockingFlowableLatest { - ; - - /** - * Returns an {@code Iterable} that blocks until or unless the {@code Observable} emits an item that has not - * been returned by the {@code Iterable}, then returns that item. - * - * @param the value type - * @param source - * the source {@code Observable} - * @return an {@code Iterable} that blocks until or unless the {@code Observable} emits an item that has not - * been returned by the {@code Iterable}, then returns that item - */ - public static Iterable latest(final Publisher source) { - return new Iterable() { - @Override - public Iterator iterator() { - LatestSubscriberIterator lio = new LatestSubscriberIterator(); - Flowable.fromPublisher(source).materialize().subscribe(lio); - return lio; - } - }; +public final class BlockingFlowableLatest implements Iterable { + + final Publisher source; + + public BlockingFlowableLatest(Publisher source) { + this.source = source; + } + + @Override + public Iterator iterator() { + LatestSubscriberIterator lio = new LatestSubscriberIterator(); + Flowable.fromPublisher(source).materialize().subscribe(lio); + return lio; } /** Subscriber of source, iterator for output. */ @@ -70,7 +63,7 @@ public void onNext(Notification args) { @Override public void onError(Throwable e) { - // not expected + RxJavaPlugins.onError(e); } @Override @@ -89,7 +82,6 @@ public boolean hasNext() { notify.acquire(); } catch (InterruptedException ex) { dispose(); - Thread.currentThread().interrupt(); iteratorNotification = Notification.createOnError(ex); throw ExceptionHelper.wrapOrThrow(ex); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java index 5b2fe5f414..27a3ffa4b9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java @@ -25,36 +25,31 @@ * seed value if no item has yet been emitted. *

* + * + * @param the value type */ -public enum BlockingFlowableMostRecent { - ; - /** - * Returns an {@code Iterable} that always returns the item most recently emitted by the {@code Observable}. - * - * @param the value type - * @param source - * the source {@code Observable} - * @param initialValue - * a default item to return from the {@code Iterable} if {@code source} has not yet emitted any - * items - * @return an {@code Iterable} that always returns the item most recently emitted by {@code source}, or - * {@code initialValue} if {@code source} has not yet emitted any items - */ - public static Iterable mostRecent(final Publisher source, final T initialValue) { - return new Iterable() { - @Override - public Iterator iterator() { - MostRecentSubscriber mostRecentSubscriber = new MostRecentSubscriber(initialValue); +public final class BlockingFlowableMostRecent implements Iterable { - /** - * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain - * since it is for BlockingObservable. - */ - source.subscribe(mostRecentSubscriber); + final Publisher source; + + final T initialValue; + + public BlockingFlowableMostRecent(Publisher source, T initialValue) { + this.source = source; + this.initialValue = initialValue; + } + + @Override + public Iterator iterator() { + MostRecentSubscriber mostRecentSubscriber = new MostRecentSubscriber(initialValue); + + /** + * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain + * since it is for BlockingObservable. + */ + source.subscribe(mostRecentSubscriber); - return mostRecentSubscriber.getIterable(); - } - }; + return mostRecentSubscriber.getIterable(); } static final class MostRecentSubscriber extends DefaultSubscriber { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java index b3f3919890..d12fc8f62f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java @@ -21,33 +21,28 @@ import io.reactivex.*; import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.DisposableSubscriber; /** * Returns an Iterable that blocks until the Observable emits another item, then returns that item. *

* + * + * @param the value type */ -public enum BlockingFlowableNext { - ; - /** - * Returns an {@code Iterable} that blocks until the {@code Observable} emits another item, then returns - * that item. - * - * @param the value type - * @param items - * the {@code Observable} to observe - * @return an {@code Iterable} that behaves like a blocking version of {@code items} - */ - public static Iterable next(final Publisher items) { - return new Iterable() { - @Override - public Iterator iterator() { - NextSubscriber nextSubscriber = new NextSubscriber(); - return new NextIterator(items, nextSubscriber); - } - }; +public final class BlockingFlowableNext implements Iterable { + + final Publisher source; + public BlockingFlowableNext(Publisher source) { + this.source = source; + } + + @Override + public Iterator iterator() { + NextSubscriber nextSubscriber = new NextSubscriber(); + return new NextIterator(source, nextSubscriber); } // test needs to access the observer.waiting flag @@ -111,7 +106,6 @@ private boolean moveToNext() { throw new IllegalStateException("Should not reach here"); } catch (InterruptedException e) { observer.dispose(); - Thread.currentThread().interrupt(); error = e; throw ExceptionHelper.wrapOrThrow(e); } @@ -149,7 +143,7 @@ public void onComplete() { @Override public void onError(Throwable e) { - // ignore + RxJavaPlugins.onError(e); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java index 6b295980e4..c66cf1ad41 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java @@ -28,8 +28,12 @@ /** * Utility methods to consume a Publisher in a blocking manner with callbacks or Subscriber. */ -public enum FlowableBlockingSubscribe { - ; +public final class FlowableBlockingSubscribe { + + /** Utility class. */ + private FlowableBlockingSubscribe() { + throw new IllegalStateException("No instances!"); + } /** * Subscribes to the source and calls the Subscriber methods on the current thread. @@ -69,7 +73,6 @@ public static void subscribe(Publisher o, Subscriber } } } catch (InterruptedException e) { - Thread.currentThread().interrupt(); subscriber.onError(e); } finally { bs.cancel(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java index 535f532730..2c234ca539 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java @@ -459,9 +459,9 @@ public void onSubscribe(Subscription s) { actual.onSubscribe(this); - s.request(Long.MAX_VALUE); - timer = w.schedulePeriodically(this, timespan, timespan, unit); + + s.request(Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java index d19f30f198..bf0c9edf13 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java @@ -264,6 +264,9 @@ public void onComplete() { @Override public void onError(Throwable e) { + if (e == null) { + e = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } if (isCancelled()) { RxJavaPlugins.onError(e); return; @@ -334,6 +337,11 @@ static final class NoneEmitter extends BaseEmitter { @Override public void onNext(T t) { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + if (isCancelled()) { return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java index 1691b3bdf5..73a7d72492 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java @@ -18,189 +18,129 @@ import org.reactivestreams.*; -import io.reactivex.Flowable; -import io.reactivex.exceptions.*; -import io.reactivex.functions.*; -import io.reactivex.internal.functions.*; -import io.reactivex.internal.subscriptions.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscribers.BasicFuseableSubscriber; +import io.reactivex.internal.subscriptions.EmptySubscription; import io.reactivex.plugins.RxJavaPlugins; public final class FlowableDistinct extends AbstractFlowableWithUpstream { + final Function keySelector; - final Callable> predicateSupplier; - public FlowableDistinct(Publisher source, Function keySelector, Callable> predicateSupplier) { + final Callable> collectionSupplier; + + public FlowableDistinct(Publisher source, Function keySelector, Callable> collectionSupplier) { super(source); - this.predicateSupplier = predicateSupplier; this.keySelector = keySelector; - } - - public static Flowable withCollection(Publisher source, Function keySelector, final Callable> collectionSupplier) { - Callable> p = new Callable>() { - @Override - public Predicate call() throws Exception { - final Collection coll = collectionSupplier.call(); - - return new Predicate() { - @Override - public boolean test(K t) { - if (t == null) { - coll.clear(); - return true; - } - return coll.add(t); - } - }; - } - }; - - return RxJavaPlugins.onAssembly(new FlowableDistinct(source, keySelector, p)); - } - - public static Flowable untilChanged(Publisher source) { - Callable> p = new Callable>() { - Object last; - @Override - public Predicate call() { - - return new Predicate() { - @Override - public boolean test(T t) { - if (t == null) { - last = null; - return true; - } - Object o = last; - last = t; - return !ObjectHelper.equals(o, t); - } - }; - } - }; - return RxJavaPlugins.onAssembly(new FlowableDistinct(source, Functions.identity(), p)); - } - - public static Flowable untilChanged(Publisher source, Function keySelector) { - Callable> p = new Callable>() { - Object last; - @Override - public Predicate call() { - - return new Predicate() { - @Override - public boolean test(K t) { - if (t == null) { - last = null; - return true; - } - Object o = last; - last = t; - return !ObjectHelper.equals(o, t); - } - }; - } - }; - return RxJavaPlugins.onAssembly(new FlowableDistinct(source, keySelector, p)); + this.collectionSupplier = collectionSupplier; } @Override - protected void subscribeActual(Subscriber s) { - Predicate coll; + protected void subscribeActual(Subscriber observer) { + Collection collection; + try { - coll = ObjectHelper.requireNonNull(predicateSupplier.call(), "predicateSupplier returned null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - EmptySubscription.error(e, s); + collection = collectionSupplier.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, observer); return; } - source.subscribe(new DistinctSubscriber(s, keySelector, coll)); + source.subscribe(new DistinctSubscriber(observer, keySelector, collection)); } - static final class DistinctSubscriber implements Subscriber, Subscription { - final Subscriber actual; - final Predicate predicate; + static final class DistinctSubscriber extends BasicFuseableSubscriber { + + final Collection collection; + final Function keySelector; - Subscription s; + Disposable d; - DistinctSubscriber(Subscriber actual, Function keySelector, Predicate predicate) { - this.actual = actual; - this.keySelector = keySelector; - this.predicate = predicate; - } + SimpleQueue queue; - @Override - public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); - } + DistinctSubscriber(Subscriber actual, Function keySelector, Collection collection) { + super(actual); + this.keySelector = keySelector; + this.collection = collection; } @Override - public void onNext(T t) { - K key; - - try { - key = ObjectHelper.requireNonNull(keySelector.apply(t), "Null key supplied"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - s.cancel(); - actual.onError(e); + public void onNext(T value) { + if (done) { return; } - - boolean b; - try { - b = predicate.test(key); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - s.cancel(); - actual.onError(e); - return; - } - - if (b) { - actual.onNext(t); + if (sourceMode == NONE) { + K key; + boolean b; + + try { + key = ObjectHelper.requireNonNull(keySelector.apply(value), "The keySelector returned a null key"); + b = collection.add(key); + } catch (Throwable ex) { + fail(ex); + return; + } + + if (b) { + actual.onNext(value); + } else { + s.request(1); + } } else { - s.request(1); + actual.onNext(null); } } @Override - public void onError(Throwable t) { - try { - predicate.test(null); // special case: poison pill - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(e, t)); - return; + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + } else { + done = true; + collection.clear(); + actual.onError(e); } - actual.onError(t); } @Override public void onComplete() { - try { - predicate.test(null); // special case: poison pill - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - actual.onError(e); - return; + if (!done) { + done = true; + collection.clear(); + actual.onComplete(); } - actual.onComplete(); } + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } @Override - public void request(long n) { - s.request(n); + public T poll() throws Exception { + for (;;) { + T v = qs.poll(); + + if (v == null || collection.add(ObjectHelper.requireNonNull(keySelector.apply(v), "The keySelector returned a null key"))) { + return v; + } else { + if (sourceMode == QueueFuseable.ASYNC) { + s.request(1); + } + } + } } @Override - public void cancel() { - s.cancel(); + public void clear() { + collection.clear(); + super.clear(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java index 8979924811..65f79ac5d9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java @@ -72,7 +72,7 @@ public static void subscribe(Subscriber s, Iterator abstract static class BaseRangeSubscription extends BasicQueueSubscription { private static final long serialVersionUID = -2252972430506210021L; - final Iterator it; + Iterator it; volatile boolean cancelled; @@ -89,6 +89,9 @@ public final int requestFusion(int mode) { @Override public final T poll() { + if (it == null) { + return null; + } if (!once) { once = true; } else { @@ -102,12 +105,12 @@ public final T poll() { @Override public final boolean isEmpty() { - return !it.hasNext(); + return it == null || !it.hasNext(); } @Override public final void clear() { - // nothing to do + it = null; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java index 7cb8c8e015..e0349f47dc 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java @@ -271,6 +271,8 @@ public void onError(Throwable e) { // since many things can happen concurrently, we have a common dispatch // loop to act on the current state serially dispatch(); + } else { + RxJavaPlugins.onError(e); } } @Override @@ -418,8 +420,13 @@ boolean checkTerminated(Object term, boolean empty) { // this will swap in a terminated array so add() in OnSubscribe will reject // child subscribers to associate themselves with a terminated and thus // never again emitting chain - for (InnerSubscriber ip : subscribers.getAndSet(TERMINATED)) { - ip.child.onError(t); + InnerSubscriber[] a = subscribers.getAndSet(TERMINATED); + if (a.length != 0) { + for (InnerSubscriber ip : a) { + ip.child.onError(t); + } + } else { + RxJavaPlugins.onError(t); } } finally { // we explicitly dispose/disconnect from the upstream diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java index e4c62cb9fb..625c8ffdd2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java @@ -148,7 +148,7 @@ static final class MulticastProcessor extends Flowable implements Subscrib final AtomicReference s; - SimpleQueue queue; + volatile SimpleQueue queue; int sourceMode; @@ -197,7 +197,10 @@ public void onSubscribe(Subscription s) { public void dispose() { SubscriptionHelper.cancel(s); if (wip.getAndIncrement() == 0) { - queue.clear(); + SimpleQueue q = queue; + if (q != null) { + q.clear(); + } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java new file mode 100644 index 0000000000..d94ce29e74 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java @@ -0,0 +1,340 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSequenceEqualSingle extends Single implements FuseToFlowable { + final Publisher first; + final Publisher second; + final BiPredicate comparer; + final int prefetch; + + public FlowableSequenceEqualSingle(Publisher first, Publisher second, + BiPredicate comparer, int prefetch) { + this.first = first; + this.second = second; + this.comparer = comparer; + this.prefetch = prefetch; + } + + @Override + public void subscribeActual(SingleObserver s) { + EqualCoordinator parent = new EqualCoordinator(s, prefetch, comparer); + s.onSubscribe(parent); + parent.subscribe(first, second); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableSequenceEqual(first, second, comparer, prefetch)); + } + + static final class EqualCoordinator + extends AtomicInteger + implements Disposable { + + private static final long serialVersionUID = -6178010334400373240L; + + final SingleObserver actual; + + final BiPredicate comparer; + + final EqualSubscriber first; + + final EqualSubscriber second; + + final AtomicThrowable error; + + T v1; + + T v2; + + EqualCoordinator(SingleObserver actual, int prefetch, BiPredicate comparer) { + this.actual = actual; + this.comparer = comparer; + this.first = new EqualSubscriber(this, prefetch); + this.second = new EqualSubscriber(this, prefetch); + this.error = new AtomicThrowable(); + } + + void subscribe(Publisher source1, Publisher source2) { + source1.subscribe(first); + source2.subscribe(second); + } + + @Override + public void dispose() { + first.cancel(); + second.cancel(); + if (getAndIncrement() == 0) { + first.clear(); + second.clear(); + } + } + + @Override + public boolean isDisposed() { + return SubscriptionHelper.isCancelled(first.get()); + } + + void cancelAndClear() { + first.cancel(); + first.clear(); + second.cancel(); + second.clear(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + SimpleQueue q1 = first.queue; + SimpleQueue q2 = second.queue; + + if (q1 != null && q2 != null) { + for (;;) { + if (isDisposed()) { + first.clear(); + second.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAndClear(); + + actual.onError(error.terminate()); + return; + } + + boolean d1 = first.done; + + T a = v1; + if (a == null) { + try { + a = q1.poll(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + actual.onError(error.terminate()); + return; + } + v1 = a; + } + boolean e1 = a == null; + + boolean d2 = second.done; + T b = v2; + if (b == null) { + try { + b = q2.poll(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + actual.onError(error.terminate()); + return; + } + v2 = b; + } + + boolean e2 = b == null; + + if (d1 && d2 && e1 && e2) { + actual.onSuccess(true); + return; + } + if ((d1 && d2) && (e1 != e2)) { + cancelAndClear(); + actual.onSuccess(false); + return; + } + + if (e1 || e2) { + break; + } + + boolean c; + + try { + c = comparer.test(a, b); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + actual.onError(error.terminate()); + return; + } + + if (!c) { + cancelAndClear(); + actual.onSuccess(false); + return; + } + + v1 = null; + v2 = null; + + first.request(); + second.request(); + } + + } else { + if (isDisposed()) { + first.clear(); + second.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAndClear(); + + actual.onError(error.terminate()); + return; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class EqualSubscriber + extends AtomicReference + implements Subscriber { + + private static final long serialVersionUID = 4804128302091633067L; + + final EqualCoordinator parent; + + final int prefetch; + + final int limit; + + long produced; + + volatile SimpleQueue queue; + + volatile boolean done; + + int sourceMode; + + EqualSubscriber(EqualCoordinator parent, int prefetch) { + this.parent = parent; + this.limit = prefetch - (prefetch >> 2); + this.prefetch = prefetch; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + parent.drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + s.request(prefetch); + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == QueueSubscription.NONE) { + if (!queue.offer(t)) { + onError(new MissingBackpressureException()); + return; + } + } + parent.drain(); + } + + @Override + public void onError(Throwable t) { + EqualCoordinator p = parent; + if (p.error.addThrowable(t)) { + p.drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + public void request() { + if (sourceMode != QueueSubscription.SYNC) { + long p = produced + 1; + if (p >= limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + } + } + + public void cancel() { + SubscriptionHelper.cancel(this); + } + + void clear() { + SimpleQueue sq = queue; + if (sq != null) { + sq.clear(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java index 34f8b795ba..a8d0027241 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java @@ -33,6 +33,16 @@ public final class FlowableTimeoutTimed extends AbstractFlowableWithUpstream< final Scheduler scheduler; final Publisher other; + static final Disposable NEW_TIMER = new Disposable() { + @Override + public void dispose() { } + + @Override + public boolean isDisposed() { + return true; + } + }; + public FlowableTimeoutTimed(Publisher source, long timeout, TimeUnit unit, Scheduler scheduler, Publisher other) { super(source); @@ -68,16 +78,6 @@ static final class TimeoutTimedOtherSubscriber implements Subscriber, Disp final AtomicReference timer = new AtomicReference(); - static final Disposable NEW_TIMER = new Disposable() { - @Override - public void dispose() { } - - @Override - public boolean isDisposed() { - return true; - } - }; - volatile long index; volatile boolean done; @@ -142,9 +142,7 @@ public void run() { } }, timeout, unit); - if (!timer.compareAndSet(NEW_TIMER, d)) { - d.dispose(); - } + DisposableHelper.replace(timer, d); } } @@ -197,16 +195,6 @@ static final class TimeoutTimedSubscriber implements Subscriber, Disposabl final AtomicReference timer = new AtomicReference(); - static final Disposable NEW_TIMER = new Disposable() { - @Override - public void dispose() { } - - @Override - public boolean isDisposed() { - return true; - } - }; - volatile long index; volatile boolean done; @@ -259,9 +247,7 @@ public void run() { } }, timeout, unit); - if (!timer.compareAndSet(NEW_TIMER, d)) { - d.dispose(); - } + DisposableHelper.replace(timer, d); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java index 2e3819e614..689ae6e8c4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java @@ -382,6 +382,7 @@ public void onSubscribe(Subscription s) { ConsumerIndexHolder consumerIndexHolder = new ConsumerIndexHolder(producerIndex, this); if (restartTimerOnMaxSize) { Scheduler.Worker sw = scheduler.createWorker(); + worker = sw; sw.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); d = sw; } else { diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java index 6442ec4185..f2bd165518 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java @@ -82,7 +82,6 @@ public boolean hasNext() { notify.acquire(); } catch (InterruptedException ex) { dispose(); - Thread.currentThread().interrupt(); iteratorNotification = Notification.createOnError(ex); throw ExceptionHelper.wrapOrThrow(ex); } diff --git a/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java index 3c95183017..eb07bf81c6 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java @@ -64,6 +64,14 @@ public void cancel() { lazySet(CANCELLED); } + /** + * Returns true if this Subscription was cancelled. + * @return true if this Subscription was cancelled + */ + public boolean isCancelled() { + return get() == CANCELLED; + } + @Override public boolean offer(T e) { throw new UnsupportedOperationException("Should not be called!"); diff --git a/src/test/java/io/reactivex/InternalWrongNaming.java b/src/test/java/io/reactivex/InternalWrongNaming.java index de9f96be9f..e1e43b8ccd 100644 --- a/src/test/java/io/reactivex/InternalWrongNaming.java +++ b/src/test/java/io/reactivex/InternalWrongNaming.java @@ -176,7 +176,8 @@ public void flowableNoObserver() throws Exception { "FlowableFlatMapCompletable", "FlowableFlatMapCompletableCompletable", "FlowableFlatMapSingle", - "FlowableFlatMapMaybe" + "FlowableFlatMapMaybe", + "FlowableSequenceEqualSingle" ); } } diff --git a/src/test/java/io/reactivex/TestHelper.java b/src/test/java/io/reactivex/TestHelper.java index 7adc63c0e6..dcac3c99d9 100644 --- a/src/test/java/io/reactivex/TestHelper.java +++ b/src/test/java/io/reactivex/TestHelper.java @@ -580,6 +580,14 @@ public static void doubleOnSubscribe(MaybeObserver subscriber) { } } + /** + * Checks if the upstream's Subscription sent through the onSubscribe reports + * isCancelled properly before and after calling dispose. + * @param source the source to test + */ + public static void checkDisposed(Flowable source) { + // actually there is no way of testing this + } /** * Checks if the upstream's Disposable sent through the onSubscribe reports * isDisposed properly before and after calling dispose. @@ -1557,6 +1565,167 @@ protected void subscribeActual(Subscriber observer) { } } + /** + * Check if the given transformed reactive type reports multiple onSubscribe calls to + * RxJavaPlugins. + * @param the input value type + * @param the output value type + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeFlowableToSingle(Function, ? extends SingleSource> transform) { + List errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable source = new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + try { + BooleanSubscription d1 = new BooleanSubscription(); + + observer.onSubscribe(d1); + + BooleanSubscription d2 = new BooleanSubscription(); + + observer.onSubscribe(d2); + + b[0] = d1.isCancelled(); + b[1] = d2.isCancelled(); + } finally { + cdl.countDown(); + } + } + }; + + SingleSource 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 cancelled?", false, b[0]); + assertEquals("Second not cancelled?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Subscription 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 the input value type + * @param the output value type + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeFlowableToMaybe(Function, ? extends MaybeSource> transform) { + List errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable source = new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + try { + BooleanSubscription d1 = new BooleanSubscription(); + + observer.onSubscribe(d1); + + BooleanSubscription d2 = new BooleanSubscription(); + + observer.onSubscribe(d2); + + b[0] = d1.isCancelled(); + b[1] = d2.isCancelled(); + } finally { + cdl.countDown(); + } + } + }; + + MaybeSource 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 cancelled?", false, b[0]); + assertEquals("Second not cancelled?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Subscription 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 the input value type + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeFlowableToCompletable(Function, ? extends Completable> transform) { + List errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable source = new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + try { + BooleanSubscription d1 = new BooleanSubscription(); + + observer.onSubscribe(d1); + + BooleanSubscription d2 = new BooleanSubscription(); + + observer.onSubscribe(d2); + + b[0] = d1.isCancelled(); + b[1] = d2.isCancelled(); + } finally { + cdl.countDown(); + } + } + }; + + Completable 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 cancelled?", false, b[0]); + assertEquals("Second not cancelled?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Subscription 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. diff --git a/src/test/java/io/reactivex/flowable/FlowableNullTests.java b/src/test/java/io/reactivex/flowable/FlowableNullTests.java index b4191f6cd0..3b5e258896 100644 --- a/src/test/java/io/reactivex/flowable/FlowableNullTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableNullTests.java @@ -1168,7 +1168,7 @@ public void distinctUntilChangedBiPredicateNull() { @Test(expected = NullPointerException.class) public void distinctUntilChangedFunctionReturnsNull() { - just1.distinctUntilChanged(new Function() { + Flowable.range(1, 2).distinctUntilChanged(new Function() { @Override public Object apply(Integer v) { return null; @@ -2348,7 +2348,17 @@ public void toListSupplierReturnsNull() { public Collection call() { return null; } - }).blockingSubscribe(); + }).toFlowable().blockingSubscribe(); + } + + @Test(expected = NullPointerException.class) + public void toListSupplierReturnsNullSingle() { + just1.toList(new Callable>() { + @Override + public Collection call() { + return null; + } + }).blockingGet(); } @Test(expected = NullPointerException.class) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java index 696263df62..5f21971395 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java @@ -13,12 +13,17 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.*; + import java.util.*; import java.util.concurrent.TimeUnit; import org.junit.*; +import org.reactivestreams.Subscriber; import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.TestScheduler; @@ -168,4 +173,62 @@ public void testFasterSource() { public void constructorshouldbeprivate() { TestHelper.checkUtilityClass(BlockingFlowableLatest.class); } + + @Test(expected = UnsupportedOperationException.class) + public void remove() { + Flowable.never().blockingLatest().iterator().remove(); + } + + @Test + public void interrupted() { + Iterator it = Flowable.never().blockingLatest().iterator(); + + Thread.currentThread().interrupt(); + + try { + it.hasNext(); + } catch (RuntimeException ex) { + assertTrue(ex.toString(), ex.getCause() instanceof InterruptedException); + } + Thread.interrupted(); + } + + @Test(expected = NoSuchElementException.class) + public void empty() { + Flowable.empty().blockingLatest().iterator().next(); + } + + @Test(expected = TestException.class) + public void error() { + Flowable.error(new TestException()).blockingLatest().iterator().next(); + } + + @Test + public void error2() { + Iterator it = Flowable.error(new TestException()).blockingLatest().iterator(); + + for (int i = 0; i < 3; i++) { + try { + it.hasNext(); + fail("Should have thrown"); + } catch (TestException ex) { + // expected + } + } + } + + @SuppressWarnings("unchecked") + @Test + public void onError() { + Iterator it = Flowable.never().blockingLatest().iterator(); + + List errors = TestHelper.trackPluginErrors(); + try { + ((Subscriber)it).onError(new TestException()); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java index 061a793f9c..faf528be35 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java @@ -15,7 +15,7 @@ import static org.junit.Assert.*; -import java.util.Iterator; +import java.util.*; import java.util.concurrent.TimeUnit; import org.junit.*; @@ -103,4 +103,25 @@ public void constructorshouldbeprivate() { TestHelper.checkUtilityClass(BlockingFlowableMostRecent.class); } + + @Test + public void empty() { + Iterator it = Flowable.empty() + .blockingMostRecent(1) + .iterator(); + + try { + it.next(); + fail("Should have thrown"); + } catch (NoSuchElementException ex) { + // expected + } + + try { + it.remove(); + fail("Should have thrown"); + } catch (UnsupportedOperationException ex) { + // expected + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java index 9f40c19bb1..0bf768fd09 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java @@ -24,7 +24,9 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.internal.operators.flowable.BlockingFlowableNext.NextSubscriber; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.Schedulers; @@ -321,4 +323,61 @@ public void constructorshouldbeprivate() { TestHelper.checkUtilityClass(BlockingFlowableNext.class); } + @Test(expected = UnsupportedOperationException.class) + public void remove() { + Flowable.never().blockingNext().iterator().remove(); + } + + @Test + public void interrupt() { + Iterator it = Flowable.never().blockingNext().iterator(); + + try { + Thread.currentThread().interrupt(); + it.next(); + } catch (RuntimeException ex) { + assertTrue(ex.toString(), ex.getCause() instanceof InterruptedException); + } + } + + @Test + public void nextObserverError() { + NextSubscriber no = new NextSubscriber(); + + List errors = TestHelper.trackPluginErrors(); + try { + no.onError(new TestException()); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void nextObserverOnNext() throws Exception { + NextSubscriber no = new NextSubscriber(); + + no.setWaiting(); + no.onNext(Notification.createOnNext(1)); + + no.setWaiting(); + no.onNext(Notification.createOnNext(1)); + + assertEquals(1, no.takeNext().getValue().intValue()); + } + + @Test + public void nextObserverOnCompleteOnNext() throws Exception { + NextSubscriber no = new NextSubscriber(); + + no.setWaiting(); + no.onNext(Notification.createOnComplete()); + + no.setWaiting(); + no.onNext(Notification.createOnNext(1)); + + assertTrue(no.takeNext().isOnComplete()); + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java index e29ef33f4f..89e73c450c 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java @@ -13,9 +13,9 @@ package io.reactivex.internal.operators.flowable; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; -import java.util.Iterator; +import java.util.*; import org.junit.*; import org.reactivestreams.*; @@ -131,4 +131,41 @@ public void remove() { throw new UnsupportedOperationException(); } } + + @Test(expected = UnsupportedOperationException.class) + public void remove() { + BlockingFlowableIterator it = new BlockingFlowableIterator(128); + it.remove(); + } + + @Test + public void dispose() { + BlockingFlowableIterator it = new BlockingFlowableIterator(128); + + assertFalse(it.isDisposed()); + + it.dispose(); + + assertTrue(it.isDisposed()); + } + + @Test + public void interruptWait() { + BlockingFlowableIterator it = new BlockingFlowableIterator(128); + + try { + Thread.currentThread().interrupt(); + + it.hasNext(); + } catch (RuntimeException ex) { + assertTrue(ex.toString(), ex.getCause() instanceof InterruptedException); + } + } + + @Test(expected = NoSuchElementException.class) + public void emptyThrowsNoSuch() { + BlockingFlowableIterator it = new BlockingFlowableIterator(128); + it.onComplete(); + it.next(); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java index 2a8251fdd1..02d4314a83 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java @@ -14,8 +14,10 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.*; @@ -23,8 +25,11 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; public class FlowableAllTest { @@ -371,4 +376,41 @@ public boolean test(String v) { // FIXME need to decide about adding the value that probably caused the crash in some way // assertTrue(ex.getCause().getMessage().contains("Boo!")); } + + @Test + @Ignore("RS Subscription can't be checked for isCancelled") + public void dispose() { + // TestHelper.checkDisposed(Flowable.just(1).all(Functions.alwaysTrue()).toFlowable()); + } + + @Test + public void predicateThrows() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + + observer.onNext(1); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .all(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + throw new TestException(); + } + }) + .toFlowable() + .test() + .assertFailure(TestException.class); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file 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 c069558e32..1a954fcc3c 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java @@ -18,7 +18,7 @@ import java.io.IOException; import java.lang.reflect.Method; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -30,6 +30,7 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Consumer; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; @@ -532,4 +533,130 @@ public void ambArraySingleElement() { assertSame(Flowable.never(), Flowable.ambArray(Flowable.never())); } + @Test + @Ignore("RS Subscription no isCancelled") + public void disposed() { + //TestHelper.checkDisposed(Flowable.ambArray(Flowable.never(), Flowable.never())); + } + + @Test + public void manySources() { + Flowable[] a = new Flowable[32]; + Arrays.fill(a, Flowable.never()); + a[31] = Flowable.just(1); + + Flowable.amb(Arrays.asList(a)) + .test() + .assertResult(1); + } + + @Test + public void emptyIterable() { + Flowable.amb(Collections.>emptyList()) + .test() + .assertResult(); + } + + @Test + public void singleIterable() { + Flowable.amb(Collections.singletonList(Flowable.just(1))) + .test() + .assertResult(1); + } + + @Test + public void onNextRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + @SuppressWarnings("unchecked") + TestSubscriber to = Flowable.ambArray(ps1, ps2).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onNext(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onNext(1); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertSubscribed().assertNoErrors() + .assertNotComplete().assertValueCount(1); + } + } + + @Test + public void onCompleteRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + @SuppressWarnings("unchecked") + TestSubscriber to = Flowable.ambArray(ps1, ps2).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onComplete(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onComplete(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertResult(); + } + } + + @Test + public void onErrorRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + @SuppressWarnings("unchecked") + TestSubscriber to = Flowable.ambArray(ps1, ps2).test(); + + final Throwable ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex); + } + }; + + List errors = TestHelper.trackPluginErrors(); + try { + TestHelper.race(r1, r2, Schedulers.single()); + } finally { + RxJavaPlugins.reset(); + } + + to.assertFailure(TestException.class); + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, TestException.class); + } + } + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java index c6c2d66350..63c094d172 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java @@ -14,16 +14,23 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.io.IOException; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; public class FlowableAnyTest { @@ -547,4 +554,51 @@ public boolean test(String v) { // FIXME value as last cause? // assertTrue(ex.getCause().getMessage().contains("Boo!")); } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + // TestHelper.checkDisposed(Flowable.just(1).any(Functions.alwaysTrue()).toFlowable()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Publisher>() { + @Override + public Publisher apply(Flowable o) throws Exception { + return o.any(Functions.alwaysTrue()).toFlowable(); + } + }); + } + + @Test + public void predicateThrowsSuppressOthers() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + + observer.onNext(1); + observer.onNext(2); + observer.onError(new IOException()); + observer.onComplete(); + } + } + .any(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + throw new TestException(); + } + }) + .toFlowable() + .test() + .assertFailure(TestException.class); + + TestHelper.assertError(errors, 0, IOException.class); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java index 833dd1256e..2e86cf34e5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.assertEquals; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.reactivestreams.*; @@ -27,6 +28,7 @@ import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; public class FlowableBlockingTest { @@ -263,4 +265,70 @@ public void subscribe(Subscriber s) { source.blockingFirst(); } + + @Test + public void interrupt() { + TestSubscriber to = new TestSubscriber(); + Thread.currentThread().interrupt(); + Flowable.never().blockingSubscribe(to); + } + + @Test(expected = NoSuchElementException.class) + public void blockingSingleEmpty() { + Flowable.empty().blockingSingle(); + } + + @Test + public void onCompleteDelayed() { + TestSubscriber to = new TestSubscriber(); + + Flowable.empty().delay(100, TimeUnit.MILLISECONDS) + .blockingSubscribe(to); + + to.assertResult(); + } + + @Test + public void utilityClass() { + TestHelper.checkUtilityClass(FlowableBlockingSubscribe.class); + } + + @Test + public void disposeUpFront() { + TestSubscriber to = new TestSubscriber(); + to.dispose(); + Flowable.just(1).blockingSubscribe(to); + + to.assertEmpty(); + } + + @SuppressWarnings("rawtypes") + @Test + public void delayed() throws Exception { + final TestSubscriber to = new TestSubscriber(); + final Subscriber[] s = { null }; + + Schedulers.single().scheduleDirect(new Runnable() { + @SuppressWarnings("unchecked") + @Override + public void run() { + to.dispose(); + s[0].onNext(1); + } + }, 200, TimeUnit.MILLISECONDS); + + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + s[0] = observer; + } + }.blockingSubscribe(to); + + while (!to.isDisposed()) { + Thread.sleep(100); + } + + to.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java index a258b9ca71..7db23a2e72 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java @@ -32,7 +32,7 @@ import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.TestScheduler; +import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; public class FlowableBufferTest { @@ -183,7 +183,7 @@ public void subscribe(Subscriber observer) { } @Test - public void testObservableBasedOpenerAndCloser() { + public void testFlowableBasedOpenerAndCloser() { Flowable source = Flowable.unsafeCreate(new Publisher() { @Override public void subscribe(Subscriber observer) { @@ -234,7 +234,7 @@ public void subscribe(Subscriber observer) { } @Test - public void testObservableBasedCloser() { + public void testFlowableBasedCloser() { Flowable source = Flowable.unsafeCreate(new Publisher() { @Override public void subscribe(Subscriber observer) { @@ -1520,4 +1520,355 @@ public Collection call() throws Exception { ts.assertFailure(TestException.class); } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.range(1, 5).buffer(1, TimeUnit.DAYS, Schedulers.single())); + + TestHelper.checkDisposed(Flowable.range(1, 5).buffer(2, 1, TimeUnit.DAYS, Schedulers.single())); + + TestHelper.checkDisposed(Flowable.range(1, 5).buffer(1, 2, TimeUnit.DAYS, Schedulers.single())); + + TestHelper.checkDisposed(Flowable.range(1, 5) + .buffer(1, TimeUnit.DAYS, Schedulers.single(), 2, Functions.createArrayList(16), true)); + + TestHelper.checkDisposed(Flowable.range(1, 5).buffer(1)); + + TestHelper.checkDisposed(Flowable.range(1, 5).buffer(2, 1)); + + TestHelper.checkDisposed(Flowable.range(1, 5).buffer(1, 2)); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierReturnsNull() { + Flowable.never() + .buffer(1, TimeUnit.MILLISECONDS, Schedulers.single(), Integer.MAX_VALUE, new Callable>() { + int count; + @Override + public Collection call() throws Exception { + if (count++ == 1) { + return null; + } else { + return new ArrayList(); + } + } + }, false) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(NullPointerException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierReturnsNull2() { + Flowable.never() + .buffer(1, TimeUnit.MILLISECONDS, Schedulers.single(), 10, new Callable>() { + int count; + @Override + public Collection call() throws Exception { + if (count++ == 1) { + return null; + } else { + return new ArrayList(); + } + } + }, false) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(NullPointerException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierReturnsNull3() { + Flowable.never() + .buffer(2, 1, TimeUnit.MILLISECONDS, Schedulers.single(), new Callable>() { + int count; + @Override + public Collection call() throws Exception { + if (count++ == 1) { + return null; + } else { + return new ArrayList(); + } + } + }) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(NullPointerException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierThrows() { + Flowable.just(1) + .buffer(1, TimeUnit.SECONDS, Schedulers.single(), Integer.MAX_VALUE, new Callable>() { + @Override + public Collection call() throws Exception { + throw new TestException(); + } + }, false) + .test() + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierThrows2() { + Flowable.just(1) + .buffer(1, TimeUnit.SECONDS, Schedulers.single(), 10, new Callable>() { + @Override + public Collection call() throws Exception { + throw new TestException(); + } + }, false) + .test() + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierThrows3() { + Flowable.just(1) + .buffer(2, 1, TimeUnit.SECONDS, Schedulers.single(), new Callable>() { + @Override + public Collection call() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierThrows4() { + Flowable.never() + .buffer(1, TimeUnit.MILLISECONDS, Schedulers.single(), Integer.MAX_VALUE, new Callable>() { + int count; + @Override + public Collection call() throws Exception { + if (count++ == 1) { + throw new TestException(); + } else { + return new ArrayList(); + } + } + }, false) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierThrows5() { + Flowable.never() + .buffer(1, TimeUnit.MILLISECONDS, Schedulers.single(), 10, new Callable>() { + int count; + @Override + public Collection call() throws Exception { + if (count++ == 1) { + throw new TestException(); + } else { + return new ArrayList(); + } + } + }, false) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void supplierThrows6() { + Flowable.never() + .buffer(2, 1, TimeUnit.MILLISECONDS, Schedulers.single(), new Callable>() { + int count; + @Override + public Collection call() throws Exception { + if (count++ == 1) { + throw new TestException(); + } else { + return new ArrayList(); + } + } + }) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void restartTimer() { + Flowable.range(1, 5) + .buffer(1, TimeUnit.DAYS, Schedulers.single(), 2, Functions.createArrayList(16), true) + .test() + .assertResult(Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5)); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferSkipError() { + Flowable.error(new TestException()) + .buffer(2, 1) + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferSupplierCrash2() { + Flowable.range(1, 2) + .buffer(1, new Callable>() { + int calls; + @Override + public List call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return new ArrayList(); + } + }) + .test() + .assertFailure(TestException.class, Arrays.asList(1)); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferSkipSupplierCrash2() { + Flowable.range(1, 2) + .buffer(2, 1, new Callable>() { + int calls; + @Override + public List call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return new ArrayList(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferSkipOverlap() { + Flowable.range(1, 5) + .buffer(5, 1) + .test() + .assertResult( + Arrays.asList(1, 2, 3, 4, 5), + Arrays.asList(2, 3, 4, 5), + Arrays.asList(3, 4, 5), + Arrays.asList(4, 5), + Arrays.asList(5) + ); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedExactError() { + Flowable.error(new TestException()) + .buffer(1, TimeUnit.DAYS) + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedSkipError() { + Flowable.error(new TestException()) + .buffer(1, 2, TimeUnit.DAYS) + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedOverlapError() { + Flowable.error(new TestException()) + .buffer(2, 1, TimeUnit.DAYS) + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedExactEmpty() { + Flowable.empty() + .buffer(1, TimeUnit.DAYS) + .test() + .assertResult(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedSkipEmpty() { + Flowable.empty() + .buffer(1, 2, TimeUnit.DAYS) + .test() + .assertResult(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedOverlapEmpty() { + Flowable.empty() + .buffer(2, 1, TimeUnit.DAYS) + .test() + .assertResult(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedExactSupplierCrash() { + TestScheduler scheduler = new TestScheduler(); + + PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber> to = ps + .buffer(1, TimeUnit.MILLISECONDS, scheduler, 1, new Callable>() { + int calls; + @Override + public List call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return new ArrayList(); + } + }, true) + .test(); + + ps.onNext(1); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + ps.onNext(2); + + to + .assertFailure(TestException.class, Arrays.asList(1)); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferTimedExactBoundedError() { + TestScheduler scheduler = new TestScheduler(); + + PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber> to = ps + .buffer(1, TimeUnit.MILLISECONDS, scheduler, 1, Functions.createArrayList(16), true) + .test(); + + ps.onError(new TestException()); + + to + .assertFailure(TestException.class); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java index d0c072b915..2cae36f9aa 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java @@ -23,10 +23,11 @@ import org.junit.*; import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -288,4 +289,91 @@ public void onNext(Integer t) { ts.assertNotComplete(); ts.assertError(TestException.class); } + + @Test + public void take() { + Flowable cache = Flowable.range(1, 5).cache(); + + cache.take(2).test().assertResult(1, 2); + cache.take(3).test().assertResult(1, 2, 3); + } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.range(1, 5).cache()); + } + + @Test + public void disposeOnArrival2() { + Flowable o = PublishProcessor.create().cache(); + + o.test(); + + o.test(0L, true) + .assertEmpty(); + } + + @Test + public void subscribeEmitRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final Flowable cache = ps.cache(); + + cache.test(); + + final TestSubscriber to = new TestSubscriber(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cache.subscribe(to); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int j = 0; j < 500; j++) { + ps.onNext(j); + } + ps.onComplete(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed().assertValueCount(500).assertComplete().assertNoErrors(); + } + } + + @Test + public void observers() { + PublishProcessor ps = PublishProcessor.create(); + FlowableCache cache = (FlowableCache)Flowable.range(1, 5).concatWith(ps).cache(); + + assertFalse(cache.hasSubscribers()); + + assertEquals(0, cache.cachedEventCount()); + + TestSubscriber to = cache.test(); + + assertTrue(cache.hasSubscribers()); + + assertEquals(5, cache.cachedEventCount()); + + ps.onComplete(); + + to.assertResult(1, 2, 3, 4, 5); + } + + @Test + public void disposeOnArrival() { + Flowable.range(1, 5).cache() + .test(0L, true) + .assertEmpty(); + } } \ No newline at end of file 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 e2532d82c8..4aa69c7f84 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.*; @@ -26,11 +27,11 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.operators.flowable.FlowableZipTest.ArgsToString; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; @@ -1215,4 +1216,159 @@ public void combineLatestEmpty() { public void combineLatestDelayErrorEmpty() { assertSame(Flowable.empty(), Flowable.combineLatestDelayError(new Flowable[0], Functions.identity(), 16)); } + + @Test + public void error() { + Flowable.combineLatest(Flowable.never(), Flowable.error(new TestException()), new BiFunction() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + @Ignore("RS Subscription no isCancelled") + public void disposed() { + TestHelper.checkDisposed(Flowable.combineLatest(Flowable.never(), Flowable.never(), new BiFunction() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + })); + } + + @Test + public void cancelWhileSubscribing() { + final TestSubscriber to = new TestSubscriber(); + + Flowable.combineLatest( + Flowable.just(1) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + to.cancel(); + } + }), + Flowable.never(), + new BiFunction() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + }) + .subscribe(to); + } + + @Test + public void onErrorRace() { + for (int i = 0; i < 500; i++) { + List errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + TestSubscriber to = Flowable.combineLatest(ps1, ps2, new BiFunction() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + }).test(); + + final TestException ex1 = new TestException(); + final TestException ex2 = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + if (to.errorCount() != 0) { + if (to.errors().get(0) instanceof CompositeException) { + to.assertSubscribed() + .assertNotComplete() + .assertNoValues(); + + for (Throwable e : TestHelper.errorList(to)) { + assertTrue(e.toString(), e instanceof TestException); + } + + } else { + to.assertFailure(TestException.class); + } + } + + for (Throwable e : errors) { + assertTrue(e.toString(), e instanceof TestException); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void combineAsync() { + Flowable source = Flowable.range(1, 1000).subscribeOn(Schedulers.computation()); + + Flowable.combineLatest(source, source, new BiFunction() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + }) + .take(500) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + } + + @SuppressWarnings("unchecked") + @Test + public void errorDelayed() { + Flowable.combineLatestDelayError( + new Function() { + @Override + public Object apply(Object[] a) throws Exception { + return a; + } + }, + 128, + Flowable.error(new TestException()), + Flowable.just(1) + ) + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void errorDelayed2() { + Flowable.combineLatestDelayError( + new Function() { + @Override + public Object apply(Object[] a) throws Exception { + return a; + } + }, + 128, + Flowable.error(new TestException()).startWith(1), + Flowable.empty() + ) + .test() + .assertFailure(TestException.class); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java index a610a0f34d..4e81cf44e7 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java @@ -17,17 +17,18 @@ import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; import org.reactivestreams.Publisher; -import io.reactivex.Flowable; -import io.reactivex.exceptions.TestException; +import io.reactivex.*; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.processors.PublishProcessor; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.*; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -817,4 +818,214 @@ public void concatEagerIterable() { .assertResult(1, 2); } + @Test + public void empty() { + Flowable.empty().hide().concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.range(1, 2); + } + }) + .test() + .assertResult(); + } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).hide().concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.range(1, 2); + } + })); + } + + @Test + public void innerError() { + Flowable.just(1).hide().concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.error(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerOuterRace() { + for (int i = 0; i < 500; i++) { + List errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + TestSubscriber to = ps1.concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return ps2; + } + }).test(); + + final TestException ex1 = new TestException(); + final TestException ex2 = new TestException(); + + ps1.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertSubscribed().assertNoValues().assertNotComplete(); + + Throwable ex = to.errors().get(0); + + if (ex instanceof CompositeException) { + List es = TestHelper.errorList(to); + TestHelper.assertError(es, 0, TestException.class); + TestHelper.assertError(es, 1, TestException.class); + } else { + to.assertError(TestException.class); + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, TestException.class); + } + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void innerErrorMaxConcurrency() { + Flowable.just(1).hide().concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.error(new TestException()); + } + }, 1, 128) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerCallableThrows() { + Flowable.just(1).hide().concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.fromCallable(new Callable() { + @Override + public Integer call() throws Exception { + throw new TestException(); + } + }); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerErrorAfterPoll() { + final UnicastProcessor us = UnicastProcessor.create(); + us.onNext(1); + + TestSubscriber to = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + us.onError(new TestException()); + } + }; + + Flowable.just(1).hide() + .concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return us; + } + }, 1, 128) + .subscribe(to); + + to + .assertFailure(TestException.class, 1); + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + + final TestSubscriber to = ps1.concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.never(); + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onNext(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertEmpty(); + } + } + + @Test + public void mapperCancels() { + final TestSubscriber to = new TestSubscriber(); + + Flowable.just(1).hide() + .concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + to.cancel(); + return Flowable.never(); + } + }, 1, 128) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void innerErrorFused() { + Flowable.just(1).hide().concatMapEager(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.range(1, 2).map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }); + } + }) + .test() + .assertFailure(TestException.class); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java index 573503a5c9..5e8dd0d05c 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java @@ -15,7 +15,8 @@ import org.junit.*; -import io.reactivex.Flowable; +import io.reactivex.*; +import io.reactivex.functions.Function; public class FlowableCountTest { @Test @@ -38,4 +39,29 @@ public void simple() { } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).count()); + + TestHelper.checkDisposed(Flowable.just(1).count().toFlowable()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.count().toFlowable(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function, SingleSource>() { + @Override + public SingleSource apply(Flowable o) throws Exception { + return o.count(); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java index 047be8738f..7ed2f1d99d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java @@ -15,6 +15,9 @@ import static org.junit.Assert.*; +import java.io.IOException; +import java.util.List; + import org.junit.Test; import org.reactivestreams.*; @@ -23,6 +26,9 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Cancellable; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; public class FlowableCreateTest { @@ -450,4 +456,425 @@ public void subscribe(FlowableEmitter e) throws Exception { assertNull(error[0]); } + @Test + public void onErrorRace() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable source = Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + final FlowableEmitter f = e.serialize(); + + final TestException ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + f.onError(null); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + f.onError(ex); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + } + }, m); + + List errors = TestHelper.trackPluginErrors(); + + try { + for (int i = 0; i < 500; i++) { + source + .test() + .assertFailure(Throwable.class); + } + } finally { + RxJavaPlugins.reset(); + } + assertFalse(errors.isEmpty()); + } + } + + @Test + public void onCompleteRace() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable source = Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + final FlowableEmitter f = e.serialize(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + f.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + f.onComplete(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + } + }, m); + + for (int i = 0; i < 500; i++) { + source + .test() + .assertResult(); + } + } + } + + @Test + public void nullValue() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e.onNext(null); + } + }, m) + .test() + .assertFailure(NullPointerException.class); + } + } + + @Test + public void nullThrowable() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + System.out.println(m); + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e.onError(null); + } + }, m) + .test() + .assertFailure(NullPointerException.class); + } + } + + @Test + public void serializedConcurrentOnNextOnError() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + final FlowableEmitter f = e.serialize(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 1000; i++) { + f.onNext(1); + } + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 100; i++) { + f.onNext(1); + } + f.onError(new TestException()); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + } + }, m) + .test() + .assertSubscribed().assertNotComplete() + .assertError(TestException.class); + } + } + + @Test + public void callbackThrows() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + throw new TestException(); + } + }, m) + .test() + .assertFailure(TestException.class); + } + } + + @Test + public void nullValueSync() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e.serialize().onNext(null); + } + }, m) + .test() + .assertFailure(NullPointerException.class); + } + } + + @Test + public void createNullValue() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + final Throwable[] error = { null }; + + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } + } + }, m) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); + } + } + + @Test(expected = NullPointerException.class) + public void nullArgument() { + Flowable.create(null, FlowableEmitter.BackpressureMode.NONE); + } + + @Test + public void onErrorCrash() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + Disposable d = Disposables.empty(); + e.setDisposable(d); + try { + e.onError(new IOException()); + fail("Should have thrown"); + } catch (TestException ex) { + // expected + } + assertTrue(d.isDisposed()); + } + }, m) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription d) { + } + + @Override + public void onNext(Object value) { + } + + @Override + public void onError(Throwable e) { + throw new TestException(); + } + + @Override + public void onComplete() { + } + }); + } + } + + @Test + public void onCompleteCrash() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + Disposable d = Disposables.empty(); + e.setDisposable(d); + try { + e.onComplete(); + fail("Should have thrown"); + } catch (TestException ex) { + // expected + } + assertTrue(d.isDisposed()); + } + }, m) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription d) { + } + + @Override + public void onNext(Object value) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + throw new TestException(); + } + }); + } + } + + @Test + public void createNullValueSerialized() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + final Throwable[] error = { null }; + + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } + } + }, m) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); + } + } + + @Test + public void nullThrowableSync() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e.serialize().onError(null); + } + }, m) + .test() + .assertFailure(NullPointerException.class); + } + } + + @Test + public void serializedConcurrentOnNext() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + final FlowableEmitter f = e.serialize(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 1000; i++) { + f.onNext(1); + } + } + }; + + TestHelper.race(r1, r1, Schedulers.single()); + } + }, m) + .take(1000) + .test() + .assertSubscribed().assertValueCount(1000).assertComplete().assertNoErrors(); + } + } + + @Test + public void serializedConcurrentOnNextOnComplete() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + TestSubscriber to = Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + final FlowableEmitter f = e.serialize(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 1000; i++) { + f.onNext(1); + } + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 100; i++) { + f.onNext(1); + } + f.onComplete(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + } + }, m) + .test() + .assertSubscribed().assertComplete() + .assertNoErrors(); + + int c = to.valueCount(); + assertTrue("" + c, c >= 100); + } + } + + @Test + public void serialized() { + for (FlowableEmitter.BackpressureMode m : FlowableEmitter.BackpressureMode.values()) { + List errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + FlowableEmitter f = e.serialize(); + + assertSame(f, f.serialize()); + + assertFalse(f.isCancelled()); + + final int[] calls = { 0 }; + + f.setCancellable(new Cancellable() { + @Override + public void cancel() throws Exception { + calls[0]++; + } + }); + + e.onComplete(); + + assertTrue(f.isCancelled()); + + assertEquals(1, calls[0]); + } + }, m) + .test() + .assertResult(); + + assertTrue(errors.toString(), errors.isEmpty()); + } finally { + RxJavaPlugins.reset(); + } + } + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java index ffae6c463d..6ee57e6281 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java @@ -21,8 +21,9 @@ import org.junit.*; import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.subscribers.TestSubscriber; @@ -158,4 +159,20 @@ public void subscribe(Subscriber t) { ts.assertComplete(); ts.assertNoErrors(); } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.never().onTerminateDetach()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.onTerminateDetach(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java index 4a46b023e3..3743c03b21 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java @@ -13,14 +13,26 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.*; +import java.util.concurrent.Callable; + import org.junit.*; import org.mockito.InOrder; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; +import io.reactivex.subscribers.*; public class FlowableDistinctTest { @@ -120,4 +132,109 @@ public void testDistinctOfSourceWithExceptionsFromKeySelector() { inOrder.verify(w, never()).onNext(anyString()); inOrder.verify(w, never()).onComplete(); } + + @Test + public void error() { + Flowable.error(new TestException()) + .distinct() + .test() + .assertFailure(TestException.class); + } + + @Test + public void fusedSync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + Flowable.just(1, 1, 2, 1, 3, 2, 4, 5, 4) + .distinct() + .subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void fusedAsync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + UnicastProcessor us = UnicastProcessor.create(); + + us + .distinct() + .subscribe(to); + + TestHelper.emit(us, 1, 1, 2, 1, 3, 2, 4, 5, 4); + + SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void fusedClear() { + Flowable.just(1, 1, 2, 1, 3, 2, 4, 5, 4) + .distinct() + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription d) { + QueueSubscription qd = (QueueSubscription)d; + + assertFalse(qd.isEmpty()); + + qd.clear(); + + assertTrue(qd.isEmpty()); + } + + @Override + public void onNext(Integer value) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + } + + @Test + public void collectionSupplierThrows() { + Flowable.just(1) + .distinct(Functions.identity(), new Callable>() { + @Override + public Collection call() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void badSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .distinct() + .test() + .assertResult(1); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java index 4ebe02c14f..cb8c5aa978 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java @@ -13,8 +13,11 @@ package io.reactivex.internal.operators.flowable; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.io.IOException; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; @@ -24,7 +27,10 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; import io.reactivex.subscribers.*; public class FlowableDistinctUntilChangedTest { @@ -262,4 +268,77 @@ public boolean test(String a, String b) { ts.assertNotComplete(); ts.assertError(TestException.class); } + + @Test + public void fused() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + Flowable.just(1, 2, 2, 3, 3, 4, 5) + .distinctUntilChanged(new BiPredicate() { + @Override + public boolean test(Integer a, Integer b) throws Exception { + return a.equals(b); + } + }) + .subscribe(to); + + to.assertOf(SubscriberFusion.assertFuseable()) + .assertOf(SubscriberFusion.assertFusionMode(QueueDisposable.SYNC)) + .assertResult(1, 2, 3, 4, 5) + ; + } + + @Test + public void fusedAsync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + UnicastProcessor up = UnicastProcessor.create(); + + up + .distinctUntilChanged(new BiPredicate() { + @Override + public boolean test(Integer a, Integer b) throws Exception { + return a.equals(b); + } + }) + .subscribe(to); + + TestHelper.emit(up, 1, 2, 2, 3, 3, 4, 5); + + to.assertOf(SubscriberFusion.assertFuseable()) + .assertOf(SubscriberFusion.assertFusionMode(QueueDisposable.ASYNC)) + .assertResult(1, 2, 3, 4, 5) + ; + } + + @Test + public void ignoreCancel() { + List errors = TestHelper.trackPluginErrors(); + + try { + new Flowable() { + @Override + public void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + s.onNext(3); + s.onError(new IOException()); + s.onComplete(); + } + } + .distinctUntilChanged(new BiPredicate() { + @Override + public boolean test(Integer a, Integer b) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class, 1); + + TestHelper.assertError(errors, 0, IOException.class); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java index dfa0da02ea..60302edafd 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java @@ -15,15 +15,17 @@ import static org.junit.Assert.*; +import java.util.*; + +import org.junit.*; +import org.reactivestreams.*; + import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import java.util.List; -import java.util.NoSuchElementException; -import org.junit.Test; -import org.reactivestreams.*; +import io.reactivex.processors.PublishProcessor; public class FlowableElementAtTest { @@ -235,4 +237,11 @@ protected void subscribeActual(Subscriber subscriber) { RxJavaPlugins.reset(); } } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(PublishProcessor.create().elementAt(0).toFlowable()); + TestHelper.checkDisposed(PublishProcessor.create().elementAt(0, 1).toFlowable()); + } } 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 d47496b5f1..ea6744c58a 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 observable = 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 observer = TestHelper.mockSubscriber(); + Subscriber Subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Flowable.subscribe(Subscriber); - verify(observer, Mockito.never()).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, 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(); } /** @@ -156,7 +156,7 @@ public void onNext(Integer t) { @Ignore("subscribers are not allowed to throw") public void testFatalError() { // try { -// Observable.just(1) +// Flowable.just(1) // .filter(new Predicate() { // @Override // public boolean test(Integer t) { @@ -545,4 +545,87 @@ public boolean test(Integer v) throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.range(1, 5).filter(Functions.alwaysTrue())); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.filter(Functions.alwaysTrue()); + } + }); + } + + @Test + public void fusedSync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + Flowable.range(1, 5) + .filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }) + .subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + .assertResult(2, 4); + } + + @Test + public void fusedAsync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + UnicastProcessor us = UnicastProcessor.create(); + + us + .filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }) + .subscribe(to); + + TestHelper.emit(us, 1, 2, 3, 4, 5); + + SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) + .assertResult(2, 4); + } + + @Test + public void fusedReject() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + + Flowable.range(1, 5) + .filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }) + .subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + .assertResult(2, 4); + } + + @Test + public void filterThrows() { + Flowable.range(1, 5) + .filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java index 80b772a66d..6f1bba0889 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; -import org.junit.Test; +import org.junit.*; import io.reactivex.*; import io.reactivex.exceptions.*; @@ -380,4 +380,16 @@ public CompletableSource apply(Integer v) throws Exception { .awaitDone(5, TimeUnit.SECONDS) .assertResult(); } + + @Test + @Ignore("RS Subscription no isCancelled") + public void disposedObservable() { + TestHelper.checkDisposed(Flowable.range(1, 10) + .flatMapCompletable(new Function() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.complete(); + } + }).toFlowable()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java index 29e68aee7c..8b79c48f11 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.processors.PublishProcessor; @@ -271,4 +272,92 @@ public Integer call() throws NumberFormatException { .test() .assertFailure(NumberFormatException.class, 1); } + + @Test + public void disposed() { + TestHelper.checkDisposed(PublishProcessor.create().flatMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) throws Exception { + return Maybe.empty(); + } + })); + } + + @Test + public void asyncFlatten() { + Flowable.range(1, 1000) + .flatMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) throws Exception { + return Maybe.just(1).subscribeOn(Schedulers.computation()); + } + }) + .take(500) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(500) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void asyncFlattenNone() { + Flowable.range(1, 1000) + .flatMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) throws Exception { + return Maybe.empty().subscribeOn(Schedulers.computation()); + } + }) + .take(500) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + } + + @Test + public void successError() { + final PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber to = Flowable.range(1, 2) + .flatMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) throws Exception { + if (v == 2) { + return ps.singleElement(); + } + return Maybe.error(new TestException()); + } + }, true, Integer.MAX_VALUE) + .test(); + + ps.onNext(1); + ps.onComplete(); + + to + .assertFailure(TestException.class, 1); + } + + @Test + public void completeError() { + final PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber to = Flowable.range(1, 2) + .flatMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) throws Exception { + if (v == 2) { + return ps.singleElement(); + } + return Maybe.error(new TestException()); + } + }, true, Integer.MAX_VALUE) + .test(); + + ps.onComplete(); + + to + .assertFailure(TestException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java index 7ce5fa16f5..b8a2c664d0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java @@ -258,4 +258,55 @@ public Integer call() throws NumberFormatException { .test() .assertFailure(NumberFormatException.class, 1); } + + @Test + public void asyncFlatten() { + Flowable.range(1, 1000) + .flatMapSingle(new Function>() { + @Override + public SingleSource apply(Integer v) throws Exception { + return Single.just(1).subscribeOn(Schedulers.computation()); + } + }) + .take(500) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(500) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void successError() { + final PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber to = Flowable.range(1, 2) + .flatMapSingle(new Function>() { + @Override + public SingleSource apply(Integer v) throws Exception { + if (v == 2) { + return ps.singleOrError(); + } + return Single.error(new TestException()); + } + }, true, Integer.MAX_VALUE) + .test(); + + ps.onNext(1); + ps.onComplete(); + + to + .assertFailure(TestException.class, 1); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(PublishProcessor.create().flatMapSingle(new Function>() { + @Override + public SingleSource apply(Integer v) throws Exception { + return Single.just(1); + } + })); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java index ad0f484668..400a9b9259 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java @@ -743,4 +743,108 @@ public Publisher apply(Object v) throws Exception { })); } + @Test + public void mergeScalar() { + Flowable.merge(Flowable.just(Flowable.just(1))) + .test() + .assertResult(1); + } + + @Test + public void mergeScalar2() { + Flowable.merge(Flowable.just(Flowable.just(1)).hide()) + .test() + .assertResult(1); + } + + @Test + public void mergeScalarEmpty() { + Flowable.merge(Flowable.just(Flowable.empty()).hide()) + .test() + .assertResult(); + } + + @Test + public void mergeScalarError() { + Flowable.merge(Flowable.just(Flowable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + throw new TestException(); + } + })).hide()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void scalarReentrant() { + final PublishProcessor> ps = PublishProcessor.create(); + + TestSubscriber to = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(Flowable.just(2)); + } + } + }; + + Flowable.merge(ps) + .subscribe(to); + + ps.onNext(Flowable.just(1)); + ps.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void scalarReentrant2() { + final PublishProcessor> ps = PublishProcessor.create(); + + TestSubscriber to = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(Flowable.just(2)); + } + } + }; + + Flowable.merge(ps, 2) + .subscribe(to); + + ps.onNext(Flowable.just(1)); + ps.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void innerCompleteCancelRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final TestSubscriber to = Flowable.merge(Flowable.just(ps)).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java index 8ce8713840..d720b14135 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java @@ -545,4 +545,17 @@ public Integer apply(Integer a, Integer b) { ts.assertNoErrors(); ts.assertComplete(); } + + @Test + public void flatMapIterablePrefetch() { + Flowable.just(1, 2) + .flatMapIterable(new Function>() { + @Override + public Iterable apply(Integer t) throws Exception { + return Arrays.asList(t * 10); + } + }, 1) + .test() + .assertResult(10, 20); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java index 2cd4c82021..951c8a540b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -28,7 +29,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.util.CrashingIterable; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; @@ -43,11 +44,11 @@ public void testNull() { @Test public void testListIterable() { - Flowable observable = Flowable.fromIterable(Arrays. asList("one", "two", "three")); + Flowable flowable = Flowable.fromIterable(Arrays. asList("one", "two", "three")); Subscriber observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(observer); verify(observer, times(1)).onNext("one"); verify(observer, times(1)).onNext("two"); @@ -87,11 +88,11 @@ public void remove() { } }; - Flowable observable = Flowable.fromIterable(it); + Flowable flowable = Flowable.fromIterable(it); Subscriber observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(observer); verify(observer, times(1)).onNext("1"); verify(observer, times(1)).onNext("2"); @@ -102,11 +103,11 @@ public void remove() { @Test public void testObservableFromIterable() { - Flowable observable = Flowable.fromIterable(Arrays. asList("one", "two", "three")); + Flowable flowable = Flowable.fromIterable(Arrays. asList("one", "two", "three")); Subscriber observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(observer); verify(observer, times(1)).onNext("one"); verify(observer, times(1)).onNext("two"); @@ -864,4 +865,105 @@ public void run() { TestHelper.race(r1, r2, Schedulers.single()); } } + + @Test + public void fusionRejected() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ASYNC); + + Flowable.fromIterable(Arrays.asList(1, 2, 3)) + .subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + .assertResult(1, 2, 3); + } + + @Test + public void fusionClear() { + Flowable.fromIterable(Arrays.asList(1, 2, 3)) + .subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription d) { + @SuppressWarnings("unchecked") + QueueSubscription qd = (QueueSubscription)d; + + qd.requestFusion(QueueSubscription.ANY); + + try { + assertEquals(1, qd.poll().intValue()); + } catch (Throwable ex) { + fail(ex.toString()); + } + + qd.clear(); + try { + assertNull(qd.poll()); + } catch (Throwable ex) { + fail(ex.toString()); + } + } + + @Override + public void onNext(Integer value) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + } + + @Test + public void iteratorThrows() { + Flowable.fromIterable(new CrashingIterable(1, 100, 100)) + .test() + .assertFailureAndMessage(TestException.class, "iterator()"); + } + + @Test + public void hasNext2Throws() { + Flowable.fromIterable(new CrashingIterable(100, 2, 100)) + .test() + .assertFailureAndMessage(TestException.class, "hasNext()", 0); + } + + @Test + public void hasNextCancels() { + final TestSubscriber to = new TestSubscriber(); + + Flowable.fromIterable(new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + int count; + + @Override + public boolean hasNext() { + if (++count == 2) { + to.cancel(); + } + return true; + } + + @Override + public Integer next() { + return 1; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }) + .subscribe(to); + + to.assertValue(1) + .assertNoErrors() + .assertNotComplete(); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java index 0dd0a63a76..bec9359fb1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java @@ -13,12 +13,16 @@ package io.reactivex.internal.operators.flowable; +import java.util.List; import java.util.concurrent.Callable; -import org.junit.Test; +import org.junit.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; public class FlowableGenerateTest { @@ -44,4 +48,83 @@ public void accept(Object d) throws Exception { .test() .assertResult(10, 10, 10, 10, 10); } + + @Test + public void stateSupplierThrows() { + Flowable.generate(new Callable() { + @Override + public Object call() throws Exception { + throw new TestException(); + } + }, new BiConsumer>() { + @Override + public void accept(Object s, Emitter e) throws Exception { + e.onNext(s); + } + }, Functions.emptyConsumer()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void generatorThrows() { + Flowable.generate(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new BiConsumer>() { + @Override + public void accept(Object s, Emitter e) throws Exception { + throw new TestException(); + } + }, Functions.emptyConsumer()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void disposerThrows() { + List errors = TestHelper.trackPluginErrors(); + try { + Flowable.generate(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new BiConsumer>() { + @Override + public void accept(Object s, Emitter e) throws Exception { + e.onComplete(); + } + }, new Consumer() { + @Override + public void accept(Object d) throws Exception { + throw new TestException(); + } + }) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.generate(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new BiConsumer>() { + @Override + public void accept(Object s, Emitter e) throws Exception { + e.onComplete(); + } + }, Functions.emptyConsumer())); + } } 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 3e79c14a05..e2412e30c6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java @@ -15,17 +15,23 @@ */ package io.reactivex.internal.operators.flowable; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import org.junit.*; import org.mockito.MockitoAnnotations; import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; public class FlowableGroupJoinTest { @@ -356,4 +362,330 @@ public Integer apply(Integer t1, Flowable t2) { verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).groupJoin( + Flowable.just(2), + new Function>() { + @Override + public Flowable apply(Integer left) throws Exception { + return Flowable.never(); + } + }, + new Function>() { + @Override + public Flowable apply(Integer right) throws Exception { + return Flowable.never(); + } + }, + new BiFunction, Object>() { + @Override + public Object apply(Integer r, Flowable l) throws Exception { + return l; + } + } + )); + } + + @Test + public void innerCompleteLeft() { + Flowable.just(1) + .groupJoin( + Flowable.just(2), + new Function>() { + @Override + public Flowable apply(Integer left) throws Exception { + return Flowable.empty(); + } + }, + new Function>() { + @Override + public Flowable apply(Integer right) throws Exception { + return Flowable.never(); + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Integer r, Flowable l) throws Exception { + return l; + } + } + ) + .flatMap(Functions.>identity()) + .test() + .assertResult(); + } + + @Test + public void innerErrorLeft() { + Flowable.just(1) + .groupJoin( + Flowable.just(2), + new Function>() { + @Override + public Flowable apply(Integer left) throws Exception { + return Flowable.error(new TestException()); + } + }, + new Function>() { + @Override + public Flowable apply(Integer right) throws Exception { + return Flowable.never(); + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Integer r, Flowable l) throws Exception { + return l; + } + } + ) + .flatMap(Functions.>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerCompleteRight() { + Flowable.just(1) + .groupJoin( + Flowable.just(2), + new Function>() { + @Override + public Flowable apply(Integer left) throws Exception { + return Flowable.never(); + } + }, + new Function>() { + @Override + public Flowable apply(Integer right) throws Exception { + return Flowable.empty(); + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Integer r, Flowable l) throws Exception { + return l; + } + } + ) + .flatMap(Functions.>identity()) + .test() + .assertResult(2); + } + + @Test + public void innerErrorRight() { + Flowable.just(1) + .groupJoin( + Flowable.just(2), + new Function>() { + @Override + public Flowable apply(Integer left) throws Exception { + return Flowable.never(); + } + }, + new Function>() { + @Override + public Flowable apply(Integer right) throws Exception { + return Flowable.error(new TestException()); + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Integer r, Flowable l) throws Exception { + return l; + } + } + ) + .flatMap(Functions.>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerErrorRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + List errors = TestHelper.trackPluginErrors(); + + try { + TestSubscriber> to = Flowable.just(1) + .groupJoin( + Flowable.just(2).concatWith(Flowable.never()), + new Function>() { + @Override + public Flowable apply(Integer left) throws Exception { + return ps1; + } + }, + new Function>() { + @Override + public Flowable apply(Integer right) throws Exception { + return ps2; + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Integer r, Flowable l) throws Exception { + return l; + } + } + ) + .test(); + + final TestException ex1 = new TestException(); + final TestException ex2 = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertValueCount(1); + + Throwable exc = to.errors().get(0); + + if (exc instanceof CompositeException) { + List es = TestHelper.compositeList(exc); + TestHelper.assertError(es, 0, TestException.class); + TestHelper.assertError(es, 1, TestException.class); + } else { + to.assertError(TestException.class); + } + + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void outerErrorRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + List errors = TestHelper.trackPluginErrors(); + + try { + TestSubscriber to = ps1 + .groupJoin( + ps2, + new Function>() { + @Override + public Flowable apply(Object left) throws Exception { + return Flowable.never(); + } + }, + new Function>() { + @Override + public Flowable apply(Object right) throws Exception { + return Flowable.never(); + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Object r, Flowable l) throws Exception { + return l; + } + } + ) + .flatMap(Functions.>identity()) + .test(); + + final TestException ex1 = new TestException(); + final TestException ex2 = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertNoValues(); + + Throwable exc = to.errors().get(0); + + if (exc instanceof CompositeException) { + List es = TestHelper.compositeList(exc); + TestHelper.assertError(es, 0, TestException.class); + TestHelper.assertError(es, 1, TestException.class); + } else { + to.assertError(TestException.class); + } + + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void rightEmission() { + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + TestSubscriber to = ps1 + .groupJoin( + ps2, + new Function>() { + @Override + public Flowable apply(Object left) throws Exception { + return Flowable.never(); + } + }, + new Function>() { + @Override + public Flowable apply(Object right) throws Exception { + return Flowable.never(); + } + }, + new BiFunction, Flowable>() { + @Override + public Flowable apply(Object r, Flowable l) throws Exception { + return l; + } + } + ) + .flatMap(Functions.>identity()) + .test(); + + ps2.onNext(2); + + ps1.onNext(1); + ps1.onComplete(); + + ps2.onComplete(); + + to.assertResult(2); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java index cd79e5348e..d38e0a717e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java @@ -22,7 +22,8 @@ import org.mockito.InOrder; import io.reactivex.*; -import io.reactivex.functions.Predicate; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; public class FlowableLastTest { @@ -46,7 +47,7 @@ public void testLastMultiSubscribe() { } @Test - public void testLastViaObservable() { + public void testLastViaFlowable() { Flowable.just(1, 2, 3).lastElement(); } @@ -294,4 +295,66 @@ public void lastOrErrorError() { .assertErrorMessage("error") .assertError(RuntimeException.class); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.never().lastElement().toFlowable()); + TestHelper.checkDisposed(Flowable.never().lastElement()); + + TestHelper.checkDisposed(Flowable.just(1).lastOrError().toFlowable()); + TestHelper.checkDisposed(Flowable.just(1).lastOrError()); + + TestHelper.checkDisposed(Flowable.just(1).last(2).toFlowable()); + TestHelper.checkDisposed(Flowable.just(1).last(2)); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowableToMaybe(new Function, MaybeSource>() { + @Override + public MaybeSource apply(Flowable o) throws Exception { + return o.lastElement(); + } + }); + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.lastElement().toFlowable(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function, SingleSource>() { + @Override + public SingleSource apply(Flowable o) throws Exception { + return o.lastOrError(); + } + }); + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.lastOrError().toFlowable(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function, SingleSource>() { + @Override + public SingleSource apply(Flowable o) throws Exception { + return o.last(2); + } + }); + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.last(2).toFlowable(); + } + }); + } + + @Test + public void error() { + Flowable.error(new TestException()) + .lastElement() + .test() + .assertFailure(TestException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java index be72c34e91..feb5865bb5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java @@ -24,8 +24,10 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; @@ -35,8 +37,8 @@ public class FlowableMapTest { - Subscriber stringObserver; - Subscriber stringObserver2; + Subscriber stringSubscriber; + Subscriber stringSubscriber2; static final BiFunction APPEND_INDEX = new BiFunction() { @Override @@ -47,8 +49,8 @@ public String apply(String value, Integer index) { @Before public void before() { - stringObserver = TestHelper.mockSubscriber(); - stringObserver2 = TestHelper.mockSubscriber(); + stringSubscriber = TestHelper.mockSubscriber(); + stringSubscriber2 = TestHelper.mockSubscriber(); } @Test @@ -64,12 +66,12 @@ public String apply(Map map) { } }); - m.subscribe(stringObserver); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("OneFirst"); - verify(stringObserver, times(1)).onNext("TwoFirst"); - verify(stringObserver, times(1)).onComplete(); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onNext("OneFirst"); + verify(stringSubscriber, times(1)).onNext("TwoFirst"); + verify(stringSubscriber, times(1)).onComplete(); } @Test @@ -82,20 +84,20 @@ public void testMapMany() { @Override public Flowable apply(Integer id) { - /* simulate making a nested async call which creates another Observable */ - Flowable> subObservable = null; + /* simulate making a nested async call which creates another Flowable */ + Flowable> subFlowable = null; if (id == 1) { Map m1 = getMap("One"); Map m2 = getMap("Two"); - subObservable = Flowable.just(m1, m2); + subFlowable = Flowable.just(m1, m2); } else { Map m3 = getMap("Three"); Map m4 = getMap("Four"); - subObservable = Flowable.just(m3, m4); + subFlowable = Flowable.just(m3, m4); } /* simulate kicking off the async call and performing a select on it to transform the data */ - return subObservable.map(new Function, String>() { + return subFlowable.map(new Function, String>() { @Override public String apply(Map map) { return map.get("firstName"); @@ -104,14 +106,14 @@ public String apply(Map map) { } }); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("OneFirst"); - verify(stringObserver, times(1)).onNext("TwoFirst"); - verify(stringObserver, times(1)).onNext("ThreeFirst"); - verify(stringObserver, times(1)).onNext("FourFirst"); - verify(stringObserver, times(1)).onComplete(); + m.subscribe(stringSubscriber); + + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onNext("OneFirst"); + verify(stringSubscriber, times(1)).onNext("TwoFirst"); + verify(stringSubscriber, times(1)).onNext("ThreeFirst"); + verify(stringSubscriber, times(1)).onNext("FourFirst"); + verify(stringSubscriber, times(1)).onComplete(); } @Test @@ -140,14 +142,14 @@ public String apply(Map map) { } }); - m.subscribe(stringObserver); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("OneFirst"); - verify(stringObserver, times(1)).onNext("TwoFirst"); - verify(stringObserver, times(1)).onNext("ThreeFirst"); - verify(stringObserver, times(1)).onNext("FourFirst"); - verify(stringObserver, times(1)).onComplete(); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onNext("OneFirst"); + verify(stringSubscriber, times(1)).onNext("TwoFirst"); + verify(stringSubscriber, times(1)).onNext("ThreeFirst"); + verify(stringSubscriber, times(1)).onNext("FourFirst"); + verify(stringSubscriber, times(1)).onComplete(); } @@ -171,12 +173,12 @@ public void accept(Throwable t1) { }); - m.subscribe(stringObserver); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, never()).onNext("two"); - verify(stringObserver, never()).onNext("three"); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onError(any(Throwable.class)); + m.subscribe(stringSubscriber); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, never()).onNext("two"); + verify(stringSubscriber, never()).onNext("three"); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onError(any(Throwable.class)); } @Test(expected = IllegalArgumentException.class) @@ -270,11 +272,11 @@ public Integer apply(Integer i) { // } // }; // -// Function> manyMapper = new Function>() { +// Function> manyMapper = new Function>() { // // @Override -// public Observable apply(Object object) { -// return Observable.just(object); +// public Flowable apply(Object object) { +// return Flowable.just(object); // } // }; // @@ -300,7 +302,7 @@ public Integer apply(Integer i) { // }; // // try { -// Observable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); +// Flowable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); // } catch (RuntimeException e) { // e.printStackTrace(); // throw e; @@ -317,15 +319,15 @@ private static Map getMap(String prefix) { @Test//(expected = OnErrorNotImplementedException.class) @Ignore("RS subscribers can't throw") public void testShouldNotSwallowOnErrorNotImplementedException() { -// Observable.just("a", "b").flatMap(new Function>() { +// Flowable.just("a", "b").flatMap(new Function>() { // @Override -// public Observable apply(String s) { -// return Observable.just(s + "1", s + "2"); +// public Flowable apply(String s) { +// return Flowable.just(s + "1", s + "2"); // } -// }).flatMap(new Function>() { +// }).flatMap(new Function>() { // @Override -// public Observable apply(String s) { -// return Observable.error(new Exception("test")); +// public Flowable apply(String s) { +// return Flowable.error(new Exception("test")); // } // }).forEach(new Consumer() { // @Override @@ -339,7 +341,7 @@ public void testShouldNotSwallowOnErrorNotImplementedException() { @Ignore("RS subscribers can't throw") public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // -// Observable.OnSubscribe creator = new Observable.OnSubscribe() { +// Flowable.OnSubscribe creator = new Flowable.OnSubscribe() { // // @Override // public void call(Subscriber observer) { @@ -350,11 +352,11 @@ public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // } // }; // -// Func1> manyMapper = new Func1>() { +// Func1> manyMapper = new Func1>() { // // @Override -// public Observable call(Object object) { -// return Observable.just(object); +// public Flowable call(Object object) { +// return Flowable.just(object); // } // }; // @@ -380,7 +382,7 @@ public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // }; // // try { -// Observable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); +// Flowable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); // } catch (RuntimeException e) { // e.printStackTrace(); // throw e; @@ -672,4 +674,59 @@ public boolean test(Integer v) throws Exception { } } + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.range(1, 5).map(Functions.identity())); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable o) throws Exception { + return o.map(Functions.identity()); + } + }); + } + + @Test + public void fusedSync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + Flowable.range(1, 5) + .map(Functions.identity()) + .subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void fusedAsync() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + UnicastProcessor us = UnicastProcessor.create(); + + us + .map(Functions.identity()) + .subscribe(to); + + TestHelper.emit(us, 1, 2, 3, 4, 5); + + SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void fusedReject() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + + Flowable.range(1, 5) + .map(Functions.identity()) + .subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + .assertResult(1, 2, 3, 4, 5); + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java index 85363c486e..dcb8d8b0a5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java @@ -22,11 +22,16 @@ import org.junit.Test; import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.TestException; import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.HasUpstreamPublisher; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.*; import io.reactivex.subscribers.TestSubscriber; @@ -416,4 +421,306 @@ public void testObserveOn() { } } } + + @Test + public void source() { + Flowable o = Flowable.never(); + + assertSame(o, (((HasUpstreamPublisher)o.publish()).source())); + } + + @Test + public void connectThrows() { + ConnectableFlowable co = Flowable.empty().publish(); + try { + co.connect(new Consumer() { + @Override + public void accept(Disposable s) throws Exception { + throw new TestException(); + } + }); + } catch (TestException ex) { + // expected + } + } + + @Test + public void addRemoveRace() { + for (int i = 0; i < 500; i++) { + + final ConnectableFlowable co = Flowable.empty().publish(); + + final TestSubscriber to = co.test(); + + final TestSubscriber to2 = new TestSubscriber(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.subscribe(to2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeOnArrival() { + ConnectableFlowable co = Flowable.empty().publish(); + + co.test(Long.MAX_VALUE, true).assertEmpty(); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.never().publish()); + + TestHelper.checkDisposed(Flowable.never().publish(Functions.>identity())); + } + + @Test + public void empty() { + ConnectableFlowable co = Flowable.empty().publish(); + + co.connect(); + } + + @Test + public void take() { + ConnectableFlowable co = Flowable.range(1, 2).publish(); + + TestSubscriber to = co.take(1).test(); + + co.connect(); + + to.assertResult(1); + } + + @Test + public void just() { + final PublishProcessor ps = PublishProcessor.create(); + + ConnectableFlowable co = ps.publish(); + + TestSubscriber to = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + ps.onComplete(); + } + }; + + co.subscribe(to); + co.connect(); + + ps.onNext(1); + + to.assertResult(1); + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < 500; i++) { + + final PublishProcessor ps = PublishProcessor.create(); + + final ConnectableFlowable co = ps.publish(); + + final TestSubscriber to = co.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void badSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .publish() + .autoConnect() + .test() + .assertResult(1); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void noErrorLoss() { + List errors = TestHelper.trackPluginErrors(); + try { + ConnectableFlowable co = Flowable.error(new TestException()).publish(); + + co.connect(); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void subscribeDisconnectRace() { + for (int i = 0; i < 500; i++) { + + final PublishProcessor ps = PublishProcessor.create(); + + final ConnectableFlowable co = ps.publish(); + + final Disposable d = co.connect(); + final TestSubscriber to = new TestSubscriber(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + d.dispose(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + co.subscribe(to); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void selectorDisconnectsIndependentSource() { + PublishProcessor ps = PublishProcessor.create(); + + ps.publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + return Flowable.range(1, 2); + } + }) + .test() + .assertResult(1, 2); + + assertFalse(ps.hasSubscribers()); + } + + @Test(timeout = 5000) + public void selectorLatecommer() { + Flowable.range(1, 5) + .publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + return v.concatWith(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .publish(Functions.>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorInnerError() { + PublishProcessor ps = PublishProcessor.create(); + + ps.publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + return Flowable.error(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + + assertFalse(ps.hasSubscribers()); + } + + @Test + public void preNextConnect() { + for (int i = 0; i < 500; i++) { + + final ConnectableFlowable co = Flowable.empty().publish(); + + co.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.test(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void connectRace() { + for (int i = 0; i < 500; i++) { + + final ConnectableFlowable co = Flowable.empty().publish(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.connect(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void selectorCrash() { + Flowable.just(1).publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java index 6267d08730..1ca1fc06d4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java @@ -13,26 +13,20 @@ package io.reactivex.internal.operators.flowable; -import io.reactivex.Flowable; -import io.reactivex.TestHelper; -import io.reactivex.functions.Consumer; -import io.reactivex.subscribers.DefaultSubscriber; -import io.reactivex.subscribers.TestSubscriber; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.util.*; +import java.util.concurrent.atomic.*; + import org.junit.Test; import org.reactivestreams.Subscriber; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import io.reactivex.*; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.subscribers.*; public class FlowableRangeLongTest { @@ -292,4 +286,41 @@ public void countOne() { .test() .assertResult(5495454L); } + + @Test + public void fused() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ANY); + + Flowable.rangeLong(1, 2).subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + .assertResult(1L, 2L); + } + + @Test + public void fusedReject() { + TestSubscriber to = SubscriberFusion.newTest(QueueDisposable.ASYNC); + + Flowable.rangeLong(1, 2).subscribe(to); + + SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + .assertResult(1L, 2L); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Flowable.rangeLong(1, 2)); + } + + @Test + public void fusedClearIsEmpty() { + TestHelper.checkFusedIsEmptyClear(Flowable.rangeLong(1, 2)); + } + + @Test + public void noOverflow() { + Flowable.rangeLong(Long.MAX_VALUE - 1, 2); + Flowable.rangeLong(Long.MIN_VALUE, 2); + Flowable.rangeLong(Long.MIN_VALUE, Long.MAX_VALUE); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java index 01ef51f7fa..42cee9d7c6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java @@ -285,4 +285,13 @@ public Integer apply(Integer a, Integer b) throws Exception { ts.assertEmpty(); } + + @Test + public void testBackpressureWithNoInitialValueObservable() throws InterruptedException { + Flowable source = Flowable.just(1, 2, 3, 4, 5, 6); + Flowable reduced = source.reduce(sum).toFlowable(); + + Integer r = reduced.blockingFirst(); + assertEquals(21, r.intValue()); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java index 011ec9ce30..05a8036e33 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java @@ -14,19 +14,22 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.ReplayProcessor; import io.reactivex.schedulers.*; import io.reactivex.subscribers.TestSubscriber; @@ -590,4 +593,31 @@ private enum CancelledSubscriber implements Subscriber { @Override public void onComplete() { } } + + @Test + @Ignore("RS Subscription no isCancelled") + public void disposed() { + TestHelper.checkDisposed(Flowable.just(1).publish().refCount()); + } + + @Test + public void noOpConnect() { + final int[] calls = { 0 }; + Flowable o = new ConnectableFlowable() { + @Override + public void connect(Consumer connection) { + calls[0]++; + } + + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + } + }.refCount(); + + o.test(); + o.test(); + + assertEquals(1, calls[0]); + } } \ No newline at end of file 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 d175b52bc0..fede189c54 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -24,7 +25,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; @@ -267,4 +267,43 @@ public void repeatLongPredicateInvalid() { } } + @Test + public void repeatUntilError() { + Flowable.error(new TestException()) + .repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return true; + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void repeatUntilFalse() { + Flowable.just(1) + .repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return true; + } + }) + .test() + .assertResult(1); + } + + @Test + public void repeatUntilSupplierCrash() { + Flowable.just(1) + .repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class, 1); + } + } \ No newline at end of file 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 3c0c6fbf19..18346eb1fb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java @@ -558,7 +558,7 @@ public void subscribe(Subscriber s) { assertEquals(1, subsCount.get()); } - static final class SlowObservable implements Publisher { + static final class SlowFlowable implements Publisher { final AtomicInteger efforts = new AtomicInteger(0); final AtomicInteger active = new AtomicInteger(0); @@ -567,7 +567,7 @@ static final class SlowObservable implements Publisher { private final int emitDelay; - SlowObservable(int emitDelay, int countNext) { + SlowFlowable(int emitDelay, int countNext) { this.emitDelay = emitDelay; this.nextBeforeFailure = new AtomicInteger(countNext); } @@ -663,7 +663,7 @@ public void testUnsubscribeAfterError() { DefaultSubscriber observer = mock(DefaultSubscriber.class); // Flowable that always fails after 100ms - SlowObservable so = new SlowObservable(100, 0); + SlowFlowable so = new SlowFlowable(100, 0); Flowable o = Flowable.unsafeCreate(so).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -688,7 +688,7 @@ public void testTimeoutWithRetry() { DefaultSubscriber observer = mock(DefaultSubscriber.class); // Flowable that sends every 100ms (timeout fails instead) - SlowObservable so = new SlowObservable(100, 10); + SlowFlowable so = new SlowFlowable(100, 10); Flowable o = Flowable.unsafeCreate(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); AsyncObserver async = new AsyncObserver(observer); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java index f606bdcdcf..5411e09931 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -27,10 +28,12 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; public class FlowableRetryWithPredicateTest { @@ -226,8 +229,8 @@ public void testUnsubscribeAfterError() { Subscriber observer = TestHelper.mockSubscriber(); - // Observable that always fails after 100ms - FlowableRetryTest.SlowObservable so = new FlowableRetryTest.SlowObservable(100, 0); + // Flowable that always fails after 100ms + FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 0); Flowable o = Flowable .unsafeCreate(so) .retry(retry5); @@ -252,8 +255,8 @@ public void testTimeoutWithRetry() { Subscriber observer = TestHelper.mockSubscriber(); - // Observable that sends every 100ms (timeout fails instead) - FlowableRetryTest.SlowObservable so = new FlowableRetryTest.SlowObservable(100, 10); + // Flowable that sends every 100ms (timeout fails instead) + FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 10); Flowable o = Flowable .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) @@ -386,4 +389,113 @@ public boolean test(Integer t1, Throwable t2) { ts.assertNotComplete(); ts.assertNoErrors(); } + + @Test + public void predicateThrows() { + + TestSubscriber to = Flowable.error(new TestException("Outer")) + .retry(new Predicate() { + @Override + public boolean test(Throwable e) throws Exception { + throw new TestException("Inner"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List errors = TestHelper.compositeList(to.errors().get(0)); + + TestHelper.assertError(errors, 0, TestException.class, "Outer"); + TestHelper.assertError(errors, 1, TestException.class, "Inner"); + } + + @Test + public void dontRetry() { + Flowable.error(new TestException("Outer")) + .retry(Functions.alwaysFalse()) + .test() + .assertFailureAndMessage(TestException.class, "Outer"); + } + + @Test + public void retryDisposeRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final TestSubscriber to = ps.retry(Functions.alwaysTrue()).test(); + + final TestException ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertEmpty(); + } + } + + @Test + public void bipredicateThrows() { + + TestSubscriber to = Flowable.error(new TestException("Outer")) + .retry(new BiPredicate() { + @Override + public boolean test(Integer n, Throwable e) throws Exception { + throw new TestException("Inner"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List errors = TestHelper.compositeList(to.errors().get(0)); + + TestHelper.assertError(errors, 0, TestException.class, "Outer"); + TestHelper.assertError(errors, 1, TestException.class, "Inner"); + } + + @Test + public void retryBiPredicateDisposeRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final TestSubscriber to = ps.retry(new BiPredicate() { + @Override + public boolean test(Object t1, Object t2) throws Exception { + return true; + } + }).test(); + + final TestException ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to.assertEmpty(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java index 039e792aff..843e2fba21 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.flowable; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.util.concurrent.Callable; @@ -24,6 +24,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.*; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class FlowableScalarXMapTest { @@ -175,4 +176,62 @@ public Publisher apply(Integer v) throws Exception { .test() .assertFailure(TestException.class); } + + @Test + public void scalarDisposableStateCheck() { + TestSubscriber to = new TestSubscriber(); + ScalarSubscription sd = new ScalarSubscription(to, 1); + to.onSubscribe(sd); + + assertFalse(sd.isCancelled()); + + assertTrue(sd.isEmpty()); + + sd.request(1); + + assertFalse(sd.isCancelled()); + + assertTrue(sd.isEmpty()); + + to.assertResult(1); + + try { + sd.offer(1); + fail("Should have thrown"); + } catch (UnsupportedOperationException ex) { + // expected + } + + try { + sd.offer(1, 2); + fail("Should have thrown"); + } catch (UnsupportedOperationException ex) { + // expected + } + } + + @Test + public void scalarDisposableRunDisposeRace() { + for (int i = 0; i < 500; i++) { + TestSubscriber to = new TestSubscriber(); + final ScalarSubscription sd = new ScalarSubscription(to, 1); + to.onSubscribe(sd); + + Runnable r1 = new Runnable() { + @Override + public void run() { + sd.request(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sd.cancel(); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java index b38854fbe7..d77e89deab 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.operators.flowable; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.*; import org.junit.*; @@ -22,12 +23,119 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.BiPredicate; +import io.reactivex.observers.TestObserver; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subscribers.TestSubscriber; public class FlowableSequenceEqualTest { @Test - public void test1() { + public void test1Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just("one", "two", "three"), + Flowable.just("one", "two", "three")).toFlowable(); + verifyResult(observable, true); + } + + @Test + public void test2Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just("one", "two", "three"), + Flowable.just("one", "two", "three", "four")).toFlowable(); + verifyResult(observable, false); + } + + @Test + public void test3Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just("one", "two", "three", "four"), + Flowable.just("one", "two", "three")).toFlowable(); + verifyResult(observable, false); + } + + @Test + public void testWithError1Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.concat(Flowable.just("one"), + Flowable. error(new TestException())), + Flowable.just("one", "two", "three")).toFlowable(); + verifyError(observable); + } + + @Test + public void testWithError2Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just("one", "two", "three"), + Flowable.concat(Flowable.just("one"), + Flowable. error(new TestException()))).toFlowable(); + verifyError(observable); + } + + @Test + public void testWithError3Flowable() { Flowable observable = Flowable.sequenceEqual( + Flowable.concat(Flowable.just("one"), + Flowable. error(new TestException())), + Flowable.concat(Flowable.just("one"), + Flowable. error(new TestException()))).toFlowable(); + verifyError(observable); + } + + @Test + public void testWithEmpty1Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable. empty(), + Flowable.just("one", "two", "three")).toFlowable(); + verifyResult(observable, false); + } + + @Test + public void testWithEmpty2Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just("one", "two", "three"), + Flowable. empty()).toFlowable(); + verifyResult(observable, false); + } + + @Test + public void testWithEmpty3Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable. empty(), Flowable. empty()).toFlowable(); + verifyResult(observable, true); + } + + @Test + @Ignore("Null values not allowed") + public void testWithNull1Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just((String) null), Flowable.just("one")).toFlowable(); + verifyResult(observable, false); + } + + @Test + @Ignore("Null values not allowed") + public void testWithNull2Flowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just((String) null), Flowable.just((String) null)).toFlowable(); + verifyResult(observable, true); + } + + @Test + public void testWithEqualityErrorFlowable() { + Flowable observable = Flowable.sequenceEqual( + Flowable.just("one"), Flowable.just("one"), + new BiPredicate() { + @Override + public boolean test(String t1, String t2) { + throw new TestException(); + } + }).toFlowable(); + verifyError(observable); + } + + @Test + public void test1() { + Single observable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.just("one", "two", "three")); verifyResult(observable, true); @@ -35,7 +143,7 @@ public void test1() { @Test public void test2() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.just("one", "two", "three", "four")); verifyResult(observable, false); @@ -43,7 +151,7 @@ public void test2() { @Test public void test3() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just("one", "two", "three", "four"), Flowable.just("one", "two", "three")); verifyResult(observable, false); @@ -51,7 +159,7 @@ public void test3() { @Test public void testWithError1() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.concat(Flowable.just("one"), Flowable. error(new TestException())), Flowable.just("one", "two", "three")); @@ -60,7 +168,7 @@ Flowable. error(new TestException())), @Test public void testWithError2() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.concat(Flowable.just("one"), Flowable. error(new TestException()))); @@ -69,7 +177,7 @@ public void testWithError2() { @Test public void testWithError3() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.concat(Flowable.just("one"), Flowable. error(new TestException())), Flowable.concat(Flowable.just("one"), @@ -79,7 +187,7 @@ Flowable. error(new TestException())), @Test public void testWithEmpty1() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable. empty(), Flowable.just("one", "two", "three")); verifyResult(observable, false); @@ -87,7 +195,7 @@ Flowable. empty(), @Test public void testWithEmpty2() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable. empty()); verifyResult(observable, false); @@ -95,7 +203,7 @@ public void testWithEmpty2() { @Test public void testWithEmpty3() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable. empty(), Flowable. empty()); verifyResult(observable, true); } @@ -103,7 +211,7 @@ public void testWithEmpty3() { @Test @Ignore("Null values not allowed") public void testWithNull1() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just((String) null), Flowable.just("one")); verifyResult(observable, false); } @@ -111,14 +219,14 @@ public void testWithNull1() { @Test @Ignore("Null values not allowed") public void testWithNull2() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just((String) null), Flowable.just((String) null)); verifyResult(observable, true); } @Test public void testWithEqualityError() { - Flowable observable = Flowable.sequenceEqual( + Single observable = Flowable.sequenceEqual( Flowable.just("one"), Flowable.just("one"), new BiPredicate() { @Override @@ -140,6 +248,16 @@ private void verifyResult(Flowable observable, boolean result) { inOrder.verifyNoMoreInteractions(); } + private void verifyResult(Single observable, boolean result) { + SingleObserver observer = TestHelper.mockSingleObserver(); + + observable.subscribe(observer); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onSuccess(result); + inOrder.verifyNoMoreInteractions(); + } + private void verifyError(Flowable observable) { Subscriber observer = TestHelper.mockSubscriber(); observable.subscribe(observer); @@ -149,6 +267,15 @@ private void verifyError(Flowable observable) { inOrder.verifyNoMoreInteractions(); } + private void verifyError(Single observable) { + SingleObserver observer = TestHelper.mockSingleObserver(); + observable.subscribe(observer); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError(isA(TestException.class)); + inOrder.verifyNoMoreInteractions(); + } + @Test public void prefetch() { @@ -156,4 +283,92 @@ public void prefetch() { .test() .assertResult(true); } + + @Test + public void disposed() { + TestHelper.checkDisposed(Flowable.sequenceEqual(Flowable.just(1), Flowable.just(2))); + } + + @Test + public void simpleInequal() { + Flowable.sequenceEqual(Flowable.just(1), Flowable.just(2)) + .test() + .assertResult(false); + } + + @Test + public void simpleInequalObservable() { + Flowable.sequenceEqual(Flowable.just(1), Flowable.just(2)) + .toFlowable() + .test() + .assertResult(false); + } + + @Test + public void onNextCancelRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final TestObserver to = Flowable.sequenceEqual(Flowable.never(), ps).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + TestHelper.race(r1, r2); + + to.assertEmpty(); + } + } + + @Test + public void onNextCancelRaceObservable() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final TestSubscriber to = Flowable.sequenceEqual(Flowable.never(), ps).toFlowable().test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + TestHelper.race(r1, r2); + + to.assertEmpty(); + } + } + + @Test + @Ignore("RS Subscription no isCancelled") + public void disposedFlowable() { + TestHelper.checkDisposed(Flowable.sequenceEqual(Flowable.just(1), Flowable.just(2)).toFlowable()); + } + + @Test + public void prefetchFlowable() { + Flowable.sequenceEqual(Flowable.range(1, 20), Flowable.range(1, 20), 2) + .toFlowable() + .test() + .assertResult(true); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java index 8cef637f0c..75f6bcbca6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java @@ -13,19 +13,20 @@ package io.reactivex.internal.operators.flowable; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; -import java.util.NoSuchElementException; +import java.util.concurrent.atomic.*; import org.junit.Test; import org.mockito.InOrder; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.functions.*; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.DefaultSubscriber; public class FlowableSingleTest { @@ -684,4 +685,44 @@ public void singleOrErrorError() { .assertErrorMessage("error") .assertError(RuntimeException.class); } + + @Test(timeout = 30000) + public void testIssue1527Flowable() throws InterruptedException { + //https://github.com/ReactiveX/RxJava/pull/1527 + Flowable source = Flowable.just(1, 2, 3, 4, 5, 6); + Flowable reduced = source.reduce(new BiFunction() { + @Override + public Integer apply(Integer i1, Integer i2) { + return i1 + i2; + } + }).toFlowable(); + + Integer r = reduced.blockingFirst(); + assertEquals(21, r.intValue()); + } + + @Test + public void singleElementOperatorDoNotSwallowExceptionWhenDone() { + final Throwable exception = new RuntimeException("some error"); + final AtomicReference error = new AtomicReference(); + + try { + RxJavaPlugins.setErrorHandler(new Consumer() { + @Override public void accept(final Throwable throwable) throws Exception { + error.set(throwable); + } + }); + + Flowable.unsafeCreate(new Publisher() { + @Override public void subscribe(final Subscriber observer) { + observer.onComplete(); + observer.onError(exception); + } + }).singleElement().test().assertComplete(); + + assertSame(exception, error.get()); + } finally { + RxJavaPlugins.reset(); + } + } } 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 830e09d48f..0881768a03 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java @@ -23,6 +23,7 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -107,4 +108,18 @@ public void testSkipLastWithNegativeCount() { Flowable.just("one").skipLast(-1); } + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).skipLast(1)); + } + + @Test + public void error() { + Flowable.error(new TestException()) + .skipLast(1) + .test() + .assertFailure(TestException.class); + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java index 0c73bbcf7f..a6f1f049c3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -30,6 +31,7 @@ import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subscribers.*; @@ -810,4 +812,269 @@ public void switchMapInnerCancelled() { assertFalse(pp.hasSubscribers()); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.switchOnNext( + Flowable.just(Flowable.just(1)).hide())); + } + + @Test + public void nextSourceErrorRace() { + for (int i = 0; i < 500; i++) { + List errors = TestHelper.trackPluginErrors(); + try { + + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + ps1.switchMap(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + if (v == 1) { + return ps2; + } + return Flowable.never(); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onNext(2); + } + }; + + final TestException ex = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + for (Throwable e : errors) { + assertTrue(e.toString(), e instanceof TestException); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void outerInnerErrorRace() { + for (int i = 0; i < 500; i++) { + List errors = TestHelper.trackPluginErrors(); + try { + + final PublishProcessor ps1 = PublishProcessor.create(); + final PublishProcessor ps2 = PublishProcessor.create(); + + ps1.switchMap(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + if (v == 1) { + return ps2; + } + return Flowable.never(); + } + }) + .test(); + + final TestException ex1 = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex1); + } + }; + + final TestException ex2 = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + for (Throwable e : errors) { + assertTrue(e.toString(), e instanceof TestException); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps1 = PublishProcessor.create(); + + final TestSubscriber to = ps1.switchMap(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.never(); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void mapperThrows() { + Flowable.just(1).hide() + .switchMap(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void badMainSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + observer.onComplete(); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .switchMap(Functions.justFunction(Flowable.never())) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void emptyInner() { + Flowable.range(1, 5) + .switchMap(Functions.justFunction(Flowable.empty())) + .test() + .assertResult(); + } + + @Test + public void justInner() { + Flowable.range(1, 5) + .switchMap(Functions.justFunction(Flowable.just(1))) + .test() + .assertResult(1, 1, 1, 1, 1); + } + + @Test + public void badInnerSource() { + List errors = TestHelper.trackPluginErrors(); + try { + Flowable.just(1).hide() + .switchMap(Functions.justFunction(new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + observer.onError(new TestException()); + observer.onComplete(); + observer.onError(new TestException()); + observer.onComplete(); + } + })) + .test() + .assertFailure(TestException.class); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerCompletesReentrant() { + final PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber to = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + ps.onComplete(); + } + }; + + Flowable.just(1).hide() + .switchMap(Functions.justFunction(ps)) + .subscribe(to); + + ps.onNext(1); + + to.assertResult(1); + } + + @Test + public void innerErrorsReentrant() { + final PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber to = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + ps.onError(new TestException()); + } + }; + + Flowable.just(1).hide() + .switchMap(Functions.justFunction(ps)) + .subscribe(to); + + ps.onNext(1); + + to.assertFailure(TestException.class, 1); + } + + @Test + public void scalarMap() { + Flowable.switchOnNext(Flowable.just(Flowable.just(1))) + .test() + .assertResult(1); + } + + @Test + public void scalarMapDelayError() { + Flowable.switchOnNextDelayError(Flowable.just(Flowable.just(1))) + .test() + .assertResult(1); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java index 7201a7ed9f..606f90b8af 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java @@ -269,4 +269,48 @@ public void takeLastTimeDelayErrorCustomScheduler() { .assertFailure(TestException.class, 1, 2); } + @Test + public void disposed() { + TestHelper.checkDisposed(PublishProcessor.create().takeLast(1, TimeUnit.MINUTES)); + } + + @Test + public void observeOn() { + Observable.range(1, 1000) + .takeLast(1, TimeUnit.DAYS) + .take(500) + .observeOn(Schedulers.single(), true, 1) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(500) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void cancelCompleteRace() { + for (int i = 0; i < 500; i++) { + final PublishProcessor ps = PublishProcessor.create(); + + final TestSubscriber to = ps.takeLast(1, TimeUnit.DAYS).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java index 26b0fb1c33..67d905b2f0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java @@ -13,8 +13,10 @@ package io.reactivex.internal.operators.flowable; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.*; @@ -24,6 +26,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.TestScheduler; @@ -159,4 +162,36 @@ public void throttleFirstDefaultScheduler() { .assertResult(1); } + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).throttleFirst(1, TimeUnit.DAYS)); + } + + @Test + public void badSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + observer.onNext(1); + observer.onNext(2); + observer.onComplete(); + observer.onNext(3); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .throttleFirst(1, TimeUnit.DAYS) + .test() + .assertResult(1); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + } \ No newline at end of file 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 327c3166b5..f093973d1d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java @@ -22,6 +22,7 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -122,4 +123,19 @@ public Long apply(Timed v) throws Exception { } } + @Test + @Ignore("RS Subscription no isCancelled") + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).timeInterval()); + } + + @SuppressWarnings("unchecked") + @Test + public void error() { + Flowable.error(new TestException()) + .timeInterval() + .test() + .assertFailure(TestException.class); + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java index f3b817254b..209de9ab73 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java @@ -13,11 +13,12 @@ package io.reactivex.internal.operators.flowable; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; +import java.util.List; import java.util.concurrent.*; import org.junit.*; @@ -25,7 +26,9 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subscribers.TestSubscriber; @@ -227,7 +230,7 @@ public void shouldSwitchToOtherAndCanBeUnsubscribedIfOnNextNotWithinTimeout() { } @Test - public void shouldTimeoutIfSynchronizedObservableEmitFirstOnNextNotWithinTimeout() + public void shouldTimeoutIfSynchronizedFlowableEmitFirstOnNextNotWithinTimeout() throws InterruptedException { final CountDownLatch exit = new CountDownLatch(1); final CountDownLatch timeoutSetuped = new CountDownLatch(1); @@ -383,4 +386,102 @@ public void timedAndOther() { .assertResult(1); } + @Test + @Ignore("RS Subscription no isCancelled") + public void disposed() { + TestHelper.checkDisposed(PublishProcessor.create().timeout(1, TimeUnit.DAYS)); + + TestHelper.checkDisposed(PublishProcessor.create().timeout(1, TimeUnit.DAYS, Flowable.just(1))); + } + + @Test + public void timedErrorOther() { + Flowable.error(new TestException()) + .timeout(1, TimeUnit.DAYS, Flowable.just(1)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void timedError() { + Flowable.error(new TestException()) + .timeout(1, TimeUnit.DAYS) + .test() + .assertFailure(TestException.class); + } + + @Test + public void timedEmptyOther() { + Flowable.empty() + .timeout(1, TimeUnit.DAYS, Flowable.just(1)) + .test() + .assertResult(); + } + + @Test + public void timedEmpty() { + Flowable.empty() + .timeout(1, TimeUnit.DAYS) + .test() + .assertResult(); + } + + @Test + public void newTimer() { + FlowableTimeoutTimed.NEW_TIMER.dispose(); + assertTrue(FlowableTimeoutTimed.NEW_TIMER.isDisposed()); + } + + @Test + public void badSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .timeout(1, TimeUnit.DAYS) + .test() + .assertResult(1); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void badSourceOther() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .timeout(1, TimeUnit.DAYS, Flowable.just(3)) + .test() + .assertResult(1); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java index 73a9c3fe8b..c09f4a738e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java @@ -282,4 +282,10 @@ public void onComplete() { inOrder.verifyNoMoreInteractions(); verify(observer, never()).onComplete(); } + + @Test + @Ignore("RS Subscription no isCancelled") + public void disposed() { + TestHelper.checkDisposed(Flowable.timer(1, TimeUnit.DAYS)); + } } \ No newline at end of file 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 45fe112d11..fa86917e6f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java @@ -23,6 +23,8 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; +import io.reactivex.Flowable; +import io.reactivex.exceptions.TestException; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; @@ -43,7 +45,7 @@ public void testListFlowable() { } @Test - public void testListViaObservableFlowable() { + public void testListViaFlowableFlowable() { Flowable w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); Flowable> observable = w.toList().toFlowable(); @@ -176,7 +178,7 @@ public void testList() { } @Test - public void testListViaObservable() { + public void testListViaFlowable() { Flowable w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); Single> observable = w.toList(); @@ -301,4 +303,59 @@ public void capacityHint() { .test() .assertResult(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).toList().toFlowable()); + + TestHelper.checkDisposed(Flowable.just(1).toList()); + } + + @SuppressWarnings("unchecked") + @Test + public void error() { + Flowable.error(new TestException()) + .toList() + .toFlowable() + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void errorSingle() { + Flowable.error(new TestException()) + .toList() + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void collectionSupplierThrows() { + Flowable.just(1) + .toList(new Callable>() { + @Override + public Collection call() throws Exception { + throw new TestException(); + } + }) + .toFlowable() + .test() + .assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void singleCollectionSupplierThrows() { + Flowable.just(1) + .toList(new Callable>() { + @Override + public Collection call() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } } \ No newline at end of file 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 22177e2686..c32f5447c0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java @@ -15,6 +15,7 @@ import static org.junit.Assert.*; +import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -22,6 +23,10 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -195,4 +200,70 @@ public void takeHalf() { .assertNoErrors() .assertSubscribed(); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.just(1).unsubscribeOn(Schedulers.single())); + } + + @Test + public void normal() { + final int[] calls = { 0 }; + + Flowable.just(1) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + calls[0]++; + } + }) + .unsubscribeOn(Schedulers.single()) + .test() + .assertResult(1); + + assertEquals(0, calls[0]); + } + + @Test + public void error() { + final int[] calls = { 0 }; + + Flowable.error(new TestException()) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + calls[0]++; + } + }) + .unsubscribeOn(Schedulers.single()) + .test() + .assertFailure(TestException.class); + + assertEquals(0, calls[0]); + } + + @Test + public void signalAfterDispose() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber observer) { + observer.onSubscribe(new BooleanSubscription()); + observer.onNext(1); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .unsubscribeOn(Schedulers.single()) + .take(1) + .test() + .assertResult(1); + + TestHelper.assertError(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java index b902d02567..7fe45345eb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java @@ -26,8 +26,10 @@ import io.reactivex.*; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; public class FlowableUsingTest { @@ -193,16 +195,16 @@ public Flowable apply(Disposable s) { } @Test - public void testUsingWithObservableFactoryError() { - performTestUsingWithObservableFactoryError(false); + public void testUsingWithFlowableFactoryError() { + performTestUsingWithFlowableFactoryError(false); } @Test - public void testUsingWithObservableFactoryErrorDisposeEagerly() { - performTestUsingWithObservableFactoryError(true); + public void testUsingWithFlowableFactoryErrorDisposeEagerly() { + performTestUsingWithFlowableFactoryError(true); } - private void performTestUsingWithObservableFactoryError(boolean disposeEagerly) { + private void performTestUsingWithFlowableFactoryError(boolean disposeEagerly) { final Runnable unsubscribe = mock(Runnable.class); Callable resourceFactory = new Callable() { @Override @@ -230,17 +232,17 @@ public Flowable apply(Disposable subscription) { @Test @Ignore("subscribe() can't throw") - public void testUsingWithObservableFactoryErrorInOnSubscribe() { - performTestUsingWithObservableFactoryErrorInOnSubscribe(false); + public void testUsingWithFlowableFactoryErrorInOnSubscribe() { + performTestUsingWithFlowableFactoryErrorInOnSubscribe(false); } @Test @Ignore("subscribe() can't throw") - public void testUsingWithObservableFactoryErrorInOnSubscribeDisposeEagerly() { - performTestUsingWithObservableFactoryErrorInOnSubscribe(true); + public void testUsingWithFlowableFactoryErrorInOnSubscribeDisposeEagerly() { + performTestUsingWithFlowableFactoryErrorInOnSubscribe(true); } - private void performTestUsingWithObservableFactoryErrorInOnSubscribe(boolean disposeEagerly) { + private void performTestUsingWithFlowableFactoryErrorInOnSubscribe(boolean disposeEagerly) { final Runnable unsubscribe = mock(Runnable.class); Callable resourceFactory = new Callable() { @Override @@ -501,4 +503,128 @@ public void accept(Integer c) { Assert.assertEquals(1, count.get()); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.using( + new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, + new Function>() { + @Override + public Flowable apply(Object v) throws Exception { + return Flowable.never(); + } + }, + Functions.emptyConsumer() + )); + } + + @Test + public void supplierDisposerCrash() { + TestSubscriber to = Flowable.using(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new Function>() { + @Override + public Flowable apply(Object v) throws Exception { + throw new TestException("First"); + } + }, new Consumer() { + @Override + public void accept(Object e) throws Exception { + throw new TestException("Second"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List errors = TestHelper.compositeList(to.errors().get(0)); + + TestHelper.assertError(errors, 0, TestException.class, "First"); + TestHelper.assertError(errors, 1, TestException.class, "Second"); + } + + @Test + public void eagerOnErrorDisposerCrash() { + TestSubscriber to = Flowable.using(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new Function>() { + @Override + public Flowable apply(Object v) throws Exception { + return Flowable.error(new TestException("First")); + } + }, new Consumer() { + @Override + public void accept(Object e) throws Exception { + throw new TestException("Second"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List errors = TestHelper.compositeList(to.errors().get(0)); + + TestHelper.assertError(errors, 0, TestException.class, "First"); + TestHelper.assertError(errors, 1, TestException.class, "Second"); + } + + @Test + public void eagerOnCompleteDisposerCrash() { + Flowable.using(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new Function>() { + @Override + public Flowable apply(Object v) throws Exception { + return Flowable.empty(); + } + }, new Consumer() { + @Override + public void accept(Object e) throws Exception { + throw new TestException("Second"); + } + }) + .test() + .assertFailureAndMessage(TestException.class, "Second"); + } + + @Test + public void nonEagerDisposerCrash() { + List errors = TestHelper.trackPluginErrors(); + try { + Flowable.using(new Callable() { + @Override + public Object call() throws Exception { + return 1; + } + }, new Function>() { + @Override + public Flowable apply(Object v) throws Exception { + return Flowable.empty(); + } + }, new Consumer() { + @Override + public void accept(Object e) throws Exception { + throw new TestException("Second"); + } + }, false) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, TestException.class, "Second"); + } finally { + RxJavaPlugins.reset(); + } + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java index f918ccdd2f..5dcc2b973f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java @@ -23,6 +23,7 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; @@ -136,8 +137,8 @@ public void run() { private Consumer> observeWindow(final List list, final List> lists) { return new Consumer>() { @Override - public void accept(Flowable stringObservable) { - stringObservable.subscribe(new DefaultSubscriber() { + public void accept(Flowable stringFlowable) { + stringFlowable.subscribe(new DefaultSubscriber() { @Override public void onComplete() { lists.add(new ArrayList(list)); @@ -495,4 +496,49 @@ public void overlapBackpressure2() { ts.assertError(MissingBackpressureException.class); } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.range(1, 5).window(1, TimeUnit.DAYS, Schedulers.single())); + + TestHelper.checkDisposed(Flowable.range(1, 5).window(2, 1, TimeUnit.DAYS, Schedulers.single())); + + TestHelper.checkDisposed(Flowable.range(1, 5).window(1, 2, TimeUnit.DAYS, Schedulers.single())); + + TestHelper.checkDisposed(Flowable.never() + .window(1, TimeUnit.DAYS, Schedulers.single(), 2, true)); + } + + @Test + public void restartTimer() { + Flowable.range(1, 5) + .window(1, TimeUnit.DAYS, Schedulers.single(), 2, true) + .flatMap(Functions.>identity()) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void exactBoundaryError() { + Flowable.error(new TestException()) + .window(1, TimeUnit.DAYS, Schedulers.single(), 2, true) + .test() + .assertSubscribed() + .assertError(TestException.class) + .assertNotComplete(); + } + + @Test + public void restartTimerMany() { + Flowable.intervalRange(1, 1000, 1, 1, TimeUnit.MILLISECONDS) + .window(1, TimeUnit.MILLISECONDS, Schedulers.single(), 2, true) + .flatMap(Functions.>identity()) + .take(500) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(500) + .assertNoErrors() + .assertComplete(); + } } \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java index 1dc7c6fbbf..6c68910cfb 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java @@ -227,6 +227,7 @@ public SingleSource apply(Integer v) throws Exception { to .assertFailure(TestException.class, 1); } + @Test public void disposed() { TestHelper.checkDisposed(PublishSubject.create().flatMapSingle(new Function>() { diff --git a/src/test/java/io/reactivex/internal/util/ObservableToFlowabeTestSync.java b/src/test/java/io/reactivex/internal/util/ObservableToFlowabeTestSync.java index d87a0c1efe..53a891f2a7 100644 --- a/src/test/java/io/reactivex/internal/util/ObservableToFlowabeTestSync.java +++ b/src/test/java/io/reactivex/internal/util/ObservableToFlowabeTestSync.java @@ -13,81 +13,107 @@ package io.reactivex.internal.util; +import java.io.*; +import java.lang.reflect.Method; +import java.util.*; + /** * Utility class that lists tests related to Observable that is not present in Flowable tests. */ -public class ObservableToFlowabeTestSync { -// Uses Java 8 and is generally not relevant -// static void list(String basepath, String basepackage) throws Exception { -// File[] observables = new File(basepath + "observable/").listFiles(); -// -// int count = 0; -// -// for (File f : observables) { -// if (!f.getName().endsWith(".java")) { -// continue; -// } -// Class clazz = Class.forName(basepackage + "observable." + f.getName().replace(".java", "")); -// -// String cn = f.getName().replace(".java", "").replace("Observable", "Flowable"); -// -// File f2 = new File(basepath + "/flowable/" + cn + ".java"); -// -// if (!f2.exists()) { -// continue; -// } -// -// Class clazz2 = Class.forName(basepackage + "flowable." + cn); -// -// Set methods2 = new HashSet(); -// -// for (Method m : clazz2.getMethods()) { -// methods2.add(m.getName()); -// } -// -// for (Method m : clazz.getMethods()) { -// if (!methods2.contains(m.getName()) && !methods2.contains(m.getName().replace("Observable", "Flowable"))) { -// count++; -// System.out.println(); -// System.out.print("java.lang.RuntimeException: missing > "); -// System.out.println(m.getName()); -// System.out.print(" at "); -// System.out.print(clazz.getName()); -// System.out.print(" ("); -// System.out.print(clazz.getSimpleName()); -// System.out.print(".java:"); -// -// List lines = Files.readAllLines(f.toPath()); -// -// int j = 1; -// for (int i = 1; i <= lines.size(); i++) { -// if (lines.get(i - 1).contains("public void " + m.getName() + "(")) { -// j = i; -// } -// } -// System.out.print(j); -// System.out.println(")"); -// -// System.out.print(" at "); -// System.out.print(clazz2.getName()); -// System.out.print(" ("); -// System.out.print(clazz2.getSimpleName()); -// -// lines = Files.readAllLines(f2.toPath()); -// -// System.out.print(".java:"); -// System.out.print(lines.size() - 1); -// System.out.println(")"); -// } -// } -// } -// -// System.out.println(); -// System.out.println(count); -// } -// -// public static void main(String[] args) throws Exception { -//// list("src/test/java/io/reactivex/internal/operators/", "io.reactivex.internal.operators."); +public final class ObservableToFlowabeTestSync { + private ObservableToFlowabeTestSync() { + throw new IllegalStateException("No instances!"); + } + + static List readAllLines(File f) { + List result = new ArrayList(); + try { + BufferedReader in = new BufferedReader(new FileReader(f)); + try { + String line; + + while ((line = in.readLine()) != null) { + result.add(line); + } + } finally { + in.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + return result; + } + + static void list(String basepath, String basepackage) throws Exception { + File[] observables = new File(basepath + "observable/").listFiles(); + + int count = 0; + + for (File f : observables) { + if (!f.getName().endsWith(".java")) { + continue; + } + Class clazz = Class.forName(basepackage + "observable." + f.getName().replace(".java", "")); + + String cn = f.getName().replace(".java", "").replace("Observable", "Flowable"); + + File f2 = new File(basepath + "/flowable/" + cn + ".java"); + + if (!f2.exists()) { + continue; + } + + Class clazz2 = Class.forName(basepackage + "flowable." + cn); + + Set methods2 = new HashSet(); + + for (Method m : clazz2.getMethods()) { + methods2.add(m.getName()); + } + + for (Method m : clazz.getMethods()) { + if (!methods2.contains(m.getName()) && !methods2.contains(m.getName().replace("Observable", "Flowable"))) { + count++; + System.out.println(); + System.out.print("java.lang.RuntimeException: missing > "); + System.out.println(m.getName()); + System.out.print(" at "); + System.out.print(clazz.getName()); + System.out.print(" ("); + System.out.print(clazz.getSimpleName()); + System.out.print(".java:"); + + List lines = readAllLines(f); + + int j = 1; + for (int i = 1; i <= lines.size(); i++) { + if (lines.get(i - 1).contains("public void " + m.getName() + "(")) { + j = i; + } + } + System.out.print(j); + System.out.println(")"); + + System.out.print(" at "); + System.out.print(clazz2.getName()); + System.out.print(" ("); + System.out.print(clazz2.getSimpleName()); + + lines = readAllLines(f2); + + System.out.print(".java:"); + System.out.print(lines.size() - 1); + System.out.println(")"); + } + } + } + + System.out.println(); + System.out.println(count); + } + + public static void main(String[] args) throws Exception { + list("src/test/java/io/reactivex/internal/operators/", "io.reactivex.internal.operators."); // list("src/test/java/io/reactivex/", "io.reactivex."); -// } + } } diff --git a/src/test/java/io/reactivex/tck/SequenceEqualTckTest.java b/src/test/java/io/reactivex/tck/SequenceEqualTckTest.java index 1f2a9b8a73..00d29fb893 100644 --- a/src/test/java/io/reactivex/tck/SequenceEqualTckTest.java +++ b/src/test/java/io/reactivex/tck/SequenceEqualTckTest.java @@ -24,7 +24,10 @@ public class SequenceEqualTckTest extends BaseTck { @Override public Publisher createPublisher(final long elements) { return FlowableTck.wrap( - Flowable.sequenceEqual(Flowable.range(1, 1000), Flowable.range(1, 1001)) + Flowable.sequenceEqual( + Flowable.range(1, 1000), + Flowable.range(1, 1001)) + .toFlowable() ); }