/** * Copyright (c) 2016-present, RxJava Contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.reactivex.internal.operators.flowable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; import org.junit.*; import org.mockito.MockitoAnnotations; import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.TestSubscriber; public class FlowableJoinTest { Subscriber<Object> observer = TestHelper.mockSubscriber(); BiFunction<Integer, Integer, Integer> add = new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }; <T> Function<Integer, Flowable<T>> just(final Flowable<T> observable) { return new Function<Integer, Flowable<T>>() { @Override public Flowable<T> apply(Integer t1) { return observable; } }; } @Before public void before() { MockitoAnnotations.initMocks(this); } @Test public void normal1() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(Flowable.never()), add); m.subscribe(observer); source1.onNext(1); source1.onNext(2); source1.onNext(4); source2.onNext(16); source2.onNext(32); source2.onNext(64); source1.onComplete(); source2.onComplete(); verify(observer, times(1)).onNext(17); verify(observer, times(1)).onNext(18); verify(observer, times(1)).onNext(20); verify(observer, times(1)).onNext(33); verify(observer, times(1)).onNext(34); verify(observer, times(1)).onNext(36); verify(observer, times(1)).onNext(65); verify(observer, times(1)).onNext(66); verify(observer, times(1)).onNext(68); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void normal1WithDuration() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); PublishProcessor<Integer> duration1 = PublishProcessor.create(); Flowable<Integer> m = source1.join(source2, just(duration1), just(Flowable.never()), add); m.subscribe(observer); source1.onNext(1); source1.onNext(2); source2.onNext(16); duration1.onNext(1); source1.onNext(4); source1.onNext(8); source1.onComplete(); source2.onComplete(); verify(observer, times(1)).onNext(17); verify(observer, times(1)).onNext(18); verify(observer, times(1)).onNext(20); verify(observer, times(1)).onNext(24); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void normal2() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(Flowable.never()), add); m.subscribe(observer); source1.onNext(1); source1.onNext(2); source1.onComplete(); source2.onNext(16); source2.onNext(32); source2.onNext(64); source2.onComplete(); verify(observer, times(1)).onNext(17); verify(observer, times(1)).onNext(18); verify(observer, times(1)).onNext(33); verify(observer, times(1)).onNext(34); verify(observer, times(1)).onNext(65); verify(observer, times(1)).onNext(66); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void leftThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(Flowable.never()), add); m.subscribe(observer); source2.onNext(1); source1.onError(new RuntimeException("Forced failure")); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void rightThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(Flowable.never()), add); m.subscribe(observer); source1.onNext(1); source2.onError(new RuntimeException("Forced failure")); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void leftDurationThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Flowable<Integer> duration1 = Flowable.<Integer> error(new RuntimeException("Forced failure")); Flowable<Integer> m = source1.join(source2, just(duration1), just(Flowable.never()), add); m.subscribe(observer); source1.onNext(1); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void rightDurationThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Flowable<Integer> duration1 = Flowable.<Integer> error(new RuntimeException("Forced failure")); Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(duration1), add); m.subscribe(observer); source2.onNext(1); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void leftDurationSelectorThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Function<Integer, Flowable<Integer>> fail = new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t1) { throw new RuntimeException("Forced failure"); } }; Flowable<Integer> m = source1.join(source2, fail, just(Flowable.never()), add); m.subscribe(observer); source1.onNext(1); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void rightDurationSelectorThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); Function<Integer, Flowable<Integer>> fail = new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t1) { throw new RuntimeException("Forced failure"); } }; Flowable<Integer> m = source1.join(source2, just(Flowable.never()), fail, add); m.subscribe(observer); source2.onNext(1); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void resultSelectorThrows() { PublishProcessor<Integer> source1 = PublishProcessor.create(); PublishProcessor<Integer> source2 = PublishProcessor.create(); BiFunction<Integer, Integer, Integer> fail = new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { throw new RuntimeException("Forced failure"); } }; Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(Flowable.never()), fail); m.subscribe(observer); source1.onNext(1); source2.onNext(2); verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onComplete(); verify(observer, never()).onNext(any()); } @Test public void dispose() { TestHelper.checkDisposed(PublishProcessor.<Integer>create().join(Flowable.just(1), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } })); } @Test public void take() { Flowable.just(1).join( Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } }) .take(1) .test() .assertResult(3); } @Test public void rightClose() { PublishProcessor<Integer> ps = PublishProcessor.create(); TestSubscriber<Integer> to = ps.join(Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.empty()), new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } }) .test() .assertEmpty(); ps.onNext(1); to.assertEmpty(); } @Test public void resultSelectorThrows2() { PublishProcessor<Integer> ps = PublishProcessor.create(); TestSubscriber<Integer> to = ps.join( Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { throw new TestException(); } }) .test(); ps.onNext(1); ps.onComplete(); to.assertFailure(TestException.class); } @Test public void badOuterSource() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { new Flowable<Integer>() { @Override protected void subscribeActual(Subscriber<? super Integer> observer) { observer.onSubscribe(new BooleanSubscription()); observer.onError(new TestException("First")); observer.onError(new TestException("Second")); } } .join(Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } }) .test() .assertFailureAndMessage(TestException.class, "First"); TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second"); } finally { RxJavaPlugins.reset(); } } @Test public void badEndSource() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @SuppressWarnings("rawtypes") final Subscriber[] o = { null }; TestSubscriber<Integer> to = Flowable.just(1) .join(Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(new Flowable<Integer>() { @Override protected void subscribeActual(Subscriber<? super Integer> observer) { o[0] = observer; observer.onSubscribe(new BooleanSubscription()); observer.onError(new TestException("First")); } }), new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } }) .test(); o[0].onError(new TestException("Second")); to .assertFailureAndMessage(TestException.class, "First"); TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second"); } finally { RxJavaPlugins.reset(); } } @Test public void backpressureOverflowRight() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); TestSubscriber<Object> ts = pp1.join(pp2, Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), new BiFunction<Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b) throws Exception { return a + b; } }) .test(0L); pp1.onNext(1); pp2.onNext(2); ts.assertFailure(MissingBackpressureException.class); } @Test public void backpressureOverflowLeft() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); TestSubscriber<Object> ts = pp1.join(pp2, Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), new BiFunction<Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b) throws Exception { return a + b; } }) .test(0L); pp2.onNext(2); pp1.onNext(1); ts.assertFailure(MissingBackpressureException.class); } }