/** * 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.lang.reflect.Method; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; public class FlowableConcatTest { @Test public void testConcat() { Subscriber<String> observer = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Flowable<String> odds = Flowable.fromArray(o); final Flowable<String> even = Flowable.fromArray(e); Flowable<String> concat = Flowable.concat(odds, even); concat.subscribe(observer); verify(observer, times(7)).onNext(anyString()); } @Test public void testConcatWithList() { Subscriber<String> observer = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Flowable<String> odds = Flowable.fromArray(o); final Flowable<String> even = Flowable.fromArray(e); final List<Flowable<String>> list = new ArrayList<Flowable<String>>(); list.add(odds); list.add(even); Flowable<String> concat = Flowable.concat(Flowable.fromIterable(list)); concat.subscribe(observer); verify(observer, times(7)).onNext(anyString()); } @Test public void testConcatObservableOfObservables() { Subscriber<String> observer = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Flowable<String> odds = Flowable.fromArray(o); final Flowable<String> even = Flowable.fromArray(e); Flowable<Flowable<String>> observableOfObservables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override public void subscribe(Subscriber<? super Flowable<String>> observer) { observer.onSubscribe(new BooleanSubscription()); // simulate what would happen in an observable observer.onNext(odds); observer.onNext(even); observer.onComplete(); } }); Flowable<String> concat = Flowable.concat(observableOfObservables); concat.subscribe(observer); verify(observer, times(7)).onNext(anyString()); } /** * Simple concat of 2 asynchronous observables ensuring it emits in correct order. */ @Test public void testSimpleAsyncConcat() { Subscriber<String> observer = TestHelper.mockSubscriber(); TestObservable<String> o1 = new TestObservable<String>("one", "two", "three"); TestObservable<String> o2 = new TestObservable<String>("four", "five", "six"); Flowable.concat(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2)).subscribe(observer); try { // wait for async observables to complete o1.t.join(); o2.t.join(); } catch (Throwable e) { throw new RuntimeException("failed waiting on threads"); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, times(1)).onNext("five"); inOrder.verify(observer, times(1)).onNext("six"); } @Test public void testNestedAsyncConcatLoop() throws Throwable { for (int i = 0; i < 500; i++) { if (i % 10 == 0) { System.out.println("testNestedAsyncConcat >> " + i); } testNestedAsyncConcat(); } } /** * Test an async Flowable that emits more async Observables. * @throws InterruptedException if the test is interrupted */ @Test public void testNestedAsyncConcat() throws InterruptedException { Subscriber<String> observer = TestHelper.mockSubscriber(); final TestObservable<String> o1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> o2 = new TestObservable<String>("four", "five", "six"); final TestObservable<String> o3 = new TestObservable<String>("seven", "eight", "nine"); final CountDownLatch allowThird = new CountDownLatch(1); final AtomicReference<Thread> parent = new AtomicReference<Thread>(); final CountDownLatch parentHasStarted = new CountDownLatch(1); final CountDownLatch parentHasFinished = new CountDownLatch(1); Flowable<Flowable<String>> observableOfObservables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override public void subscribe(final Subscriber<? super Flowable<String>> observer) { final Disposable d = Disposables.empty(); observer.onSubscribe(new Subscription() { @Override public void request(long n) { } @Override public void cancel() { d.dispose(); } }); parent.set(new Thread(new Runnable() { @Override public void run() { try { // emit first if (!d.isDisposed()) { System.out.println("Emit o1"); observer.onNext(Flowable.unsafeCreate(o1)); } // emit second if (!d.isDisposed()) { System.out.println("Emit o2"); observer.onNext(Flowable.unsafeCreate(o2)); } // wait until sometime later and emit third try { allowThird.await(); } catch (InterruptedException e) { observer.onError(e); } if (!d.isDisposed()) { System.out.println("Emit o3"); observer.onNext(Flowable.unsafeCreate(o3)); } } catch (Throwable e) { observer.onError(e); } finally { System.out.println("Done parent Flowable"); observer.onComplete(); parentHasFinished.countDown(); } } })); parent.get().start(); parentHasStarted.countDown(); } }); Flowable.concat(observableOfObservables).subscribe(observer); // wait for parent to start parentHasStarted.await(); try { // wait for first 2 async observables to complete System.out.println("Thread1 is starting ... waiting for it to complete ..."); o1.waitForThreadDone(); System.out.println("Thread2 is starting ... waiting for it to complete ..."); o2.waitForThreadDone(); } catch (Throwable e) { throw new RuntimeException("failed waiting on threads", e); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, times(1)).onNext("five"); inOrder.verify(observer, times(1)).onNext("six"); // we shouldn't have the following 3 yet inOrder.verify(observer, never()).onNext("seven"); inOrder.verify(observer, never()).onNext("eight"); inOrder.verify(observer, never()).onNext("nine"); // we should not be completed yet verify(observer, never()).onComplete(); verify(observer, never()).onError(any(Throwable.class)); // now allow the third allowThird.countDown(); try { // wait for 3rd to complete o3.waitForThreadDone(); } catch (Throwable e) { throw new RuntimeException("failed waiting on threads", e); } try { // wait for the parent to complete if (!parentHasFinished.await(5, TimeUnit.SECONDS)) { fail("Parent didn't finish within the time limit"); } } catch (Throwable e) { throw new RuntimeException("failed waiting on threads", e); } inOrder.verify(observer, times(1)).onNext("seven"); inOrder.verify(observer, times(1)).onNext("eight"); inOrder.verify(observer, times(1)).onNext("nine"); verify(observer, never()).onError(any(Throwable.class)); inOrder.verify(observer, times(1)).onComplete(); } @Test public void testBlockedObservableOfObservables() { Subscriber<String> observer = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Flowable<String> odds = Flowable.fromArray(o); final Flowable<String> even = Flowable.fromArray(e); final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); @SuppressWarnings("unchecked") TestObservable<Flowable<String>> observableOfObservables = new TestObservable<Flowable<String>>(callOnce, okToContinue, odds, even); Flowable<String> concatF = Flowable.concat(Flowable.unsafeCreate(observableOfObservables)); concatF.subscribe(observer); try { //Block main thread to allow observables to serve up o1. callOnce.await(); } catch (Throwable ex) { ex.printStackTrace(); fail(ex.getMessage()); } // The concated observable should have served up all of the odds. verify(observer, times(1)).onNext("1"); verify(observer, times(1)).onNext("3"); verify(observer, times(1)).onNext("5"); verify(observer, times(1)).onNext("7"); try { // unblock observables so it can serve up o2 and complete okToContinue.countDown(); observableOfObservables.t.join(); } catch (Throwable ex) { ex.printStackTrace(); fail(ex.getMessage()); } // The concatenated observable should now have served up all the evens. verify(observer, times(1)).onNext("2"); verify(observer, times(1)).onNext("4"); verify(observer, times(1)).onNext("6"); } @Test public void testConcatConcurrentWithInfinity() { final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); //This observable will send "hello" MAX_VALUE time. final TestObservable<String> w2 = new TestObservable<String>("hello", Integer.MAX_VALUE); Subscriber<String> observer = TestHelper.mockSubscriber(); @SuppressWarnings("unchecked") TestObservable<Flowable<String>> observableOfObservables = new TestObservable<Flowable<String>>(Flowable.unsafeCreate(w1), Flowable.unsafeCreate(w2)); Flowable<String> concatF = Flowable.concat(Flowable.unsafeCreate(observableOfObservables)); concatF.take(50).subscribe(observer); //Wait for the thread to start up. try { w1.waitForThreadDone(); w2.waitForThreadDone(); } catch (InterruptedException e) { e.printStackTrace(); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(47)).onNext("hello"); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void testConcatNonBlockingObservables() { final CountDownLatch okToContinueW1 = new CountDownLatch(1); final CountDownLatch okToContinueW2 = new CountDownLatch(1); final TestObservable<String> w1 = new TestObservable<String>(null, okToContinueW1, "one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(null, okToContinueW2, "four", "five", "six"); Subscriber<String> observer = TestHelper.mockSubscriber(); Flowable<Flowable<String>> observableOfObservables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override public void subscribe(Subscriber<? super Flowable<String>> observer) { observer.onSubscribe(new BooleanSubscription()); // simulate what would happen in an observable observer.onNext(Flowable.unsafeCreate(w1)); observer.onNext(Flowable.unsafeCreate(w2)); observer.onComplete(); } }); Flowable<String> concat = Flowable.concat(observableOfObservables); concat.subscribe(observer); verify(observer, times(0)).onComplete(); try { // release both threads okToContinueW1.countDown(); okToContinueW2.countDown(); // wait for both to finish w1.t.join(); w2.t.join(); } catch (InterruptedException e) { e.printStackTrace(); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, times(1)).onNext("five"); inOrder.verify(observer, times(1)).onNext("six"); verify(observer, times(1)).onComplete(); } /** * Test unsubscribing the concatenated Flowable in a single thread. */ @Test public void testConcatUnsubscribe() { final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); Subscriber<String> observer = TestHelper.mockSubscriber(); TestSubscriber<String> ts = new TestSubscriber<String>(observer, 0L); final Flowable<String> concat = Flowable.concat(Flowable.unsafeCreate(w1), Flowable.unsafeCreate(w2)); try { // Subscribe concat.subscribe(ts); //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext once. callOnce.await(); // Unsubcribe ts.dispose(); //Unblock the observable to continue. okToContinue.countDown(); w1.t.join(); w2.t.join(); } catch (Throwable e) { e.printStackTrace(); fail(e.getMessage()); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, never()).onNext("five"); inOrder.verify(observer, never()).onNext("six"); inOrder.verify(observer, never()).onComplete(); } /** * All observables will be running in different threads so subscribe() is unblocked. CountDownLatch is only used in order to call unsubscribe() in a predictable manner. */ @Test public void testConcatUnsubscribeConcurrent() { final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); Subscriber<String> observer = TestHelper.mockSubscriber(); TestSubscriber<String> ts = new TestSubscriber<String>(observer, 0L); @SuppressWarnings("unchecked") TestObservable<Flowable<String>> observableOfObservables = new TestObservable<Flowable<String>>(Flowable.unsafeCreate(w1), Flowable.unsafeCreate(w2)); Flowable<String> concatF = Flowable.concat(Flowable.unsafeCreate(observableOfObservables)); concatF.subscribe(ts); try { //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext exactly once. callOnce.await(); //"four" from w2 has been processed by onNext() ts.dispose(); //"five" and "six" will NOT be processed by onNext() //Unblock the observable to continue. okToContinue.countDown(); w1.t.join(); w2.t.join(); } catch (Throwable e) { e.printStackTrace(); fail(e.getMessage()); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, never()).onNext("five"); inOrder.verify(observer, never()).onNext("six"); verify(observer, never()).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } private static class TestObservable<T> implements Publisher<T> { private final Subscription s = new Subscription() { @Override public void request(long n) { } @Override public void cancel() { subscribed = false; } }; private final List<T> values; private Thread t; private int count; private volatile boolean subscribed = true; private final CountDownLatch once; private final CountDownLatch okToContinue; private final CountDownLatch threadHasStarted = new CountDownLatch(1); private final T seed; private final int size; TestObservable(T... values) { this(null, null, values); } TestObservable(CountDownLatch once, CountDownLatch okToContinue, T... values) { this.values = Arrays.asList(values); this.size = this.values.size(); this.once = once; this.okToContinue = okToContinue; this.seed = null; } TestObservable(T seed, int size) { values = null; once = null; okToContinue = null; this.seed = seed; this.size = size; } @Override public void subscribe(final Subscriber<? super T> observer) { observer.onSubscribe(s); t = new Thread(new Runnable() { @Override public void run() { try { while (count < size && subscribed) { if (null != values) { observer.onNext(values.get(count)); } else { observer.onNext(seed); } count++; //Unblock the main thread to call unsubscribe. if (null != once) { once.countDown(); } //Block until the main thread has called unsubscribe. if (null != okToContinue) { okToContinue.await(5, TimeUnit.SECONDS); } } if (subscribed) { observer.onComplete(); } } catch (InterruptedException e) { e.printStackTrace(); fail(e.getMessage()); } } }); t.start(); threadHasStarted.countDown(); } void waitForThreadDone() throws InterruptedException { threadHasStarted.await(); t.join(); } } @Test public void testMultipleObservers() { Subscriber<Object> o1 = TestHelper.mockSubscriber(); Subscriber<Object> o2 = TestHelper.mockSubscriber(); TestScheduler s = new TestScheduler(); Flowable<Long> timer = Flowable.interval(500, TimeUnit.MILLISECONDS, s).take(2); Flowable<Long> o = Flowable.concat(timer, timer); o.subscribe(o1); o.subscribe(o2); InOrder inOrder1 = inOrder(o1); InOrder inOrder2 = inOrder(o2); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(0L); inOrder2.verify(o2, times(1)).onNext(0L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(1L); inOrder2.verify(o2, times(1)).onNext(1L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(0L); inOrder2.verify(o2, times(1)).onNext(0L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(1L); inOrder2.verify(o2, times(1)).onNext(1L); inOrder1.verify(o1, times(1)).onComplete(); inOrder2.verify(o2, times(1)).onComplete(); verify(o1, never()).onError(any(Throwable.class)); verify(o2, never()).onError(any(Throwable.class)); } @Test public void concatVeryLongObservableOfObservables() { final int n = 10000; Flowable<Flowable<Integer>> source = Flowable.range(0, n).map(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) { return Flowable.just(v); } }); Single<List<Integer>> result = Flowable.concat(source).toList(); SingleObserver<List<Integer>> o = TestHelper.mockSingleObserver(); InOrder inOrder = inOrder(o); result.subscribe(o); List<Integer> list = new ArrayList<Integer>(n); for (int i = 0; i < n; i++) { list.add(i); } inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; Flowable<Flowable<Integer>> source = Flowable.range(0, n).map(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) { return Flowable.just(v); } }); Single<List<Integer>> result = Flowable.concat(source).take(n / 2).toList(); SingleObserver<List<Integer>> o = TestHelper.mockSingleObserver(); InOrder inOrder = inOrder(o); result.subscribe(o); List<Integer> list = new ArrayList<Integer>(n); for (int i = 0; i < n / 2; i++) { list.add(i); } inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } @Test public void testConcatOuterBackpressure() { assertEquals(1, (int) Flowable.<Integer> empty() .concatWith(Flowable.just(1)) .take(1) .blockingSingle()); } @Test public void testInnerBackpressureWithAlignedBoundaries() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.range(0, Flowable.bufferSize() * 2) .concatWith(Flowable.range(0, Flowable.bufferSize() * 2)) .observeOn(Schedulers.computation()) // observeOn has a backpressured RxRingBuffer .subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); assertEquals(Flowable.bufferSize() * 4, ts.valueCount()); } /* * Testing without counts aligned with buffer sizes because concat must prevent the subscription * to the next Flowable if request == 0 which can happen at the end of a subscription * if the request size == emitted size. It needs to delay subscription until the next request when aligned, * when not aligned, it just subscribesNext with the outstanding request amount. */ @Test public void testInnerBackpressureWithoutAlignedBoundaries() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.range(0, (Flowable.bufferSize() * 2) + 10) .concatWith(Flowable.range(0, (Flowable.bufferSize() * 2) + 10)) .observeOn(Schedulers.computation()) // observeOn has a backpressured RxRingBuffer .subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); assertEquals((Flowable.bufferSize() * 4) + 20, ts.valueCount()); } // https://github.com/ReactiveX/RxJava/issues/1818 @Test public void testConcatWithNonCompliantSourceDoubleOnComplete() { Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(Subscriber<? super String> s) { s.onSubscribe(new BooleanSubscription()); s.onNext("hello"); s.onComplete(); s.onComplete(); } }); TestSubscriber<String> ts = new TestSubscriber<String>(); Flowable.concat(o, o).subscribe(ts); ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); ts.assertTerminated(); ts.assertNoErrors(); ts.assertValues("hello", "hello"); } @Test(timeout = 30000) public void testIssue2890NoStackoverflow() throws InterruptedException { final ExecutorService executor = Executors.newFixedThreadPool(2); final Scheduler sch = Schedulers.from(executor); Function<Integer, Flowable<Integer>> func = new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t) { Flowable<Integer> observable = Flowable.just(t) .subscribeOn(sch) ; FlowableProcessor<Integer> subject = UnicastProcessor.create(); observable.subscribe(subject); return subject; } }; int n = 5000; final AtomicInteger counter = new AtomicInteger(); Flowable.range(1, n).concatMap(func).subscribe(new DefaultSubscriber<Integer>() { @Override public void onNext(Integer t) { // Consume after sleep for 1 ms try { Thread.sleep(1); } catch (InterruptedException e) { // ignored } if (counter.getAndIncrement() % 100 == 0) { System.out.print("testIssue2890NoStackoverflow -> "); System.out.println(counter.get()); }; } @Override public void onComplete() { executor.shutdown(); } @Override public void onError(Throwable e) { executor.shutdown(); } }); executor.awaitTermination(20000, TimeUnit.MILLISECONDS); assertEquals(n, counter.get()); } @Test public void testRequestOverflowDoesNotStallStream() { Flowable<Integer> o1 = Flowable.just(1,2,3); Flowable<Integer> o2 = Flowable.just(4,5,6); final AtomicBoolean completed = new AtomicBoolean(false); o1.concatWith(o2).subscribe(new DefaultSubscriber<Integer>() { @Override public void onComplete() { completed.set(true); } @Override public void onError(Throwable e) { } @Override public void onNext(Integer t) { request(2); }}); assertTrue(completed.get()); } @Test//(timeout = 100000) public void concatMapRangeAsyncLoopIssue2876() { final long durationSeconds = 2; final long startTime = System.currentTimeMillis(); for (int i = 0;; i++) { //only run this for a max of ten seconds if (System.currentTimeMillis() - startTime > TimeUnit.SECONDS.toMillis(durationSeconds)) { return; } if (i % 1000 == 0) { System.out.println("concatMapRangeAsyncLoop > " + i); } TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.range(0, 1000) .concatMap(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t) { return Flowable.fromIterable(Arrays.asList(t)); } }) .observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); ts.assertTerminated(); ts.assertNoErrors(); assertEquals(1000, ts.valueCount()); assertEquals((Integer)999, ts.values().get(999)); } } @SuppressWarnings("unchecked") @Test public void arrayDelayError() { Publisher<Integer>[] sources = new Publisher[] { Flowable.just(1), null, Flowable.range(2, 3), Flowable.error(new TestException()), Flowable.empty() }; TestSubscriber<Integer> ts = Flowable.concatArrayDelayError(sources).test(); ts.assertFailure(CompositeException.class, 1, 2, 3, 4); CompositeException composite = (CompositeException)ts.errors().get(0); List<Throwable> list = composite.getExceptions(); assertTrue(list.get(0).toString(), list.get(0) instanceof NullPointerException); assertTrue(list.get(1).toString(), list.get(1) instanceof TestException); } @Test public void scalarAndRangeBackpressured() { TestSubscriber<Integer> ts = TestSubscriber.create(0); Flowable.just(1).concatWith(Flowable.range(2, 3)).subscribe(ts); ts.assertNoValues(); ts.request(5); ts.assertValues(1, 2, 3, 4); ts.assertComplete(); ts.assertNoErrors(); } @Test public void scalarAndEmptyBackpressured() { TestSubscriber<Integer> ts = TestSubscriber.create(0); Flowable.just(1).concatWith(Flowable.<Integer>empty()).subscribe(ts); ts.assertNoValues(); ts.request(5); ts.assertValue(1); ts.assertComplete(); ts.assertNoErrors(); } @Test public void rangeAndEmptyBackpressured() { TestSubscriber<Integer> ts = TestSubscriber.create(0); Flowable.range(1, 2).concatWith(Flowable.<Integer>empty()).subscribe(ts); ts.assertNoValues(); ts.request(5); ts.assertValues(1, 2); ts.assertComplete(); ts.assertNoErrors(); } @Test public void emptyAndScalarBackpressured() { TestSubscriber<Integer> ts = TestSubscriber.create(0); Flowable.<Integer>empty().concatWith(Flowable.just(1)).subscribe(ts); ts.assertNoValues(); ts.request(5); ts.assertValue(1); ts.assertComplete(); ts.assertNoErrors(); } @SuppressWarnings("unchecked") @Test @Ignore("concat(a, b, ...) replaced by concatArray(T...)") public void concatMany() throws Exception { for (int i = 2; i < 10; i++) { Class<?>[] clazz = new Class[i]; Arrays.fill(clazz, Flowable.class); Flowable<Integer>[] obs = new Flowable[i]; Arrays.fill(obs, Flowable.just(1)); Integer[] expected = new Integer[i]; Arrays.fill(expected, 1); Method m = Flowable.class.getMethod("concat", clazz); TestSubscriber<Integer> ts = TestSubscriber.create(); ((Flowable<Integer>)m.invoke(null, (Object[])obs)).subscribe(ts); ts.assertValues(expected); ts.assertNoErrors(); ts.assertComplete(); } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void concatMapJustJust() { TestSubscriber<Integer> ts = TestSubscriber.create(); Flowable.just(Flowable.just(1)).concatMap((Function)Functions.identity()).subscribe(ts); ts.assertValue(1); ts.assertNoErrors(); ts.assertComplete(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void concatMapJustRange() { TestSubscriber<Integer> ts = TestSubscriber.create(); Flowable.just(Flowable.range(1, 5)).concatMap((Function)Functions.identity()).subscribe(ts); ts.assertValues(1, 2, 3, 4, 5); ts.assertNoErrors(); ts.assertComplete(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void concatMapDelayErrorJustJust() { TestSubscriber<Integer> ts = TestSubscriber.create(); Flowable.just(Flowable.just(1)).concatMapDelayError((Function)Functions.identity()).subscribe(ts); ts.assertValue(1); ts.assertNoErrors(); ts.assertComplete(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void concatMapDelayErrorJustRange() { TestSubscriber<Integer> ts = TestSubscriber.create(); Flowable.just(Flowable.range(1, 5)).concatMapDelayError((Function)Functions.identity()).subscribe(ts); ts.assertValues(1, 2, 3, 4, 5); ts.assertNoErrors(); ts.assertComplete(); } @SuppressWarnings("unchecked") @Test @Ignore("startWith(a, b, ...) replaced by startWithArray(T...)") public void startWith() throws Exception { for (int i = 2; i < 10; i++) { Class<?>[] clazz = new Class[i]; Arrays.fill(clazz, Object.class); Object[] obs = new Object[i]; Arrays.fill(obs, 1); Integer[] expected = new Integer[i]; Arrays.fill(expected, 1); Method m = Flowable.class.getMethod("startWith", clazz); TestSubscriber<Integer> ts = TestSubscriber.create(); ((Flowable<Integer>)m.invoke(Flowable.empty(), obs)).subscribe(ts); ts.assertValues(expected); ts.assertNoErrors(); ts.assertComplete(); } } static final class InfiniteIterator implements Iterator<Integer>, Iterable<Integer> { int count; @Override public boolean hasNext() { return true; } @Override public Integer next() { return count++; } @Override public void remove() { } @Override public Iterator<Integer> iterator() { return this; } } @Test(timeout = 5000) public void veryLongTake() { Flowable.fromIterable(new InfiniteIterator()).concatWith(Flowable.<Integer>empty()).take(10) .test() .assertResult(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); } @Test public void concat3() { Flowable<Integer> source = Flowable.just(1); Flowable.concat(source, source, source) .test() .assertResult(1, 1, 1); } @Test public void concat4() { Flowable<Integer> source = Flowable.just(1); Flowable.concat(source, source, source, source) .test() .assertResult(1, 1, 1, 1); } @SuppressWarnings("unchecked") @Test public void concatArrayDelayError() { Flowable.concatArrayDelayError(Flowable.just(1), Flowable.just(2), Flowable.just(3), Flowable.just(4)) .test() .assertResult(1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatArrayDelayErrorWithError() { Flowable.concatArrayDelayError(Flowable.just(1), Flowable.just(2), Flowable.just(3).concatWith(Flowable.<Integer>error(new TestException())), Flowable.just(4)) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatIterableDelayError() { Flowable.concatDelayError( Arrays.asList(Flowable.just(1), Flowable.just(2), Flowable.just(3), Flowable.just(4))) .test() .assertResult(1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatIterableDelayErrorWithError() { Flowable.concatDelayError( Arrays.asList(Flowable.just(1), Flowable.just(2), Flowable.just(3).concatWith(Flowable.<Integer>error(new TestException())), Flowable.just(4))) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @Test public void concatObservableDelayError() { Flowable.concatDelayError( Flowable.just(Flowable.just(1), Flowable.just(2), Flowable.just(3), Flowable.just(4))) .test() .assertResult(1, 2, 3, 4); } @Test public void concatObservableDelayErrorWithError() { Flowable.concatDelayError( Flowable.just(Flowable.just(1), Flowable.just(2), Flowable.just(3).concatWith(Flowable.<Integer>error(new TestException())), Flowable.just(4))) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @Test public void concatObservableDelayErrorBoundary() { Flowable.concatDelayError( Flowable.just(Flowable.just(1), Flowable.just(2), Flowable.just(3).concatWith(Flowable.<Integer>error(new TestException())), Flowable.just(4)), 2, false) .test() .assertFailure(TestException.class, 1, 2, 3); } @Test public void concatObservableDelayErrorTillEnd() { Flowable.concatDelayError( Flowable.just(Flowable.just(1), Flowable.just(2), Flowable.just(3).concatWith(Flowable.<Integer>error(new TestException())), Flowable.just(4)), 2, true) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @Test public void concatMapDelayError() { Flowable.just(Flowable.just(1), Flowable.just(2)) .concatMapDelayError(Functions.<Flowable<Integer>>identity()) .test() .assertResult(1, 2); } @Test public void concatMapDelayErrorWithError() { Flowable.just(Flowable.just(1).concatWith(Flowable.<Integer>error(new TestException())), Flowable.just(2)) .concatMapDelayError(Functions.<Flowable<Integer>>identity()) .test() .assertFailure(TestException.class, 1, 2); } @Test public void concatMapIterableBufferSize() { Flowable.just(1, 2).concatMapIterable(new Function<Integer, Iterable<Integer>>() { @Override public Iterable<Integer> apply(Integer v) throws Exception { return Arrays.asList(1, 2, 3, 4, 5); } }, 1) .test() .assertResult(1, 2, 3, 4, 5, 1, 2, 3, 4, 5); } @SuppressWarnings("unchecked") @Test public void emptyArray() { assertSame(Flowable.empty(), Flowable.concatArrayDelayError()); } @SuppressWarnings("unchecked") @Test public void singleElementArray() { assertSame(Flowable.never(), Flowable.concatArrayDelayError(Flowable.never())); } @Test public void concatMapDelayErrorEmptySource() { assertSame(Flowable.empty(), Flowable.<Object>empty() .concatMapDelayError(new Function<Object, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Object v) throws Exception { return Flowable.just(1); } }, 16, true)); } @Test public void concatMapDelayErrorJustSource() { Flowable.just(0) .concatMapDelayError(new Function<Object, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Object v) throws Exception { return Flowable.just(1); } }, 16, true) .test() .assertResult(1); } @SuppressWarnings("unchecked") @Test public void concatArrayEmpty() { assertSame(Flowable.empty(), Flowable.concatArray()); } @SuppressWarnings("unchecked") @Test public void concatArraySingleElement() { assertSame(Flowable.never(), Flowable.concatArray(Flowable.never())); } @Test public void concatMapErrorEmptySource() { assertSame(Flowable.empty(), Flowable.<Object>empty() .concatMap(new Function<Object, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Object v) throws Exception { return Flowable.just(1); } }, 16)); } @Test public void concatMapJustSource() { Flowable.just(0).hide() .concatMap(new Function<Object, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Object v) throws Exception { return Flowable.just(1); } }, 16) .test() .assertResult(1); } @Test public void concatMapJustSourceDelayError() { Flowable.just(0).hide() .concatMapDelayError(new Function<Object, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Object v) throws Exception { return Flowable.just(1); } }, 16, false) .test() .assertResult(1); } @Test public void concatMapScalarBackpressured() { Flowable.just(1).hide() .concatMap(Functions.justFunction(Flowable.just(2))) .test(1L) .assertResult(2); } @Test public void concatMapScalarBackpressuredDelayError() { Flowable.just(1).hide() .concatMapDelayError(Functions.justFunction(Flowable.just(2))) .test(1L) .assertResult(2); } @Test public void concatMapEmpty() { Flowable.just(1).hide() .concatMap(Functions.justFunction(Flowable.empty())) .test() .assertResult(); } @Test public void concatMapEmptyDelayError() { Flowable.just(1).hide() .concatMapDelayError(Functions.justFunction(Flowable.empty())) .test() .assertResult(); } @Test public void ignoreBackpressure() { new Flowable<Integer>() { @Override protected void subscribeActual(Subscriber<? super Integer> s) { s.onSubscribe(new BooleanSubscription()); for (int i = 0; i < 10; i++) { s.onNext(i); } } } .concatMap(Functions.justFunction(Flowable.just(2)), 8) .test(0L) .assertFailure(IllegalStateException.class); } @Test public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Integer>>() { @Override public Publisher<Integer> apply(Flowable<Object> f) throws Exception { return f.concatMap(Functions.justFunction(Flowable.just(2))); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Integer>>() { @Override public Publisher<Integer> apply(Flowable<Object> f) throws Exception { return f.concatMapDelayError(Functions.justFunction(Flowable.just(2))); } }); } @Test public void immediateInnerNextOuterError() { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { pp.onError(new TestException("First")); } } }; pp.concatMap(Functions.justFunction(Flowable.just(1))) .subscribe(ts); pp.onNext(1); assertFalse(pp.hasSubscribers()); ts.assertFailureAndMessage(TestException.class, "First", 1); } @Test public void immediateInnerNextOuterError2() { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { pp.onError(new TestException("First")); } } }; pp.concatMap(Functions.justFunction(Flowable.just(1).hide())) .subscribe(ts); pp.onNext(1); assertFalse(pp.hasSubscribers()); ts.assertFailureAndMessage(TestException.class, "First", 1); } @Test public void concatMapInnerError() { Flowable.just(1).hide() .concatMap(Functions.justFunction(Flowable.error(new TestException()))) .test() .assertFailure(TestException.class); } @Test public void concatMapInnerErrorDelayError() { Flowable.just(1).hide() .concatMapDelayError(Functions.justFunction(Flowable.error(new TestException()))) .test() .assertFailure(TestException.class); } @Test public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override public Object apply(Flowable<Integer> f) throws Exception { return f.concatMap(Functions.justFunction(Flowable.just(1).hide())); } }, true, 1, 1, 1); } @Test public void badInnerSource() { @SuppressWarnings("rawtypes") final Subscriber[] ts0 = { null }; TestSubscriber<Integer> ts = Flowable.just(1).hide().concatMap(Functions.justFunction(new Flowable<Integer>() { @Override protected void subscribeActual(Subscriber<? super Integer> s) { ts0[0] = s; s.onSubscribe(new BooleanSubscription()); s.onError(new TestException("First")); } })) .test(); ts.assertFailureAndMessage(TestException.class, "First"); List<Throwable> errors = TestHelper.trackPluginErrors(); try { ts0[0].onError(new TestException("Second")); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { RxJavaPlugins.reset(); } } @Test public void badInnerSourceDelayError() { @SuppressWarnings("rawtypes") final Subscriber[] ts0 = { null }; TestSubscriber<Integer> ts = Flowable.just(1).hide().concatMapDelayError(Functions.justFunction(new Flowable<Integer>() { @Override protected void subscribeActual(Subscriber<? super Integer> s) { ts0[0] = s; s.onSubscribe(new BooleanSubscription()); s.onError(new TestException("First")); } })) .test(); ts.assertFailureAndMessage(TestException.class, "First"); List<Throwable> errors = TestHelper.trackPluginErrors(); try { ts0[0].onError(new TestException("Second")); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { RxJavaPlugins.reset(); } } @Test public void badSourceDelayError() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override public Object apply(Flowable<Integer> f) throws Exception { return f.concatMap(Functions.justFunction(Flowable.just(1).hide())); } }, true, 1, 1, 1); } @Test public void fusedCrash() { Flowable.range(1, 2) .map(new Function<Integer, Object>() { @Override public Object apply(Integer v) throws Exception { throw new TestException(); } }) .concatMap(Functions.justFunction(Flowable.just(1))) .test() .assertFailure(TestException.class); } @Test public void fusedCrashDelayError() { Flowable.range(1, 2) .map(new Function<Integer, Object>() { @Override public Object apply(Integer v) throws Exception { throw new TestException(); } }) .concatMapDelayError(Functions.justFunction(Flowable.just(1))) .test() .assertFailure(TestException.class); } @Test public void callableCrash() { Flowable.just(1).hide() .concatMap(Functions.justFunction(Flowable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { throw new TestException(); } }))) .test() .assertFailure(TestException.class); } @Test public void callableCrashDelayError() { Flowable.just(1).hide() .concatMapDelayError(Functions.justFunction(Flowable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { throw new TestException(); } }))) .test() .assertFailure(TestException.class); } @Test public void dispose() { TestHelper.checkDisposed(Flowable.range(1, 2) .concatMap(Functions.justFunction(Flowable.just(1)))); TestHelper.checkDisposed(Flowable.range(1, 2) .concatMapDelayError(Functions.justFunction(Flowable.just(1)))); } @Test public void notVeryEnd() { Flowable.range(1, 2) .concatMapDelayError(Functions.justFunction(Flowable.error(new TestException())), 16, false) .test() .assertFailure(TestException.class); } @Test public void error() { Flowable.error(new TestException()) .concatMapDelayError(Functions.justFunction(Flowable.just(2)), 16, false) .test() .assertFailure(TestException.class); } @Test public void mapperThrows() { Flowable.range(1, 2) .concatMap(new Function<Integer, Publisher<Object>>() { @Override public Publisher<Object> apply(Integer v) throws Exception { throw new TestException(); } }) .test() .assertFailure(TestException.class); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscription() { final int[] calls = { 0 }; Flowable<Integer> source = Flowable.create(new FlowableOnSubscribe<Integer>() { @Override public void subscribe(FlowableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }, BackpressureStrategy.MISSING); Flowable.concatArray(source, source).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionDelayError() { final int[] calls = { 0 }; Flowable<Integer> source = Flowable.create(new FlowableOnSubscribe<Integer>() { @Override public void subscribe(FlowableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }, BackpressureStrategy.MISSING); Flowable.concatArrayDelayError(source, source).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionIterable() { final int[] calls = { 0 }; Flowable<Integer> source = Flowable.create(new FlowableOnSubscribe<Integer>() { @Override public void subscribe(FlowableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }, BackpressureStrategy.MISSING); Flowable.concat(Arrays.asList(source, source)).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionDelayErrorIterable() { final int[] calls = { 0 }; Flowable<Integer> source = Flowable.create(new FlowableOnSubscribe<Integer>() { @Override public void subscribe(FlowableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }, BackpressureStrategy.MISSING); Flowable.concatDelayError(Arrays.asList(source, source)).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } }