/** * Copyright (c) 2016-present, RxJava Contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See * the License for the specific language governing permissions and limitations under the License. */ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; import org.junit.Test; import org.mockito.InOrder; import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.internal.util.CrashingMappedIterable; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.TestSubscriber; public class FlowableWithLatestFromTest { static final BiFunction<Integer, Integer, Integer> COMBINER = new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return (t1 << 8) + t2; } }; static final BiFunction<Integer, Integer, Integer> COMBINER_ERROR = new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { throw new TestException("Forced failure"); } }; @Test public void testSimple() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Subscriber<Integer> o = TestHelper.mockSubscriber(); InOrder inOrder = inOrder(o); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); result.subscribe(o); source.onNext(1); inOrder.verify(o, never()).onNext(anyInt()); other.onNext(1); inOrder.verify(o, never()).onNext(anyInt()); source.onNext(2); inOrder.verify(o).onNext((2 << 8) + 1); other.onNext(2); inOrder.verify(o, never()).onNext(anyInt()); other.onComplete(); inOrder.verify(o, never()).onComplete(); source.onNext(3); inOrder.verify(o).onNext((3 << 8) + 2); source.onComplete(); inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } @Test public void testEmptySource() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(other.hasSubscribers()); other.onNext(1); source.onComplete(); ts.assertNoErrors(); ts.assertTerminated(); ts.assertNoValues(); assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } @Test public void testEmptyOther() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(other.hasSubscribers()); source.onNext(1); source.onComplete(); ts.assertNoErrors(); ts.assertTerminated(); ts.assertNoValues(); assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } @Test public void testUnsubscription() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(other.hasSubscribers()); other.onNext(1); source.onNext(1); ts.dispose(); ts.assertValue((1 << 8) + 1); ts.assertNoErrors(); ts.assertNotComplete(); assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } @Test public void testSourceThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(other.hasSubscribers()); other.onNext(1); source.onNext(1); source.onError(new TestException()); ts.assertTerminated(); ts.assertValue((1 << 8) + 1); ts.assertError(TestException.class); ts.assertNotComplete(); assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } @Test public void testOtherThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(other.hasSubscribers()); other.onNext(1); source.onNext(1); other.onError(new TestException()); ts.assertTerminated(); ts.assertValue((1 << 8) + 1); ts.assertNotComplete(); ts.assertError(TestException.class); assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } @Test public void testFunctionThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER_ERROR); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(other.hasSubscribers()); other.onNext(1); source.onNext(1); ts.assertTerminated(); ts.assertNotComplete(); ts.assertNoValues(); ts.assertError(TestException.class); assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } @Test public void testNoDownstreamUnsubscribe() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); result.subscribe(ts); source.onComplete(); assertFalse(ts.isCancelled()); } @Test public void testBackpressure() { Flowable<Integer> source = Flowable.range(1, 10); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); result.subscribe(ts); assertTrue("Other has no observers!", other.hasSubscribers()); ts.request(1); assertTrue("Other has no observers!", other.hasSubscribers()); ts.assertNoValues(); other.onNext(1); ts.request(1); ts.assertValue((2 << 8) + 1); ts.request(5); ts.assertValues( (2 << 8) + 1, (3 << 8) + 1, (4 << 8) + 1, (5 << 8) + 1, (6 << 8) + 1, (7 << 8) + 1 ); ts.dispose(); assertFalse("Other has observers!", other.hasSubscribers()); ts.assertNoErrors(); } static final Function<Object[], String> toArray = new Function<Object[], String>() { @Override public String apply(Object[] args) { return Arrays.toString(args); } }; @Test public void manySources() { PublishProcessor<String> ps1 = PublishProcessor.create(); PublishProcessor<String> ps2 = PublishProcessor.create(); PublishProcessor<String> ps3 = PublishProcessor.create(); PublishProcessor<String> main = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(); main.withLatestFrom(new Flowable[] { ps1, ps2, ps3 }, toArray) .subscribe(ts); main.onNext("1"); ts.assertNoValues(); ps1.onNext("a"); ts.assertNoValues(); ps2.onNext("A"); ts.assertNoValues(); ps3.onNext("="); ts.assertNoValues(); main.onNext("2"); ts.assertValues("[2, a, A, =]"); ps2.onNext("B"); ts.assertValues("[2, a, A, =]"); ps3.onComplete(); ts.assertValues("[2, a, A, =]"); ps1.onNext("b"); main.onNext("3"); ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); main.onComplete(); ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); ts.assertNoErrors(); ts.assertComplete(); assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); assertFalse("ps3 has subscribers?", ps3.hasSubscribers()); } @Test public void manySourcesIterable() { PublishProcessor<String> ps1 = PublishProcessor.create(); PublishProcessor<String> ps2 = PublishProcessor.create(); PublishProcessor<String> ps3 = PublishProcessor.create(); PublishProcessor<String> main = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(); main.withLatestFrom(Arrays.<Flowable<?>>asList(ps1, ps2, ps3), toArray) .subscribe(ts); main.onNext("1"); ts.assertNoValues(); ps1.onNext("a"); ts.assertNoValues(); ps2.onNext("A"); ts.assertNoValues(); ps3.onNext("="); ts.assertNoValues(); main.onNext("2"); ts.assertValues("[2, a, A, =]"); ps2.onNext("B"); ts.assertValues("[2, a, A, =]"); ps3.onComplete(); ts.assertValues("[2, a, A, =]"); ps1.onNext("b"); main.onNext("3"); ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); main.onComplete(); ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); ts.assertNoErrors(); ts.assertComplete(); assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); assertFalse("ps3 has subscribers?", ps3.hasSubscribers()); } @Test public void manySourcesIterableSweep() { for (String val : new String[] { "1" /*, null*/ }) { int n = 35; for (int i = 0; i < n; i++) { List<Flowable<?>> sources = new ArrayList<Flowable<?>>(); List<String> expected = new ArrayList<String>(); expected.add(val); for (int j = 0; j < i; j++) { sources.add(Flowable.just(val)); expected.add(String.valueOf(val)); } TestSubscriber<String> ts = new TestSubscriber<String>(); PublishProcessor<String> main = PublishProcessor.create(); main.withLatestFrom(sources, toArray).subscribe(ts); ts.assertNoValues(); main.onNext(val); main.onComplete(); ts.assertValue(expected.toString()); ts.assertNoErrors(); ts.assertComplete(); } } } @Test public void backpressureNoSignal() { PublishProcessor<String> ps1 = PublishProcessor.create(); PublishProcessor<String> ps2 = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(0); Flowable.range(1, 10).withLatestFrom(new Flowable<?>[] { ps1, ps2 }, toArray) .subscribe(ts); ts.assertNoValues(); ts.request(1); ts.assertNoValues(); ts.assertNoErrors(); ts.assertComplete(); assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); } @Test public void backpressureWithSignal() { PublishProcessor<String> ps1 = PublishProcessor.create(); PublishProcessor<String> ps2 = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(0); Flowable.range(1, 3).withLatestFrom(new Flowable<?>[] { ps1, ps2 }, toArray) .subscribe(ts); ts.assertNoValues(); ps1.onNext("1"); ps2.onNext("1"); ts.request(1); ts.assertValue("[1, 1, 1]"); ts.request(1); ts.assertValues("[1, 1, 1]", "[2, 1, 1]"); ts.request(1); ts.assertValues("[1, 1, 1]", "[2, 1, 1]", "[3, 1, 1]"); ts.assertNoErrors(); ts.assertComplete(); assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); } @Test public void withEmpty() { TestSubscriber<String> ts = new TestSubscriber<String>(0); Flowable.range(1, 3).withLatestFrom( new Flowable<?>[] { Flowable.just(1), Flowable.empty() }, toArray) .subscribe(ts); ts.assertNoValues(); ts.assertNoErrors(); ts.assertComplete(); } @Test public void withError() { TestSubscriber<String> ts = new TestSubscriber<String>(0); Flowable.range(1, 3).withLatestFrom( new Flowable<?>[] { Flowable.just(1), Flowable.error(new TestException()) }, toArray) .subscribe(ts); ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotComplete(); } @Test public void withMainError() { TestSubscriber<String> ts = new TestSubscriber<String>(0); Flowable.error(new TestException()).withLatestFrom( new Flowable<?>[] { Flowable.just(1), Flowable.just(1) }, toArray) .subscribe(ts); ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotComplete(); } @Test public void with2Others() { Flowable<Integer> just = Flowable.just(1); TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); just.withLatestFrom(just, just, new Function3<Integer, Integer, Integer, List<Integer>>() { @Override public List<Integer> apply(Integer a, Integer b, Integer c) { return Arrays.asList(a, b, c); } }) .subscribe(ts); ts.assertValue(Arrays.asList(1, 1, 1)); ts.assertNoErrors(); ts.assertComplete(); } @Test public void with3Others() { Flowable<Integer> just = Flowable.just(1); TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); just.withLatestFrom(just, just, just, new Function4<Integer, Integer, Integer, Integer, List<Integer>>() { @Override public List<Integer> apply(Integer a, Integer b, Integer c, Integer d) { return Arrays.asList(a, b, c, d); } }) .subscribe(ts); ts.assertValue(Arrays.asList(1, 1, 1, 1)); ts.assertNoErrors(); ts.assertComplete(); } @Test public void with4Others() { Flowable<Integer> just = Flowable.just(1); TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); just.withLatestFrom(just, just, just, just, new Function5<Integer, Integer, Integer, Integer, Integer, List<Integer>>() { @Override public List<Integer> apply(Integer a, Integer b, Integer c, Integer d, Integer e) { return Arrays.asList(a, b, c, d, e); } }) .subscribe(ts); ts.assertValue(Arrays.asList(1, 1, 1, 1, 1)); ts.assertNoErrors(); ts.assertComplete(); } @Test public void dispose() { TestHelper.checkDisposed(Flowable.just(1).withLatestFrom(Flowable.just(2), new BiFunction<Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b) throws Exception { return a; } })); TestHelper.checkDisposed(Flowable.just(1).withLatestFrom(Flowable.just(2), Flowable.just(3), new Function3<Integer, Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b, Integer c) throws Exception { return a; } })); } @Test public void manyIteratorThrows() { Flowable.just(1) .withLatestFrom(new CrashingMappedIterable<Flowable<Integer>>(1, 100, 100, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return Flowable.just(2); } }), new Function<Object[], Object>() { @Override public Object apply(Object[] a) throws Exception { return a; } }) .test() .assertFailureAndMessage(TestException.class, "iterator()"); } @Test public void manyCombinerThrows() { Flowable.just(1).withLatestFrom(Flowable.just(2), Flowable.just(3), new Function3<Integer, Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b, Integer c) throws Exception { throw new TestException(); } }) .test() .assertFailure(TestException.class); } @Test public void manyErrors() { 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.onNext(1); observer.onError(new TestException("Second")); observer.onComplete(); } }.withLatestFrom(Flowable.just(2), Flowable.just(3), new Function3<Integer, Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b, Integer c) throws Exception { return a; } }) .test() .assertFailureAndMessage(TestException.class, "First"); TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second"); } finally { RxJavaPlugins.reset(); } } @Test public void otherErrors() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { Flowable.just(1) .withLatestFrom(new Flowable<Integer>() { @Override protected void subscribeActual(Subscriber<? super Integer> s) { s.onSubscribe(new BooleanSubscription()); s.onError(new TestException("First")); s.onError(new TestException("Second")); } }, 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 combineToNull1() { Flowable.just(1) .withLatestFrom(Flowable.just(2), new BiFunction<Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b) throws Exception { return null; } }) .test() .assertFailure(NullPointerException.class); } @SuppressWarnings("unchecked") @Test public void combineToNull2() { Flowable.just(1) .withLatestFrom(Arrays.asList(Flowable.just(2), Flowable.just(3)), new Function<Object[], Object>() { @Override public Object apply(Object[] o) throws Exception { return null; } }) .test() .assertFailure(NullPointerException.class); } }